From 61a49f1ea6c22410b9939e371943127b1a6fc8a3 Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 13 Jul 2026 16:17:34 +0200 Subject: [PATCH] fix(planner): default a new accommodation to checking out the next day MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hotel picker pre-filled "Apply to days" with the same day for check-in and check-out — a stay that ends the day it begins. New accommodations now default to the following day for check-out; the last trip day keeps the same-day range, and editing still seeds from the stored range. --- .../Planner/DayDetailPanel.test.tsx | 24 +++++++++++++++++++ client/src/components/Planner/useDayDetail.ts | 10 ++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/client/src/components/Planner/DayDetailPanel.test.tsx b/client/src/components/Planner/DayDetailPanel.test.tsx index ef5aa39ec..eb0b47936 100644 --- a/client/src/components/Planner/DayDetailPanel.test.tsx +++ b/client/src/components/Planner/DayDetailPanel.test.tsx @@ -564,6 +564,30 @@ describe('DayDetailPanel', () => { }); }); + it('FE-PLANNER-DAYDETAIL-067: hotel picker defaults check-out to the day AFTER check-in', async () => { + const day2 = buildDay({ id: 2, trip_id: 1, date: '2025-06-16', title: 'Day 2' }); + const day3 = buildDay({ id: 3, trip_id: 1, date: '2025-06-17', title: 'Day 3' }); + render(); + await userEvent.click(await screen.findByText(/Add accommodation/i)); + await waitFor(() => { + const portal = document.body.querySelector('[style*="z-index: 99999"]'); + // Closed selects render only their chosen label: check-in on the opened + // day, check-out on the following one — nobody stays a few hours. + expect(portal?.textContent).toContain('Day in Paris'); + expect(portal?.textContent).toContain('Day 2'); + expect(portal?.textContent).not.toContain('Day 3'); + }); + }); + + it('FE-PLANNER-DAYDETAIL-068: hotel picker falls back to a same-day range on the last trip day', async () => { + render(); + await userEvent.click(await screen.findByText(/Add accommodation/i)); + await waitFor(() => { + const portal = document.body.querySelector('[style*="z-index: 99999"]'); + expect(portal?.textContent?.match(/Day in Paris/g)).toHaveLength(2); + }); + }); + it('FE-PLANNER-DAYDETAIL-034: accommodation with all fields shows full details grid', async () => { server.use( http.get('/api/trips/1/accommodations', () => diff --git a/client/src/components/Planner/useDayDetail.ts b/client/src/components/Planner/useDayDetail.ts index e86c00ffa..b4e5ad94a 100644 --- a/client/src/components/Planner/useDayDetail.ts +++ b/client/src/components/Planner/useDayDetail.ts @@ -11,7 +11,13 @@ export function useDayDetail(day: any, days: any, tripId: any, lat: any, lng: an const [dayAccommodations, setDayAccommodations] = useState([]) const [accommodations, setAccommodations] = useState([]) const [showHotelPicker, setShowHotelPicker] = useState(false) - const [hotelDayRange, setHotelDayRange] = useState({ start: day?.id, end: day?.id }) + // A stay virtually never checks out the day it checks in — default the range + // to check-out on the next day, unless the trip ends here. + const defaultHotelDayRange = (d: any) => { + const idx = (days || []).findIndex((x: any) => x.id === d?.id) + return { start: d?.id, end: (idx >= 0 && days[idx + 1]?.id) || d?.id } + } + const [hotelDayRange, setHotelDayRange] = useState(() => defaultHotelDayRange(day)) const [hotelCategoryFilter, setHotelCategoryFilter] = useState('') const [hotelForm, setHotelForm] = useState({ check_in: '', check_in_end: '', check_out: '', confirmation: '', place_id: null }) @@ -38,7 +44,7 @@ export function useDayDetail(day: any, days: any, tripId: any, lat: any, lng: an .catch(() => {}) }, [tripId, day?.id]) - useEffect(() => { if (day) setHotelDayRange({ start: day.id, end: day.id }) }, [day?.id]) + useEffect(() => { if (day) setHotelDayRange(defaultHotelDayRange(day)) }, [day?.id]) const handleSelectPlace = (placeId) => { setHotelForm(f => ({ ...f, place_id: placeId }))