From 2a5c54c0259bbe9c69694fb35b235d18eb487fa0 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 24 Jul 2026 14:41:08 +0200 Subject: [PATCH] feat(reservations): traveler filter on the mobile bookings and transports tabs --- .../mobile/screens/trip/tabs/MBookingsTab.tsx | 25 ++++++++-------- .../screens/trip/tabs/MTransportsTab.tsx | 25 ++++++++-------- .../mobile/screens/trip/tabs/tabChrome.tsx | 29 +++++++++++++++++++ 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/client/src/mobile/screens/trip/tabs/MBookingsTab.tsx b/client/src/mobile/screens/trip/tabs/MBookingsTab.tsx index eca81880d..a226209ea 100644 --- a/client/src/mobile/screens/trip/tabs/MBookingsTab.tsx +++ b/client/src/mobile/screens/trip/tabs/MBookingsTab.tsx @@ -7,7 +7,7 @@ import { openFile } from '../../../../utils/fileDownload' import { useTranslation } from '../../../../i18n' import type { Reservation } from '../../../../types' import MConfirmSheet from '../../settings/MConfirmSheet' -import { CountPill, Field, SectionHeader, StatusDot, TabScroller, TravelerAvatars } from './tabChrome' +import { CountPill, Field, SectionHeader, StatusDot, TabScroller, TravelerAvatars, TravelerFilterRow } from './tabChrome' import { STATUS_COLOR, type MTabScreenProps } from './tabModel' import { groupTransports, orderedEndpoints, parseTransportMeta } from './transportsModel' import { BOOKING_TYPE_COLOR } from './bookingsModel' @@ -22,9 +22,13 @@ import { BOOKING_TYPE_COLOR } from './bookingsModel' */ export default function MBookingsTab({ planner, shell }: MTabScreenProps) { const { t, reservations, days } = planner - const bookings = reservations.filter(r => !planner.TRANSPORT_TYPES.has(r.type)) + const [travelerFilter, setTravelerFilter] = useState>(new Set()) + const allBookings = reservations.filter(r => !planner.TRANSPORT_TYPES.has(r.type)) + const bookings = travelerFilter.size === 0 ? allBookings : allBookings.filter(r => (r.travelers || []).some(tv => travelerFilter.has(tv.user_id))) const groups = groupTransports(bookings, days) const canEdit = planner.can('reservation_edit', planner.trip) + const showTravelerFilter = planner.tripMembers.length > 1 && allBookings.some(r => (r.travelers || []).length > 0) + const toggleTravelerFilter = (id: number) => setTravelerFilter(prev => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n }) const [collapsed, setCollapsed] = useState>({}) const toggle = (id: string) => setCollapsed(c => ({ ...c, [id]: !c[id] })) @@ -34,20 +38,17 @@ export default function MBookingsTab({ planner, shell }: MTabScreenProps) { { id: 'pending', label: t('reservations.pending'), rows: groups.pending }, ].filter(s => s.rows.length > 0) - if (sections.length === 0) { - return ( - + return ( + + {showTravelerFilter && ( + + )} + {sections.length === 0 ? (

{t('mobileTrip.bookingsEmpty')}

-
- ) - } - - return ( - - {sections.map(section => ( + ) : sections.map(section => (
planner.TRANSPORT_TYPES.has(r.type)) + const [travelerFilter, setTravelerFilter] = useState>(new Set()) + const allTransports = reservations.filter(r => planner.TRANSPORT_TYPES.has(r.type)) + const transports = travelerFilter.size === 0 ? allTransports : allTransports.filter(r => (r.travelers || []).some(tv => travelerFilter.has(tv.user_id))) const groups = groupTransports(transports, days) const canEdit = planner.can('day_edit', planner.trip) + const showTravelerFilter = planner.tripMembers.length > 1 && allTransports.some(r => (r.travelers || []).length > 0) + const toggleTravelerFilter = (id: number) => setTravelerFilter(prev => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n }) const [collapsed, setCollapsed] = useState>({}) const toggle = (id: string) => setCollapsed(c => ({ ...c, [id]: !c[id] })) @@ -39,20 +43,17 @@ export default function MTransportsTab({ planner, shell }: MTabScreenProps) { { id: 'transit', label: t('reservations.type.transit'), rows: groups.transit }, ].filter(s => s.rows.length > 0) - if (sections.length === 0) { - return ( - + return ( + + {showTravelerFilter && ( + + )} + {sections.length === 0 ? (

{t('mobileTrip.transportsEmpty')}

-
- ) - } - - return ( - - {sections.map(section => ( + ) : sections.map(section => (
) } + +/** Avatar toggle row to filter the list by assigned traveler (#1517/#1557). */ +export function TravelerFilterRow({ members, active, onToggle, label }: { + members: { id: number; username: string; avatar_url?: string | null }[] + active: Set + onToggle: (id: number) => void + label: string +}) { + return ( +
+ {members.map(m => { + const on = active.has(m.id) + return ( + + ) + })} +
+ ) +}