diff --git a/backend/package.json b/backend/package.json index 8ef3706..ce1923c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.11.3", + "version": "0.11.4", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index 5378f43..06c90c0 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.11.3}" +VERSION="${1:-0.11.4}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index 8d7356a..83df088 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.11.3", + "version": "0.11.4", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/MobileEventForm.jsx b/frontend/src/components/MobileEventForm.jsx index d0eb272..6b643ae 100644 --- a/frontend/src/components/MobileEventForm.jsx +++ b/frontend/src/components/MobileEventForm.jsx @@ -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'); diff --git a/frontend/src/components/SchedulePage.jsx b/frontend/src/components/SchedulePage.jsx index 7b07e74..686d8ae 100644 --- a/frontend/src/components/SchedulePage.jsx +++ b/frontend/src/components/SchedulePage.jsx @@ -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(sto) 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 */}
Filter Events
- {setSelDate(d);setPanel('calendar');}} eventDates={eventDates}/> + { + setSelDate(d); + setPanel('calendar'); + setFilterFromDate(d); + setFilterKeyword(''); + setFilterTypeId(''); + setFilterAvailability(false); + }} eventDates={eventDates}/>
{/* List view filters — only shown in Schedule list view */} @@ -1287,7 +1319,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel {(filterKeyword||filterTypeId||filterAvailability) && ( )} @@ -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 ( - ); @@ -1355,7 +1387,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel {/* Calendar or panel content */}
- {panel === 'calendar' && view === 'schedule' &&
} + {panel === 'calendar' && view === 'schedule' &&
} {panel === 'calendar' && view === 'day' && { const d=new Date(selDate); d.setDate(d.getDate()+dir); setSelDate(d); } : undefined}/>} {panel === 'calendar' && view === 'week' && } {panel === 'calendar' && view === 'month' && {setSelDate(d);setView('day');}}/>}