import { formatDate, isSameDay, isToday, isPast, dayOfWeek, getISOWeekNumber } from '../utils.js'; const DOW_MONDAY = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']; const DOW_SUNDAY = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']; export function renderMonth(container, currentDate, events, onDayClick, onEventClick, weekStartDay = 'monday') { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const DOW = weekStartDay === 'sunday' ? DOW_SUNDAY : DOW_MONDAY; const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); // Start grid on the correct weekday const gridStart = new Date(firstDay); const offset = dayOfWeek(firstDay, weekStartDay); gridStart.setDate(gridStart.getDate() - offset); const cells = []; const d = new Date(gridStart); for (let i = 0; i < 42; i++) { cells.push(new Date(d)); d.setDate(d.getDate() + 1); } // Build event map keyed by date string const evMap = {}; events.forEach(ev => { const s = new Date(ev.start); const e = ev.allDay ? new Date(ev.end) : new Date(ev.end); // Spread multi-day events across cells const cur = new Date(s); cur.setHours(0, 0, 0, 0); const endNorm = new Date(e); endNorm.setHours(0, 0, 0, 0); if (ev.allDay && endNorm > cur) endNorm.setDate(endNorm.getDate() - 1); while (cur <= endNorm) { const key = dateKey(cur); if (!evMap[key]) evMap[key] = []; evMap[key].push(ev); cur.setDate(cur.getDate() + 1); } }); // Header: KW-Spalte + Wochentage const headerHtml = `
KW
` + DOW.map(d => `
${d}
`).join(''); // Build rows (6 weeks × 7 days) let cellsHtml = ''; for (let row = 0; row < 6; row++) { // KW cell for the first day of this row const rowFirstDay = cells[row * 7]; const kw = getISOWeekNumber(rowFirstDay); cellsHtml += `
${kw}
`; for (let col = 0; col < 7; col++) { const cell = cells[row * 7 + col]; const key = dateKey(cell); const cellEvs = (evMap[key] || []).slice().sort((a, b) => { if (a.allDay && !b.allDay) return -1; if (!a.allDay && b.allDay) return 1; return new Date(a.start) - new Date(b.start); }); const isOther = cell.getMonth() !== month; const todayClass = isToday(cell) ? 'today' : ''; const otherClass = isOther ? 'other-month' : ''; const numClass = isToday(cell) ? 'today' : ''; const MAX_VISIBLE = 3; const visible = cellEvs.slice(0, MAX_VISIBLE); const hiddenCount = cellEvs.length - MAX_VISIBLE; const evHtml = visible.map(ev => { const color = ev.color || ev.calendarColor || '#4285f4'; const pastClass = isPast(ev) ? 'past' : ''; const title = ev.allDay ? ev.title : `${fmtTime(new Date(ev.start))} ${ev.title}`; return `
${escHtml(title)}
`; }).join(''); const moreHtml = hiddenCount > 0 ? `
+${hiddenCount} weitere
` : ''; cellsHtml += `
${cell.getDate()}
${evHtml}${moreHtml}
`; } } container.innerHTML = `
${headerHtml}
${cellsHtml}
`; // Events container.querySelectorAll('.month-cell').forEach(cell => { cell.addEventListener('click', e => { const evEl = e.target.closest('.month-event'); if (evEl) { e.stopPropagation(); const ev = events.find(ev => ev.id === evEl.dataset.id && ev.url === evEl.dataset.url); if (ev) onEventClick(ev, evEl); return; } const moreEl = e.target.closest('.month-more'); if (moreEl) { e.stopPropagation(); onDayClick(new Date(moreEl.dataset.date + 'T00:00:00')); return; } onDayClick(new Date(cell.dataset.date + 'T00:00:00')); }); }); } function dateKey(d) { return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; } function fmtTime(d) { return d.toLocaleTimeString('de', { hour: '2-digit', minute: '2-digit' }); } function escHtml(s) { return String(s).replace(/&/g,'&').replace(//g,'>'); } function escAttr(s) { return String(s).replace(/"/g,'"').replace(/'/g,'''); }