Der Button "Als erledigt markieren" stand bisher an jedem Marker, obwohl die updateRule der Collection Änderungen nur dem Ersteller, Trail-Paten oder Team-Admins erlaubt — für alle anderen scheiterte toggleResolved mit einer stillen unhandled Rejection. Neuer Getter TrailMarkerStore.canEdit() spiegelt die Regel im Frontend; der Button ist jetzt entsprechend gegated und Fehler landen als sichtbarer Alert statt zu verschwinden. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBMBfip6BAG8VV9raSf6vu
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import { getContext, setContext } from 'svelte'
|
|
import { api, auth } from './pocketbase.svelte'
|
|
import { getTeamContext } from './teams.svelte'
|
|
import type { TrailMarkersResponse } from '$lib/types'
|
|
|
|
const KEY = Symbol('trailMarker')
|
|
|
|
export class TrailMarkerStore {
|
|
records = $state<TrailMarkersResponse[]>([])
|
|
loading = $state(false)
|
|
error = $state<string | null>(null)
|
|
|
|
private unsubscribe: (() => void) | null = null
|
|
private teams = getTeamContext()
|
|
|
|
/** Alle Marker eines Trails, jüngste zuerst */
|
|
byTrail(trailId: string): TrailMarkersResponse[] {
|
|
return this.records
|
|
.filter((r) => r.trail === trailId)
|
|
.sort((a, b) => (a.created < b.created ? 1 : -1))
|
|
}
|
|
|
|
/** Nur die offenen — was auf der Karte auffallen soll */
|
|
openByTrail(trailId: string): TrailMarkersResponse[] {
|
|
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: TrailMarkersResponse, 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)
|
|
)
|
|
}
|
|
|
|
async load() {
|
|
this.loading = true
|
|
this.error = null
|
|
try {
|
|
this.records = await api.collection('trail_markers').getFullList({
|
|
sort: '-created',
|
|
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) => {
|
|
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('trail_markers subscribe failed:', e))
|
|
}
|
|
|
|
async create(data: { trail: string; flag: 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<TrailMarkersResponse>) {
|
|
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<TrailMarkerStore>(KEY)
|
|
}
|