Termine: Wochen- und Monatsansicht via @event-calendar/core, umschaltbar zur Listenansicht

This commit is contained in:
rene 2026-05-20 20:33:09 +02:00
parent 59d94f9c47
commit 81f34905cf
4 changed files with 106 additions and 21 deletions

View file

@ -2,6 +2,8 @@
import { pb } from '$lib/pb';
import { onMount } from 'svelte';
import { RRule } from 'rrule';
import { Calendar, TimeGrid, DayGrid } from '@event-calendar/core';
import '@event-calendar/core/index.css';
import type { Termin, Gruppe, Verfuegbarkeit, Veranstaltungsort, OrtAusfall } from '$lib/types';
const isAdmin = () => !pb.authStore.record?.rolle || pb.authStore.record?.rolle === 'admin';
@ -14,6 +16,11 @@
let ausfaelle = $state<OrtAusfall[]>([]);
let loading = $state(true);
// Ansicht
type Ansicht = 'liste' | 'woche' | 'monat';
let ansicht = $state<Ansicht>('liste');
const calPlugins = [TimeGrid, DayGrid];
// Formular
let showForm = $state(false);
let editId = $state<string | null>(null);
@ -232,6 +239,45 @@
function istMeinTermin(t: Termin): boolean {
return t.durchfuehrender_id === userId();
}
function verfuegbarkeitFarbe(t: Termin): string {
switch (t.verfuegbarkeit) {
case 'bestaetigt': return '#16a34a';
case 'abgesagt': return '#dc2626';
case 'vertretung_gesucht': return '#7c3aed';
default: return '#1e40af';
}
}
const calEvents = $derived(termine.map(t => ({
id: t.id,
title: t.titel,
start: t.beginn,
end: t.ende ?? new Date(new Date(t.beginn).getTime() + 60 * 60 * 1000).toISOString(),
backgroundColor: verfuegbarkeitFarbe(t),
extendedProps: { termin: t },
})));
const calOptions = $derived({
view: ansicht === 'monat' ? 'dayGridMonth' : 'timeGridWeek',
locale: 'de',
firstDay: 1,
height: ansicht === 'monat' ? 'auto' : '72dvh',
events: calEvents,
slotMinTime: '07:00:00',
slotMaxTime: '23:00:00',
allDaySlot: false,
headerToolbar: {
start: 'prev,next today',
center: 'title',
end: '',
},
buttonText: { today: 'Heute' },
eventClick: (info: any) => {
const t: Termin = info.event.extendedProps.termin;
if (isAdmin()) bearbeiten(t);
},
});
</script>
<svelte:head><title>Termine — vereins.haus</title></svelte:head>
@ -241,6 +287,13 @@
<div class="top-rechts">
{#if isAdmin()}
<a href="/orte" class="btn-orte">Orte</a>
{/if}
<div class="ansicht-switcher">
<button class:aktiv={ansicht === 'liste'} onclick={() => ansicht = 'liste'} title="Listenansicht"></button>
<button class:aktiv={ansicht === 'woche'} onclick={() => ansicht = 'woche'} title="Wochenansicht">W</button>
<button class:aktiv={ansicht === 'monat'} onclick={() => ansicht = 'monat'} title="Monatsansicht">M</button>
</div>
{#if isAdmin()}
<button class="btn-primary" onclick={neuerTermin}>+ Termin</button>
{/if}
</div>
@ -254,6 +307,10 @@
{#if loading}
<p class="hint">Laden…</p>
{:else if ansicht !== 'liste'}
<div class="kalender-wrap">
<Calendar plugins={calPlugins} options={calOptions} />
</div>
{:else if termine.length === 0}
<p class="hint">Noch keine Termine geplant.</p>
{:else}
@ -497,13 +554,37 @@
<style>
.top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.top-rechts { display: flex; align-items: center; gap: 0.5rem; }
.top-rechts { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
h1 { font-size: 1.4rem; font-weight: 700; color: #1e293b; }
.btn-orte {
padding: 0.5rem 0.85rem; background: none;
border: 1.5px solid #e2e8f0; border-radius: 8px;
font-size: 0.85rem; color: #475569; text-decoration: none;
}
.ansicht-switcher {
display: flex; border: 1.5px solid #e2e8f0; border-radius: 8px; overflow: hidden;
}
.ansicht-switcher button {
padding: 0.4rem 0.75rem; background: none; border: none;
font-size: 0.82rem; font-weight: 600; color: #64748b; cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.ansicht-switcher button + button { border-left: 1px solid #e2e8f0; }
.ansicht-switcher button.aktiv { background: #1e40af; color: #fff; }
.kalender-wrap {
margin-bottom: 1rem;
--ec-today-bg-color: #eff6ff;
--ec-event-bg-color: #1e40af;
--ec-border-color: #e2e8f0;
--ec-text-color: #1e293b;
--ec-button-bg-color: #fff;
--ec-button-border-color: #e2e8f0;
--ec-button-text-color: #475569;
--ec-active-bg-color: #1e40af;
--ec-active-text-color: #fff;
}
.ausfall-warn { font-size: 0.75rem; color: #dc2626; font-weight: 600; }
.ort-link { font-size: 0.75rem; color: #1e40af; margin-left: 0.5rem; }
.hint { color: #94a3b8; font-size: 0.95rem; text-align: center; margin-top: 3rem; }