// Summary panel built from the /route response's `summary` block: // days, nights, flights, hotels, travel legs, activities, total km, and the // list of locations in visit order. import { el } from '../dom.js'; export function renderSummary(tctx) { const route = tctx.route || {}; const s = route.summary || { days: 0, nights: 0, flights: 0, flightSegments: 0, hotels: 0, travelLegs: 0, activities: 0, locations: [], }; const totalKm = route.totalKm || 0; // Show the leg count under the Flights tile only when a flight has segments. const flightSub = s.flightSegments > 0 ? `${s.flightSegments} ${s.flightSegments === 1 ? 'leg' : 'legs'}` : ''; const section = el('section', { class: 'card summary-section' }); section.appendChild( el( 'div', { class: 'section-head' }, el('h2', {}, 'Summary'), el('p', { class: 'muted' }, 'Trip at a glance.'), ), ); const tiles = el( 'div', { class: 'stat-grid' }, tile('🗓️', s.days, s.days === 1 ? 'Day' : 'Days'), tile('🌙', s.nights, s.nights === 1 ? 'Night' : 'Nights'), tile('✈️', s.flights, 'Flights', flightSub), tile('🏨', s.hotels, 'Hotels'), tile('🚗', s.travelLegs, 'Travel legs'), tile('📍', s.activities, 'Activities'), s.rentals > 0 ? tile('🚙', s.rentals, s.rentals === 1 ? 'Rental' : 'Rentals') : null, s.stays > 0 ? tile('🏙️', s.stays, s.stays === 1 ? 'Stay' : 'Stays') : null, ); section.appendChild(tiles); const kmDriven = s.kmDriven || 0; const kmAir = s.kmAir || 0; const includedKm = s.includedKm != null ? s.includedKm : null; const kmBlock = el( 'div', { class: 'km-block' }, el( 'div', { class: 'total-km' }, el('span', { class: 'total-km-value' }, fmtKm(totalKm)), el('span', { class: 'total-km-label' }, 'km total (great-circle)'), ), ); const breakdown = el('div', { class: 'km-breakdown' }); if (kmDriven > 0) { // Rental allowance (includedKm) is paired with the rough driven figure; // flag red when the rough estimate already exceeds the included allowance. const over = includedKm != null && kmDriven > includedKm; const drivenText = includedKm != null ? `≈ ${fmtKm(kmDriven)} km / ${fmtKm(includedKm)} incl.` : `≈ ${fmtKm(kmDriven)} km`; breakdown.appendChild(kmRow('🚗', drivenText, 'driven', over ? 'Rough great-circle estimate already exceeds the included allowance — real road km will be higher' : 'Rough great-circle estimate — not routed driving distance', over ? 'km-over' : '')); } if (kmAir > 0) { breakdown.appendChild(kmRow('✈️', `${fmtKm(kmAir)} km`, 'flown', 'Great-circle distance between airports')); } if (breakdown.childElementCount) kmBlock.appendChild(breakdown); section.appendChild(kmBlock); const areas = s.areas || []; if (areas.length) { const areaBlock = el('div', { class: 'loc-block' }, el('h3', {}, 'Areas')); const ul = el('ul', { class: 'area-list' }); for (const a of areas) { ul.appendChild(el('li', {}, `🏙️ ${a.name} — ${a.days} ${a.days === 1 ? 'day' : 'days'}`)); } areaBlock.appendChild(ul); section.appendChild(areaBlock); } const locations = s.locations || []; const locBlock = el('div', { class: 'loc-block' }, el('h3', {}, 'Locations in order')); if (!locations.length) { locBlock.appendChild(el('p', { class: 'muted' }, 'No located stops yet.')); } else { const ol = el('ol', { class: 'loc-order' }); for (const name of locations) ol.appendChild(el('li', {}, name)); locBlock.appendChild(ol); } section.appendChild(locBlock); return section; } function kmRow(icon, valueText, label, title, cls = '') { return el( 'div', { class: `km-row${cls ? ` ${cls}` : ''}`, title }, el('span', { class: 'km-row-icon' }, icon), el('span', { class: 'km-row-value' }, valueText), el('span', { class: 'km-row-label muted' }, label), ); } function tile(icon, value, label, sub) { return el( 'div', { class: 'stat-tile' }, el('span', { class: 'stat-icon' }, icon), el('span', { class: 'stat-value' }, String(value ?? 0)), el('span', { class: 'stat-label' }, label), sub ? el('span', { class: 'stat-sub' }, sub) : null, ); } function fmtKm(n) { return (Math.round((Number(n) || 0) * 10) / 10).toLocaleString(); }