diff --git a/client/src/components/Tours/planner/TourEditorFields.tsx b/client/src/components/Tours/planner/TourEditorFields.tsx index cd4d0701d..d4a6a2a19 100644 --- a/client/src/components/Tours/planner/TourEditorFields.tsx +++ b/client/src/components/Tours/planner/TourEditorFields.tsx @@ -212,7 +212,7 @@ function TourTimeFields({ planner, locked }: TourFieldProps) { id="tour-planner-planned-duration" label={t('tours.planner.plannedTotalDuration')} value={planner.plannedDurationMinutes ?? planner.plannedTotalMinutes} - invalid={planner.plannedDurationInvalid} + invalid={planner.plannedDurationInvalid || planner.plannedTotalInvalid} errorId="tour-planner-planned-duration-error" locked={locked} onCommit={planner.setPlannedDurationMinutes} @@ -220,6 +220,10 @@ function TourTimeFields({ planner, locked }: TourFieldProps) { {planner.plannedDurationInvalid && ( {t('tours.planner.timeValueInvalid')} )} + {/* #2869: over a day a stage Save waits, and says why rather than only greying out. */} + {!planner.plannedDurationInvalid && planner.plannedTotalInvalid && ( + {t('tours.planner.plannedTotalOverDay')} + )} ); @@ -337,7 +341,11 @@ export function TourDetailsSection({ }: TourFieldProps & { onDifficultyChange: (value: Difficulty) => void }) { const { t } = useTranslation(); const [open, setOpen] = useState(readDetailsOpen); - const forcedOpen = planner.websiteInvalid || planner.plannedDurationInvalid || planner.breakAdditionalInvalid; + const forcedOpen = + planner.websiteInvalid || + planner.plannedDurationInvalid || + planner.breakAdditionalInvalid || + planner.plannedTotalInvalid; const expanded = open || forcedOpen; const toggle = () => { const next = !expanded; diff --git a/client/src/components/Tours/planner/TourPlannerPanels.test.tsx b/client/src/components/Tours/planner/TourPlannerPanels.test.tsx index 16bb42d4c..14e8a4525 100644 --- a/client/src/components/Tours/planner/TourPlannerPanels.test.tsx +++ b/client/src/components/Tours/planner/TourPlannerPanels.test.tsx @@ -526,6 +526,18 @@ describe('TourPlannerRail', () => { expect(screen.getByRole('alert')).toHaveTextContent('Enter a whole value from 0 to 1,440 minutes.') }) + it('says why Save waits on a tour over a day a stage, and opens the details to say it (#2869)', () => { + window.localStorage.clear() + render() + expect(screen.getByLabelText(/Planned total duration/)).toHaveAttribute('aria-invalid', 'true') + expect(screen.getByRole('alert')).toHaveTextContent( + 'Walking time and breaks add up to more than 24 hours a day. Make it a multi-day tour with “End stage here” at a stop, or enter a shorter planned total.', + ) + }) + it('RS-02: Info controls are keyboard focusable and explain themselves only through the shared tooltip', async () => { render(