Files
TREK/client/src/components/Tours/planner/stationModel.ts
T
Maurice 188953158a fix(tours): a city walk's walking time, stays and total read apart and alike everywhere, and a stay plans no route again (CW-02)
A stay, a name, an importance or a protection set in a stop's dialog no
longer plans a city walk's route again: the walk stays ready to save, its
time overview follows at once, and one undo takes the change back without
routing either. A stop moved or added plans the route again as before. A
walk saves no breaks, so a cafe never counts as a stay and as a break at
once, and a break left from another type neither counts nor holds its save
up.

The planner and the tour detail show a walk's walking time, its stays and
their total apart and in the same words: 45 minutes of walking, a museum of
90 minutes and a cafe of 30 minutes make 2 h 45 min. A stay not entered yet
makes the stays and the total at least what is known, stays nobody entered
read not known yet, and stays entered as 0 read 0 min. A total entered by
hand keeps its own label, and the computed total shows beside it. The head
of the tour detail, the day row on the desktop and the phone, the tour
summary and the library of other trips show that same total, with a ≥ where
a stay is missing. Their time on the move is called walking, and the summary
says a walk is planned with its stays rather than breaks.

The tour list and the library carry the minutes a walk's stops are entered
with and how many have no stay yet, counted from the walk's points, so the
way back of a loop is no stop and its start's stay counts once. A loop run
the other way keeps its start as the stop there, with its key, name and
stay.

The planner's waypoint list keeps the rows of a leg in a file of their own,
and the tour detail its name heading in a part of its own, so both stay
within their size limits.
2026-10-20 07:09:57 +00:00

71 lines
3.2 KiB
TypeScript

import { walkStationsOf } from '@trek/shared';
import type { TourPlannerWaypoint } from './tourPlannerWaypoints';
/**
* CW-02: a city walk's time, walking and staying kept apart. A stay nobody
* entered is unknown, never nothing: the total then counts only what is
* known and says it is at least that much. A point passed through is a stay
* of 0, entered on purpose.
*/
export interface WalkTimes {
walkingMinutes: number | null;
/** The stays entered, summed. */
stayMinutes: number;
/** Stations whose stay nobody entered. */
unknownStays: number;
/** The walk's stations, so that some stays unknown and all of them unknown read apart. */
stations: number;
/** Walking and the known stays; the known stays alone while the walking time is not known, null while nothing is. */
totalMinutes: number | null;
/** The total leaves out a stay or the walking time that is not known. */
partial: boolean;
}
/** The times of a walk from its parts: the walking time, the stays entered, how many are unknown and of how many. */
export function walkTimesOf(parts: Omit<WalkTimes, 'totalMinutes' | 'partial'>): WalkTimes {
const { walkingMinutes, stayMinutes, unknownStays } = parts;
// Without a walking time the stays entered are still part of the whole: at least that much, never nothing.
const totalMinutes = walkingMinutes !== null ? walkingMinutes + stayMinutes : stayMinutes > 0 ? stayMinutes : null;
return {
...parts,
totalMinutes,
partial: totalMinutes !== null && (unknownStays > 0 || walkingMinutes === null),
};
}
export function walkTimes(points: readonly TourPlannerWaypoint[], walkingMinutes: number | null): WalkTimes {
// A shaping point is no station, and the way back of a loop is its start again: its stay counts once.
const stations = walkStationsOf(points);
return walkTimesOf({
walkingMinutes,
stayMinutes: stations.reduce((sum, station) => sum + (station.stayMinutes ?? 0), 0),
unknownStays: stations.filter((station) => station.stayMinutes == null).length,
stations: stations.length,
});
}
/** What the station dialog edits; the rest of the point stays as it is. */
export type StationFields = Pick<TourPlannerWaypoint, 'name' | 'stayMinutes' | 'importance' | 'protected'>;
/** The point with the fields as edited, each one left out where it is empty. */
export function withStationFields(point: TourPlannerWaypoint, fields: StationFields): TourPlannerWaypoint {
const { name: _name, stayMinutes: _stay, importance: _importance, protected: _protected, ...rest } = point;
const name = fields.name?.trim();
return {
...rest,
...(name ? { name } : {}),
...(fields.stayMinutes != null ? { stayMinutes: fields.stayMinutes } : {}),
...(fields.importance ? { importance: fields.importance } : {}),
...(fields.protected ? { protected: true } : {}),
};
}
/** A stay as typed: empty is unknown, a whole number from 0 to a day is minutes, anything else is not taken. */
export function parseStay(text: string): number | null | undefined {
const trimmed = text.trim();
if (trimmed === '') return null;
if (!/^\d{1,4}$/.test(trimmed)) return undefined;
const minutes = Number(trimmed);
return minutes <= 24 * 60 ? minutes : undefined;
}