/* global React, DATA, Panel, LineChart, KPI, fmtN, fmtPct, fmtSignPct */ const Pace = () => { const ods = Object.keys(DATA.booking_pace).sort(); const [od, setOd] = useState(ods.includes('RUH-JED') ? 'RUH-JED' : ods[0]); const bp = DATA.booking_pace[od]; // pace index table: cum bookings at T-30 CY vs LY across all O&Ds const paceTable = useMemo(() => Object.values(DATA.booking_pace).map(b => { const at = (yr, dtd) => { const p = b.points.find(x => x.dtd === dtd); return p ? p[yr] : null; }; const cy30 = at('cy', 30), ly30 = at('ly', 30); return { od: b.od, market: b.market, cy30, ly30, idx: ly30 ? Math.round((cy30 / ly30 - 1) * 1000) / 10 : null, final_cy: b.final_cy, final_ly: b.final_ly }; }).filter(x => x.idx != null).sort((a, b) => b.idx - a.idx), []); const curve = bp.points.map(p => ({ label: p.dtd, dtd: p.dtd, cy: p.cy, ly: p.ly })); const paceNow = bp.points.find(x => x.dtd === 30); const idxNow = paceNow && paceNow.ly ? (paceNow.cy / paceNow.ly - 1) * 100 : 0; return React.createElement('div', { className: 'page' }, React.createElement('div', { className: 'page-head' }, React.createElement('div', { className: 'eyebrow', style: { marginBottom: 8 } }, 'Booking Pace'), React.createElement('h1', { className: 'headline', style: { fontSize: 46 } }, 'Are we ahead of ', React.createElement('em', null, 'last year?')), React.createElement('div', { className: 'subhead' }, 'Cumulative bookings by days-to-departure, this year versus last — normalised for the booking window of each market.')), React.createElement('div', { className: 'row gap-1', style: { marginBottom: 16, flexWrap: 'wrap' } }, React.createElement('span', { className: 'label', style: { marginRight: 6 } }, 'O&D'), ods.slice(0, 22).map(x => React.createElement('button', { key: x, className: 'filter-chip' + (od === x ? ' active' : ''), onClick: () => setOd(x) }, x))), React.createElement('div', { className: 'grid gap-3', style: { gridTemplateColumns: '1.6fr 1fr', marginBottom: 22 } }, React.createElement(Panel, { title: `${od} · booking pace`, sub: `${bp.market} · economy cabin · T-90 → departure` }, React.createElement(LineChart, { data: curve, xKey: 'dtd', height: 300, smooth: true, xFormat: v => v === 0 ? 'DEP' : 'T-' + v, format: fmtN, series: [ { key: 'cy', label: '2026', color: 'var(--accent)', width: 2.1, fill: true }, { key: 'ly', label: '2025', color: 'var(--text-muted)', width: 1.6, dashed: true } ] })), React.createElement('div', { className: 'grid gap-2' }, React.createElement(KPI, { label: 'Pace index · T-30', value: fmtSignPct(idxNow), sub: 'vs same point last year', delta: idxNow }), React.createElement(KPI, { label: 'Final forecast · 2026', value: fmtN(bp.final_cy), sub: `per departure · ${fmtSignPct((bp.final_cy / bp.final_ly - 1) * 100)} YoY` }), React.createElement(KPI, { label: 'On the books · T-30', value: fmtN(paceNow ? paceNow.cy : 0), sub: `${fmtN(paceNow ? paceNow.ly : 0)} last year` }), React.createElement('div', { className: 'boxed', style: { fontSize: 11.5, lineHeight: 1.5 } }, React.createElement('div', { className: 'label', style: { marginBottom: 5 } }, 'Read'), idxNow >= 5 ? `${od} is pacing ${fmtSignPct(idxNow)} ahead of last year at T-30 — protect higher classes and hold deep discounts closed.` : idxNow <= -5 ? `${od} is pacing ${fmtSignPct(idxNow)} behind at T-30 — consider opening lower classes or a tactical fare action to defend load factor.` : `${od} is tracking close to last year's curve — no pace-driven action indicated.`))), React.createElement(Panel, { title: 'Pace index across the network', sub: 'Cumulative bookings at T-30 · this year vs last', flush: true }, React.createElement('div', { style: { maxHeight: 420, overflowY: 'auto' } }, React.createElement('table', { className: 'tbl tbl-zebra' }, React.createElement('thead', null, React.createElement('tr', null, ['O&D', 'Market', 'OTB T-30 ’26', 'OTB T-30 ’25', 'Pace idx', 'Final ’26 fcst'].map((h, i) => React.createElement('th', { key: i, className: i > 1 ? 'num' : '' }, h)))), React.createElement('tbody', null, paceTable.map((r, i) => React.createElement('tr', { key: i, className: 'clickable', onClick: () => setOd(r.od) }, React.createElement('td', { style: { fontWeight: 600 } }, r.od), React.createElement('td', { className: 'text-muted' }, r.market), React.createElement('td', { className: 'num mono' }, fmtN(r.cy30)), React.createElement('td', { className: 'num mono text-muted' }, fmtN(r.ly30)), React.createElement('td', { className: 'num mono ' + (r.idx >= 0 ? 'text-pos' : 'text-neg') }, fmtSignPct(r.idx)), React.createElement('td', { className: 'num mono' }, fmtN(r.final_cy))))))) )); }; window.Pace = Pace;