Feature: Mehrtägige Termine in Wochen-/Tagesansicht vollständig anzeigen

Timed-Events die mehrere Tage überspannen werden neu in der Ganztags-Zeile für jeden betroffenen Tag als Bar angezeigt (am Starttag mit Uhrzeit). Die Tagesspalten erhalten einen 15%-Farbhintergrund (col-span-tint) um die Abdeckung zu visualisieren.
This commit is contained in:
2026-04-08 14:47:11 +02:00
parent 77936b3b8d
commit e317b799d0
2 changed files with 39 additions and 4 deletions

View File

@@ -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; }

View File

@@ -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 `<div class="allday-event" style="background:${color};color:#fff"
data-id="${ev.id}" data-url="${escAttr(ev.url)}" title="${escAttr(ev.title)}">${escHtml(ev.title)}</div>`;
}).join('');
return `<div class="allday-col" data-date="${key}">${inner}</div>`;
// 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 `<div class="allday-event multiday-timed" style="background:${color};color:#fff"
data-id="${ev.id}" data-url="${escAttr(ev.url)}" title="${escAttr(ev.title)}">${escHtml(label)}</div>`;
}).join('');
return `<div class="allday-col" data-date="${key}">${allDayHtml}${spanHtml}</div>`;
}).join('');
// ── Time column labels ────────────────────────────────
@@ -92,7 +113,14 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC
</div>`;
}).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 `<div class="col-span-tint" style="background:${color}26"></div>`;
}).join('');
return `<div class="week-day-col" data-date="${key}" style="height:${hourH * 24}px">
${tintHtml}
${hourLines}
${evHtml}
</div>`;