mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
290961cced
The walk's detail opened from a day lists its stops in order, each with its stay, importance and protection, and marks one visited or skipped for that day, or takes it back. The day row shows how many were visited. Marks reach the other travellers at once and change no route, place or order; a copy or another day starts with none.
69 lines
2.8 KiB
TypeScript
69 lines
2.8 KiB
TypeScript
import type { TourVisit, TourVisitStatus } from '@trek/shared';
|
|
import { create } from 'zustand';
|
|
import { tourVisitRepo } from '../repo/tourVisitRepo';
|
|
|
|
/** A station's status on one day of its walk; a station not listed is planned. */
|
|
type VisitsByStation = Readonly<Record<string, TourVisitStatus>>;
|
|
|
|
interface TourVisitState {
|
|
/** The trip the visits were read for. */
|
|
tripId: string | null;
|
|
/** Per day assignment, the stations visited or skipped. */
|
|
byAssignment: Readonly<Record<number, VisitsByStation>>;
|
|
load: (tripId: number | string) => Promise<void>;
|
|
/** Nothing known for this trip: its visits could not be read. */
|
|
reset: (tripId: number | string) => void;
|
|
/** Marks a station, at once on screen; a write the server refuses puts it back and throws. */
|
|
mark: (
|
|
tripId: number | string,
|
|
assignmentId: number,
|
|
stationKey: string,
|
|
status: TourVisitStatus | 'planned'
|
|
) => Promise<void>;
|
|
/** Another traveller marked a station: the day's visits as the server has them now. */
|
|
applyRemote: (assignmentId: number, visits: readonly TourVisit[]) => void;
|
|
}
|
|
|
|
function byStation(visits: readonly TourVisit[]): VisitsByStation {
|
|
return Object.fromEntries(visits.map((visit) => [visit.station_key, visit.status]));
|
|
}
|
|
|
|
function withStatus(day: VisitsByStation | undefined, key: string, status: TourVisitStatus | 'planned') {
|
|
const next = { ...day };
|
|
if (status === 'planned') delete next[key];
|
|
else next[key] = status;
|
|
return next;
|
|
}
|
|
|
|
/**
|
|
* CW-03: how far each city walk got on its days, for the day plan's rows and
|
|
* the walk's stations. Read once a trip opens, kept current by the trip's
|
|
* socket, and written one station at a time.
|
|
*/
|
|
export const useTourVisitStore = create<TourVisitState>((set, get) => ({
|
|
tripId: null,
|
|
byAssignment: {},
|
|
load: async (tripId) => {
|
|
const { visits } = await tourVisitRepo.list(tripId);
|
|
const grouped: Record<number, Record<string, TourVisitStatus>> = {};
|
|
for (const visit of visits) (grouped[visit.assignment_id] ??= {})[visit.station_key] = visit.status;
|
|
set({ tripId: String(tripId), byAssignment: grouped });
|
|
},
|
|
reset: (tripId) => set({ tripId: String(tripId), byAssignment: {} }),
|
|
mark: async (tripId, assignmentId, stationKey, status) => {
|
|
const before = get().byAssignment[assignmentId];
|
|
set((state) => ({
|
|
byAssignment: { ...state.byAssignment, [assignmentId]: withStatus(before, stationKey, status) },
|
|
}));
|
|
try {
|
|
const { visits } = await tourVisitRepo.update(tripId, assignmentId, stationKey, { status });
|
|
get().applyRemote(assignmentId, visits);
|
|
} catch (failure) {
|
|
set((state) => ({ byAssignment: { ...state.byAssignment, [assignmentId]: before ?? {} } }));
|
|
throw failure;
|
|
}
|
|
},
|
|
applyRemote: (assignmentId, visits) =>
|
|
set((state) => ({ byAssignment: { ...state.byAssignment, [assignmentId]: byStation(visits) } })),
|
|
}));
|