From c6f45b9be69a2add688c6ff2595ed95711bc6db0 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sun, 11 Oct 2026 14:22:46 +0200 Subject: [PATCH] refactor(planner): a day head's pill row moves into a module of its own The stays, hire cars and plugin label under a day's name now live in DayHeadPills, so DayPlanSidebar shrinks back under its size baseline. The stay fields are read through their types instead of an any cast, and the size, eslint and theme-disable baselines are lowered to the new counts. --- client/scripts/eslint-baseline.json | 4 +- client/scripts/size-baseline.json | 2 +- client/scripts/theme-disable-baseline.json | 1 - .../src/components/Planner/DayHeadPills.tsx | 112 ++++++++++++++++++ .../src/components/Planner/DayPlanSidebar.tsx | 57 +-------- 5 files changed, 119 insertions(+), 57 deletions(-) create mode 100644 client/src/components/Planner/DayHeadPills.tsx diff --git a/client/scripts/eslint-baseline.json b/client/scripts/eslint-baseline.json index 1788f48cd..6b322f5f2 100644 --- a/client/scripts/eslint-baseline.json +++ b/client/scripts/eslint-baseline.json @@ -1,10 +1,10 @@ { "src": { - "@typescript-eslint/no-non-null-assertion": 407, + "@typescript-eslint/no-non-null-assertion": 406, "@typescript-eslint/no-misused-promises": 876, "no-console": 88, "complexity": 231, - "@typescript-eslint/no-explicit-any": 154, + "@typescript-eslint/no-explicit-any": 149, "trek/no-swallowed-catch": 135, "max-params": 35, "trek/store-without-selector": 69, diff --git a/client/scripts/size-baseline.json b/client/scripts/size-baseline.json index 40fb14cc5..0686ec8b8 100644 --- a/client/scripts/size-baseline.json +++ b/client/scripts/size-baseline.json @@ -12,7 +12,7 @@ "src/components/PDF/TripPDF.tsx": 1097, "src/components/Planner/DayDetailPanel.test.tsx": 2333, "src/components/Planner/DayPlanSidebar.test.tsx": 5381, - "src/components/Planner/DayPlanSidebar.tsx": 3052, + "src/components/Planner/DayPlanSidebar.tsx": 3001, "src/components/Planner/PlaceFormModal.test.tsx": 2864, "src/components/Planner/PlaceFormModal.tsx": 1250, "src/components/Planner/PlaceInspector.tsx": 1308, diff --git a/client/scripts/theme-disable-baseline.json b/client/scripts/theme-disable-baseline.json index de8d283c1..0a39028fa 100644 --- a/client/scripts/theme-disable-baseline.json +++ b/client/scripts/theme-disable-baseline.json @@ -30,7 +30,6 @@ "components/Roadtrip/RoadtripCategoryPicker.tsx": 2, "components/Roadtrip/RoadtripCorridorPanel.tsx": 3, "components/Roadtrip/RoadtripSidebarStopRows.tsx": 4, - "components/Roadtrip/RoadtripStopPopup.tsx": 1, "components/Roadtrip/RoadtripTrackModal.tsx": 1, "components/Roadtrip/StopKindPicker.tsx": 2, "components/Roadtrip/dayColors.ts": 8, diff --git a/client/src/components/Planner/DayHeadPills.tsx b/client/src/components/Planner/DayHeadPills.tsx new file mode 100644 index 000000000..07663902f --- /dev/null +++ b/client/src/components/Planner/DayHeadPills.tsx @@ -0,0 +1,112 @@ +import { Car, Hotel, Ticket } from 'lucide-react'; +import React from 'react'; +import { useTranslation } from '../../i18n'; +import type { Accommodation, Reservation } from '../../types'; +import { fs } from '../shared/DialogShell'; +import { Tooltip } from '../shared/Tooltip'; +import { SoftPill } from './planParts'; + +/** The second line of a day head: its stays, its hire cars and a plugin's name for the day. */ +export function DayHeadPills({ + dayId, + stays, + rentals, + pendingStays, + reservations, + label, + onPlaceClick, + onOpenBooking, + onOpenRental, +}: { + dayId: number; + stays: Accommodation[]; + rentals: Reservation[]; + pendingStays: Set; + reservations: Reservation[]; + label?: string | null; + onPlaceClick: (placeId: number) => void; + onOpenBooking?: (reservation: Reservation) => void; + onOpenRental: (reservation: Reservation) => void; +}): React.ReactElement { + const { t } = useTranslation(); + return ( +
+ {stays.map((acc) => { + const isCheckIn = acc.start_day_id === dayId; + const isCheckOut = acc.end_day_id === dayId; + const accName = acc.place_name || acc.reservation_title || ''; + // The state is said in words as well as colour: check-in green, + // check-out red, a stay in between quiet. + const state = isCheckOut && !isCheckIn ? t('day.checkOut') : isCheckIn ? t('day.checkIn') : null; + const iconTone = isCheckOut && !isCheckIn ? 'text-danger' : isCheckIn ? 'text-success' : 'text-content-faint'; + // Not confirmed yet: a dashed ring says so at a glance, the tooltip in words (#2281). + const pending = pendingStays.has(acc.id); + const named = state ? `${state}: ${accName}` : accName; + const tip = pending ? `${named} (${t('reservations.pending')})` : named; + // The name opens the place, as before; a stay with a booking gets a + // second target in the same pill that opens the booking (#2363). + const stayBooking = onOpenBooking + ? reservations.find((r) => r.accommodation_id != null && Number(r.accommodation_id) === acc.id) + : undefined; + return ( + + + + + {stayBooking && onOpenBooking && ( + + + + )} + + ); + })} + {/* Active rental car badges */} + {rentals.map((r) => ( + + ))} + {/* A plugin's name for this day (which leg of the trip it belongs to). */} + {label && {label}} +
+ ); +} diff --git a/client/src/components/Planner/DayPlanSidebar.tsx b/client/src/components/Planner/DayPlanSidebar.tsx index c48f0d0c5..07db1adbe 100644 --- a/client/src/components/Planner/DayPlanSidebar.tsx +++ b/client/src/components/Planner/DayPlanSidebar.tsx @@ -70,6 +70,7 @@ import { projectDayItinerary } from '../Map/dayTourProjection' import { usePluginDaySchedule, usePluginDayTints, dayTintBackground, dayTinted, PluginDayScheduleRow, formatScheduleMinutes } from '../Plugins/PluginDaySchedule' import { MobileAddPlaceButton } from './DayPlanSidebarMobileAddPlaceButton' import { DayPlanSidebarToolbar } from './DayPlanSidebarToolbar' +import { DayHeadPills } from './DayHeadPills' import { DayPlanSidebarNoteModal } from './DayPlanSidebarNoteModal' import { DayPlanSidebarTimeConfirmModal } from './DayPlanSidebarTimeConfirmModal' import { DayPlanSidebarTransportDetailModal } from './DayPlanSidebarTransportDetailModal' @@ -1735,59 +1736,9 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP {hasDayPills && ( -
- {dayAccs.map(acc => { - const isCheckIn = acc.start_day_id === day.id - const isCheckOut = acc.end_day_id === day.id - const accName = (acc as any).place_name || (acc as any).reservation_title - // The state is said in words as well as colour: check-in green, - // check-out red, a stay in between quiet. - const state = isCheckOut && !isCheckIn ? t('day.checkOut') : isCheckIn ? t('day.checkIn') : null - const iconTone = isCheckOut && !isCheckIn ? 'text-danger' : isCheckIn ? 'text-success' : 'text-content-faint' - // Not confirmed yet: a dashed ring says so at a glance, the tooltip in words (#2281). - const pending = pendingStays.has(acc.id) - const named = state ? `${state}: ${accName}` : accName - const tip = pending ? `${named} (${t('reservations.pending')})` : named - // The name opens the place, as before; a stay with a booking gets a - // second target in the same pill that opens the booking (#2363). - const stayBooking = onOpenBooking - ? reservations.find(r => r.accommodation_id != null && Number(r.accommodation_id) === acc.id) - : undefined - return ( - - - - - {stayBooking && ( - - - - )} - - ) - })} - {/* Active rental car badges */} - {activeRentals.map(r => ( - - ))} - {/* A plugin's name for this day (which leg of the trip it belongs to). */} - {dayTint?.label && {dayTint.label}} -
+ { if (onOpenBooking) onOpenBooking(r); else setTransportDetail(r) }} /> )}