v0.10.4 fix event date/time functions
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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');}}/>}
|
||||
|
||||
Reference in New Issue
Block a user