diff --git a/client/src/components/Tours/planner/TourPlannerPanels.test.tsx b/client/src/components/Tours/planner/TourPlannerPanels.test.tsx index bebd71ced..93d0c9053 100644 --- a/client/src/components/Tours/planner/TourPlannerPanels.test.tsx +++ b/client/src/components/Tours/planner/TourPlannerPanels.test.tsx @@ -146,16 +146,18 @@ describe('TourPlannerToursRail', () => { expect(controller.openTour).toHaveBeenCalledWith(tour) }) - it('TOUR-PLANNER-RAIL-003: assigns the saved tour immediately when a day is picked', async () => { + it('keeps the post-save confirmation compact and assigns through the saved TOUR-PLANNER row action', async () => { const controller = planner({ saveOutcome: tour, editingPlaceId: tour.place_id }) const onAssignToDay = vi.fn().mockResolvedValue(undefined) render() expect(screen.getByRole('status')).toHaveTextContent('Tour saved') + expect(screen.queryByRole('button', { name: 'Assign to a day' })).not.toBeInTheDocument() + expect(onAssignToDay).not.toHaveBeenCalled() fireEvent.click(screen.getByRole('button', { name: 'Plan another' })) expect(controller.startNewTour).toHaveBeenCalledOnce() expect(controller.discard).not.toHaveBeenCalled() - fireEvent.click(screen.getByRole('button', { name: 'Assign to a day' })) + fireEvent.click(screen.getByRole('button', { name: 'Add to day: Saved ridge walk' })) const dayOption = screen.getByRole('button', { name: /Summit day/ }) expect(dayOption).toHaveTextContent(/Day 1/i) expect(dayOption).toHaveTextContent(/May 15/i) @@ -164,16 +166,17 @@ describe('TourPlannerToursRail', () => { await waitFor(() => expect(onAssignToDay).toHaveBeenCalledWith(42, 7)) }) - it('keeps the saved outcome and its day action when assignment fails', async () => { + it('keeps the save confirmation and row assignment action available when row assignment fails', async () => { const controller = planner({ saveOutcome: tour, editingPlaceId: tour.place_id }) const onAssignToDay = vi.fn().mockResolvedValue(false) render() - fireEvent.click(screen.getByRole('button', { name: 'Assign to a day' })) + fireEvent.click(screen.getByRole('button', { name: 'Add to day: Saved ridge walk' })) fireEvent.click(screen.getByRole('button', { name: /Summit day/ })) await waitFor(() => expect(onAssignToDay).toHaveBeenCalledWith(42, 7)) expect(screen.getByRole('status')).toHaveTextContent('Tour saved') - expect(screen.getByRole('button', { name: 'Assign to a day' })).toBeEnabled() + expect(screen.queryByRole('button', { name: 'Assign to a day' })).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Add to day: Saved ridge walk' })).toBeEnabled() }) it('TOUR-PLANNER-RAIL-004: protects a dirty draft before selecting a GPX Tour', () => { diff --git a/client/src/components/Tours/planner/TourPlannerPanels.tsx b/client/src/components/Tours/planner/TourPlannerPanels.tsx index e9690d64d..270641196 100644 --- a/client/src/components/Tours/planner/TourPlannerPanels.tsx +++ b/client/src/components/Tours/planner/TourPlannerPanels.tsx @@ -398,7 +398,6 @@ export function TourPlannerToursRail({ planner, tours, days, loading, onAssignTo const toast = useToast() const distanceUnit = useSettingsStore(state => state.settings.distance_unit) const [pendingTour, setPendingTour] = useState(null) - const [assigning, setAssigning] = useState(false) const isSaving = planner.isSaving const hasRoute = (planner.mode.type === 'new-draft' || planner.mode.type === 'edit-saved') && planner.route !== null const routeAnalysis = planner.routeAnalysis @@ -422,17 +421,6 @@ export function TourPlannerToursRail({ planner, tours, days, loading, onAssignTo selectTour(tour) } - const assignSavedTour = async (dayId: number) => { - const savedTour = planner.saveOutcome - if (!canAssign || !savedTour) return - setAssigning(true) - try { - await onAssignToDay(savedTour.place_id, dayId) - } finally { - setAssigning(false) - } - } - const walkingTime = { label: t('tours.planner.inspector.duration'), value: t('tours.durationMinutes', { count: Math.max(1, Math.round((planner.durationSeconds ?? 0) / 60)) }) } return ( @@ -464,20 +452,9 @@ export function TourPlannerToursRail({ planner, tours, days, loading, onAssignTo {t('tours.planner.saved')} -
- { if (canEdit && !isSaving) planner.startNewTour() }} disabled={!canEdit || isSaving}> - {t('tours.planner.planAnother')} - - { void assignSavedTour(day.id) }} - /> -
+ { if (canEdit && !isSaving) planner.startNewTour() }} disabled={!canEdit || isSaving}> + {t('tours.planner.planAnother')} + )} diff --git a/client/src/components/Tours/planner/useTourPlanner.test.tsx b/client/src/components/Tours/planner/useTourPlanner.test.tsx index 907dcc662..04aedac09 100644 --- a/client/src/components/Tours/planner/useTourPlanner.test.tsx +++ b/client/src/components/Tours/planner/useTourPlanner.test.tsx @@ -1101,4 +1101,27 @@ describe('useTourPlanner', () => { await act(async () => { expect(await result.current.save()).toBeNull() }) expect(updateTour).not.toHaveBeenCalled() }) + + it('keeps the existing save-failure notice and draft when a Tour update fails', async () => { + detailTour.mockResolvedValue({ + tour: savedTour, + waypoints: [ + { lat: 48, lng: 11, role: 'start', sequence: 0 }, + { lat: 48.01, lng: 11.02, role: 'end', sequence: 1 }, + ], + }) + updateTour.mockRejectedValue(new Error('Tour update failed')) + const { result } = renderHook(() => useTourPlanner({ tripId: 29 })) + await act(async () => { expect(await result.current.openTour(savedTour)).toBe(true) }) + act(() => result.current.setName('Keep this draft')) + await act(async () => { await vi.advanceTimersByTimeAsync(450) }) + const view = render() + + await act(async () => { expect(await result.current.save()).toBeNull() }) + view.rerender() + + expect(result.current.editingPlaceId).toBe(savedTour.place_id) + expect(result.current.name).toBe('Keep this draft') + expect(screen.getByText('Tour could not be saved. Your draft is still here.')).toBeInTheDocument() + }) })