v0.12.28 new modal window for event edit/delete

This commit is contained in:
2026-03-25 13:00:43 -04:00
parent 2b2e98fa48
commit ba91fce44c
8 changed files with 130 additions and 30 deletions

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import { api } from '../utils/api.js';
import ColourPickerSheet from './ColourPickerSheet.jsx';
import { useToast } from '../contexts/ToastContext.jsx';
@@ -319,6 +320,31 @@ function MobileRow({ icon, label, children, onPress, border=true }) {
);
}
// ── Recurring choice modal ────────────────────────────────────────────────────
function RecurringChoiceModal({ title, onConfirm, onCancel }) {
const [choice, setChoice] = useState('this');
return ReactDOM.createPortal(
<div className="modal-overlay" onClick={e=>e.target===e.currentTarget&&onCancel()}>
<div className="modal" style={{maxWidth:360}}>
<h3 style={{fontSize:17,fontWeight:700,margin:'0 0 20px'}}>{title}</h3>
<div style={{display:'flex',flexDirection:'column',gap:14,marginBottom:24}}>
{[['this','This event'],['future','This and following events'],['all','All events']].map(([val,label])=>(
<label key={val} style={{display:'flex',alignItems:'center',gap:10,fontSize:14,cursor:'pointer'}}>
<input type="radio" name="rec-scope" value={val} checked={choice===val} onChange={()=>setChoice(val)} style={{accentColor:'var(--primary)',width:16,height:16}}/>
{label}
</label>
))}
</div>
<div style={{display:'flex',justifyContent:'flex-end',gap:8}}>
<button className="btn btn-secondary btn-sm" onClick={onCancel}>Cancel</button>
<button className="btn btn-primary btn-sm" onClick={()=>onConfirm(choice)}>OK</button>
</div>
</div>
</div>,
document.body
);
}
// ── Main Mobile Event Form ────────────────────────────────────────────────────
export default function MobileEventForm({ event, eventTypes, userGroups, selectedDate, onSave, onCancel, onDelete, isToolManager }) {
const toast = useToast();
@@ -353,6 +379,7 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
const [description, setDescription] = useState(event?.description||'');
const [recRule, setRecRule] = useState(event?.recurrence_rule||null);
const [saving, setSaving] = useState(false);
const [showScopeModal, setShowScopeModal] = useState(false);
// Overlay state
const [showStartDate, setShowStartDate] = useState(false);
@@ -410,24 +437,22 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
setEt(toTimeIn(endIso));
}, [sd, st, typeId]);
const handle = async () => {
const handle = () => {
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');
// No past start times for new events
if(!event && !allDay && new Date(buildISO(sd,st)) < new Date()) return toast('Start date and time cannot be in the past','error');
if(!event && allDay && sd < toDateIn(new Date().toISOString())) return toast('Start date cannot be in the past','error');
if(event && event.recurrence_rule?.freq) { setShowScopeModal(true); return; }
doSave('this');
};
const doSave = async (scope) => {
setShowScopeModal(false);
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'); }
@@ -558,6 +583,7 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
{showStartDate && <CalendarPicker value={sd} onChange={v=>{setSd(v);setShowStartDate(false);}} onClose={()=>setShowStartDate(false)}/>}
{showEndDate && <CalendarPicker value={ed} onChange={v=>{setEd(v);setShowEndDate(false);}} onClose={()=>setShowEndDate(false)}/>}
{showRecurrence && <RecurrenceSheet value={recRule} onChange={v=>{setRecRule(v);}} onClose={()=>setShowRecurrence(false)}/>}
{showScopeModal && <RecurringChoiceModal title="Edit recurring event" onConfirm={doSave} onCancel={()=>setShowScopeModal(false)}/>}
{showTypeColourPicker && (
<ColourPickerSheet value={newTypeColour} onChange={setNewTypeColour} onClose={()=>setShowTypeColourPicker(false)} title="Event Type Colour"/>
)}