v0.12.28 new modal window for event edit/delete
This commit is contained in:
@@ -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"/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user