mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
perf(planner): load the PDF export and the transport modal on demand (#1821)
Two more things every trip paid for whether or not anyone used them. downloadTripPDF hung statically off the days sidebar, so opening any trip pulled in the whole PDF builder. Both call sites are already async and already wrap the call in try/catch, so a failed chunk surfaces as the export error the handler had anyway. TransportModal was already rendered conditionally, which means lazy bites immediately. It is worth more than its own 32 kB: it is the only path to TransitSearchPanel, and that chunk carried tz-lookup — about 200 kB of packed zone geometry. TripPlannerPage 339.78 -> 305.70 kB entry chunk 258.24 -> 218.70 kB TransitSearchPanel 198.67 -> 26.51 kB (and now on demand) TripPDF 226.02 -> 27.78 kB (and now on demand) eager path unchanged at 1,483 kB Worth stating plainly: tz-lookup itself did not go away. It sits in PlaceFormModal.helpers now, and PlaceFormModal is still a static import of the planner, so a trip still loads it. Prying that loose means touching placeOpenState, whose consumers assume a synchronous lookup — a separate change with its own care.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
/>
|
||||
<TripMembersModal isOpen={showMembersModal} onClose={() => setShowMembersModal(false)} tripId={tripId} tripTitle={trip?.title} onMembersChanged={refreshMembers} />
|
||||
<ReservationModal isOpen={showReservationModal} onClose={() => { 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 && <TransportModal isOpen={showTransportModal} onClose={() => { 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 && (
|
||||
<ErrorBoundary boundaryId="planner-panel:transport" fallback={null}>
|
||||
<Suspense fallback={null}>
|
||||
<TransportModal isOpen={showTransportModal} onClose={() => { 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} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
{/* Journey view for a saved public-transit entry (#1065) */}
|
||||
{transitJourney && (
|
||||
<TransitJourneyModal
|
||||
|
||||
@@ -1041,7 +1041,9 @@ describe('TripPlannerPage — modals', () => {
|
||||
|
||||
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' })
|
||||
|
||||
Reference in New Issue
Block a user