import { useId, useState } from 'react' import { CalendarDays, CalendarPlus, ChevronRight, Download, FileText, Loader2, MapPin, Route as RouteIcon, UserRound } from 'lucide-react' import type { LucideIcon } from 'lucide-react' import { DialogHeader, DialogSection, DialogShell, DialogTile, NEUTRAL_TINT, fs } from '../shared/DialogShell' import { IcsSubscribeModal } from './IcsSubscribeModal' import { SoftPill } from './planParts' import { useToast } from '../shared/Toast' import type { Trip, Day, Place, Category, AssignmentsMap, Reservation, DayNote } from '../../types' import { useTripExport } from './useTripExport' /** * What a GPX download can carry. Worded by what someone wants on their device * rather than by the GPX element it maps to: "everything", "just the places" for * an offline map, "just the days" for following a plan. An omitted flag defaults * to true server-side, so the first entry needs no query at all. */ const GPX_SCOPES = [ { key: 'all', query: '', labelKey: 'dayplan.gpxAll', icon: Download }, { key: 'places', query: '?dayRoutes=false', labelKey: 'dayplan.gpxPlaces', icon: MapPin }, { key: 'days', query: '?waypoints=false&tracks=false', labelKey: 'dayplan.gpxDays', icon: RouteIcon }, ] as const interface TripExportModalProps { isOpen: boolean onClose: () => void tripId: number trip: Trip days: Day[] places: Place[] categories: Category[] assignments: AssignmentsMap reservations: Reservation[] dayNotes: Record t: (key: string, params?: Record) => string locale: string toast: ReturnType /** * Gates "Subscribe to calendar" only. The one-off ICS download above it stays * open to every member: it is a file they already have the right to read, * while the subscription mints a link that works without an account. */ canManageShare?: boolean } /** * Every way a trip leaves TREK, in one dialog: the day plan as a PDF, the * bookings as a calendar (a one-off .ics or a live subscription) and the map * data as GPX in the three scopes a device actually wants. * * It replaces three separate toolbar buttons, two of which opened hover menus of * their own. On a narrow sidebar that row simply ran out of width and the * exports dropped off the edge; one button cannot. */ export function TripExportModal({ isOpen, onClose, tripId, trip, days, places, categories, assignments, reservations, dayNotes, t, locale, toast, canManageShare = true, }: TripExportModalProps) { const [subscribeOpen, setSubscribeOpen] = useState(false) const titleId = useId() const { fileBase, offerMine, isRunning, anyRunning, exportPdf, downloadIcs, downloadGpx, } = useTripExport({ tripId, data: { trip, days, places, assignments, categories, reservations, dayNotes }, t, locale, toast, exclusive: true, onExported: onClose, requireTrip: false, closeAfterPdf: true, logPdfErrors: true, }) const header = ( } tint={NEUTRAL_TINT} labelId={titleId} onClose={onClose} title={t('dayplan.export')} sub={t('dayplan.exportIntro')} subWraps /> ) return ( <> {/* While the subscription dialog sits on top, Escape and the backdrop are its own: one key press closes only the dialog in front. */}
exportPdf()} /> {offerMine && ( exportPdf(true)} /> )}
{canManageShare && ( setSubscribeOpen(true)} /> )}
{/* GPX: the counterpart to the GPX import in the places sidebar. The format name carries more than a translated heading would, so it stands beside the plain-language one as a pill of its own. */} {t('dayplan.exportMaps')}GPX}>
{GPX_SCOPES.map(scope => ( downloadGpx(scope.key, scope.query)} /> ))}
{/* Mounted after the export dialog, so its backdrop stacks above it. */} {subscribeOpen && canManageShare && ( setSubscribeOpen(false)} /> )} ) } /** One export as a card: its icon tile (a spinner while it works), what it makes, and the chevron. */ function ExportRow({ icon: Icon, title, sub, busy = false, disabled = false, onClick }: { icon: LucideIcon title: string sub?: string busy?: boolean disabled?: boolean onClick: () => void }) { return ( ) }