);
}
// ── Row — must be defined OUTSIDE the component to avoid focus loss ─────────────
function MobileRow({ icon, label, children, onPress, border=true }) {
return (
{icon}
{label &&
{label}
}
{children}
);
}
// ── Main Mobile Event Form ────────────────────────────────────────────────────
export default function MobileEventForm({ event, eventTypes, userGroups, selectedDate, onSave, onCancel, onDelete, isToolManager }) {
const toast = useToast();
// Use local date for default, not UTC slice (avoids off-by-one for UTC- timezones)
const defDate = selectedDate || new Date();
const _pad = n => String(n).padStart(2,'0');
const def = `${defDate.getFullYear()}-${_pad(defDate.getMonth()+1)}-${_pad(defDate.getDate())}`;
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 [showTypeColourPicker, setShowTypeColourPicker] = useState(false);
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);
const [et, setEt] = useState(event ? toTimeIn(event.end_at) : '10:00');
// Track the saved event duration (minutes) so editing preserves it
const savedDurMins = event
? (new Date(event.end_at) - new Date(event.start_at)) / 60000
: 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 [allDay, setAllDay] = useState(!!event?.all_day);
const [track, setTrack] = useState(!!event?.track_availability);
const [isPrivate, setIsPrivate] = useState(event ? !event.is_public : false);
const [groups, setGroups] = useState(new Set((event?.user_groups||[]).map(g=>g.id)));
const [location, setLocation] = useState(event?.location||'');
const [description, setDescription] = useState(event?.description||'');
const [recRule, setRecRule] = useState(event?.recurrence_rule||null);
const [saving, setSaving] = useState(false);
// Overlay state
const [showStartDate, setShowStartDate] = useState(false);
const [showEndDate, setShowEndDate] = useState(false);
const [showRecurrence, setShowRecurrence] = useState(false);
const [showGroups, setShowGroups] = useState(false);
// 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); }
};
// 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)
useEffect(() => {
if(!sd||!st) return;
const start = buildISO(sd,st);
if(!start) return;
const typeChanged = typeId !== prevTypeIdRef.current;
prevTypeIdRef.current = typeId;
let durMins;
if(!event || typeChanged) {
// New event or 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
durMins = savedDurMins || 60;
}
const endIso = addHours(start, durMins/60);
setEd(toDateIn(endIso));
setEt(toTimeIn(endIso));
}, [sd, st, typeId]);
const handle = async () => {
if(!title.trim()) return toast('Title required','error');
// Validation rules
const startMs = new Date(buildISO(sd, allDay?'00:00':st)).getTime();
const endMs = new Date(buildISO(ed, allDay?'23:59':et)).getTime();
if(ed < sd) return toast('End date cannot be before start date','error');
if(!allDay && endMs <= startMs && ed === sd) return toast('End time must be after start time, or set 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, description, isPublic:!isPrivate, trackAvailability:track, userGroupIds:[...groups], recurrenceRule:recRule||null };
let scope = 'this';
if(event && event.recurrence_rule?.freq) {
const choice = window.confirm('This is a recurring event.\n\nOK = Update this and all future occurrences\nCancel = Update this event only');
scope = choice ? 'future' : 'this';
}
const r = event ? await api.updateEvent(event.id, {...body, recurringScope:scope}) : await api.createEvent(body);
onSave(r.event);
} catch(e) { toast(e.message,'error'); }
finally { setSaving(false); }
};
const currentType = eventTypes.find(t=>t.id===Number(typeId));
return (