import { getContext, setContext } from 'svelte' import { api, auth } from './pocketbase.svelte' import { getTeamContext } from './teams.svelte' import { isRecent } from '$lib/time' import type { TrailMarkersResponse, UsersResponse } from '$lib/types' const KEY = Symbol('trailMarker') /** * Marker samt aufgelöstem Melder. Ob `expand.created_by` gefüllt ist, hängt * an den Sichtbarkeitsregeln der users-Collection — deshalb optional. */ export type TrailMarker = Omit & { expand?: { created_by?: UsersResponse } } export class TrailMarkerStore { records = $state([]) loading = $state(false) error = $state(null) private unsubscribe: (() => void) | null = null private teams = getTeamContext() /** Alle Marker eines Trails, jüngste zuerst */ byTrail(trailId: string): TrailMarker[] { return this.records .filter((r) => r.trail === trailId) .sort((a, b) => (a.created < b.created ? 1 : -1)) } /** Gab es zuletzt Bewegung? Für die Hervorhebung in der Liste. */ hasRecent(trailId: string, days?: number): boolean { return this.byTrail(trailId).some((r) => isRecent(r.created, days)) } /** Nur die offenen — was auf der Karte auffallen soll */ openByTrail(trailId: string): TrailMarker[] { return this.byTrail(trailId).filter((r) => !r.resolved) } /** * Ändern darf, wer den Marker angelegt hat, sowie Trail-Paten und * Team-Admins — dieselbe Regel wie in der updateRule der Collection. * Der Trail wird übergeben, weil der Store ihn nicht kennt. */ canEdit(marker: TrailMarker, stewards: string[], teamOwner?: string, teamAdmins: string[] = []): boolean { const uid = auth.user?.id if (!uid) return false return ( marker.created_by === uid || stewards.includes(uid) || teamOwner === uid || teamAdmins.includes(uid) ) } /** * Wer den Marker gemeldet hat. Fremde User-Datensätze sind derzeit nicht * sichtbar (users.listRule: `id = @request.auth.id`), deshalb bleibt der * Name bei Meldungen anderer offen. */ authorName(marker: TrailMarker): string { const name = marker.expand?.created_by?.name if (name) return name if (marker.created_by && marker.created_by === auth.user?.id) return 'Du' return 'Unbekannt' } async load() { this.loading = true this.error = null try { this.records = await api.collection('trail_markers').getFullList({ sort: '-created', expand: 'created_by', requestKey: null, }) } catch (e: any) { this.error = e.message ?? 'Fehler beim Laden der Marker' console.error(e) } finally { this.loading = false } } subscribe() { if (this.unsubscribe) return api.collection('trail_markers').subscribe('*', (e) => { // Realtime liefert den Datensatz ungetypt; angefordert wird unten // dasselbe expand wie beim Laden. const record = e.record as TrailMarker const idx = this.records.findIndex((r) => r.id === record.id) if (e.action === 'create' && idx === -1) this.records = [record, ...this.records] else if (e.action === 'update' && idx !== -1) this.records[idx] = record else if (e.action === 'delete' && idx !== -1) this.records = this.records.filter((r) => r.id !== e.record.id) }, { expand: 'created_by' }).then((unsub) => { this.unsubscribe = unsub }).catch((e) => console.warn('trail_markers subscribe failed:', e)) } /** * `flag` ist optional: Ein Marker darf mit eigenem `label` und `icon` * auskommen, wenn kein vorgefertigter Typ passt. */ async create(data: { trail: string flag?: string label?: string icon?: string lat: number lng: number note?: string }) { const teamId = this.teams.activeId if (!teamId) throw new Error('Kein aktives Team') return await api.collection('trail_markers').create({ ...data, team: teamId, resolved: false, created_by: auth.user?.id, }) } async edit(id: string, data: Partial) { return await api.collection('trail_markers').update(id, data) } /** Erledigt statt gelöscht — die Meldung bleibt als Historie erhalten. */ async toggleResolved(id: string) { const marker = this.records.find((r) => r.id === id) if (!marker) throw new Error('Marker nicht gefunden') await api.collection('trail_markers').update(id, { resolved: !marker.resolved }) } async remove(id: string) { await api.collection('trail_markers').delete(id) } destroy() { if (this.unsubscribe) { this.unsubscribe() this.unsubscribe = null } } } export function setTrailMarkerContext() { const store = new TrailMarkerStore() setContext(KEY, store) return store } export function getTrailMarkerContext(): TrailMarkerStore { return getContext(KEY) }