import { $, api, esc, sortByName, showError, renderHeader, toIso, fromIso, formatDate, formatTime, getMe, setMe, } from './common.js'; renderHeader('kalender'); let members = []; let events = []; let me = getMe(); let editingId = null; // Termin, der gerade im Formular bearbeitet wird const todayIso = toIso(new Date()); let selectedDay = todayIso; let viewMonth = fromIso(todayIso); viewMonth.setDate(1); const memberName = (id) => members.find((m) => m.id === id)?.name ?? '(gelöscht)'; // ---------- "Ich bin" ---------- function renderMe() { if (me && !members.some((m) => m.id === me)) me = null; $('#me-select').innerHTML = '' + sortByName(members).map((m) => ``).join(''); $('#me-hint').hidden = !!me; $('#event-submit').disabled = !me && !editingId; } $('#me-select').addEventListener('change', (e) => { me = e.target.value || null; setMe(me); renderAll(); }); // ---------- Monatsansicht ---------- function renderCalendar() { const title = viewMonth.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' }); $('#month-title').textContent = title; // Raster beginnt am Montag vor (oder am) Monatsersten const start = new Date(viewMonth); start.setDate(1 - ((start.getDay() + 6) % 7)); const byDate = new Map(); for (const ev of events) { if (!byDate.has(ev.date)) byDate.set(ev.date, []); byDate.get(ev.date).push(ev); } let html = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'].map((d) => `
${d}
`).join(''); const day = new Date(start); for (let i = 0; i < 42; i++) { const iso = toIso(day); const classes = ['cal-day']; if (day.getMonth() !== viewMonth.getMonth()) classes.push('other'); if (day.getDay() === 0 || day.getDay() === 6) classes.push('weekend'); if (iso === todayIso) classes.push('today'); if (iso === selectedDay) classes.push('selected'); const dayEvents = byDate.get(iso) || []; html += ` `; day.setDate(day.getDate() + 1); } $('#calendar').innerHTML = html; } $('#calendar').addEventListener('click', (e) => { const cell = e.target.closest('[data-day]'); if (!cell) return; selectDay(cell.dataset.day); // Auf schmalen Bildschirmen stehen die Details unter dem Kalender if (matchMedia('(max-width: 999px)').matches) $('#day-title').scrollIntoView({ behavior: 'smooth', block: 'start' }); }); function selectDay(iso) { selectedDay = iso; const d = fromIso(iso); if (d.getMonth() !== viewMonth.getMonth()) viewMonth = new Date(d.getFullYear(), d.getMonth(), 1); if (!editingId) $('#event-date').value = iso; renderAll(); } function shiftMonth(delta) { viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + delta, 1); renderCalendar(); } $('#prev-month').addEventListener('click', () => shiftMonth(-1)); $('#next-month').addEventListener('click', () => shiftMonth(1)); $('#today-btn').addEventListener('click', () => selectDay(todayIso)); // ---------- Termine ---------- const downloadIcon = ''; function eventHtml(ev) { const going = ev.attendees.includes(me); const attendees = sortByName(ev.attendees.map((id) => ({ id, name: memberName(id) }))); const when = [formatDate(ev.date, true), formatTime(ev)].filter(Boolean).join(' · '); return `

${esc(ev.title)}

${ev.isGame ? 'Spiel' : 'Kein Spiel'}
${when}${ev.createdBy ? ` · eingetragen von ${esc(memberName(ev.createdBy))}` : ''}
${ev.note ? `
${esc(ev.note)}
` : ''}
${ev.attendees.length} zugesagt:
${attendees.length ? attendees.map((a) => `${esc(a.name)}`).join('') : 'noch niemand'}
${me ? (going ? `` : ``) : ''} Fahrt planen ${downloadIcon}Kalender
`; } function renderDay() { $('#day-title').textContent = fromIso(selectedDay).toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', }); const dayEvents = events.filter((e) => e.date === selectedDay); $('#day-events').innerHTML = dayEvents.length ? dayEvents.map(eventHtml).join('') : '

An diesem Tag ist noch nichts eingetragen.

'; } function renderUpcoming() { const upcoming = events.filter((e) => e.date >= todayIso); $('#upcoming').innerHTML = upcoming.length ? upcoming.map(eventHtml).join('') : '

Keine kommenden Termine.

'; } document.addEventListener('click', async (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const { action, id } = btn.dataset; try { if (action === 'edit') return startEdit(id); if (action === 'join') await api('PUT', `/events/${id}/attendees/${me}`); if (action === 'leave') await api('DELETE', `/events/${id}/attendees/${me}`); if (action === 'delete') { const ev = events.find((x) => x.id === id); if (!confirm(`Termin „${ev.title}“ am ${formatDate(ev.date)} wirklich löschen?`)) return; await api('DELETE', `/events/${id}`); if (editingId === id) stopEdit(); } await load(); } catch (err) { showError(err); } }); // ---------- Formular: eintragen und bearbeiten ---------- function startEdit(id) { const ev = events.find((x) => x.id === id); if (!ev) return; editingId = id; $('#event-form-title').textContent = 'Termin bearbeiten'; $('#event-submit').textContent = 'Speichern'; $('#event-cancel').hidden = false; $('#event-title').value = ev.title; $('#event-game').checked = ev.isGame; $('#event-date').value = ev.date; $('#event-time').value = ev.time || ''; $('#event-end').value = ev.endTime || ''; $('#event-note').value = ev.note || ''; renderMe(); $('#event-form').scrollIntoView({ behavior: 'smooth', block: 'center' }); $('#event-title').focus({ preventScroll: true }); } function stopEdit() { editingId = null; $('#event-form').reset(); $('#event-form-title').textContent = 'Termin an diesem Tag eintragen'; $('#event-submit').textContent = 'Eintragen'; $('#event-cancel').hidden = true; $('#event-date').value = selectedDay; renderMe(); } $('#event-cancel').addEventListener('click', stopEdit); $('#event-form').addEventListener('submit', async (e) => { e.preventDefault(); const body = { date: $('#event-date').value, title: $('#event-title').value, note: $('#event-note').value, isGame: $('#event-game').checked, time: $('#event-time').value, endTime: $('#event-end').value, }; try { if (editingId) { await api('PUT', `/events/${editingId}`, body); } else { await api('POST', '/events', { ...body, createdBy: me }); } const date = body.date; stopEdit(); await load(); if (date && date !== selectedDay) selectDay(date); } catch (err) { showError(err); } }); // ---------- Laden ---------- function renderAll() { renderMe(); renderCalendar(); renderDay(); renderUpcoming(); } async function load() { [members, events] = await Promise.all([api('GET', '/members'), api('GET', '/events')]); renderAll(); } $('#event-date').value = selectedDay; load().catch(showError);