diff --git a/app/src/lib/types.ts b/app/src/lib/types.ts index 7bc4aed..bcc34d3 100644 --- a/app/src/lib/types.ts +++ b/app/src/lib/types.ts @@ -53,6 +53,25 @@ export interface Gruppe { trainer_ids: string[]; } +export type OrtTyp = 'halle' | 'platz' | 'gebaeude' | 'sonstiges'; + +export interface Veranstaltungsort { + id: string; + verein_id: string; + name: string; + adresse?: string; + typ?: OrtTyp; + aktiv: boolean; +} + +export interface OrtAusfall { + id: string; + ort_id: string; + von: string; + bis: string; + grund?: string; +} + export interface Einladung { id: string; verein_id: string; @@ -92,6 +111,7 @@ export interface Termin { verfuegbarkeit?: Verfuegbarkeit; rrule?: string; serie_id?: string; + ort_id?: string; } export interface Nachricht { diff --git a/app/src/routes/(app)/orte/+page.svelte b/app/src/routes/(app)/orte/+page.svelte new file mode 100644 index 0000000..eb8e937 --- /dev/null +++ b/app/src/routes/(app)/orte/+page.svelte @@ -0,0 +1,346 @@ + + +Veranstaltungsorte — vereins.haus + +
+
+ ← Termine +

Veranstaltungsorte

+
+ +
+ +{#if loading} +

Laden…

+{:else if orte.length === 0} +

Noch keine Orte angelegt.

+{:else} + +{/if} + + +{#if ausfaelle.length > 0} +

Geplante Ausfälle

+ +{/if} + + +{#if showOrtForm} + +{/if} + + +{#if showAusfallForm} + +{/if} + + diff --git a/app/src/routes/(app)/termine/+page.svelte b/app/src/routes/(app)/termine/+page.svelte index dc56e6b..121b488 100644 --- a/app/src/routes/(app)/termine/+page.svelte +++ b/app/src/routes/(app)/termine/+page.svelte @@ -2,15 +2,17 @@ import { pb } from '$lib/pb'; import { onMount } from 'svelte'; import { RRule } from 'rrule'; - import type { Termin, Gruppe, Verfuegbarkeit } from '$lib/types'; + import type { Termin, Gruppe, Verfuegbarkeit, Veranstaltungsort, OrtAusfall } from '$lib/types'; const isAdmin = () => !pb.authStore.record?.rolle || pb.authStore.record?.rolle === 'admin'; const userId = () => pb.authStore.record?.id as string; - let termine = $state([]); - let gruppen = $state([]); - let alleUser = $state([]); - let loading = $state(true); + let termine = $state([]); + let gruppen = $state([]); + let alleUser = $state([]); + let orte = $state([]); + let ausfaelle = $state([]); + let loading = $state(true); // Formular let showForm = $state(false); @@ -22,6 +24,7 @@ let fOrt = $state(''); let fGruppeIds = $state([]); let fDurchfuehrenderId = $state(''); + let fOrtId = $state(''); let fWiederholung = $state(false); let fRhythmus = $state<'woechentlich' | 'zweıwoechentlich' | 'monatlich'>('woechentlich'); let fBis = $state(''); @@ -50,12 +53,14 @@ onMount(async () => { const vid = pb.authStore.record?.verein_id as string; - [termine, gruppen, alleUser] = await Promise.all([ + [termine, gruppen, alleUser, orte, ausfaelle] = await Promise.all([ pb.collection('termine').getFullList({ sort: 'beginn' }), pb.collection('gruppen').getFullList({ sort: 'name' }), isAdmin() ? pb.collection('users').getFullList({ filter: `verein_id = "${vid}" && rolle = "trainer"` }) : Promise.resolve([]), + pb.collection('veranstaltungsorte').getFullList({ sort: 'name', filter: `verein_id = "${vid}" && aktiv = true` }), + pb.collection('ort_ausfaelle').getFullList({ sort: 'von' }), ]); loading = false; }); @@ -71,7 +76,7 @@ function neuerTermin() { editId = null; fTitel = ''; fBeschr = ''; fBeginn = ''; fEnde = ''; - fOrt = ''; fGruppeIds = []; fDurchfuehrenderId = ''; + fOrt = ''; fOrtId = ''; fGruppeIds = []; fDurchfuehrenderId = ''; fWiederholung = false; fRhythmus = 'woechentlich'; fBis = ''; formError = ''; showForm = true; } @@ -79,19 +84,32 @@ function bearbeiten(t: Termin) { editId = t.id; fTitel = t.titel; fBeschr = t.beschreibung ?? ''; fBeginn = toLocal(t.beginn); fEnde = toLocal(t.ende); - fOrt = t.ort ?? ''; fGruppeIds = t.gruppe_ids ?? []; + fOrt = t.ort ?? ''; fOrtId = t.ort_id ?? ''; fGruppeIds = t.gruppe_ids ?? []; fDurchfuehrenderId = t.durchfuehrender_id ?? ''; - fWiederholung = false; // Einzeltermin bearbeiten, nie die ganze Serie + fWiederholung = false; formError = ''; showForm = true; } + // Ausfall-Check für einen Termin + function ortAusfall(t: Termin): OrtAusfall | undefined { + if (!t.ort_id) return undefined; + const d = t.beginn.slice(0, 10); + return ausfaelle.find(a => a.ort_id === t.ort_id && a.von <= d && a.bis >= d); + } + + function ortNameById(id: string | undefined): string { + if (!id) return ''; + return orte.find(o => o.id === id)?.name ?? ''; + } + function generiereTerminDaten(beginn: Date, rruleStr: string | null) { const vid = pb.authStore.record?.verein_id as string; return { verein_id: vid, titel: fTitel.trim(), beschreibung: fBeschr.trim() || null, - ort: fOrt.trim() || null, + ort: fOrtId ? null : (fOrt.trim() || null), + ort_id: fOrtId || null, gruppe_ids: fGruppeIds, durchfuehrender_id: fDurchfuehrenderId || null, verfuegbarkeit: fDurchfuehrenderId ? 'offen' : null, @@ -220,9 +238,12 @@

Termine

- {#if isAdmin()} - - {/if} +
+ {#if isAdmin()} + Orte + + {/if} +
{#if isAdmin() && offene.length > 0} @@ -254,9 +275,16 @@ {formatZeit(t.beginn)}{t.ende ? ' – ' + formatZeit(t.ende) : ''}{t.ort ? ' · ' + t.ort : ''} + {#if t.ort_id || t.ort} + {ortNameById(t.ort_id) || t.ort} + {/if} {#if t.gruppe_ids?.length} {gruppenLabel(t.gruppe_ids)} {/if} + {#if ortAusfall(t)} + {@const af = ortAusfall(t)!} + ⚠ Ort gesperrt{af.grund ? ': ' + af.grund : ''} + {/if} {#if t.durchfuehrender_id}
@@ -352,10 +380,28 @@
-
- - -
+ {#if orte.length > 0} +
+ + +
+ {#if !fOrtId} +
+ + +
+ {/if} + {:else} +
+ + +
+ {/if}
@@ -451,7 +497,15 @@