diff --git a/client/src/components/PDF/TripPDF.tsx b/client/src/components/PDF/TripPDF.tsx index 6d1fa1f..347cf34 100644 --- a/client/src/components/PDF/TripPDF.tsx +++ b/client/src/components/PDF/TripPDF.tsx @@ -12,6 +12,13 @@ function noteIconSvg(iconId) { return _renderToStaticMarkup(createElement(Icon, { size: 14, strokeWidth: 1.8, color: '#94a3b8' })) } +const TRANSPORT_ICON_MAP = { flight: Plane, train: Train, bus: Bus, car: Car, cruise: Ship } +function transportIconSvg(type) { + if (!_renderToStaticMarkup) return '' + const Icon = TRANSPORT_ICON_MAP[type] || Ticket + return _renderToStaticMarkup(createElement(Icon, { size: 14, strokeWidth: 1.8, color: '#3b82f6' })) +} + // ── SVG inline icons (for chips) ───────────────────────────────────────────── const svgPin = `` const svgClock = `` @@ -96,11 +103,12 @@ interface downloadTripPDFProps { assignments: AssignmentsMap categories: Category[] dayNotes: DayNotesMap + reservations?: any[] t: (key: string, params?: Record) => string locale: string } -export async function downloadTripPDF({ trip, days, places, assignments, categories, dayNotes, t: _t, locale: _locale }: downloadTripPDFProps) { +export async function downloadTripPDF({ trip, days, places, assignments, categories, dayNotes, reservations = [], t: _t, locale: _locale }: downloadTripPDFProps) { await ensureRenderer() const loc = _locale || 'de-DE' const tr = _t || (k => k) @@ -123,15 +131,46 @@ export async function downloadTripPDF({ trip, days, places, assignments, categor const notes = (dayNotes || []).filter(n => n.day_id === day.id) const cost = dayCost(assignments, day.id, loc) + // Transport bookings for this day + const TRANSPORT_TYPES = new Set(['flight', 'train', 'bus', 'car', 'cruise']) + const dayTransport = (reservations || []).filter(r => { + if (!r.reservation_time || !TRANSPORT_TYPES.has(r.type)) return false + return day.date && r.reservation_time.split('T')[0] === day.date + }) + const merged = [] assigned.forEach(a => merged.push({ type: 'place', k: a.order_index ?? a.sort_order ?? 0, data: a })) notes.forEach(n => merged.push({ type: 'note', k: n.sort_order ?? 0, data: n })) + dayTransport.forEach(r => { + const pos = r.day_plan_position ?? (merged.length > 0 ? Math.max(...merged.map(m => m.k)) + 0.5 : 0.5) + merged.push({ type: 'transport', k: pos, data: r }) + }) merged.sort((a, b) => a.k - b.k) let pi = 0 const itemsHtml = merged.length === 0 ? `
${escHtml(tr('dayplan.emptyDay'))}
` : merged.map(item => { + if (item.type === 'transport') { + const r = item.data + const meta = typeof r.metadata === 'string' ? JSON.parse(r.metadata || '{}') : (r.metadata || {}) + const icon = transportIconSvg(r.type) + let subtitle = '' + if (r.type === 'flight') subtitle = [meta.airline, meta.flight_number, meta.departure_airport && meta.arrival_airport ? `${meta.departure_airport} → ${meta.arrival_airport}` : ''].filter(Boolean).join(' · ') + else if (r.type === 'train') subtitle = [meta.train_number, meta.platform ? `Gl. ${meta.platform}` : '', meta.seat ? `Seat ${meta.seat}` : ''].filter(Boolean).join(' · ') + const time = r.reservation_time?.includes('T') ? r.reservation_time.split('T')[1]?.substring(0, 5) : '' + return ` +
+
+ ${icon} +
+
${escHtml(r.title)}${time ? ` ${time}` : ''}
+ ${subtitle ? `
${escHtml(subtitle)}
` : ''} + ${r.confirmation_number ? `
Code: ${escHtml(r.confirmation_number)}
` : ''} +
+
` + } + if (item.type === 'note') { const note = item.data return ` diff --git a/client/src/components/Planner/DayPlanSidebar.tsx b/client/src/components/Planner/DayPlanSidebar.tsx index 91898ff..ce0ebec 100644 --- a/client/src/components/Planner/DayPlanSidebar.tsx +++ b/client/src/components/Planner/DayPlanSidebar.tsx @@ -697,7 +697,7 @@ export default function DayPlanSidebar({ notes.map(n => ({ ...n, day_id: Number(dayId) })) ) try { - await downloadTripPDF({ trip, days, places, assignments, categories, dayNotes: flatNotes, t, locale }) + await downloadTripPDF({ trip, days, places, assignments, categories, dayNotes: flatNotes, reservations, t, locale }) } catch (e) { console.error('PDF error:', e) toast.error(t('dayplan.pdfError') + ': ' + (e?.message || String(e)))