fix(planner): the delete question says plainly what goes with a booked place

"The night booked at "Rostock" goes with the place, together with the
booking "Rostock" and any expense linked to it" named the place twice and
read awkwardly. The note now says "This also deletes the stay booked at
"Rostock", its booking and any expense linked to it", quotes the booking
only when its title differs from the place, and reads the same in all 23
languages.
This commit is contained in:
Maurice
2026-09-23 21:56:02 +02:00
parent 1423ba8933
commit 436f0740fb
25 changed files with 132 additions and 50 deletions
@@ -1314,6 +1314,25 @@ describe('useTripPlanner — place CRUD', () => {
expect(result.current.deletePlaceNote).toBeNull()
})
it('FE-TP-HOOK-140: a booking named after its hotel is not quoted a second time', async () => {
const hotel = buildPlace({ id: 1, name: 'Rostock', lat: 54.09, lng: 12.1 })
seedTrip({
places: [hotel],
reservations: [buildReservation({ id: 9, type: 'hotel', title: 'Rostock', accommodation_id: 7 })],
})
vi.mocked(accommodationRepo.list).mockResolvedValue({
accommodations: [{ id: 7, trip_id: 42, place_id: 1, start_day_id: 5, end_day_id: 6 }] as never,
})
const { result } = await renderPlanner()
await waitFor(() => expect(result.current.tripAccommodations).toHaveLength(1))
const { result: i18n } = renderHook(() => useTranslation(), { wrapper })
act(() => { result.current.handleDeletePlace(1) })
expect(result.current.deletePlaceNote).toBe(i18n.current.t('trip.confirm.deletePlaceBookedSame', { name: 'Rostock' }))
expect(result.current.deletePlaceNote).toBe('This also deletes the stay booked at “Rostock”, its booking and any expense linked to it.')
})
it('FE-TP-HOOK-119: a bulk delete warns once any of the places carries a night, booking or not', async () => {
const hotel = buildPlace({ id: 1, name: 'Hotel Fjord', lat: 60.39, lng: 5.32 })
const cafe = buildPlace({ id: 2, name: 'Cafe', lat: 1, lng: 2 })
@@ -513,7 +513,8 @@ export function useTripPlanner() {
* itself only names the place, and those are the rows the traveller least expects
* to lose, so the dialog says so before the yes. The expense list is loaded with
* the costs tab, not here, so the sentence speaks of any expense rather than
* counting them. Null when nothing beyond the place is at stake.
* counting them. A booking named after its hotel, which is how most are named,
* is not quoted a second time. Null when nothing beyond the place is at stake.
*/
const bookedNightsNote = useCallback((placeIds: number[]): string | null => {
const stays = tripAccommodations.filter(stay => stay.place_id != null && placeIds.includes(stay.place_id))
@@ -525,9 +526,10 @@ export function useTripPlanner() {
?? stay.reservation_title ?? null)
.filter((title): title is string => !!title)
const name = names.join(', ')
return bookings.length > 0
? t('trip.confirm.deletePlaceBooked', { name, booking: bookings.join(', ') })
: t('trip.confirm.deletePlaceNight', { name })
if (bookings.length === 0) return t('trip.confirm.deletePlaceNight', { name })
return bookings.every(title => names.includes(title))
? t('trip.confirm.deletePlaceBookedSame', { name })
: t('trip.confirm.deletePlaceBooked', { name, booking: bookings.join(', ') })
}, [tripAccommodations, allPlaces, reservations, t])
const deletePlaceNote = useMemo(
() => (deletePlaceId ? bookedNightsNote([deletePlaceId]) : null),