stammtisch-hersbruck/frontend/src/lib/stores/events.svelte.ts
Daniel Michelberger 1061a8b9ad chore: Repo-Struktur mit frontend/ und backend/ aufsetzen
Die SvelteKit-App liegt unter frontend/, das Backend unter backend/ als
eigenständige PocketBase-Instanz mit Dockerfile, docker-compose und
versioniertem Schema.

- PocketBase-URL über PUBLIC_PB_URL konfigurierbar, Default bleibt die
  produktive Instanz https://api.stammtisch-hersbruck.de
- Schema als Snapshot-Migration der sechs fachlichen Collections
  (users, teams, events, runs, riders, times), abgezogen von der
  produktiven Instanz. Verifiziert: ein Erststart gegen leere pb_data
  legt alle sechs an, Felder und API-Rules stimmen überein.
- pb_data und pb_migrations als Bind-Mounts, damit Daten persistieren und
  im Admin-UI erzeugte Migrationen im Repo landen
- Veraltete Dokumentation entfernt: pocketbase_schema.json nannte
  Collections (stages, results, organizers), die es nicht gibt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 13:16:43 +02:00

116 lines
3.6 KiB
TypeScript

import { getContext, setContext } from 'svelte'
import { api } from './pocketbase.svelte'
import { app } from './app.svelte'
import { getTeamContext } from './teams.svelte'
import type { EventsResponse } from '$lib/types'
const KEY = Symbol('event')
export class EventStore {
records = $state<EventsResponse[]>([])
loading = $state(false)
error = $state<string | null>(null)
private unsubscribe: (() => void) | null = null
private teams = getTeamContext()
/** Records gefiltert nach aktivem Team */
get scoped(): EventsResponse[] {
const teamId = this.teams.activeId
if (!teamId) return []
return this.records.filter((r) => r.team === teamId)
}
async load() {
this.loading = true
this.error = null
try {
this.records = await api.collection('events').getFullList({
sort: '-created',
requestKey: null,
})
} catch (e: any) {
this.error = e.message ?? 'Fehler beim Laden der Events'
console.error(e)
} finally {
this.loading = false
}
}
subscribe() {
if (this.unsubscribe) return
api.collection('events').subscribe('*', (e) => {
const idx = this.records.findIndex((r) => r.id === e.record.id)
if (e.action === 'create' && idx === -1) this.records = [e.record, ...this.records]
else if (e.action === 'update' && idx !== -1) this.records[idx] = e.record
else if (e.action === 'delete' && idx !== -1) this.records = this.records.filter((r) => r.id !== e.record.id)
}).then((unsub) => {
this.unsubscribe = unsub
}).catch((e) => console.warn('events subscribe failed:', e))
}
async create(data: Partial<EventsResponse>) {
const teamId = this.teams.activeId
if (!teamId) throw new Error('Kein aktives Team')
const rec = await api.collection('events').create({ ...data, team: teamId })
return rec
}
async edit(id: string, data: Partial<EventsResponse>) {
return await api.collection('events').update(id, data)
}
remove(record: EventsResponse) {
return new Promise<void>((resolve, reject) => {
app.confirm.request({
title: 'Event löschen?',
text: `Möchten Sie das Event „${record.name || record.description}" wirklich löschen?`,
yes: async () => {
try {
await api.collection('events').delete(record.id)
app.confirm.close()
resolve()
} catch (e) {
app.confirm.close()
reject(e)
}
},
no: () => {
app.confirm.close()
resolve()
},
})
})
}
getById(id: string) {
return this.records.find((r) => r.id === id)
}
get active() {
return this.scoped.filter((r) => r.status === 'active')
}
get drafts() {
return this.scoped.filter((r) => r.status === 'draft')
}
get finished() {
return this.scoped.filter((r) => r.status === 'finished')
}
destroy() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
}
}
}
export function setEventContext() {
const store = new EventStore()
setContext(KEY, store)
return store
}
export function getEventContext(): EventStore {
return getContext<EventStore>(KEY)
}