v0.10.4 fix event date/time functions

This commit is contained in:
2026-03-21 09:22:02 -04:00
parent 596fd0f969
commit f60730d0a5
5 changed files with 58 additions and 21 deletions

View File

@@ -253,17 +253,22 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
finally { setSavingType(false); }
};
// When start date changes, match end date
useEffect(() => { if(!event) setEd(sd); }, [sd]);
// When type or start time changes, auto-set end time
// When start date or start time changes, update end date/time to maintain duration
useEffect(() => {
if(!sd||!st) return;
const typ = localTypes.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]);
if(!start) return;
if(event) {
// Editing: only sync end date when start date changes, preserve manual end time
setEd(toDateIn(addHours(start, 0)));
} else {
// New event: always auto-set end to start + duration
setEd(toDateIn(addHours(start,dur)));
setEt(toTimeIn(addHours(start,dur)));
}
}, [sd, st, typeId]);
const handle = async () => {
if(!title.trim()) return toast('Title required','error');

View File

@@ -835,7 +835,7 @@ function parseKeywords(raw) {
return terms;
}
function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filterTypeId='', filterAvailability=false, isMobile=false }) {
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 terms=parseKeywords(filterKeyword);
@@ -845,11 +845,23 @@ function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filter
const expandedEvents = expandEvents(events, new Date(y,m,1), farFuture);
const now = new Date(); // exact now for end-time comparison
const isCurrentMonth = y === today.getFullYear() && m === today.getMonth();
// No filters: show from today (if current month) or start of month (future months) to end of month.
// Past months show nothing — navigate forward to see events.
// Filters active: today + all future events.
const from = hasFilters ? today : (isCurrentMonth ? today : new Date(y,m,1));
const to = hasFilters ? new Date(9999,11,31) : new Date(y,m+1,0,23,59,59);
// from/to logic:
// - filterFromDate set (mini-calendar click): show from that date to end of its month
// - hasFilters (keyword/type/avail): show from today to far future
// - month nav (no filterFromDate, no filters): show full month, including past events in grey
let from, to;
if (filterFromDate) {
const fd = new Date(filterFromDate); fd.setHours(0,0,0,0);
from = fd;
to = new Date(fd.getFullYear(), fd.getMonth()+1, 0, 23, 59, 59);
} else if (hasFilters) {
from = today;
to = new Date(9999,11,31);
} else {
// Full month — start of month to end of month, past events included (shown grey)
from = new Date(y,m,1);
to = new Date(y,m+1,0,23,59,59);
}
const filtered=expandedEvents.filter(e=>{
const s=new Date(e.start_at);
if(s<from||s>to) return false;
@@ -1165,6 +1177,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
const [filterKeyword, setFilterKeyword] = useState('');
const [filterTypeId, setFilterTypeId] = useState('');
const [filterAvailability, setFilterAvailability] = useState(false);
const [filterFromDate, setFilterFromDate] = useState(null); // set by mini-calendar click
const [inputFocused, setInputFocused] = useState(false); // hides footer when keyboard open on mobile
const [detailEvent, setDetailEvent] = useState(null);
const [loading, setLoading] = useState(true);
@@ -1187,13 +1200,25 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
return () => document.removeEventListener('mousedown', h);
}, [createOpen]);
const eventDates = new Set(events.map(e => e.start_at?.slice(0,10)));
const eventDates = new Set(events.map(e => {
if (!e.start_at) return null;
const d = new Date(e.start_at);
const pad = n => String(n).padStart(2,'0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
}).filter(Boolean));
const navDate = dir => {
const d = new Date(selDate);
if (view==='day') d.setDate(d.getDate()+dir);
else if (view==='week') d.setDate(d.getDate()+dir*7);
else d.setMonth(d.getMonth()+dir);
else {
d.setMonth(d.getMonth()+dir);
// Month nav: clear mini-calendar filter and show full month
setFilterFromDate(null);
setFilterKeyword('');
setFilterTypeId('');
setFilterAvailability(false);
}
setSelDate(d);
};
@@ -1258,7 +1283,14 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
{/* Mini calendar */}
<div style={{ padding:'8px 16px 16px' }}>
<div className="section-label" style={{ marginBottom:8 }}>Filter Events</div>
<MiniCalendar selected={selDate} onChange={d=>{setSelDate(d);setPanel('calendar');}} eventDates={eventDates}/>
<MiniCalendar selected={selDate} onChange={d=>{
setSelDate(d);
setPanel('calendar');
setFilterFromDate(d);
setFilterKeyword('');
setFilterTypeId('');
setFilterAvailability(false);
}} eventDates={eventDates}/>
</div>
{/* List view filters — only shown in Schedule list view */}
@@ -1287,7 +1319,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
{(filterKeyword||filterTypeId||filterAvailability) && (
<button
className="btn btn-secondary btn-sm"
onClick={()=>{setFilterKeyword('');setFilterTypeId('');setFilterAvailability(false);}}
onClick={()=>{setFilterKeyword('');setFilterTypeId('');setFilterAvailability(false);setFilterFromDate(null);}}
style={{ marginTop:8, width:'100%' }}
>Clear filters</button>
)}
@@ -1324,7 +1356,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
{allowedViews.map(v => {
const labels = { schedule:'Schedule', day:'Day', week:'Week', month:'Month' };
return (
<button key={v} onClick={()=>{setView(v);setPanel('calendar');setSelDate(new Date());setFilterKeyword('');setFilterTypeId('');setFilterAvailability(false);}} style={{ padding:'4px 10px', borderRadius:5, border:'none', cursor:'pointer', fontSize:12, fontWeight:600, background:view===v?'var(--surface)':'transparent', color:view===v?'var(--text-primary)':'var(--text-tertiary)', boxShadow:view===v?'0 1px 3px rgba(0,0,0,0.1)':'none', transition:'all 0.15s', whiteSpace:'nowrap' }}>
<button key={v} onClick={()=>{setView(v);setPanel('calendar');setSelDate(new Date());setFilterKeyword('');setFilterTypeId('');setFilterAvailability(false);setFilterFromDate(null);}} style={{ padding:'4px 10px', borderRadius:5, border:'none', cursor:'pointer', fontSize:12, fontWeight:600, background:view===v?'var(--surface)':'transparent', color:view===v?'var(--text-primary)':'var(--text-tertiary)', boxShadow:view===v?'0 1px 3px rgba(0,0,0,0.1)':'none', transition:'all 0.15s', whiteSpace:'nowrap' }}>
{labels[v]}
</button>
);
@@ -1355,7 +1387,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
{/* Calendar or panel content */}
<div style={{ flex:1, display:'flex', flexDirection:'column', overflow: view==='month' && panel==='calendar' ? 'hidden' : (panel==='eventForm'?'auto':'auto'), overflowX: panel==='eventForm'?'auto':'hidden' }}>
{panel === 'calendar' && view === 'schedule' && <div style={{paddingBottom: isMobile ? 80 : 0}}><ScheduleView events={events} selectedDate={selDate} onSelect={openDetail} filterKeyword={filterKeyword} filterTypeId={filterTypeId} filterAvailability={filterAvailability} isMobile={isMobile}/></div>}
{panel === 'calendar' && view === 'schedule' && <div style={{paddingBottom: isMobile ? 80 : 0}}><ScheduleView events={events} selectedDate={selDate} onSelect={openDetail} filterKeyword={filterKeyword} filterTypeId={filterTypeId} filterAvailability={filterAvailability} filterFromDate={filterFromDate} isMobile={isMobile}/></div>}
{panel === 'calendar' && view === 'day' && <DayView events={events} selectedDate={selDate} onSelect={openDetail} onSwipe={isMobile ? dir => { const d=new Date(selDate); d.setDate(d.getDate()+dir); setSelDate(d); } : undefined}/>}
{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');}}/>}