feat: task 2.7 event picker (subscription driver)
- src/data/events.ts: useUserEvents() TanStack Query (5-min stale, sort -starts_at). EventSummary type is a Pick of EventRow. - src/live/active-event.ts: useActiveEventOrchestration() returns the swap fn — unsubscribe previous + clearForEvent + subscribe new + applySnapshot on success + persist to localStorage. Out-of-order safety via per-call version counter. Plus readSavedActiveEventId(). - src/ui/components/event-picker.tsx: <EventPicker> dropdown. useState + click-outside; rows show name + date + discipline. - src/live/index.ts: re-exports active-event helpers. - src/routes/_authed/monitor.tsx: auto-select effect (one-shot via initializedRef, gated on events loaded + WS connected); renders <EventPicker> wired to setActiveEvent. Deviations: 1. Vanilla div + useState dropdown instead of shadcn Popover — no new shadcn primitive add; easy to swap later for keyboard nav. 2. Auto-select gated on connectionStatus === 'connected' so the subscribe call gets the snapshot path (not 'not-connected'). 3. Logout-clears-saved-event-id deferred to a small Phase 1.8 follow-up; documented in task risks. Bundle: 395KB / 120KB gz (~1KB up from 2.6).
This commit is contained in:
@@ -1,15 +1,45 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { useUserEvents } from '@/data/events';
|
||||
import {
|
||||
readSavedActiveEventId,
|
||||
useActiveEventOrchestration,
|
||||
useConnectionStore,
|
||||
usePositionStore,
|
||||
} from '@/live';
|
||||
import { BasemapSwitcher } from '@/map/core/basemap-switcher';
|
||||
import { MapView } from '@/map/core/map-view';
|
||||
import { TrailsToggle } from '@/map/core/trails-toggle';
|
||||
import { MapPositions } from '@/map/layers/map-positions';
|
||||
import { MapTrails } from '@/map/layers/map-trails';
|
||||
import { EventPicker } from '@/ui/components/event-picker';
|
||||
|
||||
export const Route = createFileRoute('/_authed/monitor')({
|
||||
component: MonitorPage,
|
||||
});
|
||||
|
||||
function MonitorPage() {
|
||||
const activeEventId = usePositionStore((s) => s.activeEventId);
|
||||
const setActiveEvent = useActiveEventOrchestration();
|
||||
const events = useUserEvents();
|
||||
const connectionStatus = useConnectionStore((s) => s.status);
|
||||
const initializedRef = useRef(false);
|
||||
|
||||
// Auto-select on first mount: prefer the previously-saved event id;
|
||||
// otherwise the most recent. Gated on the WS being connected so the
|
||||
// subscribe call doesn't immediately resolve with `not-connected`.
|
||||
useEffect(() => {
|
||||
if (initializedRef.current) return;
|
||||
if (!events.data || events.data.length === 0) return;
|
||||
if (connectionStatus !== 'connected') return;
|
||||
initializedRef.current = true;
|
||||
|
||||
const saved = readSavedActiveEventId();
|
||||
const target =
|
||||
saved && events.data.some((e) => e.id === saved) ? saved : (events.data[0]?.id ?? null);
|
||||
if (target) void setActiveEvent(target);
|
||||
}, [events.data, connectionStatus, setActiveEvent]);
|
||||
|
||||
return (
|
||||
// 3.5rem ≈ 56 px reserved for a future top-bar (Phase 3 chrome).
|
||||
// For now the parent _authed layout has no top-bar, so the map can
|
||||
@@ -20,6 +50,12 @@ function MonitorPage() {
|
||||
UI controls. Floating cards positioned absolutely inside
|
||||
<MapView>'s wrapper.
|
||||
*/}
|
||||
<EventPicker
|
||||
activeEventId={activeEventId}
|
||||
onChange={(id) => {
|
||||
void setActiveEvent(id);
|
||||
}}
|
||||
/>
|
||||
<BasemapSwitcher />
|
||||
<TrailsToggle />
|
||||
{/*
|
||||
|
||||
Reference in New Issue
Block a user