From 82a521f12c3f2a8d4296af5a4c31f251b29a4f98 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Sat, 21 Mar 2026 11:07:22 -0400 Subject: [PATCH] v0.11.6 added event form rules. --- backend/package.json | 2 +- build.sh | 2 +- frontend/package.json | 2 +- frontend/src/components/MobileEventForm.jsx | 17 +++++----- .../src/components/ScheduleManagerModal.jsx | 11 +++++-- frontend/src/components/SchedulePage.jsx | 33 +++++++++++-------- 6 files changed, 39 insertions(+), 28 deletions(-) diff --git a/backend/package.json b/backend/package.json index df650ae..c959c29 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.11.5", + "version": "0.11.6", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index ec94744..9547587 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.11.5}" +VERSION="${1:-0.11.6}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index dad2013..11c5d3c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.11.5", + "version": "0.11.6", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/MobileEventForm.jsx b/frontend/src/components/MobileEventForm.jsx index a25f2b6..c8f69ac 100644 --- a/frontend/src/components/MobileEventForm.jsx +++ b/frontend/src/components/MobileEventForm.jsx @@ -226,6 +226,7 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte : null; // Track previous typeId so we can detect a type change vs start time change const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : ''); + const mountedRef = useRef(false); const [allDay, setAllDay] = useState(!!event?.all_day); const [track, setTrack] = useState(!!event?.track_availability); const [isPrivate, setIsPrivate] = useState(event ? !event.is_public : false); @@ -262,13 +263,13 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte finally { setSavingType(false); } }; - // Auto-calculate end date/time when start date, start time, or event type changes. - // Rules: - // - New event: use eventType duration (default 1hr) - // - Editing + type changed: use new eventType duration - // - Editing + type same: use saved event duration (preserve original length) - // - Always: if end < start, advance end date by 1 day (overnight events) + // Mark mounted after first render + useEffect(() => { mountedRef.current = true; }, []); + + // Auto-calculate end date/time ONLY when start date, start time, or type actually changes. + // Skips initial mount so edit mode fields are never overwritten on open. useEffect(() => { + if(!mountedRef.current) return; // skip initial mount — never auto-change on open if(!sd||!st) return; const start = buildISO(sd,st); if(!start) return; @@ -278,11 +279,11 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte let durMins; if(!event || typeChanged) { - // New event or type change: use eventType duration + // New event or explicit type change: use eventType duration const typ = localTypes.find(t=>t.id===Number(typeId)); durMins = (typ?.default_duration_hrs||1) * 60; } else { - // Editing with same type: preserve the saved event duration + // Editing start date/time with same type: preserve saved duration durMins = savedDurMins || 60; } diff --git a/frontend/src/components/ScheduleManagerModal.jsx b/frontend/src/components/ScheduleManagerModal.jsx index 63ae52f..9f856db 100644 --- a/frontend/src/components/ScheduleManagerModal.jsx +++ b/frontend/src/components/ScheduleManagerModal.jsx @@ -179,9 +179,14 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc ? (new Date(event.end_at) - new Date(event.start_at)) / 60000 : null; const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : ''); + const mountedRef = useRef(false); - // Auto-update end time when event type or start time changes + // Mark mounted after first render so effects skip initial fire + useEffect(() => { mountedRef.current = true; }, []); + + // Auto-update end time only when type, start date, or start time actually changes useEffect(() => { + if (!mountedRef.current) return; // skip initial mount if (!startDate || !startTime) return; const et = localEventTypes.find(t => t.id === Number(eventTypeId)); const start = buildISO(startDate, startTime); @@ -189,12 +194,12 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc const typeChanged = String(eventTypeId) !== prevTypeIdRef.current; prevTypeIdRef.current = String(eventTypeId); if (!event || typeChanged) { - // New event or type change: apply eventType duration + // New event or explicit type change: apply eventType duration const dur = et?.default_duration_hrs || 1; setEndDate(toLocalDateInput(addHours(start, dur))); setEndTime(toLocalTimeInput(addHours(start, dur))); } else { - // Editing with same type: preserve saved duration + // Editing start date/time with same type: preserve saved duration const durMins = savedDurMins || 60; setEndDate(toLocalDateInput(addHours(start, durMins/60))); setEndTime(toLocalTimeInput(addHours(start, durMins/60))); diff --git a/frontend/src/components/SchedulePage.jsx b/frontend/src/components/SchedulePage.jsx index 237b09f..8a47310 100644 --- a/frontend/src/components/SchedulePage.jsx +++ b/frontend/src/components/SchedulePage.jsx @@ -370,12 +370,14 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc ? (new Date(event.end_at) - new Date(event.start_at)) / 60000 : null; const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : ''); + const mountedRef = useRef(false); // skip all auto-calc effects on initial mount // When event type changes: // - Creating: always apply the type's duration to compute end time // - Editing: only apply duration if the type HAS a defined duration // (if no duration on type, keep existing saved end time) useEffect(()=>{ + if(!mountedRef.current) return; // skip on initial mount if(!sd||!st) return; const typ=localTypes.find(t=>t.id===Number(typeId)); const start=buildISO(sd,st); @@ -383,37 +385,40 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc const typeChanged = typeId !== prevTypeIdRef.current; prevTypeIdRef.current = String(typeId); if(!event || typeChanged) { - // New event or type change: apply eventType duration + // New event or type change only: apply eventType duration const dur=typ?.default_duration_hrs||1; const endIso=addHours(start,dur); setEd(toDateIn(endIso)); setEt(toTimeIn(endIso)); userSetEndTime.current = false; } - // Editing with same type: do not override end time here (start-time effect handles it) if(typ?.default_user_group_id&&!event) setGrps(prev=>new Set([...prev,Number(typ.default_user_group_id)])); },[typeId]); - // When start date changes: match end date (both modes) unless user set it manually + // When start date changes: recalculate end preserving duration useEffect(()=>{ - if(!userSetEndTime.current) setEd(sd); + if(!mountedRef.current) return; + if(!sd||!st) return; + const start=buildISO(sd,st); + if(!start) return; + const durMins = (event && savedDurMins) ? savedDurMins : (localTypes.find(t=>t.id===Number(typeId))?.default_duration_hrs||1)*60; + const endIso=addHours(start,durMins/60); + setEd(toDateIn(endIso)); setEt(toTimeIn(endIso)); },[sd]); // When start time changes: recompute end preserving duration useEffect(()=>{ + if(!mountedRef.current) return; if(!sd||!st) return; - if(userSetEndTime.current && event) { - // Editing with user-preserved end: maintain saved duration - const durMins = savedDurMins || 60; - const start=buildISO(sd,st); - if(start){ setEd(toDateIn(addHours(start,durMins/60))); setEt(toTimeIn(addHours(start,durMins/60))); } - return; - } - const typ=localTypes.find(t=>t.id===Number(typeId)); - const dur=typ?.default_duration_hrs||1; const start=buildISO(sd,st); - if(start){ setEd(toDateIn(addHours(start,dur))); setEt(toTimeIn(addHours(start,dur))); } + if(!start) return; + const durMins = (event && savedDurMins) ? savedDurMins : (localTypes.find(t=>t.id===Number(typeId))?.default_duration_hrs||1)*60; + setEd(toDateIn(addHours(start,durMins/60))); + setEt(toTimeIn(addHours(start,durMins/60))); },[st]); + // Mark mounted after all effects have registered — effects skip on initial render + useEffect(()=>{ mountedRef.current = true; },[]); + const toggleGrp=id=>setGrps(prev=>{const n=new Set(prev);n.has(id)?n.delete(id):n.add(id);return n;}); const groupsRequired=track; // when tracking, groups are required