51 lines
1.9 KiB
TypeScript
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'));
|
|
}
|