diff --git a/frontend/css/app.css b/frontend/css/app.css index 939f50b..d1e7920 100644 --- a/frontend/css/app.css +++ b/frontend/css/app.css @@ -554,11 +554,18 @@ a { color: var(--primary); text-decoration: none; } .allday-gutter { width: 56px; flex-shrink: 0; display: flex; align-items: center; justify-content: flex-end; padding-right: 6px; font-size: 10px; color: var(--text-3); } .allday-cols { display: flex; flex: 1; } .allday-col { flex: 1; border-left: 1px solid var(--border); padding: 2px; } +.col-span-tint { + position: absolute; inset: 0; pointer-events: none; z-index: 0; +} .allday-event { font-size: 11px; font-weight: 500; padding: 2px 6px; border-radius: 3px; margin-bottom: 2px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.allday-event.multiday-timed { + opacity: .88; + border-left: 3px solid rgba(255,255,255,.45); +} /* Time grid */ .week-body { display: flex; flex: 1; overflow-y: auto; position: relative; } diff --git a/frontend/js/views/week.js b/frontend/js/views/week.js index eed4c3f..b62a68e 100644 --- a/frontend/js/views/week.js +++ b/frontend/js/views/week.js @@ -16,8 +16,19 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC } // Separate all-day and timed events - const allDayEvs = events.filter(ev => ev.allDay); - const timedEvs = events.filter(ev => !ev.allDay); + const allDayEvs = events.filter(ev => ev.allDay); + const timedEvs = events.filter(ev => !ev.allDay); + // Multi-day timed events: timed but spanning more than one calendar day + const multiDayTimedEvs = timedEvs.filter(ev => !isSameDay(new Date(ev.start), new Date(ev.end))); + + // Returns true if event overlaps any part of the given day + function spansDay(ev, day) { + const evStart = new Date(ev.start); + const evEnd = new Date(ev.end); + const dayStart = new Date(day); dayStart.setHours(0, 0, 0, 0); + const dayEnd = new Date(day); dayEnd.setHours(24, 0, 0, 0); + return evStart < dayEnd && evEnd > dayStart; + } // ── KW Badge ────────────────────────────────────────── const kwNum = getISOWeekNumber(days[0]); @@ -43,12 +54,22 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC const d = new Date(day); d.setHours(0,0,0,0); return d >= s && d < e || isSameDay(d, s); }); - const inner = dayEvs.map(ev => { + const allDayHtml = dayEvs.map(ev => { const color = ev.color || ev.calendarColor || '#4285f4'; return `
${escHtml(ev.title)}
`; }).join(''); - return `
${inner}
`; + + // Multi-day timed events: show in all-day row for every day they span + const spanHtml = multiDayTimedEvs.filter(ev => spansDay(ev, day)).map(ev => { + const color = ev.color || ev.calendarColor || '#4285f4'; + const isStart = isSameDay(new Date(ev.start), day); + const label = isStart ? `${fmtTime(new Date(ev.start))} ${ev.title}` : ev.title; + return `
${escHtml(label)}
`; + }).join(''); + + return `
${allDayHtml}${spanHtml}
`; }).join(''); // ── Time column labels ──────────────────────────────── @@ -92,7 +113,14 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC `; }).join(''); + // Background tint for days covered by multi-day timed events + const tintHtml = multiDayTimedEvs.filter(ev => spansDay(ev, day)).map(ev => { + const color = ev.color || ev.calendarColor || '#4285f4'; + return `
`; + }).join(''); + return `
+ ${tintHtml} ${hourLines} ${evHtml}
`;