diff --git a/.env.example b/.env.example index 8f9c02d..76c1f3f 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ PROJECT_NAME=jama # Image version to run (set by build.sh, or use 'latest') -JAMA_VERSION=0.9.68 +JAMA_VERSION=0.9.69 # App port — the host port Docker maps to the container PORT=3000 diff --git a/backend/package.json b/backend/package.json index aa1eb60..c10deb3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.9.68", + "version": "0.9.69", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index 81fb7e8..4f45087 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.9.68}" +VERSION="${1:-0.9.69}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index c3e5c8d..6dc3089 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.9.68", + "version": "0.9.69", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/MobileEventForm.jsx b/frontend/src/components/MobileEventForm.jsx index 6bfcb2e..2a7a83c 100644 --- a/frontend/src/components/MobileEventForm.jsx +++ b/frontend/src/components/MobileEventForm.jsx @@ -189,6 +189,11 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte const def = selectedDate ? selectedDate.toISOString().slice(0,10) : new Date().toISOString().slice(0,10); const [title, setTitle] = useState(event?.title||''); const [typeId, setTypeId] = useState(event?.event_type_id ? String(event.event_type_id) : ''); + const [localTypes, setLocalTypes] = useState(eventTypes); + const [showAddType, setShowAddType] = useState(false); + const [newTypeName, setNewTypeName] = useState(''); + const [newTypeColour, setNewTypeColour] = useState('#6366f1'); + const [savingType, setSavingType] = useState(false); const [sd, setSd] = useState(event ? toDateIn(event.start_at) : def); const [st, setSt] = useState(event ? toTimeIn(event.start_at) : '09:00'); const [ed, setEd] = useState(event ? toDateIn(event.end_at) : def); @@ -208,21 +213,34 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte const [showRecurrence, setShowRecurrence] = useState(false); const [showGroups, setShowGroups] = useState(false); - // Auto-set typeId to default event type + // Sync and initialise typeId useEffect(() => { + setLocalTypes(eventTypes); if(!event && typeId==='' && eventTypes.length>0) { const def = eventTypes.find(t=>t.is_default) || eventTypes[0]; if(def) setTypeId(String(def.id)); } }, [eventTypes]); + const createEventType = async () => { + if(!newTypeName.trim()) return; + setSavingType(true); + try { + const r = await api.createEventType({ name: newTypeName.trim(), colour: newTypeColour }); + setLocalTypes(prev => [...prev, r.eventType]); + setTypeId(String(r.eventType.id)); + setNewTypeName(''); setShowAddType(false); + } catch(e) { toast(e.message, 'error'); } + finally { setSavingType(false); } + }; + // When start date changes, match end date useEffect(() => { if(!event) setEd(sd); }, [sd]); // When type or start time changes, auto-set end time useEffect(() => { if(!sd||!st) return; - const typ = eventTypes.find(t=>t.id===Number(typeId)); + const typ = localTypes.find(t=>t.id===Number(typeId)); const dur = typ?.default_duration_hrs||1; const start = buildISO(sd,st); if(start && !event) { setEd(toDateIn(addHours(start,dur))); setEt(toTimeIn(addHours(start,dur))); } @@ -262,8 +280,28 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte {/* Event Type */} } label="Event Type"> - setTypeId(e.target.value)} style={{ background:'transparent',border:'none',fontSize:15,color:'var(--text-primary)',flex:1,outline:'none' }}> + {localTypes.map(t=>)} + + {isToolManager && ( + + )} + + {showAddType && ( +
+ setNewTypeName(e.target.value)} placeholder="Type name…" style={{ width:'100%', padding:'8px 10px', border:'1px solid var(--border)', borderRadius:'var(--radius)', fontSize:14, marginBottom:8, boxSizing:'border-box', background:'var(--surface)', color:'var(--text-primary)' }}/> +
+ + setNewTypeColour(e.target.value)} style={{ flex:1, height:32, border:'1px solid var(--border)', borderRadius:4, padding:2 }}/> +
+ +
+ )} +
diff --git a/frontend/src/components/SchedulePage.jsx b/frontend/src/components/SchedulePage.jsx index 77cedc1..0815492 100644 --- a/frontend/src/components/SchedulePage.jsx +++ b/frontend/src/components/SchedulePage.jsx @@ -1003,7 +1003,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel {createOpen && ( -
+
{[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);}], ['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);}], ['Bulk Event Import', ()=>{setPanel('bulkImport');setCreateOpen(false);}] @@ -1063,11 +1063,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
{/* Mobile title + create */} {isMobile && ( - <> - Team Schedule - {/* User footer trigger on mobile — compact avatar button */} - - + Team Schedule )} {!isMobile && ( @@ -1172,13 +1168,31 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
)} - {/* Mobile FAB for creating events */} - {isMobile && isToolManager && panel === 'calendar' && ( - + {/* Mobile bottom bar: UserFooter + Create Event button */} + {isMobile && ( +
+ {isToolManager && panel === 'calendar' && ( +
+ + {createOpen && ( +
+ {[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);}], + ['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);}], + ].map(([label,action])=>( + + ))} +
+ )} +
+ )} + +
)} {/* Event detail modal */}