v0.11.5 event form bug fixes

This commit is contained in:
2026-03-21 10:08:10 -04:00
parent f60730d0a5
commit 60df2cf97e
6 changed files with 116 additions and 43 deletions

View File

@@ -333,7 +333,9 @@ function FormRow({ label, children, required }) {
// ── Event Form ────────────────────────────────────────────────────────────────
function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCancel, onDelete, isToolManager }) {
const toast=useToast();
const def=selectedDate?selectedDate.toISOString().slice(0,10):new Date().toISOString().slice(0,10);
const _defD = selectedDate || new Date();
const _p = n => String(n).padStart(2,'0');
const def = `${_defD.getFullYear()}-${_p(_defD.getMonth()+1)}-${_p(_defD.getDate())}`;
const [title,setTitle]=useState(event?.title||'');
const [typeId,setTypeId]=useState(event?.event_type_id||'');
const [sd,setSd]=useState(event?toDateIn(event.start_at):def);
@@ -363,6 +365,11 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
// Track whether the user has manually changed the end time (vs auto-computed)
const userSetEndTime = useRef(!!event); // editing mode: treat saved end as user-set
// Duration of the saved event in minutes (preserved when editing with same type)
const savedDurMins = event
? (new Date(event.end_at) - new Date(event.start_at)) / 60000
: null;
const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : '');
// When event type changes:
// - Creating: always apply the type's duration to compute end time
@@ -373,21 +380,16 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
const typ=localTypes.find(t=>t.id===Number(typeId));
const start=buildISO(sd,st);
if(!start) return;
if(!event) {
// Creating new event — always apply duration (default 1hr)
const typeChanged = typeId !== prevTypeIdRef.current;
prevTypeIdRef.current = String(typeId);
if(!event || typeChanged) {
// New event or type change: apply eventType duration
const dur=typ?.default_duration_hrs||1;
setEd(toDateIn(addHours(start,dur)));
setEt(toTimeIn(addHours(start,dur)));
const endIso=addHours(start,dur);
setEd(toDateIn(endIso)); setEt(toTimeIn(endIso));
userSetEndTime.current = false;
} else {
// Editing — only update end time if the new type has an explicit duration
if(typ?.default_duration_hrs) {
setEd(toDateIn(addHours(start,typ.default_duration_hrs)));
setEt(toTimeIn(addHours(start,typ.default_duration_hrs)));
userSetEndTime.current = false;
}
// else: keep existing saved end time — do nothing
}
// 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]);
@@ -396,17 +398,20 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
if(!userSetEndTime.current) setEd(sd);
},[sd]);
// When start time changes: recompute end using current duration offset
// When start time changes: recompute end preserving duration
useEffect(()=>{
if(!sd||!st) return;
if(userSetEndTime.current) return; // user already picked a specific end time — respect it
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){ setEd(toDateIn(addHours(start,dur))); setEt(toTimeIn(addHours(start,dur))); }
},[st]);
const toggleGrp=id=>setGrps(prev=>{const n=new Set(prev);n.has(id)?n.delete(id):n.add(id);return n;});
@@ -416,6 +421,8 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
if(!title.trim()) return toast('Title required','error');
if(!allDay&&(!sd||!st||!ed||!et)) return toast('Start and end required','error');
if(groupsRequired&&grps.size===0) return toast('Select at least one group for availability tracking','error');
if(ed<sd) return toast('End date cannot be before start date','error');
if(!allDay&&ed===sd&&buildISO(ed,et)<=buildISO(sd,st)) return toast('End time must be after start time, or use a later end date','error');
setSaving(true);
try{
const body={title:title.trim(),eventTypeId:typeId||null,startAt:allDay?buildISO(sd,'00:00'):buildISO(sd,st),endAt:allDay?buildISO(ed,'23:59'):buildISO(ed,et),allDay,location:loc,description:desc,isPublic:pub,trackAvailability:track,userGroupIds:[...grps],recurrenceRule:recRule||null};
@@ -460,7 +467,11 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
{TIME_SLOTS.map(s=><option key={s.value} value={s.value}>{s.label}</option>)}
</select>
<span style={{color:'var(--text-tertiary)',fontSize:13,flexShrink:0}}>to</span>
<select className="input" value={et} onChange={e=>{setEt(e.target.value);userSetEndTime.current=true;}} style={{width:120,flexShrink:0}}>
<select className="input" value={et} onChange={e=>{
const newEt=e.target.value; setEt(newEt); userSetEndTime.current=true;
// Overnight: if end < start on same day, advance end date
if(sd===ed && newEt<=st){ const d=new Date(buildISO(sd,st)); d.setDate(d.getDate()+1); const p=n=>String(n).padStart(2,'0'); setEd(`${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}`); }
}} style={{width:120,flexShrink:0}}>
{TIME_SLOTS.map(s=><option key={s.value} value={s.value}>{s.label}</option>)}
</select>
<input type="date" className="input" value={ed} onChange={e => {setEd(e.target.value);userSetEndTime.current=true;}} style={{width:150,flexShrink:0}}/>