|
|
|
|
@@ -538,7 +538,7 @@ function ConfirmModal({ title, message, confirmLabel='Delete', onConfirm, onCanc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Event Form ────────────────────────────────────────────────────────────────
|
|
|
|
|
function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCancel, onDelete, isToolManager }) {
|
|
|
|
|
function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCancel, onDelete, isToolManager, userId }) {
|
|
|
|
|
const toast=useToast();
|
|
|
|
|
const _defD = selectedDate || new Date();
|
|
|
|
|
const _p = n => String(n).padStart(2,'0');
|
|
|
|
|
@@ -552,7 +552,7 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|
|
|
|
const [allDay,setAllDay]=useState(!!event?.all_day);
|
|
|
|
|
const [loc,setLoc]=useState(event?.location||'');
|
|
|
|
|
const [desc,setDesc]=useState(event?.description||'');
|
|
|
|
|
const [pub,setPub]=useState(event?!!event.is_public:true);
|
|
|
|
|
const [pub,setPub]=useState(event?!!event.is_public:!!isToolManager);
|
|
|
|
|
const [track,setTrack]=useState(!!event?.track_availability);
|
|
|
|
|
const [grps,setGrps]=useState(new Set((event?.user_groups||[]).map(g=>g.id)));
|
|
|
|
|
const [saving,setSaving]=useState(false);
|
|
|
|
|
@@ -628,12 +628,12 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|
|
|
|
useEffect(()=>{ mountedRef.current = true; },[]);
|
|
|
|
|
|
|
|
|
|
const toggleGrp=id=>setGrps(prev=>{const n=new Set(prev);n.has(id)?n.delete(id):n.add(id);return n;});
|
|
|
|
|
const groupsRequired=track; // when tracking, groups are required
|
|
|
|
|
const groupsRequired = track || !isToolManager; // tracking requires groups; non-managers always require groups
|
|
|
|
|
|
|
|
|
|
const handle=()=>{
|
|
|
|
|
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(groupsRequired&&grps.size===0) return toast('Select at least one group','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');
|
|
|
|
|
if(!event && !allDay && new Date(buildISO(sd,st)) < new Date()) return toast('Start date and time cannot be in the past','error');
|
|
|
|
|
@@ -645,7 +645,7 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|
|
|
|
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:loc,description:desc,isPublic:pub,trackAvailability:track,userGroupIds:[...grps],recurrenceRule:recRule||null};
|
|
|
|
|
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:isToolManager?pub:false,trackAvailability:track,userGroupIds:[...grps],recurrenceRule:recRule||null};
|
|
|
|
|
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);}
|
|
|
|
|
@@ -725,14 +725,14 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|
|
|
|
</div>
|
|
|
|
|
<p style={{fontSize:11,color:groupsRequired&&grps.size===0?'var(--error)':'var(--text-tertiary)',marginTop:4}}>
|
|
|
|
|
{grps.size===0
|
|
|
|
|
? (groupsRequired?'At least one group required for availability tracking':'No groups — event visible to all (if public)')
|
|
|
|
|
? (groupsRequired?'At least one group required':'No groups — event visible to all (if public)')
|
|
|
|
|
: `${grps.size} group${grps.size!==1?'s':''} selected`}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</FormRow>
|
|
|
|
|
|
|
|
|
|
{/* Visibility — only shown if groups selected OR tracking */}
|
|
|
|
|
{(grps.size>0||track) && (
|
|
|
|
|
{/* Visibility — only tool managers can set; regular users always create private events */}
|
|
|
|
|
{isToolManager && (grps.size>0||track) && (
|
|
|
|
|
<FormRow label="Visibility">
|
|
|
|
|
<label style={{display:'flex',alignItems:'center',gap:10,fontSize:13,cursor:'pointer',paddingTop:6}}>
|
|
|
|
|
<input type="checkbox" checked={!pub} onChange={e=>setPub(!e.target.checked)}/>
|
|
|
|
|
@@ -754,7 +754,7 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|
|
|
|
<div style={{display:'flex',gap:8,marginTop:8}}>
|
|
|
|
|
<button className="btn btn-primary btn-sm" onClick={handle} disabled={saving}>{saving?'Saving…':event?'Save Changes':'Create Event'}</button>
|
|
|
|
|
<button className="btn btn-secondary btn-sm" onClick={onCancel}>Cancel</button>
|
|
|
|
|
{event&&isToolManager&&<button className="btn btn-sm" style={{marginLeft:'auto',background:'var(--error)',color:'white'}} onClick={()=>onDelete(event)}>Delete</button>}
|
|
|
|
|
{event&&(isToolManager||(userId&&event.created_by===userId))&&<button className="btn btn-sm" style={{marginLeft:'auto',background:'var(--error)',color:'white'}} onClick={()=>onDelete(event)}>Delete</button>}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -764,7 +764,7 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Event Detail Modal ────────────────────────────────────────────────────────
|
|
|
|
|
function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isToolManager }) {
|
|
|
|
|
function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isToolManager, userId }) {
|
|
|
|
|
const toast=useToast();
|
|
|
|
|
const [myResp,setMyResp]=useState(event.my_response);
|
|
|
|
|
const [avail,setAvail]=useState(event.availability||[]);
|
|
|
|
|
@@ -800,7 +800,7 @@ function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isTool
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{display:'flex',gap:6,flexShrink:0}}>
|
|
|
|
|
{isToolManager&&<button className="btn btn-secondary btn-sm" onClick={()=>{onClose();onEdit();}}>Edit</button>}
|
|
|
|
|
{(isToolManager||(userId&&event.created_by===userId))&&<button className="btn btn-secondary btn-sm" onClick={()=>{onClose();onEdit();}}>Edit</button>}
|
|
|
|
|
<button className="btn-icon" onClick={onClose}><svg width="18" height="18" 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>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -1081,6 +1081,10 @@ function parseKeywords(raw) {
|
|
|
|
|
function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filterTypeId='', filterAvailability=false, filterFromDate=null, isMobile=false }) {
|
|
|
|
|
const y=selectedDate.getFullYear(), m=selectedDate.getMonth();
|
|
|
|
|
const today=new Date(); today.setHours(0,0,0,0);
|
|
|
|
|
const todayRef = useRef(null);
|
|
|
|
|
useEffect(()=>{
|
|
|
|
|
if(todayRef.current) todayRef.current.scrollIntoView({ block:'start', behavior:'instant' });
|
|
|
|
|
},[selectedDate.getFullYear(), selectedDate.getMonth()]);
|
|
|
|
|
const terms=parseKeywords(filterKeyword);
|
|
|
|
|
const hasFilters = terms.length > 0 || !!filterTypeId || filterAvailability;
|
|
|
|
|
// Only keyword/availability filters should shift the date window to today-onwards.
|
|
|
|
|
@@ -1135,9 +1139,13 @@ function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filter
|
|
|
|
|
? `No events — ${MONTHS[m]} ${y} is in the past`
|
|
|
|
|
: `No events in ${MONTHS[m]} ${y}`;
|
|
|
|
|
if(!filtered.length) return <div style={{textAlign:'center',padding:'60px 20px',color:'var(--text-tertiary)',fontSize:14}}>{emptyMsg}</div>;
|
|
|
|
|
let todayMarked = false;
|
|
|
|
|
return <>{filtered.map(e=>{
|
|
|
|
|
const s=new Date(e.start_at);
|
|
|
|
|
const end=new Date(e.end_at);
|
|
|
|
|
const sDay=new Date(s); sDay.setHours(0,0,0,0);
|
|
|
|
|
const isFirstTodayOrFuture = !todayMarked && sDay >= today;
|
|
|
|
|
if(isFirstTodayOrFuture) todayMarked = true;
|
|
|
|
|
const isPast = !e.all_day && end < now; // event fully ended
|
|
|
|
|
const col = isPast ? '#9ca3af' : (e.event_type?.colour||'#9ca3af');
|
|
|
|
|
const textColor = isPast ? 'var(--text-tertiary)' : 'var(--text-primary)';
|
|
|
|
|
@@ -1158,7 +1166,7 @@ function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filter
|
|
|
|
|
: BELL_ICON
|
|
|
|
|
);
|
|
|
|
|
return(
|
|
|
|
|
<div key={`${e.id}-${e.start_at}`} onClick={()=>onSelect(e)} style={{display:'flex',alignItems:'center',gap:rowGap,padding:rowPad,borderBottom:'1px solid var(--border)',cursor:'pointer',opacity:isPast?0.7:1}} onMouseEnter={el=>el.currentTarget.style.background='var(--background)'} onMouseLeave={el=>el.currentTarget.style.background=''}>
|
|
|
|
|
<div key={`${e.id}-${e.start_at}`} ref={isFirstTodayOrFuture ? todayRef : null} onClick={()=>onSelect(e)} style={{display:'flex',alignItems:'center',gap:rowGap,padding:rowPad,borderBottom:'1px solid var(--border)',cursor:'pointer',opacity:isPast?0.7:1}} onMouseEnter={el=>el.currentTarget.style.background='var(--background)'} onMouseLeave={el=>el.currentTarget.style.background=''}>
|
|
|
|
|
{/* Date column */}
|
|
|
|
|
<div style={{width:datW,textAlign:'center',flexShrink:0}}>
|
|
|
|
|
<div style={{fontSize:datFs,fontWeight:700,lineHeight:1,color:textColor}}>{s.getDate()}</div>
|
|
|
|
|
@@ -1254,7 +1262,12 @@ function DayView({ events: rawEvents, selectedDate, onSelect, onSwipe }) {
|
|
|
|
|
const tzLabel=`GMT${tzOff>=0?'+':'-'}${String(Math.floor(Math.abs(tzOff)/60)).padStart(2,'0')}`;
|
|
|
|
|
const scrollRef = useRef(null);
|
|
|
|
|
const touchRef = useRef({ x:0, y:0 });
|
|
|
|
|
useEffect(()=>{ if(scrollRef.current) scrollRef.current.scrollTop = 7 * HOUR_H; },[selectedDate]);
|
|
|
|
|
useEffect(()=>{
|
|
|
|
|
if(!scrollRef.current) return;
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const topPx = Math.max(0, now.getHours() * HOUR_H + (now.getMinutes() / 60) * HOUR_H - 2 * HOUR_H);
|
|
|
|
|
scrollRef.current.scrollTop = topPx;
|
|
|
|
|
},[selectedDate]);
|
|
|
|
|
const fmtHour = h => h===0?'12 AM':h<12?`${h} AM`:h===12?'12 PM':`${h-12} PM`;
|
|
|
|
|
const handleTouchStart = e => { touchRef.current = { x:e.touches[0].clientX, y:e.touches[0].clientY }; };
|
|
|
|
|
const handleTouchEnd = e => {
|
|
|
|
|
@@ -1325,7 +1338,12 @@ function WeekView({ events: rawEvents, selectedDate, onSelect }) {
|
|
|
|
|
const tzLabel=`GMT${tzOff>=0?'+':'-'}${String(Math.floor(Math.abs(tzOff)/60)).padStart(2,'0')}`;
|
|
|
|
|
const scrollRef = useRef(null);
|
|
|
|
|
const touchRef = useRef({ x:0, y:0 });
|
|
|
|
|
useEffect(()=>{ if(scrollRef.current) scrollRef.current.scrollTop = 7 * HOUR_H; },[selectedDate]);
|
|
|
|
|
useEffect(()=>{
|
|
|
|
|
if(!scrollRef.current) return;
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const topPx = Math.max(0, now.getHours() * HOUR_H + (now.getMinutes() / 60) * HOUR_H - 2 * HOUR_H);
|
|
|
|
|
scrollRef.current.scrollTop = topPx;
|
|
|
|
|
},[selectedDate]);
|
|
|
|
|
const fmtHour = h => h===0?'12 AM':h<12?`${h} AM`:h===12?'12 PM':`${h-12} PM`;
|
|
|
|
|
const handleTouchStart = e => { touchRef.current = { x:e.touches[0].clientX, y:e.touches[0].clientY }; };
|
|
|
|
|
const handleTouchEnd = e => {
|
|
|
|
|
@@ -1476,7 +1494,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
|
|
|
|
const contentRef = useRef(null);
|
|
|
|
|
|
|
|
|
|
const load = useCallback(() => {
|
|
|
|
|
const ugPromise = isToolManager ? api.getUserGroups() : Promise.resolve({ groups: [] });
|
|
|
|
|
const ugPromise = isToolManager ? api.getUserGroups() : api.getMyScheduleGroups();
|
|
|
|
|
Promise.all([api.getEvents(), api.getEventTypes(), ugPromise])
|
|
|
|
|
.then(([ev,et,ug]) => { setEvents(ev.events||[]); setEventTypes(et.eventTypes||[]); setUserGroups(ug.groups||[]); setLoading(false); })
|
|
|
|
|
.catch(() => setLoading(false));
|
|
|
|
|
@@ -1484,8 +1502,8 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
|
|
|
|
|
|
|
|
|
useEffect(() => { load(); }, [load]);
|
|
|
|
|
|
|
|
|
|
// Reset scroll to top whenever the selected date or view changes
|
|
|
|
|
useEffect(() => { if (contentRef.current) contentRef.current.scrollTop = 0; }, [selDate, view]);
|
|
|
|
|
// Reset scroll to top on date/view change; schedule view scrolls to today via ScheduleView's own effect
|
|
|
|
|
useEffect(() => { if (contentRef.current && view !== 'schedule') contentRef.current.scrollTop = 0; }, [selDate, view]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!createOpen) return;
|
|
|
|
|
@@ -1556,26 +1574,27 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
|
|
|
|
<div style={{ padding:'16px 16px 0' }}>
|
|
|
|
|
<div style={{ fontSize:16, fontWeight:700, marginBottom:12, color:'var(--text-primary)' }}>Team Schedule</div>
|
|
|
|
|
|
|
|
|
|
{/* Create button — styled like new-chat-btn */}
|
|
|
|
|
{isToolManager && (
|
|
|
|
|
<div style={{ position:'relative', marginBottom:12 }} ref={createRef}>
|
|
|
|
|
<button className="newchat-btn" onClick={() => setCreateOpen(v=>!v)} style={{ width:'100%', justifyContent:'center', gap:8 }}>
|
|
|
|
|
Create Event
|
|
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="6 9 12 15 18 9"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
{createOpen && (
|
|
|
|
|
<div style={{ position:'absolute', top:'100%', left:0, right:0, zIndex:100, background:'var(--surface-variant)', border:'1px solid var(--border)', borderRadius:'var(--radius)', marginTop:4, boxShadow:'0 4px 16px rgba(0,0,0,0.18)' }}>
|
|
|
|
|
{[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
|
|
|
|
|
{/* Create button — visible to all users */}
|
|
|
|
|
<div style={{ position:'relative', marginBottom:12 }} ref={createRef}>
|
|
|
|
|
<button className="newchat-btn" onClick={() => setCreateOpen(v=>!v)} style={{ width:'100%', justifyContent:'center', gap:8 }}>
|
|
|
|
|
Create Event
|
|
|
|
|
{isToolManager && <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="6 9 12 15 18 9"/></svg>}
|
|
|
|
|
</button>
|
|
|
|
|
{createOpen && (
|
|
|
|
|
<div style={{ position:'absolute', top:'100%', left:0, right:0, zIndex:100, background:'var(--surface-variant)', border:'1px solid var(--border)', borderRadius:'var(--radius)', marginTop:4, boxShadow:'0 4px 16px rgba(0,0,0,0.18)' }}>
|
|
|
|
|
{[
|
|
|
|
|
['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
|
|
|
|
|
...(isToolManager ? [
|
|
|
|
|
['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
|
|
|
|
|
['Bulk Event Import', ()=>{setPanel('bulkImport');setCreateOpen(false);}]
|
|
|
|
|
].map(([label,action])=>(
|
|
|
|
|
<button key={label} onClick={action} style={{display:'block',width:'100%',padding:'9px 16px',textAlign:'left',fontSize:14,background:'none',border:'none',cursor:'pointer',color:'var(--text-primary)'}}
|
|
|
|
|
onMouseEnter={e=>e.currentTarget.style.background='var(--background)'} onMouseLeave={e=>e.currentTarget.style.background=''}>{label}</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
['Bulk Event Import', ()=>{setPanel('bulkImport');setCreateOpen(false);}],
|
|
|
|
|
] : []),
|
|
|
|
|
].map(([label,action])=>(
|
|
|
|
|
<button key={label} onClick={action} style={{display:'block',width:'100%',padding:'9px 16px',textAlign:'left',fontSize:14,background:'none',border:'none',cursor:'pointer',color:'var(--text-primary)'}}
|
|
|
|
|
onMouseEnter={e=>e.currentTarget.style.background='var(--background)'} onMouseLeave={e=>e.currentTarget.style.background=''}>{label}</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Mini calendar */}
|
|
|
|
|
@@ -1691,10 +1710,10 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
|
|
|
|
{panel === 'calendar' && view === 'week' && <WeekView events={events} selectedDate={selDate} onSelect={openDetail}/>}
|
|
|
|
|
{panel === 'calendar' && view === 'month' && <MonthView events={events} selectedDate={selDate} onSelect={openDetail} onSelectDay={d=>{setSelDate(d);setView('day');}}/>}
|
|
|
|
|
|
|
|
|
|
{panel === 'eventForm' && isToolManager && !isMobile && (
|
|
|
|
|
{panel === 'eventForm' && !isMobile && (
|
|
|
|
|
<div style={{ padding:28, maxWidth:1024 }}>
|
|
|
|
|
<h2 style={{ fontSize:17, fontWeight:700, marginBottom:24 }}>{editingEvent?'Edit Event':'New Event'}</h2>
|
|
|
|
|
<EventForm event={editingEvent} userGroups={userGroups} eventTypes={eventTypes} selectedDate={selDate} isToolManager={isToolManager}
|
|
|
|
|
<EventForm event={editingEvent} userGroups={userGroups} eventTypes={eventTypes} selectedDate={selDate} isToolManager={isToolManager} userId={user.id}
|
|
|
|
|
onSave={handleSaved} onCancel={()=>{setPanel('calendar');setEditingEvent(null);setFilterKeyword('');setFilterTypeId('');}} onDelete={handleDelete}/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
@@ -1739,7 +1758,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
|
|
|
|
<MobileGroupManager onClose={() => setMobilePanel(null)}/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{panel === 'eventForm' && isToolManager && isMobile && (
|
|
|
|
|
{panel === 'eventForm' && isMobile && (
|
|
|
|
|
<div style={{ position:'fixed', top:0, left:0, right:0, bottom:0, zIndex:40, background:'var(--background)', display:'flex', flexDirection:'column' }}>
|
|
|
|
|
<MobileEventForm
|
|
|
|
|
event={editingEvent}
|
|
|
|
|
@@ -1747,6 +1766,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
|
|
|
|
eventTypes={eventTypes}
|
|
|
|
|
selectedDate={selDate}
|
|
|
|
|
isToolManager={isToolManager}
|
|
|
|
|
userId={user.id}
|
|
|
|
|
onSave={handleSaved}
|
|
|
|
|
onCancel={()=>{setPanel('calendar');setEditingEvent(null);setFilterKeyword('');setFilterTypeId('');}}
|
|
|
|
|
onDelete={handleDelete} />
|
|
|
|
|
@@ -1754,14 +1774,17 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Mobile FAB — same position as Messages newchat-fab */}
|
|
|
|
|
{isMobile && isToolManager && panel === 'calendar' && (
|
|
|
|
|
{isMobile && panel === 'calendar' && (
|
|
|
|
|
<div ref={createRef} style={{ position:'fixed', bottom:80, right:16, zIndex:30 }}>
|
|
|
|
|
<button className="newchat-fab" style={{ position:'static' }} onClick={() => setCreateOpen(v=>!v)}>
|
|
|
|
|
<button className="newchat-fab" style={{ position:'static' }} onClick={() => {
|
|
|
|
|
if (isToolManager) { setCreateOpen(v=>!v); }
|
|
|
|
|
else { setPanel('eventForm'); setEditingEvent(null); setFilterKeyword(''); setFilterTypeId(''); }
|
|
|
|
|
}}>
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" width="24" height="24">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
{createOpen && (
|
|
|
|
|
{isToolManager && createOpen && (
|
|
|
|
|
<div style={{ position:'absolute', bottom:'calc(100% + 8px)', right:0, zIndex:100, background:'var(--surface-variant)', border:'1px solid var(--border)', borderRadius:'var(--radius)', boxShadow:'0 -4px 16px rgba(0,0,0,0.15)', minWidth:180 }}>
|
|
|
|
|
{[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
|
|
|
|
|
['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
|
|
|
|
|
@@ -1779,6 +1802,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
|
|
|
|
<EventDetailModal
|
|
|
|
|
event={detailEvent}
|
|
|
|
|
isToolManager={isToolManager}
|
|
|
|
|
userId={user.id}
|
|
|
|
|
onClose={() => setDetailEvent(null)}
|
|
|
|
|
onEdit={() => { setEditingEvent(detailEvent); setPanel('eventForm'); setDetailEvent(null); }}
|
|
|
|
|
onAvailabilityChange={(resp) => {
|
|
|
|
|
|