Files
rosterchirp/frontend/src/components/MobileEventForm.jsx
2026-03-18 09:45:58 -04:00

358 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useRef } from 'react';
import { api } from '../utils/api.js';
import { useToast } from '../contexts/ToastContext.jsx';
// ── Utilities ─────────────────────────────────────────────────────────────────
const DAYS = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
const SHORT_MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const DAY_PILLS = ['S','M','T','W','T','F','S'];
const DAY_KEYS = ['SU','MO','TU','WE','TH','FR','SA'];
const TIME_SLOTS = (() => {
const s=[];
for(let h=0;h<24;h++) for(let m of [0,30]) {
const hh=String(h).padStart(2,'0'), mm=String(m).padStart(2,'0');
const disp=`${h===0?12:h>12?h-12:h}:${mm} ${h<12?'AM':'PM'}`;
s.push({value:`${hh}:${mm}`,label:disp});
}
return s;
})();
function toDateIn(iso) { return iso ? iso.slice(0,10) : ''; }
function toTimeIn(iso) {
if(!iso) return '';
const d=new Date(iso);
const h=String(d.getHours()).padStart(2,'0'), m=d.getMinutes()<30?'00':'30';
return `${h}:${m}`;
}
function buildISO(d,t) { return d&&t?`${d}T${t}:00`:''; }
function addHours(iso,h){ const d=new Date(iso); d.setMinutes(d.getMinutes()+h*60); const pad=n=>String(n).padStart(2,'0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:00`; }
function fmtDateDisplay(iso) { if(!iso) return ''; const d=new Date(iso); return `${DAYS[d.getDay()]}, ${SHORT_MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`; }
function fmtTimeDisplay(slot) { const f=TIME_SLOTS.find(s=>s.value===slot); return f?f.label:slot; }
const FREQ_OPTIONS = [
{ value: '', label: 'Does not repeat' },
{ value: 'daily', label: 'Every day' },
{ value: 'weekly', label: 'Every week' },
{ value: 'monthly', label: 'Every month' },
{ value: 'yearly', label: 'Every year' },
{ value: 'custom', label: 'Custom…' },
];
function recurrenceLabel(rule) {
if (!rule || !rule.freq) return 'Does not repeat';
if (rule.freq === 'custom') { const unit = (rule.interval||1)===1 ? rule.unit : `${rule.interval} ${rule.unit}s`; return `Every ${unit}`; }
return FREQ_OPTIONS.find(o=>o.value===rule.freq)?.label || rule.freq;
}
// ── Toggle Switch ─────────────────────────────────────────────────────────────
function Toggle({ checked, onChange }) {
return (
<div onClick={()=>onChange(!checked)} style={{ width:44,height:24,borderRadius:12,background:checked?'var(--primary)':'var(--surface-variant)',cursor:'pointer',position:'relative',transition:'background 0.2s',flexShrink:0 }}>
<div style={{ position:'absolute',top:2,left:checked?22:2,width:20,height:20,borderRadius:'50%',background:'white',transition:'left 0.2s',boxShadow:'0 1px 3px rgba(0,0,0,0.2)' }}/>
</div>
);
}
// ── Calendar Picker Overlay ───────────────────────────────────────────────────
function CalendarPicker({ value, onChange, onClose }) {
const [cur, setCur] = useState(() => { const d = new Date(value||Date.now()); d.setDate(1); return d; });
const y=cur.getFullYear(), m=cur.getMonth(), first=new Date(y,m,1).getDay(), total=new Date(y,m+1,0).getDate(), today=new Date();
const cells=[]; for(let i=0;i<first;i++) cells.push(null); for(let d=1;d<=total;d++) cells.push(d);
const selDate = value ? new Date(value+'T00:00:00') : null;
return (
<div style={{ position:'fixed',inset:0,zIndex:200,display:'flex',alignItems:'flex-end' }} onClick={e=>e.target===e.currentTarget&&onClose()}>
<div style={{ width:'100%',background:'var(--surface)',borderRadius:'16px 16px 0 0',padding:20,boxShadow:'0 -4px 20px rgba(0,0,0,0.2)' }}>
<div style={{ fontSize:13,color:'var(--text-tertiary)',marginBottom:4 }}>Select Date</div>
<div style={{ fontSize:22,fontWeight:700,marginBottom:12 }}>
{selDate ? `${SHORT_MONTHS[selDate.getMonth()]} ${selDate.getDate()}, ${selDate.getFullYear()}` : '—'}
</div>
<div style={{ display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:8 }}>
<button onClick={()=>{const n=new Date(cur);n.setMonth(m-1);setCur(n);}} style={{ background:'none',border:'none',fontSize:20,cursor:'pointer',color:'var(--text-secondary)',padding:'4px 10px' }}></button>
<span style={{ fontWeight:600 }}>{MONTHS[m]} {y}</span>
<button onClick={()=>{const n=new Date(cur);n.setMonth(m+1);setCur(n);}} style={{ background:'none',border:'none',fontSize:20,cursor:'pointer',color:'var(--text-secondary)',padding:'4px 10px' }}></button>
</div>
<div style={{ display:'grid',gridTemplateColumns:'repeat(7,1fr)',gap:2,marginBottom:12 }}>
{['S','M','T','W','T','F','S'].map((d,i)=><div key={i} style={{ textAlign:'center',fontSize:11,fontWeight:600,color:'var(--text-tertiary)',padding:'4px 0' }}>{d}</div>)}
{cells.map((d,i) => {
if(!d) return <div key={i}/>;
const date=new Date(y,m,d);
const isSel = selDate && date.toDateString()===selDate.toDateString();
const isToday = date.toDateString()===today.toDateString();
return <div key={i} onClick={()=>onChange(`${y}-${String(m+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`)} style={{ textAlign:'center',padding:'8px 4px',borderRadius:'50%',cursor:'pointer',background:isSel?'var(--primary)':'transparent',color:isSel?'white':isToday?'var(--primary)':'var(--text-primary)',fontWeight:isToday&&!isSel?700:400,fontSize:14 }}>{d}</div>;
})}
</div>
<div style={{ display:'flex',justifyContent:'flex-end',gap:12 }}>
<button onClick={onClose} style={{ background:'none',border:'none',color:'var(--text-secondary)',fontSize:14,cursor:'pointer',padding:'8px 16px' }}>Cancel</button>
<button onClick={onClose} style={{ background:'none',border:'none',color:'var(--primary)',fontSize:14,fontWeight:700,cursor:'pointer',padding:'8px 16px' }}>OK</button>
</div>
</div>
</div>
);
}
// ── Recurrence Sheet ──────────────────────────────────────────────────────────
function RecurrenceSheet({ value, onChange, onClose }) {
const rule = value || {};
const [showCustom, setShowCustom] = useState(rule.freq==='custom');
const [customRule, setCustomRule] = useState(rule.freq==='custom' ? rule : {freq:'custom',interval:1,unit:'week',byDay:[],ends:'never',endDate:'',endCount:13});
const selectFreq = (freq) => {
if(freq==='custom') { setShowCustom(true); return; }
onChange(freq ? {freq} : null);
onClose();
};
const upd = (k,v) => setCustomRule(r=>({...r,[k]:v}));
if(showCustom) return (
<div style={{ position:'fixed',inset:0,zIndex:200,display:'flex',alignItems:'flex-end' }}>
<div style={{ width:'100%',background:'var(--surface)',borderRadius:'16px 16px 0 0',padding:20,boxShadow:'0 -4px 20px rgba(0,0,0,0.2)',maxHeight:'90vh',overflowY:'auto' }}>
<div style={{ display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:20 }}>
<button onClick={()=>setShowCustom(false)} style={{ background:'none',border:'none',cursor:'pointer',color:'var(--text-secondary)',display:'flex',alignItems:'center',gap:6,fontSize:14 }}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="15 18 9 12 15 6"/></svg>
</button>
<span style={{ fontWeight:700,fontSize:16 }}>Custom recurrence</span>
<button onClick={()=>{onChange(customRule);onClose();}} style={{ background:'none',border:'none',cursor:'pointer',color:'var(--primary)',fontSize:14,fontWeight:700 }}>Done</button>
</div>
<div style={{ marginBottom:16 }}>
<div style={{ fontSize:12,color:'var(--text-tertiary)',marginBottom:8 }}>Repeats every</div>
<div style={{ display:'flex',gap:10 }}>
<input type="number" className="input" min={1} max={99} value={customRule.interval||1} onChange={e=>upd('interval',Math.max(1,parseInt(e.target.value)||1))} style={{ width:70,textAlign:'center',fontSize:16 }}/>
<select className="input" value={customRule.unit||'week'} onChange={e=>upd('unit',e.target.value)} style={{ flex:1,fontSize:14 }}>
{['day','week','month','year'].map(u=><option key={u} value={u}>{u}{(customRule.interval||1)>1?'s':''}</option>)}
</select>
</div>
</div>
{(customRule.unit||'week')==='week' && (
<div style={{ marginBottom:16 }}>
<div style={{ fontSize:12,color:'var(--text-tertiary)',marginBottom:8 }}>Repeats on</div>
<div style={{ display:'flex',gap:8 }}>
{DAY_PILLS.map((d,i)=>{
const key=DAY_KEYS[i], sel=(customRule.byDay||[]).includes(key);
return <button key={key} type="button" onClick={()=>upd('byDay',sel?(customRule.byDay||[]).filter(x=>x!==key):[...(customRule.byDay||[]),key])} style={{ flex:1,aspectRatio:'1',borderRadius:'50%',border:'1px solid var(--border)',background:sel?'var(--primary)':'transparent',color:sel?'white':'var(--text-primary)',fontSize:12,fontWeight:600,cursor:'pointer',padding:4 }}>{d}</button>;
})}
</div>
</div>
)}
<div style={{ marginBottom:16 }}>
<div style={{ fontSize:12,color:'var(--text-tertiary)',marginBottom:8 }}>Ends</div>
{[['never','Never'],['on','On'],['after','After']].map(([val,lbl])=>(
<div key={val} style={{ display:'flex',alignItems:'center',gap:12,padding:'12px 0',borderBottom:'1px solid var(--border)' }}>
<div onClick={()=>upd('ends',val)} style={{ width:20,height:20,borderRadius:'50%',border:`2px solid ${(customRule.ends||'never')===val?'var(--primary)':'var(--border)'}`,display:'flex',alignItems:'center',justifyContent:'center',cursor:'pointer',flexShrink:0 }}>
{(customRule.ends||'never')===val&&<div style={{ width:10,height:10,borderRadius:'50%',background:'var(--primary)' }}/>}
</div>
<span style={{ flex:1,fontSize:15 }}>{lbl}</span>
{val==='on'&&(customRule.ends||'never')==='on'&&<input type="date" className="input" value={customRule.endDate||''} onChange={e=>upd('endDate',e.target.value)} style={{ width:150 }}/>}
{val==='after'&&(customRule.ends||'never')==='after'&&<><input type="number" className="input" min={1} max={999} value={customRule.endCount||13} onChange={e=>upd('endCount',parseInt(e.target.value)||1)} style={{ width:64,textAlign:'center' }}/><span style={{ fontSize:13,color:'var(--text-tertiary)' }}>occurrences</span></>}
</div>
))}
</div>
</div>
</div>
);
return (
<div style={{ position:'fixed',inset:0,zIndex:200,display:'flex',alignItems:'flex-end' }} onClick={e=>e.target===e.currentTarget&&onClose()}>
<div style={{ width:'100%',background:'var(--surface)',borderRadius:'16px 16px 0 0',padding:20,boxShadow:'0 -4px 20px rgba(0,0,0,0.2)' }}>
{FREQ_OPTIONS.map(opt=>(
<div key={opt.value} onClick={()=>selectFreq(opt.value)} style={{ display:'flex',alignItems:'center',gap:12,padding:'14px 4px',borderBottom:'1px solid var(--border)',cursor:'pointer' }}>
<div style={{ width:20,height:20,borderRadius:'50%',border:`2px solid ${(rule.freq||'')===(opt.value)?'var(--primary)':'var(--border)'}`,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0 }}>
{(rule.freq||'')===(opt.value)&&<div style={{ width:10,height:10,borderRadius:'50%',background:'var(--primary)' }}/>}
</div>
<span style={{ fontSize:16 }}>{opt.label}</span>
</div>
))}
</div>
</div>
);
}
// ── Row — must be defined OUTSIDE the component to avoid focus loss ─────────────
function MobileRow({ icon, label, children, onPress, border=true }) {
return (
<div onClick={onPress} style={{ display:'flex',alignItems:'center',gap:16,padding:'14px 20px',borderBottom:border?'1px solid var(--border)':'none',cursor:onPress?'pointer':'default',minHeight:52 }}>
<span style={{ color:'var(--text-tertiary)',flexShrink:0,width:20,textAlign:'center' }}>{icon}</span>
<div style={{ flex:1,minWidth:0 }}>
{label && <div style={{ fontSize:12,color:'var(--text-tertiary)',marginBottom:2 }}>{label}</div>}
{children}
</div>
</div>
);
}
// ── Main Mobile Event Form ────────────────────────────────────────────────────
export default function MobileEventForm({ event, eventTypes, userGroups, selectedDate, onSave, onCancel, onDelete, isToolManager }) {
const toast = useToast();
const def = selectedDate ? selectedDate.toISOString().slice(0,10) : new Date().toISOString().slice(0,10);
const [title, setTitle] = useState(event?.title||'');
const [typeId, setTypeId] = useState(event?.event_type_id ? String(event.event_type_id) : '');
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');
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);
// Auto-set typeId to default event type
useEffect(() => {
if(!event && typeId==='' && eventTypes.length>0) {
const def = eventTypes.find(t=>t.is_default) || eventTypes[0];
if(def) setTypeId(String(def.id));
}
}, [eventTypes]);
// When start date changes, match end date
useEffect(() => { if(!event) setEd(sd); }, [sd]);
// When type or start time changes, auto-set end time
useEffect(() => {
if(!sd||!st) return;
const typ = eventTypes.find(t=>t.id===Number(typeId));
const dur = typ?.default_duration_hrs||1;
const start = buildISO(sd,st);
if(start && !event) { setEd(toDateIn(addHours(start,dur))); setEt(toTimeIn(addHours(start,dur))); }
}, [typeId, st]);
const handle = async () => {
if(!title.trim()) return toast('Title required','error');
setSaving(true);
try {
const body = { title:title.trim(), eventTypeId:typeId||null, startAt:allDay?`${sd}T00:00:00`:buildISO(sd,st), endAt:allDay?`${ed}T23:59:59`:buildISO(ed,et), allDay, location, description, isPublic:!isPrivate, trackAvailability:track, userGroupIds:[...groups], recurrenceRule:recRule||null };
const r = event ? await api.updateEvent(event.id, body) : 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 (
<div style={{ display:'flex',flexDirection:'column',height:'100%',background:'var(--background)' }}>
{/* Header */}
<div style={{ display:'flex',alignItems:'center',justifyContent:'space-between',padding:'12px 16px',background:'var(--surface)',borderBottom:'1px solid var(--border)',flexShrink:0 }}>
<button onClick={onCancel} style={{ background:'none',border:'none',cursor:'pointer',color:'var(--text-secondary)',display:'flex',alignItems:'center',gap:4,fontSize:14 }}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
<span style={{ fontWeight:700,fontSize:16 }}>{event ? 'Edit Event' : 'New Event'}</span>
<button onClick={handle} disabled={saving} style={{ background:'var(--primary)',border:'none',cursor:'pointer',color:'white',borderRadius:20,padding:'8px 20px',fontSize:14,fontWeight:700,opacity:saving?0.6:1 }}>{saving?'…':'Save'}</button>
</div>
<div style={{ flex:1,overflowY:'auto' }}>
{/* Title */}
<div style={{ padding:'16px 20px',borderBottom:'1px solid var(--border)' }}>
<input value={title} onChange={e=>setTitle(e.target.value)} placeholder="Add title" style={{ width:'100%',border:'none',background:'transparent',fontSize:22,fontWeight:700,color:'var(--text-primary)',outline:'none' }}/>
</div>
{/* Event Type */}
<MobileRow icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M22 2H2l8 9.46V19l4 2v-8.54L22 2z"/></svg>} label="Event Type">
<select value={typeId} onChange={e=>setTypeId(e.target.value)} style={{ background:'transparent',border:'none',fontSize:15,color:'var(--text-primary)',width:'100%',outline:'none' }}>
{eventTypes.map(t=><option key={t.id} value={t.id}>{t.name}</option>)}
</select>
</MobileRow>
{/* All-day toggle */}
<div style={{ display:'flex',alignItems:'center',padding:'14px 20px',borderBottom:'1px solid var(--border)' }}>
<span style={{ color:'var(--text-tertiary)',width:20,textAlign:'center',marginRight:16 }}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></span>
<span style={{ flex:1,fontSize:15 }}>All day</span>
<Toggle checked={allDay} onChange={setAllDay}/>
</div>
{/* Start date/time */}
<div style={{ display:'flex',alignItems:'center',padding:'12px 20px 6px 56px' }}>
<span onClick={()=>setShowStartDate(true)} style={{ flex:1,fontSize:15,cursor:'pointer' }}>{fmtDateDisplay(sd)}</span>
{!allDay && (
<select value={st} onChange={e=>setSt(e.target.value)} style={{ fontSize:15,color:'var(--primary)',fontWeight:600,background:'transparent',border:'none',outline:'none',cursor:'pointer' }}>
{TIME_SLOTS.map(s=><option key={s.value} value={s.value}>{s.label}</option>)}
</select>
)}
</div>
{/* End date/time */}
<div onClick={()=>setShowEndDate(true)} style={{ display:'flex',alignItems:'center',padding:'6px 20px 14px 56px',cursor:'pointer',borderBottom:'1px solid var(--border)' }}>
<span style={{ flex:1,fontSize:15,color:'var(--text-secondary)' }}>{fmtDateDisplay(ed)}</span>
{!allDay && (
<select value={et} onChange={e=>setEt(e.target.value)} onClick={e=>e.stopPropagation()} style={{ fontSize:15,color:'var(--primary)',fontWeight:600,background:'transparent',border:'none',outline:'none' }}>
{TIME_SLOTS.map(s=><option key={s.value} value={s.value}>{s.label}</option>)}
</select>
)}
</div>
{/* Recurrence */}
<MobileRow icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>} onPress={()=>setShowRecurrence(true)}>
<span style={{ fontSize:15 }}>{recurrenceLabel(recRule)}</span>
</MobileRow>
{/* Track Availability */}
<div style={{ display:'flex',alignItems:'center',padding:'14px 20px',borderBottom:'1px solid var(--border)' }}>
<span style={{ color:'var(--text-tertiary)',width:20,textAlign:'center',marginRight:16 }}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span>
<span style={{ flex:1,fontSize:15 }}>Track Availability</span>
<Toggle checked={track} onChange={setTrack}/>
</div>
{/* Groups */}
<div>
<div onClick={()=>setShowGroups(!showGroups)} style={{ display:'flex',alignItems:'center',padding:'14px 20px',borderBottom:'1px solid var(--border)',cursor:'pointer' }}>
<span style={{ color:'var(--text-tertiary)',width:20,textAlign:'center',marginRight:16 }}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></span>
<span style={{ flex:1,fontSize:15 }}>{groups.size>0 ? `${groups.size} group${groups.size!==1?'s':''} selected` : 'Add Groups'}</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--text-tertiary)" strokeWidth="2"><polyline points={showGroups?"18 15 12 9 6 15":"6 9 12 15 18 9"}/></svg>
</div>
{showGroups && userGroups.map(g=>(
<label key={g.id} style={{ display:'flex',alignItems:'center',gap:14,padding:'12px 20px 12px 56px',borderBottom:'1px solid var(--border)',cursor:'pointer' }}>
<input type="checkbox" checked={groups.has(g.id)} onChange={()=>setGroups(prev=>{const n=new Set(prev);n.has(g.id)?n.delete(g.id):n.add(g.id);return n;})} style={{ width:18,height:18,accentColor:'var(--primary)' }}/>
<span style={{ fontSize:15 }}>{g.name}</span>
</label>
))}
</div>
{/* Private Event */}
<div style={{ display:'flex',alignItems:'center',padding:'14px 20px',borderBottom:'1px solid var(--border)' }}>
<span style={{ color:'var(--text-tertiary)',width:20,textAlign:'center',marginRight:16 }}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg></span>
<span style={{ flex:1,fontSize:15 }}>Private Event</span>
<Toggle checked={isPrivate} onChange={setIsPrivate}/>
</div>
{/* Location */}
<MobileRow icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>}>
<input value={location} onChange={e=>setLocation(e.target.value)} placeholder="Add location" style={{ width:'100%',border:'none',background:'transparent',fontSize:15,color:'var(--text-primary)',outline:'none' }}/>
</MobileRow>
{/* Description */}
<MobileRow icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="21" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="21" y1="18" x2="3" y2="18"/></svg>} border={false}>
<textarea value={description} onChange={e=>setDescription(e.target.value)} placeholder="Add description" rows={3} style={{ width:'100%',border:'none',background:'transparent',fontSize:15,color:'var(--text-primary)',outline:'none',resize:'none' }}/>
</MobileRow>
{/* Delete */}
{event && isToolManager && (
<div style={{ padding:'16px 20px' }}>
<button onClick={()=>onDelete(event)} style={{ width:'100%',padding:'14px',border:'1px solid var(--error)',borderRadius:'var(--radius)',background:'transparent',color:'var(--error)',fontSize:15,fontWeight:600,cursor:'pointer' }}>Delete Event</button>
</div>
)}
</div>
{/* Overlays */}
{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)}/>}
</div>
);
}