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
+
+
+
+{#if loading}
+ Laden…
+{:else if orte.length === 0}
+ Noch keine Orte angelegt.
+{:else}
+
+{/if}
+
+
+{#if ausfaelle.length > 0}
+ Geplante Ausfälle
+
+{/if}
+
+
+{#if showOrtForm}
+
+
+
{editOrtId ? 'Ort bearbeiten' : 'Neuer Ort'}
+
+
+
+{/if}
+
+
+{#if showAusfallForm}
+
+
+
Ausfall eintragen
+
{ortName(aOrtId)}
+
+
+
+{/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 @@