diff --git a/client/src/components/Planner/DayPlanSidebarToolbar.tsx b/client/src/components/Planner/DayPlanSidebarToolbar.tsx index eb1925583..ff743b52d 100644 --- a/client/src/components/Planner/DayPlanSidebarToolbar.tsx +++ b/client/src/components/Planner/DayPlanSidebarToolbar.tsx @@ -1,6 +1,5 @@ import React, { useState, useRef } from 'react' import { ChevronsDownUp, ChevronsUpDown, FileDown, Undo2, ArrowUpDown, CalendarPlus, Route as RouteIcon } from 'lucide-react' -import { downloadTripPDF } from '../PDF/TripPDF' import { DayReorderPopup } from './DayReorderPopup' import Tooltip from '../shared/Tooltip' import { useToast } from '../shared/Toast' @@ -79,6 +78,11 @@ export function DayPlanSidebarToolbar({ notes.map(n => ({ ...n, day_id: Number(dayId) })) ) try { + // Loaded on click: the PDF builder is ~226 kB and hangs off the + // days sidebar, so every trip used to pay for it whether or not + // anyone exported. A missing chunk lands in the catch below and + // shows the same error the export already had. + const { downloadTripPDF } = await import('../PDF/TripPDF') await downloadTripPDF({ trip, days, places, assignments, categories, dayNotes: flatNotes, reservations, t, locale }) } catch (e) { console.error('PDF error:', e) diff --git a/client/src/mobile/screens/trip/sheets/MExportSheet.tsx b/client/src/mobile/screens/trip/sheets/MExportSheet.tsx index ec532e435..04b7eb579 100644 --- a/client/src/mobile/screens/trip/sheets/MExportSheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MExportSheet.tsx @@ -2,7 +2,6 @@ import { useState, type ReactNode } from 'react' import { CalendarPlus, ChevronRight, FileDown } from 'lucide-react' import MSheet from '../../../components/MSheet' import { IcsSubscribeModal } from '../../../../components/Planner/IcsSubscribeModal' -import { downloadTripPDF } from '../../../../components/PDF/TripPDF' import { useTripStore } from '../../../../store/tripStore' import { useTranslation } from '../../../../i18n' import { INNER_CLS, TileHeader } from './MTripSheetUi' @@ -30,6 +29,8 @@ export default function MExportSheet({ planner, shell }: MTripSheetsProps) { ) setPdfBusy(true) try { + // See DayPlanSidebarToolbar: loaded on demand, not with the trip. + const { downloadTripPDF } = await import('../../../../components/PDF/TripPDF') await downloadTripPDF({ trip: planner.trip, days: planner.days, diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index e70960fe2..1fe164468 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -17,7 +17,6 @@ import TripFormModal from '../components/Trips/TripFormModal' import SlidingTabs from '../components/shared/SlidingTabs' import TripMembersModal from '../components/Trips/TripMembersModal' import { ReservationModal } from '../components/Planner/ReservationModal' -import { TransportModal } from '../components/Planner/TransportModal' import TransitJourneyModal from '../components/Planner/TransitJourneyModal' import BookingImportModal from '../components/Planner/BookingImportModal' import AirTrailImportModal from '../components/Planner/AirTrailImportModal' @@ -67,6 +66,12 @@ const ExpenseModal = lazyWithRetry(() => import('../components/Budget/CostsPanel').then(m => ({ default: m.ExpenseModal })) ) const CollabPanel = lazyWithRetry(() => import('../components/Collab/CollabPanel')) +// Already rendered conditionally, so lazy bites immediately. Worth it beyond its +// own 63 kB: it is the only path to TransitSearchPanel, which drags in tz-lookup +// — about 200 kB of packed zone geometry that every trip used to load. +const TransportModal = lazyWithRetry(() => + import('../components/Planner/TransportModal').then(m => ({ default: m.TransportModal })) +) /** * One tab panel, with its own net. @@ -794,7 +799,13 @@ function TripPlannerPageDesktop(): React.ReactElement | null { /> setShowMembersModal(false)} tripId={tripId} tripTitle={trip?.title} onMembersChanged={refreshMembers} /> { if (importReviewActive) { advanceImportReview() } else { setShowReservationModal(false); setEditingReservation(null); setBookingForAssignmentId(null) } }} onSave={async (data) => { const r = await handleSaveReservation(data); if (importReviewActive && r) advanceImportReview(); return r }} reservation={editingReservation} prefill={reservationPrefill} days={days} places={places} assignments={assignments} selectedDayId={selectedDayId} files={files} onFileUpload={canUploadFiles ? (fd) => tripActions.addFile(tripId, fd) : undefined} onFileDelete={(id) => tripActions.deleteFile(tripId, id)} accommodations={tripAccommodations} defaultAssignmentId={bookingForAssignmentId} onOpenExpense={openBookingExpense} tripMembers={tripMembers} /> - {showTransportModal && { if (importReviewActive) { advanceImportReview() } else { setShowTransportModal(false); setEditingTransport(null); setTransportModalDayId(null); setTransportModalAutomated(false); setTransitPrefill(null) } }} onSave={async (data) => { const r = await handleSaveTransport(data); if (importReviewActive && r) advanceImportReview(); return r }} reservation={editingTransport} prefill={transportPrefill} days={days} selectedDayId={transportModalDayId} files={files} onFileUpload={canUploadFiles ? (fd) => tripActions.addFile(tripId, fd) : undefined} onFileDelete={(id) => tripActions.deleteFile(tripId, id)} onOpenExpense={openBookingExpense} places={places} assignments={assignments} accommodations={tripAccommodations} initialAutomated={transportModalAutomated} transitPrefill={transitPrefill} tripHasDates={tripHasDates} tripMembers={tripMembers} />} + {showTransportModal && ( + + + { if (importReviewActive) { advanceImportReview() } else { setShowTransportModal(false); setEditingTransport(null); setTransportModalDayId(null); setTransportModalAutomated(false); setTransitPrefill(null) } }} onSave={async (data) => { const r = await handleSaveTransport(data); if (importReviewActive && r) advanceImportReview(); return r }} reservation={editingTransport} prefill={transportPrefill} days={days} selectedDayId={transportModalDayId} files={files} onFileUpload={canUploadFiles ? (fd) => tripActions.addFile(tripId, fd) : undefined} onFileDelete={(id) => tripActions.deleteFile(tripId, id)} onOpenExpense={openBookingExpense} places={places} assignments={assignments} accommodations={tripAccommodations} initialAutomated={transportModalAutomated} transitPrefill={transitPrefill} tripHasDates={tripHasDates} tripMembers={tripMembers} /> + + + )} {/* Journey view for a saved public-transit entry (#1065) */} {transitJourney && ( { cleanup() renderPage({ showTransportModal: true }) - expect(screen.getByTestId('transport-modal')).toBeInTheDocument() + // Loads on demand now — it is the only path to TransitSearchPanel, which + // carries tz-lookup. + expect(await screen.findByTestId('transport-modal')).toBeInTheDocument() await act(async () => { await props('transportModal').onSave({ title: 'ICE' }) }) expect(hookState.handleSaveTransport).toHaveBeenCalledWith({ title: 'ICE' })