From f03bb97d502807cd7a05d81fb1b16d973eb2c05f Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 9 Nov 2026 12:56:50 +0100 Subject: [PATCH] fix(mobile): a booking opens its detail sheet first, as a transport and the desktop do; Wallet and Map buttons read short Bookings in the Bookings tab, the day sheet, a place's sheet and the road trip opened their editor straight away, while a transport showed its details with a pencil to edit. Every booking now opens the same detail sheet; its edit and delete follow the booking's own permission (reservation_edit for a stay, a table or a ticket, day_edit for a transport). Notes keep opening for editing, as on the desktop. The wallet button reads "Wallet" (its accessible name still says which one), and "On map" reads "Map" in every language. --- .../mobile/components/MWalletButton.test.tsx | 6 +++-- .../src/mobile/components/MWalletButton.tsx | 3 ++- .../screens/trip/roadtrip/MRoadtripTab.tsx | 10 ++------ .../mobile/screens/trip/sheets/MDaySheet.tsx | 14 +++-------- .../screens/trip/sheets/MPlaceSheet.tsx | 14 +++-------- .../screens/trip/sheets/MTransportSheet.tsx | 8 +++++-- .../mobile/screens/trip/tabs/MBookingsTab.tsx | 11 +++++---- .../unit/mobile/trip/MBookingsTab.test.tsx | 14 +++++------ .../tests/unit/mobile/trip/MDaySheet.test.tsx | 12 +++++----- .../unit/mobile/trip/MPlaceSheet.test.tsx | 24 ++++++++----------- .../unit/mobile/trip/MRoadtripTab.test.tsx | 12 ++++------ .../unit/mobile/trip/MTransportSheet.test.tsx | 20 ++++++++-------- shared/src/i18n/ar/mobileTrip.ts | 2 +- shared/src/i18n/ar/reservations.ts | 1 + shared/src/i18n/az/mobileTrip.ts | 2 +- shared/src/i18n/az/reservations.ts | 1 + shared/src/i18n/br/mobileTrip.ts | 2 +- shared/src/i18n/br/reservations.ts | 1 + shared/src/i18n/ca/mobileTrip.ts | 2 +- shared/src/i18n/ca/reservations.ts | 1 + shared/src/i18n/cs/mobileTrip.ts | 2 +- shared/src/i18n/cs/reservations.ts | 1 + shared/src/i18n/de/mobileTrip.ts | 2 +- shared/src/i18n/de/reservations.ts | 1 + shared/src/i18n/en/mobileTrip.ts | 2 +- shared/src/i18n/en/reservations.ts | 1 + shared/src/i18n/es/mobileTrip.ts | 2 +- shared/src/i18n/es/reservations.ts | 1 + shared/src/i18n/et/mobileTrip.ts | 2 +- shared/src/i18n/et/reservations.ts | 1 + shared/src/i18n/fr/mobileTrip.ts | 2 +- shared/src/i18n/fr/reservations.ts | 1 + shared/src/i18n/gr/mobileTrip.ts | 2 +- shared/src/i18n/gr/reservations.ts | 1 + shared/src/i18n/he/mobileTrip.ts | 2 +- shared/src/i18n/he/reservations.ts | 1 + shared/src/i18n/hu/mobileTrip.ts | 2 +- shared/src/i18n/hu/reservations.ts | 1 + shared/src/i18n/id/mobileTrip.ts | 2 +- shared/src/i18n/id/reservations.ts | 1 + shared/src/i18n/it/mobileTrip.ts | 2 +- shared/src/i18n/it/reservations.ts | 1 + shared/src/i18n/ja/mobileTrip.ts | 2 +- shared/src/i18n/ja/reservations.ts | 1 + shared/src/i18n/ko/mobileTrip.ts | 2 +- shared/src/i18n/ko/reservations.ts | 1 + shared/src/i18n/nl/mobileTrip.ts | 2 +- shared/src/i18n/nl/reservations.ts | 1 + shared/src/i18n/pl/mobileTrip.ts | 2 +- shared/src/i18n/pl/reservations.ts | 1 + shared/src/i18n/ru/mobileTrip.ts | 2 +- shared/src/i18n/ru/reservations.ts | 1 + shared/src/i18n/sk/mobileTrip.ts | 2 +- shared/src/i18n/sk/reservations.ts | 1 + shared/src/i18n/sv/mobileTrip.ts | 2 +- shared/src/i18n/sv/reservations.ts | 1 + shared/src/i18n/th/mobileTrip.ts | 2 +- shared/src/i18n/th/reservations.ts | 1 + shared/src/i18n/tr/mobileTrip.ts | 2 +- shared/src/i18n/tr/reservations.ts | 1 + shared/src/i18n/uk/mobileTrip.ts | 2 +- shared/src/i18n/uk/reservations.ts | 1 + shared/src/i18n/vi/mobileTrip.ts | 2 +- shared/src/i18n/vi/reservations.ts | 1 + shared/src/i18n/zh-TW/mobileTrip.ts | 2 +- shared/src/i18n/zh-TW/reservations.ts | 1 + shared/src/i18n/zh/mobileTrip.ts | 2 +- shared/src/i18n/zh/reservations.ts | 1 + 68 files changed, 121 insertions(+), 111 deletions(-) diff --git a/client/src/mobile/components/MWalletButton.test.tsx b/client/src/mobile/components/MWalletButton.test.tsx index 32a7fa254..075a4d56e 100644 --- a/client/src/mobile/components/MWalletButton.test.tsx +++ b/client/src/mobile/components/MWalletButton.test.tsx @@ -61,12 +61,14 @@ describe('MWalletButton', () => { await waitFor(() => expect(onError).toHaveBeenLastCalledWith(expect.stringMatching(/could not be made/i))); }); - it('on Android adds the booking to Google Wallet, under its own name', async () => { + it('on Android adds the booking to Google Wallet, under its own name for screen readers', async () => { wallet.kind = 'google'; wallet.addGoogle.mockResolvedValue('added'); const onError = vi.fn(); render(); - expect(screen.getByRole('button')).toHaveTextContent(/Google Wallet/); + // The label is short, the accessible name says which wallet. + expect(screen.getByRole('button')).toHaveTextContent(/^Wallet$/); + expect(screen.getByRole('button', { name: /Google Wallet/ })).toBeInTheDocument(); fireEvent.click(screen.getByRole('button')); await waitFor(() => expect(wallet.addGoogle).toHaveBeenCalledWith(42, 7, expect.any(String))); expect(wallet.add).not.toHaveBeenCalled(); diff --git a/client/src/mobile/components/MWalletButton.tsx b/client/src/mobile/components/MWalletButton.tsx index 49c052786..2b79635ab 100644 --- a/client/src/mobile/components/MWalletButton.tsx +++ b/client/src/mobile/components/MWalletButton.tsx @@ -57,10 +57,11 @@ export default function MWalletButton({ tripId, reservationId, onError, classNam onClick={() => void add()} disabled={busy} aria-busy={busy} + aria-label={t(wallet === 'google' ? 'reservations.wallet.addGoogle' : 'reservations.wallet.add')} className="flex items-center gap-[6px] rounded-full bg-black px-3 py-[7px] text-[length:calc(12px*var(--fs-scale-body,1))] font-semibold text-white transition-transform active:scale-[.97] disabled:opacity-70" // theme-lint-disable: the wallets' badges are black in either theme > {busy ? : } - {t(wallet === 'google' ? 'reservations.wallet.addGoogle' : 'reservations.wallet.add')} + {t('reservations.wallet.short')} ); return className ?
{button}
: button; diff --git a/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx b/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx index 3da13e163..d40babee6 100644 --- a/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx +++ b/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx @@ -180,14 +180,8 @@ function RoadtripStagePage({ planner, shell, dayId: pageDayId, alts, chrome }: O // A chip that may not be opened is not a button in the first place (`bookingOpens`), // so nothing is turned away silently here. const canEditBookings = planner.can('reservation_edit', planner.trip) - const openBooking = (res: Reservation) => { - if (planner.TRANSPORT_TYPES.has(res.type)) { - shell.openSheet('transport', { reservationId: res.id }) - return - } - planner.setEditingReservation(res) - planner.setShowReservationModal(true) - } + // Every booking opens its detail sheet, as on the desktop; its pencil edits it there. + const openBooking = (res: Reservation) => shell.openSheet('transport', { reservationId: res.id }) // A booked night at the edge of the stage is no stop, and has no stop sheet: it opens the // booking behind the night, else the stay for somebody who may edit days, else the // hotel's place, the way a stay chip in the day timeline does (#2210). diff --git a/client/src/mobile/screens/trip/sheets/MDaySheet.tsx b/client/src/mobile/screens/trip/sheets/MDaySheet.tsx index 0f2d11e00..9bac8813d 100644 --- a/client/src/mobile/screens/trip/sheets/MDaySheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MDaySheet.tsx @@ -111,19 +111,11 @@ export default function MDaySheet({ planner, shell }: MTripSheetsProps) { } } + // A booking opens its detail sheet, as on the desktop; its pencil edits it there. const openReservation = (r: Reservation) => { const isTransport = planner.TRANSPORT_TYPES.has(r.type) - if (isTransport && canEditDays) { - planner.setEditingTransport(r) - planner.setTransportModalDayId(r.day_id ?? null) - planner.setShowTransportModal(true) - } else if (!isTransport && canEditReservations) { - planner.setEditingReservation(r) - planner.setShowReservationModal(true) - } else { - return - } - shell.closeSheet() + if (isTransport ? !canEditDays : !canEditReservations) return + shell.openSheet('transport', { reservationId: r.id }) } const planTransit = () => { diff --git a/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx b/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx index 0344bebf4..03b057fbe 100644 --- a/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx @@ -112,18 +112,11 @@ export default function MPlaceSheet({ planner, shell }: MTripSheetsProps) { ) const openRes = (res: Reservation) => { if (!canOpenRes(res)) return - if (planner.TRANSPORT_TYPES.has(res.type)) { - planner.setEditingTransport(res) - planner.setTransportModalDayId(res.day_id ?? null) - planner.setTransportModalAutomated(false) - planner.setShowTransportModal(true) - } else { - planner.setEditingReservation(res) - planner.setShowReservationModal(true) - } // This sheet hangs off the place selection, not the sheet stack, so its own - // close() is what dismisses it — otherwise the editor opens on top of it. + // close() is what dismisses it; the booking's detail sheet then opens alone, + // with its pencil for editing, as on the desktop. close() + shell.openSheet('transport', { reservationId: res.id }) } const participants = assignmentInDay?.participants || [] @@ -413,7 +406,6 @@ export default function MPlaceSheet({ planner, shell }: MTripSheetsProps) { type="button" onClick={() => openRes(res)} disabled={!canOpenRes(res)} - aria-label={canOpenRes(res) ? t('inspector.editRes') : undefined} className={`flex w-full items-center gap-2 rounded-[14px] px-3 py-[10px] text-start ${INNER_CLS}`} > s.settings.time_format) || '24h' const blurCodes = useSettingsStore(s => s.settings.blur_booking_codes) const [codeRevealed, setCodeRevealed] = useState(false) @@ -118,6 +119,9 @@ export default function MTransportSheet({ planner, shell }: MTripSheetsProps) { } const meta = parseMetadata(res) + // A transport is part of the day plan, a stay, a table or a ticket a booking: + // each is edited under its own permission, as in the tab it belongs to. + const canEdit = isTransportReservationType(res.type) ? canEditDays : canEditBookings const ResIcon = RES_ICONS[res.type as keyof typeof RES_ICONS] || RES_ICONS.other const from = (res.endpoints || []).find(e => e.role === 'from') @@ -362,7 +366,7 @@ export default function MTransportSheet({ planner, shell }: MTripSheetsProps) { {f.original_name} ))} - {canEditDays && ( + {canEdit && (