mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 07:26:30 +00:00
fix(tours): clear the stage spread's lint, read the vias after render and test against the reloaded vias
The vias the spread keeps on their legs are taken over in a layout effect instead of during render, the route redraw takes the planner's async updateRouteForDay, and the menu items for whole or stages move out of the button component into their own module. Two leftovers of the old day pills in tourParts go. The hook test's reanchor reloads the vias like the real one does, and the detail test finds the button by its label, Add to Day.
This commit is contained in:
@@ -57,8 +57,8 @@ import { PaddleWindSection } from './paddle/PaddleWindCard';
|
||||
import { TourProvenanceBlock } from './provenance/TourProvenanceSection';
|
||||
import { RunHeadPills } from './run/RunHeadPills';
|
||||
import { SkiHeadPill } from './ski/skiMarks';
|
||||
import { stageKeysOfTour, type TourDayHow } from './stages/stageSpread';
|
||||
import { TourDayAddButton } from './stages/TourDayAddButton';
|
||||
import { stageKeysOfTour, type TourDayHow } from './stages/stageSpread';
|
||||
import { tourDayRowIcon } from './tourDayRowIcon';
|
||||
import { tourDaysOf } from './tourDays';
|
||||
import { heightsUnknown, movingTimeLabel, withoutHeights } from './tourFacts';
|
||||
|
||||
@@ -1,54 +1,9 @@
|
||||
import { CalendarDays, CalendarRange, ChevronDown, Plus } from 'lucide-react';
|
||||
import { ChevronDown, Plus } from 'lucide-react';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import type { Day } from '../../../types';
|
||||
import { ContextMenu, useContextMenu } from '../../shared/ContextMenu';
|
||||
import { DialogButton } from '../../shared/DialogShell';
|
||||
import { dayMenuLabel } from '../tourDays';
|
||||
import type { TourDayAssignment } from '../useTourDayAssignment';
|
||||
import { stagesThatFit, tourDayChoices } from './stageSpread';
|
||||
|
||||
type Translate = ReturnType<typeof useTranslation>['t'];
|
||||
type MenuItems = Parameters<ReturnType<typeof useContextMenu>['open']>[1];
|
||||
|
||||
interface TourDayHowInput {
|
||||
placeId: number;
|
||||
dayId: number;
|
||||
days: Day[];
|
||||
/** The stages of the multi-day tour. */
|
||||
stageCount: number;
|
||||
assignment: TourDayAssignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* MDT-02: the two ways a multi-day tour goes on a day, as menu items: whole,
|
||||
* or a stage a day from it on, under the day they act on, so the step after
|
||||
* the day menu still says which day was picked. Whole is greyed out on a day
|
||||
* that holds the tour already, and says so; the stages say how many fit when
|
||||
* the trip ends before them.
|
||||
*/
|
||||
export function tourDayHowItems(input: TourDayHowInput, t: Translate, locale: string): MenuItems {
|
||||
const { placeId, dayId, days, stageCount, assignment } = input;
|
||||
const facts = {
|
||||
count: stageCount,
|
||||
fit: stagesThatFit(days, dayId, stageCount),
|
||||
wholeFree: !assignment.isOnDay(placeId, dayId),
|
||||
};
|
||||
const ways = tourDayChoices(facts, t).map((choice) => {
|
||||
const disabled = choice.disabled || !assignment.isAvailable(placeId, dayId, choice.how);
|
||||
return {
|
||||
label: choice.title,
|
||||
// Greyed out with no reason reads as broken; the reason goes right under it.
|
||||
hint: choice.disabled && choice.how === 'whole' ? choice.hint : undefined,
|
||||
icon: choice.how === 'whole' ? CalendarDays : CalendarRange,
|
||||
disabled,
|
||||
onClick: () => void assignment.assign(placeId, dayId, choice.how),
|
||||
};
|
||||
});
|
||||
const at = days.findIndex((day) => day.id === dayId);
|
||||
if (at < 0) return ways;
|
||||
return [{ label: dayMenuLabel(days[at], at, t, locale), heading: true }, ...ways];
|
||||
}
|
||||
import { tourDayHowItems, type TourDayHowInput } from './tourDayHowItems';
|
||||
|
||||
/**
|
||||
* The detail's "Add to day" for the day selected in the plan. A day tour goes
|
||||
|
||||
@@ -68,13 +68,13 @@ describe('the detail button and the dialog', () => {
|
||||
it('FE-TOUR-STAGE-SPREAD-019: the detail adds a day tour at once and offers a multi-day tour both ways', async () => {
|
||||
const props = { placeId: 81, dayId: 4, days: DAYS, assignment };
|
||||
const { unmount } = render(<TourDayAddButton {...props} stageCount={0} />);
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Add to day' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Add to Day' }));
|
||||
expect(assignment.assign).toHaveBeenCalledWith(81, 4);
|
||||
unmount();
|
||||
|
||||
vi.mocked(assignment.isOnDay).mockReturnValue(true);
|
||||
render(<TourDayAddButton {...props} stageCount={3} />);
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Add to day' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Add to Day' }));
|
||||
const whole = screen.getByRole('button', { name: /^Whole tour on this day/ });
|
||||
expect(whole).toBeDisabled();
|
||||
expect(whole).toHaveTextContent('This day already holds the tour.');
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { CalendarDays, CalendarRange } from 'lucide-react';
|
||||
import type { useTranslation } from '../../../i18n';
|
||||
import type { Day } from '../../../types';
|
||||
import type { useContextMenu } from '../../shared/ContextMenu';
|
||||
import { dayMenuLabel } from '../tourDays';
|
||||
import type { TourDayAssignment } from '../useTourDayAssignment';
|
||||
import { stagesThatFit, tourDayChoices } from './stageSpread';
|
||||
|
||||
type Translate = ReturnType<typeof useTranslation>['t'];
|
||||
type MenuItems = Parameters<ReturnType<typeof useContextMenu>['open']>[1];
|
||||
|
||||
export interface TourDayHowInput {
|
||||
placeId: number;
|
||||
dayId: number;
|
||||
days: Day[];
|
||||
/** The stages of the multi-day tour. */
|
||||
stageCount: number;
|
||||
assignment: TourDayAssignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* MDT-02: the two ways a multi-day tour goes on a day, as menu items: whole,
|
||||
* or a stage a day from it on, under the day they act on, so the step after
|
||||
* the day menu still says which day was picked. Whole is greyed out on a day
|
||||
* that holds the tour already, and says so; the stages say how many fit when
|
||||
* the trip ends before them.
|
||||
*/
|
||||
export function tourDayHowItems(input: TourDayHowInput, t: Translate, locale: string): MenuItems {
|
||||
const { placeId, dayId, days, stageCount, assignment } = input;
|
||||
const facts = {
|
||||
count: stageCount,
|
||||
fit: stagesThatFit(days, dayId, stageCount),
|
||||
wholeFree: !assignment.isOnDay(placeId, dayId),
|
||||
};
|
||||
const ways = tourDayChoices(facts, t).map((choice) => {
|
||||
const disabled = choice.disabled || !assignment.isAvailable(placeId, dayId, choice.how);
|
||||
return {
|
||||
label: choice.title,
|
||||
// Greyed out with no reason reads as broken; the reason goes right under it.
|
||||
hint: choice.disabled && choice.how === 'whole' ? choice.hint : undefined,
|
||||
icon: choice.how === 'whole' ? CalendarDays : CalendarRange,
|
||||
disabled,
|
||||
onClick: () => void assignment.assign(placeId, dayId, choice.how),
|
||||
};
|
||||
});
|
||||
const at = days.findIndex((day) => day.id === dayId);
|
||||
if (at < 0) return ways;
|
||||
return [{ label: dayMenuLabel(days[at], at, t, locale), heading: true }, ...ways];
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { ContextMenu, useContextMenu } from '../shared/ContextMenu'
|
||||
import { DialogButton, fs } from '../shared/DialogShell'
|
||||
import { Tooltip } from '../shared/Tooltip'
|
||||
import { BOX, Eyebrow } from '../Planner/bookings/bookingParts'
|
||||
import { SoftPill, WhiteBadge } from '../Planner/planParts'
|
||||
import { WhiteBadge } from '../Planner/planParts'
|
||||
import { hikeSourceBadgeLabel, tourSource } from './tourPresentation'
|
||||
import { type TourDay, dayMenuLabel } from './tourDays'
|
||||
import { formatDate } from '../../utils/formatters'
|
||||
@@ -18,7 +18,7 @@ import type { TourDayAssignment } from './useTourDayAssignment'
|
||||
import { useRailFold } from './railFold'
|
||||
import { tourDayRowIcon } from './tourDayRowIcon'
|
||||
import { SkiCornerMark } from './ski/skiMarks'
|
||||
import { tourDayHowItems } from './stages/TourDayAddButton'
|
||||
import { tourDayHowItems } from './stages/tourDayHowItems'
|
||||
|
||||
/**
|
||||
* A tour's tile in a list: its T-scale grade in place of the mountain, named in
|
||||
@@ -87,9 +87,6 @@ export function TourListCard({ icon,
|
||||
)
|
||||
}
|
||||
|
||||
/** How many days a list row names before the rest fold into one "+n" pill. */
|
||||
const SHOWN_TOUR_DAYS = 3
|
||||
|
||||
/** The days a tour is on as one white badge of a plan row: "Days 2, 4", each day named in full in the tooltip. */
|
||||
export function TourDaysBadge({ tourDays }: { tourDays: readonly TourDay[] }) {
|
||||
const { t, locale } = useTranslation()
|
||||
|
||||
Reference in New Issue
Block a user