Files
hcrb/private/js/kalender.js
T
2026-10-10 13:54:33 +02:00

183 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
$, api, esc, sortByName, showError, renderHeader,
toIso, fromIso, formatDate, getMe, setMe,
} from './common.js';
renderHeader('kalender');
let members = [];
let events = [];
let me = getMe();
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 = '<option value="">– bitte auswählen –</option>' +
sortByName(members).map((m) =>
`<option value="${m.id}" ${m.id === me ? 'selected' : ''}>${esc(m.name)}</option>`).join('');
$('#me-hint').hidden = !!me;
$('#event-form button[type=submit]').disabled = !me;
}
$('#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) => `<div class="dow">${d}</div>`).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 (iso === todayIso) classes.push('today');
if (iso === selectedDay) classes.push('selected');
const dayEvents = byDate.get(iso) || [];
html += `
<button class="${classes.join(' ')}" data-day="${iso}">
<span class="num">${day.getDate()}</span>
${dayEvents.map((ev) => `<span class="ev ${ev.attendees.includes(me) ? 'mine' : ''}"
title="${esc(ev.title)} (${ev.attendees.length} zugesagt)">${esc(ev.title)} · ${ev.attendees.length}</span>`).join('')}
</button>`;
day.setDate(day.getDate() + 1);
}
$('#calendar').innerHTML = html;
}
$('#calendar').addEventListener('click', (e) => {
const cell = e.target.closest('[data-day]');
if (!cell) return;
selectedDay = cell.dataset.day;
const d = fromIso(selectedDay);
if (d.getMonth() !== viewMonth.getMonth()) viewMonth = new Date(d.getFullYear(), d.getMonth(), 1);
renderAll();
$('#day-title').scrollIntoView({ behavior: 'smooth', block: 'start' });
});
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', () => {
selectedDay = todayIso;
viewMonth = fromIso(todayIso);
viewMonth.setDate(1);
renderAll();
});
// ---------- Termine ----------
function eventHtml(ev) {
const going = ev.attendees.includes(me);
const attendees = sortByName(ev.attendees.map((id) => ({ id, name: memberName(id) })));
return `
<div class="event">
<h3>${esc(ev.title)}</h3>
<div class="when">${formatDate(ev.date, true)}${ev.createdBy ? ` · eingetragen von ${esc(memberName(ev.createdBy))}` : ''}</div>
${ev.note ? `<div class="note">${esc(ev.note)}</div>` : ''}
<div class="muted">${ev.attendees.length} zugesagt:</div>
<div class="chips">
${attendees.length
? attendees.map((a) => `<span class="chip ${a.id === me ? 'me' : ''}">${esc(a.name)}</span>`).join('')
: '<span class="muted">noch niemand</span>'}
</div>
<div class="row wrap">
${me ? (going
? `<button data-action="leave" data-id="${ev.id}">Absagen</button>`
: `<button class="primary" data-action="join" data-id="${ev.id}">Ich bin dabei</button>`) : ''}
<a class="button" href="/fahrt?termin=${ev.id}">Fahrt planen</a>
<button class="link danger" data-action="delete" data-id="${ev.id}">Löschen</button>
</div>
</div>`;
}
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('')
: '<p class="muted">An diesem Tag ist noch kein Spieltag eingetragen.</p>';
}
function renderUpcoming() {
const upcoming = events.filter((e) => e.date >= todayIso);
$('#upcoming').innerHTML = upcoming.length
? upcoming.map(eventHtml).join('')
: '<p class="muted">Keine kommenden Spieltage.</p>';
}
document.addEventListener('click', async (e) => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const { action, id } = btn.dataset;
try {
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(`Spieltag „${ev.title}“ am ${formatDate(ev.date)} wirklich löschen?`)) return;
await api('DELETE', `/events/${id}`);
}
await load();
} catch (err) {
showError(err);
}
});
$('#event-form').addEventListener('submit', async (e) => {
e.preventDefault();
try {
await api('POST', '/events', {
date: selectedDay,
title: $('#event-title').value,
note: $('#event-note').value,
createdBy: me,
});
e.target.reset();
await load();
} 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();
}
load().catch(showError);