mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 05:56:53 +00:00
fix(tours): count the trip totals' tours on a day from the day lists (STA-01)
The totals counted a tour as on a day by the list's planned flag, which lags behind a day assignment made a moment ago. They read the day lists now, as the row's day badge does, so the count follows at once.
This commit is contained in:
@@ -3,6 +3,7 @@ import { ChartColumnBig, ChevronDown, Mountain, Route, Waves } from 'lucide-reac
|
||||
import { useMemo, useState, type ComponentType } from 'react';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { useSettingsStore } from '../../store/settingsStore';
|
||||
import { useTripStore } from '../../store/tripStore';
|
||||
import { formatDistance, formatElevation } from '../../utils/units';
|
||||
import { fs } from '../shared/DialogShell';
|
||||
import { InfoDot, RailCard, RailFacts, type TourMetricField } from './tourParts';
|
||||
@@ -129,7 +130,12 @@ function TypeScope({ scope }: { scope: TourTypeScope }) {
|
||||
export function TourScopeSummary({ tours }: { tours: readonly TourListItem[] }) {
|
||||
const { t } = useTranslation();
|
||||
const [folded, setFolded] = useState(readFolded);
|
||||
const scope = useMemo(() => tourScopeSummary(tours), [tours]);
|
||||
// On a day as the day lists say, not the list's flag, so a tour put on a day counts at once (as its row's badge does).
|
||||
const assignments = useTripStore((state) => state.assignments);
|
||||
const scope = useMemo(() => {
|
||||
const onDays = new Set(Object.values(assignments ?? {}).flatMap((list) => list.map((item) => item.place_id)));
|
||||
return tourScopeSummary(tours, (tour) => onDays.has(tour.place_id));
|
||||
}, [assignments, tours]);
|
||||
if (scope.count === 0) return null;
|
||||
const toggle = () => {
|
||||
setFolded((value) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { TourListItem } from '@trek/shared';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { fireEvent, render, screen, within } from '../../../tests/helpers/render';
|
||||
import { useTripStore } from '../../store/tripStore';
|
||||
import { TourScopeSummary } from './TourScopeSummary';
|
||||
import { tourScopeSummary } from './tourScope';
|
||||
|
||||
@@ -60,6 +61,12 @@ const PADDLE = tour({
|
||||
});
|
||||
|
||||
describe('tourScopeSummary (STA-01)', () => {
|
||||
it('counts a tour on a day by the caller, once however many days it is on', () => {
|
||||
const scope = tourScopeSummary([HIKE_A, HIKE_B], (tour) => tour.place_id === 2);
|
||||
expect(scope.onDays).toBe(1);
|
||||
expect(scope.types[0].onDays).toBe(1);
|
||||
});
|
||||
|
||||
it('counts each tour once, by type, the most tours first, with how many are on a day', () => {
|
||||
const scope = tourScopeSummary([HIKE_A, PADDLE, HIKE_B, GPX_HIKE, HIKE_A]);
|
||||
expect(scope.count).toBe(4);
|
||||
@@ -95,7 +102,13 @@ describe('tourScopeSummary (STA-01)', () => {
|
||||
});
|
||||
|
||||
describe('TourScopeSummary', () => {
|
||||
beforeEach(() => localStorage.clear());
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
// The day lists say which tours are on a day: HIKE_B on two days, PADDLE on one; the flags say otherwise.
|
||||
useTripStore.setState({
|
||||
assignments: { 1: [{ place_id: 2 }], 2: [{ place_id: 2 }, { place_id: 4 }] },
|
||||
} as unknown as Parameters<typeof useTripStore.setState>[0]);
|
||||
});
|
||||
|
||||
it('shows the counts and each type with its sums, an incomplete one marked as at least', () => {
|
||||
render(<TourScopeSummary tours={[HIKE_A, HIKE_B, PADDLE]} />);
|
||||
|
||||
@@ -48,7 +48,7 @@ function sumOf(values: Array<number | null | undefined>): ScopeSum {
|
||||
};
|
||||
}
|
||||
|
||||
function scopeOfType(type: TourTypeKey, tours: readonly TourListItem[]): TourTypeScope {
|
||||
function scopeOfType(type: TourTypeKey, tours: readonly TourListItem[], onDay: OnDay): TourTypeScope {
|
||||
const planned = tours.filter((tour) => tourSource(tour) === 'trek');
|
||||
const imported = tours.filter((tour) => tourSource(tour) !== 'trek');
|
||||
// A paddle tour drawn in TREK has no true heights (they would be the banks'), so it does not count as having them.
|
||||
@@ -58,7 +58,7 @@ function scopeOfType(type: TourTypeKey, tours: readonly TourListItem[]): TourTyp
|
||||
return {
|
||||
type,
|
||||
count: tours.length,
|
||||
onDays: tours.filter((tour) => tour.planned).length,
|
||||
onDays: tours.filter(onDay).length,
|
||||
distanceKm: sumOf(tours.map((tour) => tour.distance)),
|
||||
ascentM: heights ? sumOf(withHeights.map((tour) => tour?.elevation_gain)) : NO_SUM,
|
||||
descentM: heights ? sumOf(withHeights.map((tour) => tour?.elevation_loss)) : NO_SUM,
|
||||
@@ -68,18 +68,22 @@ function scopeOfType(type: TourTypeKey, tours: readonly TourListItem[]): TourTyp
|
||||
};
|
||||
}
|
||||
|
||||
/** Whether a tour is on a day of the trip. */
|
||||
type OnDay = (tour: TourListItem) => boolean;
|
||||
|
||||
/**
|
||||
* STA-01: what the tours of a trip add up to, by type. Each tour counts once,
|
||||
* however many days it is on; the list is the trip's tours, one row each, so
|
||||
* a tour on three days is still one tour and one distance.
|
||||
* a tour on three days is still one tour and one distance. `onDay` reads the
|
||||
* day lists where the caller has them; the list's own flag is the fallback.
|
||||
*/
|
||||
export function tourScopeSummary(tours: readonly TourListItem[]): TourScope {
|
||||
export function tourScopeSummary(tours: readonly TourListItem[], onDay: OnDay = (tour) => tour.planned): TourScope {
|
||||
const byType = new Map<TourTypeKey, TourListItem[]>();
|
||||
for (const tour of new Map(tours.map((tour) => [tour.place_id, tour])).values()) {
|
||||
byType.set(tour.tour_type, [...(byType.get(tour.tour_type) ?? []), tour]);
|
||||
}
|
||||
const types = [...byType.entries()]
|
||||
.map(([type, ofType]) => scopeOfType(type, ofType))
|
||||
.map(([type, ofType]) => scopeOfType(type, ofType, onDay))
|
||||
.sort((a, b) => b.count - a.count || a.type.localeCompare(b.type));
|
||||
return {
|
||||
count: types.reduce((total, scope) => total + scope.count, 0),
|
||||
|
||||
Reference in New Issue
Block a user