Files
website/src/lib/spotting/seen.svelte.ts

51 lines
1.9 KiB
TypeScript

// Client-side collection state for the spotting pokedexes: optimistic overrides on top of
// what the server sent, so a toggle updates every card/panel for that entry at once.
import { invalidateAll } from '$app/navigation';
export type SpotTracker = 'cars' | 'planes' | 'trains' | 'wildlife';
const overrides = $state<Record<string, boolean>>({});
const key = (tracker: SpotTracker, id: number) => `${tracker}:${id}`;
export function isSeen(tracker: SpotTracker, id: number, fromServer: boolean): boolean {
const k = key(tracker, id);
return k in overrides ? overrides[k] : fromServer;
}
/** Owner-only POST; reverts the optimistic state when the server refuses. */
export async function saveSeen(
tracker: SpotTracker,
id: number,
seen: boolean,
extra: { seenOn?: string | null; note?: string | null } = {}
): Promise<boolean> {
const k = key(tracker, id);
const before = k in overrides ? overrides[k] : undefined;
overrides[k] = seen;
try {
const res = await fetch(`/trackers/${tracker}/seen`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ id, seen, ...extra })
});
if (!res.ok) throw new Error(String(res.status));
await invalidateAll();
delete overrides[k];
return true;
} catch {
if (before === undefined) delete overrides[k];
else overrides[k] = before;
return false;
}
}
type HasSeen = { id: number; seen: boolean };
/** How many of these entries are collected (respecting optimistic toggles). */
export const countSeen = (tracker: SpotTracker, items: HasSeen[]) =>
items.reduce((n, x) => n + (isSeen(tracker, x.id, x.seen) ? 1 : 0), 0);
/** Client-side ?got= filter for group pages that list every entry at once. */
export function filterGot<T extends HasSeen>(tracker: SpotTracker, items: T[], got: string): T[] {
if (got !== 'yes' && got !== 'no') return items;
return items.filter((x) => isSeen(tracker, x.id, x.seen) === (got === 'yes'));
}