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(