V0.7.78 fixes

This commit is contained in:
2026-03-18 15:21:29 -04:00
parent d88d74bd49
commit d9f0986e26
5 changed files with 51 additions and 9 deletions

View File

@@ -10,7 +10,7 @@
PROJECT_NAME=jama PROJECT_NAME=jama
# Image version to run (set by build.sh, or use 'latest') # Image version to run (set by build.sh, or use 'latest')
JAMA_VERSION=0.9.77 JAMA_VERSION=0.9.78
# App port — the host port Docker maps to the container # App port — the host port Docker maps to the container
PORT=3000 PORT=3000

View File

@@ -1,6 +1,6 @@
{ {
"name": "jama-backend", "name": "jama-backend",
"version": "0.9.77", "version": "0.9.78",
"description": "TeamChat backend server", "description": "TeamChat backend server",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {

View File

@@ -13,7 +13,7 @@
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
set -euo pipefail set -euo pipefail
VERSION="${1:-0.9.77}" VERSION="${1:-0.9.78}"
ACTION="${2:-}" ACTION="${2:-}"
REGISTRY="${REGISTRY:-}" REGISTRY="${REGISTRY:-}"
IMAGE_NAME="jama" IMAGE_NAME="jama"

View File

@@ -1,6 +1,6 @@
{ {
"name": "jama-frontend", "name": "jama-frontend",
"version": "0.9.77", "version": "0.9.78",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -597,10 +597,27 @@ function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isTool
} }
// ── Event Types Panel ───────────────────────────────────────────────────────── // ── Event Types Panel ─────────────────────────────────────────────────────────
function EventTypesPanel({ eventTypes, userGroups, onUpdated }) { function EventTypesPanel({ eventTypes, userGroups, onUpdated, isMobile=false }) {
const toast=useToast(); const toast=useToast();
const [editingType,setEditingType]=useState(null); const [editingType,setEditingType]=useState(null);
const [showForm,setShowForm]=useState(false); const [showForm,setShowForm]=useState(false);
// Mobile bottom sheet state
const [sheetMode,setSheetMode]=useState(null); // null | 'create' | 'edit'
const [sheetName,setSheetName]=useState('');
const [sheetColour,setSheetColour]=useState('#6366f1');
const [sheetSaving,setSheetSaving]=useState(false);
const openCreateSheet=()=>{setSheetName('');setSheetColour('#6366f1');setSheetMode('create');};
const openEditSheet=(et)=>{setSheetName(et.name);setSheetColour(et.colour);setEditingType(et);setSheetMode('edit');};
const closeSheet=()=>{setSheetMode(null);setEditingType(null);};
const saveSheet=async()=>{
if(!sheetName.trim()) return;
setSheetSaving(true);
try{
if(sheetMode==='create') await api.createEventType({name:sheetName.trim(),colour:sheetColour});
else await api.updateEventType(editingType.id,{name:sheetName.trim(),colour:sheetColour});
onUpdated(); closeSheet();
}catch(e){} finally{setSheetSaving(false);}
};
const handleDel=async et=>{ const handleDel=async et=>{
if(!confirm(`Delete "${et.name}"?`)) return; if(!confirm(`Delete "${et.name}"?`)) return;
try{await api.deleteEventType(et.id);toast('Deleted','success');onUpdated();}catch(e){toast(e.message,'error');} try{await api.deleteEventType(et.id);toast('Deleted','success');onUpdated();}catch(e){toast(e.message,'error');}
@@ -609,7 +626,10 @@ function EventTypesPanel({ eventTypes, userGroups, onUpdated }) {
<div style={{maxWidth:560}}> <div style={{maxWidth:560}}>
<div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:16}}> <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:16}}>
<div className="settings-section-label" style={{margin:0}}>Event Types</div> <div className="settings-section-label" style={{margin:0}}>Event Types</div>
<div style={{position:'relative'}}><button className="btn btn-primary btn-sm" onClick={()=>{setShowForm(v=>!v);setEditingType(null);}}>+ New Type</button>{showForm&&!editingType&&<EventTypePopup userGroups={userGroups} onSave={()=>onUpdated()} onClose={()=>setShowForm(false)}/>}</div> <div style={{position:'relative'}}>
<button className="btn btn-primary btn-sm" onClick={()=>isMobile?openCreateSheet():(setShowForm(v=>!v),setEditingType(null))}>+ New Type</button>
{!isMobile&&showForm&&!editingType&&<EventTypePopup userGroups={userGroups} onSave={()=>onUpdated()} onClose={()=>setShowForm(false)}/>}
</div>
</div> </div>
<div style={{display:'flex',flexDirection:'column',gap:6}}> <div style={{display:'flex',flexDirection:'column',gap:6}}>
{eventTypes.map(et=>( {eventTypes.map(et=>(
@@ -619,14 +639,36 @@ function EventTypesPanel({ eventTypes, userGroups, onUpdated }) {
{et.default_duration_hrs&&<span style={{fontSize:12,color:'var(--text-tertiary)'}}>{et.default_duration_hrs}hr default</span>} {et.default_duration_hrs&&<span style={{fontSize:12,color:'var(--text-tertiary)'}}>{et.default_duration_hrs}hr default</span>}
{!et.is_protected?( {!et.is_protected?(
<div style={{display:'flex',gap:6,position:'relative'}}> <div style={{display:'flex',gap:6,position:'relative'}}>
<button className="btn btn-secondary btn-sm" onClick={()=>{setEditingType(et);setShowForm(true);}}>Edit</button> <button className="btn btn-secondary btn-sm" onClick={()=>isMobile?openEditSheet(et):(setEditingType(et),setShowForm(true))}>Edit</button>
{showForm&&editingType?.id===et.id&&<EventTypePopup editing={et} userGroups={userGroups} onSave={()=>{onUpdated();setShowForm(false);setEditingType(null);}} onClose={()=>{setShowForm(false);setEditingType(null);}}/>} {!isMobile&&showForm&&editingType?.id===et.id&&<EventTypePopup editing={et} userGroups={userGroups} onSave={()=>{onUpdated();setShowForm(false);setEditingType(null);}} onClose={()=>{setShowForm(false);setEditingType(null);}}/>}
<button className="btn btn-sm" style={{background:'var(--error)',color:'white'}} onClick={()=>handleDel(et)}>Delete</button> <button className="btn btn-sm" style={{background:'var(--error)',color:'white'}} onClick={()=>handleDel(et)}>Delete</button>
</div> </div>
):<span style={{fontSize:11,color:'var(--text-tertiary)'}}>{et.is_default?'Default':'Protected'}</span>} ):<span style={{fontSize:11,color:'var(--text-tertiary)'}}>{et.is_default?'Default':'Protected'}</span>}
</div> </div>
))} ))}
</div> </div>
{/* Mobile bottom sheet for create/edit event type */}
{isMobile && sheetMode && (
<div style={{position:'fixed',inset:0,zIndex:200,display:'flex',alignItems:'flex-end'}} onClick={e=>e.target===e.currentTarget&&closeSheet()}>
<div style={{width:'100%',background:'var(--surface)',borderRadius:'16px 16px 0 0',padding:20,boxShadow:'0 -4px 20px rgba(0,0,0,0.2)'}}>
<div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:16}}>
<span style={{fontWeight:700,fontSize:16}}>{sheetMode==='create'?'New Event Type':'Edit Event Type'}</span>
<button onClick={closeSheet} style={{background:'none',border:'none',cursor:'pointer',color:'var(--text-secondary)',fontSize:20,lineHeight:1}}></button>
</div>
<input autoFocus value={sheetName} onChange={e=>setSheetName(e.target.value)} onKeyDown={e=>e.key==='Enter'&&saveSheet()} placeholder="Type name…"
style={{width:'100%',padding:'12px 14px',border:'1px solid var(--border)',borderRadius:'var(--radius)',fontSize:16,marginBottom:12,boxSizing:'border-box',background:'var(--background)',color:'var(--text-primary)'}}/>
<div style={{display:'flex',alignItems:'center',gap:12,marginBottom:16}}>
<label style={{fontSize:14,color:'var(--text-tertiary)',flexShrink:0}}>Colour</label>
<input type="color" value={sheetColour} onChange={e=>setSheetColour(e.target.value)} style={{flex:1,height:40,border:'1px solid var(--border)',borderRadius:'var(--radius)',padding:3,cursor:'pointer'}}/>
</div>
<button onClick={saveSheet} disabled={sheetSaving||!sheetName.trim()}
style={{width:'100%',padding:'14px',background:'var(--primary)',color:'white',border:'none',borderRadius:'var(--radius)',fontSize:16,fontWeight:700,cursor:'pointer',opacity:sheetSaving?0.6:1}}>
{sheetSaving?'Saving…':'Save'}
</button>
</div>
</div>
)}
</div> </div>
); );
} }
@@ -1130,7 +1172,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
<h2 style={{ fontSize:17, fontWeight:700, margin:0 }}>Event Types</h2> <h2 style={{ fontSize:17, fontWeight:700, margin:0 }}>Event Types</h2>
<button className="btn btn-secondary btn-sm" onClick={()=>setPanel('calendar')}> Back</button> <button className="btn btn-secondary btn-sm" onClick={()=>setPanel('calendar')}> Back</button>
</div> </div>
<EventTypesPanel eventTypes={eventTypes} userGroups={userGroups} onUpdated={load}/> <EventTypesPanel eventTypes={eventTypes} userGroups={userGroups} onUpdated={load} isMobile={isMobile}/>
</div> </div>
)} )}
{panel === 'bulkImport' && isToolManager && ( {panel === 'bulkImport' && isToolManager && (