/* global React, DATA */ /* Raporlama — segment kesitleri + aylık görünüm */ const Reporting = () => { const [mode, setMode] = useState('segment'); // 'segment' | 'monthly' const [view, setView] = useState('market'); // Segment Detay period — re-aggregates each tab from the monthly cross-tabs. // Default = YTD (1 Jan → today) to match the prior "2025 YTD vs 2026 YTD" view. const [segPeriod, setSegPeriod] = useState({ from: '2026-01-01', to: '2026-05-05' }); const segWeights = useMemo(() => monthWeights(segPeriod.from, segPeriod.to), [segPeriod]); const segAgg = (name, key) => [ aggMonthly(DATA[`by_${name}_monthly_2025`], key, segWeights), aggMonthly(DATA[`by_${name}_monthly_2026`], key, segWeights), ]; const segFm = new Date(segPeriod.from + 'T00:00:00').getMonth() + 1; const segTm = new Date(segPeriod.to + 'T00:00:00').getMonth() + 1; const periodLabel = `${MONTHS[segFm]}–${MONTHS[segTm]}`; const tabs = [ { id: 'market', label: 'Pazar' }, { id: 'agency', label: 'Acente' }, { id: 'hotel', label: 'Otel' }, { id: 'room', label: 'Oda' }, { id: 'star', label: 'Yıldız Sınıfı' }, { id: 'concept', label: 'Konsept' }, { id: 'channel', label: 'Kanal' }, { id: 'airport', label: 'Havalimanı' }, ]; return React.createElement('div', { style: { padding: 16, display: 'flex', flexDirection: 'column', gap: 12 } }, // Mode toggle: Segment Detay vs Aylık Görünüm React.createElement('div', { className: 'panel', style: { padding: '10px 16px', display: 'flex', alignItems: 'center', gap: 16 } }, React.createElement('span', { style: { fontSize: 10, letterSpacing: '0.22em', textTransform: 'uppercase', fontWeight: 700, color: 'var(--text-muted)' } }, 'GÖRÜNÜM'), React.createElement('div', { className: 'filter-chip' + (mode === 'segment' ? ' active' : ''), onClick: () => setMode('segment') }, 'Segment Detay'), React.createElement('div', { className: 'filter-chip' + (mode === 'monthly' ? ' active' : ''), onClick: () => setMode('monthly') }, 'Aylık Görünüm'), React.createElement('div', { style: { marginLeft: 'auto', fontSize: 10.5, fontStyle: 'italic', color: 'var(--text-muted)' } }, mode === 'segment' ? `Segment kırılımı · 2025 vs 2026 · ${periodLabel}` : 'Tüm 2026 vs 2025 · sezon Apr-Eki içinde aylık karşılaştırma') ), mode === 'monthly' && React.createElement(MonthlyView), mode === 'segment' && React.createElement('div', { className: 'panel' }, React.createElement('div', { className: 'tabs' }, tabs.map(t => React.createElement('div', { key: t.id, className: 'tab' + (view === t.id ? ' active' : ''), onClick: () => setView(t.id) }, t.label)) ), React.createElement('div', { style: { padding: '8px 16px', display: 'flex', gap: 16, fontSize: 10.5, color: 'var(--text-dim)', alignItems: 'center', borderBottom: '1px solid var(--line)' } }, React.createElement('div', { className: 'row gap-2' }, React.createElement('span', { style: { width: 12, height: 10, background: 'color-mix(in srgb, var(--text-dim) 40%, transparent)' } }), `2025 (${periodLabel})` ), React.createElement('div', { className: 'row gap-2' }, React.createElement('span', { style: { width: 12, height: 10, background: 'var(--brand)' } }), `2026 (${periodLabel})` ), React.createElement('div', { style: { marginLeft: 'auto' } }, React.createElement('button', { className: 'btn btn-sm' }, 'Dışa Aktar'), ) ) ), mode === 'segment' && React.createElement(PeriodFilter, { value: segPeriod, onChange: setSegPeriod }), mode === 'segment' && view === 'market' && (() => { const [a, b] = segAgg('market', 'SourceMarket'); return React.createElement(SegmentBlock, { key: 'm', d25: a, d26: b, keyName: 'SourceMarket', label: 'Kaynak Pazar', topN: 13, periodLabel }); })(), mode === 'segment' && view === 'agency' && (() => { const [a, b] = segAgg('agency', 'AgencyName'); return React.createElement(SegmentBlock, { key: 'a', d25: a, d26: b, keyName: 'AgencyName', label: 'Acente', topN: 20, horizontal: true, periodLabel }); })(), mode === 'segment' && view === 'hotel' && React.createElement(HotelView, { period: segPeriod, periodLabel }), mode === 'segment' && view === 'room' && (() => { const [a, b] = segAgg('room', 'RoomCategory'); return React.createElement(SegmentBlock, { key: 'r', d25: a, d26: b, keyName: 'RoomCategory', label: 'Oda Kategorisi', periodLabel }); })(), mode === 'segment' && view === 'star' && (() => { const [a, b] = segAgg('stars', 'Stars'); return React.createElement(SegmentBlock, { key: 's', d25: a, d26: b, keyName: 'Stars', label: 'Yıldız Sınıfı', formatLabel: v => `${v}★`, periodLabel }); })(), mode === 'segment' && view === 'concept' && (() => { const [a, b] = segAgg('concept', 'Concept'); return React.createElement(SegmentBlock, { key: 'c', d25: a, d26: b, keyName: 'Concept', label: 'Konsept', periodLabel }); })(), mode === 'segment' && view === 'channel' && (() => { const [a, b] = segAgg('channel', 'Channel'); return React.createElement(SegmentBlock, { key: 'ch', d25: a, d26: b, keyName: 'Channel', label: 'Kanal', periodLabel }); })(), mode === 'segment' && view === 'airport' && React.createElement(AirportView, { period: segPeriod, periodLabel }) ); }; // Aylık Görünüm: 12-row monthly table comparing 2025 vs 2026 with full-season chart on top. const MonthlyView = () => { const m25 = useMemo(() => { const m = {}; (DATA.by_month_2025 || []).forEach(r => m[r.CheckInMonth] = r); return m; }, []); const budgetByMonth = useMemo(() => { const m = {}; (DATA.budget_vs_actual_2026 || []).forEach(r => m[r.month] = r); return m; }, []); // Use budget_vs_actual_2026 as the source of truth for 2026 monthly Gerçekleşen const rows = Array.from({ length: 12 }, (_, i) => { const m = i + 1; const ly = m25[m] || { revenue: 0, pax: 0, bookings: 0 }; const bud = budgetByMonth[m] || { budget: 0, actual_otb: 0 }; const yoyRev = ly.revenue ? (bud.actual_otb / ly.revenue - 1) * 100 : null; return { month: m, label: MONTHS[m], ly_rev: ly.revenue || 0, ly_pax: ly.pax || 0, ly_bookings: ly.bookings || 0, cur_rev: bud.actual_otb || 0, budget_rev: bud.budget || 0, yoyRev, }; }); const [period, setPeriod] = useState({ from: '2026-04-01', to: '2026-10-31' }); const pMonths = new Set(monthWeights(period.from, period.to).filter(w => w.weight > 0).map(w => w.month)); const fm = new Date(period.from + 'T00:00:00').getMonth() + 1; const tm = new Date(period.to + 'T00:00:00').getMonth() + 1; const fRows = rows.filter(r => pMonths.has(r.month)); const chartData = fRows.map(r => ({ label: r.label, rev25: r.ly_rev / 1e6, rev26: r.cur_rev / 1e6, bud26: r.budget_rev / 1e6, })); const totals = fRows.reduce((acc, r) => ({ ly: acc.ly + r.ly_rev, cur: acc.cur + r.cur_rev, bud: acc.bud + r.budget_rev, pax: acc.pax + r.ly_pax, }), { ly: 0, cur: 0, bud: 0, pax: 0 }); const monthlyFacts = { donem: `${MONTHS[fm]}–${MONTHS[tm]}`, toplam_2025_gerceklesen_M: +(totals.ly / 1e6).toFixed(2), toplam_2026_butce_M: +(totals.bud / 1e6).toFixed(2), toplam_2026_gerceklesen_M: +(totals.cur / 1e6).toFixed(2), butce_gerceklesme_pct: totals.bud ? +((totals.cur / totals.bud) * 100).toFixed(1) : null, yoy_pct: totals.ly ? +(((totals.cur / totals.ly) - 1) * 100).toFixed(1) : null, aylar: fRows.map(r => ({ ay: r.label, gerceklesen_2026_M: +(r.cur_rev / 1e6).toFixed(2), butce_2026_M: +(r.budget_rev / 1e6).toFixed(2), gerceklesen_2025_M: +(r.ly_rev / 1e6).toFixed(2), yoy_pct: r.yoyRev != null ? +r.yoyRev.toFixed(1) : null, })), }; return React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } }, React.createElement(PeriodFilter, { value: period, onChange: setPeriod }), React.createElement(Panel, { title: 'Aylık Performans · Sezon Karşılaştırma', sub: `${MONTHS_FULL[fm]}–${MONTHS_FULL[tm]} · € milyon · 2025 Gerçekleşen (gri) · 2026 Gerçekleşen (kırmızı) · 2026 Bütçe (kesikli)` }, React.createElement(LineChart, { data: chartData, height: 280, format: v => '€' + v.toFixed(0) + 'M', series: [ { key: 'rev25', label: '2025 Gerçekleşen', color: 'color-mix(in srgb, var(--text-dim) 60%, transparent)', width: 1.6 }, { key: 'bud26', label: '2026 Bütçe', color: 'var(--info)', width: 1.5, dashed: true }, { key: 'rev26', label: '2026 Gerçekleşen', color: 'var(--brand)', width: 2.2, fill: true }, ] }) ), React.createElement(InsightBox, { label: 'Aylık Performans · Sezon Karşılaştırma', hint: 'Aylık 2026 Gerçekleşen vs Bütçe vs 2025 Gerçekleşen. Sezon toplamını, bütçe gerçekleşme oranını, YoY\'u ve en güçlü/zayıf ayları vurgula.', facts: monthlyFacts, }), React.createElement(Panel, { title: 'Aylık Detay', sub: `${fRows.length} ay (${MONTHS[fm]}–${MONTHS[tm]}) · 2025 Gerçekleşen: ${fmtEurM(totals.ly)} · 2026 Bütçe: ${fmtEurM(totals.bud)} · 2026 Gerçekleşen: ${fmtEurM(totals.cur)}`, flush: true }, React.createElement('table', { className: 'tbl' }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', null, 'Ay'), React.createElement('th', { className: 'num' }, '2025 Gerçekleşen'), React.createElement('th', { className: 'num' }, '2026 Bütçe'), React.createElement('th', { className: 'num' }, '2026 Gerçekleşen'), React.createElement('th', { className: 'num' }, 'Bütçeye %'), React.createElement('th', { className: 'num' }, 'YoY'), React.createElement('th', { className: 'num' }, '2025 Yolcu'), React.createElement('th', { className: 'num' }, 'Pay') ) ), React.createElement('tbody', null, fRows.map((r, i) => { const budRatio = r.budget_rev ? r.cur_rev / r.budget_rev * 100 : null; const inSeason = r.month >= 4 && r.month <= 10; const share = totals.cur ? r.cur_rev / totals.cur * 100 : 0; return React.createElement('tr', { key: i, style: inSeason ? {} : { opacity: 0.55 } }, React.createElement('td', { style: { fontWeight: 500 } }, r.label + (inSeason ? '' : ' ')), React.createElement('td', { className: 'num text-muted' }, fmtEurM(r.ly_rev)), React.createElement('td', { className: 'num' }, fmtEurM(r.budget_rev)), React.createElement('td', { className: 'num', style: { fontWeight: 600 } }, fmtEurM(r.cur_rev)), React.createElement('td', { className: 'num' }, budRatio != null ? React.createElement('span', { style: { fontWeight: 600, color: budRatio >= 90 ? 'var(--green)' : budRatio >= 80 ? 'var(--amber)' : 'var(--red)' } }, budRatio.toFixed(0) + '%') : React.createElement('span', { className: 'text-faint' }, '—') ), React.createElement('td', { className: 'num' }, r.yoyRev != null ? React.createElement(Delta, { value: r.yoyRev }) : React.createElement('span', { className: 'text-faint' }, '—') ), React.createElement('td', { className: 'num text-muted' }, fmtN(r.ly_pax)), React.createElement('td', { className: 'num text-muted' }, share.toFixed(1) + '%') ); }) ) ) ) ); }; const SegmentBlock = ({ d25, d26, keyName, label, topN = 100, horizontal = false, formatLabel, periodLabel = 'YTD' }) => { // Re-evaluated on every render; orientation/resize re-renders via App's // useIsMobile. Drives the horizontal-scroll de-densify for the bar chart (#8). const isMobile = typeof window !== 'undefined' && (window.innerWidth <= 768 || window.innerHeight <= 480); const merged = useMemo(() => { const m = {}; (d25 || []).slice(0, topN).forEach(r => { m[r[keyName]] = { label: formatLabel ? formatLabel(r[keyName]) : r[keyName], rev25: (r.revenue || 0) / 1e6, pax25: r.pax || 0, adr25: r.adr || 0, rn25: r.roomnights || 0, }; }); (d26 || []).slice(0, topN).forEach(r => { const k = r[keyName]; if (!m[k]) m[k] = { label: formatLabel ? formatLabel(k) : k }; m[k].rev26 = (r.revenue || 0) / 1e6; m[k].pax26 = r.pax || 0; m[k].adr26 = r.adr || 0; m[k].rn26 = r.roomnights || 0; }); return Object.values(m).sort((a, b) => (b.rev25 || 0) - (a.rev25 || 0)); }, [d25, d26, keyName, topN]); const totals = merged.reduce((acc, r) => ({ rev25: acc.rev25 + (r.rev25 || 0), rev26: acc.rev26 + (r.rev26 || 0), pax25: acc.pax25 + (r.pax25 || 0), }), { rev25: 0, rev26: 0, pax25: 0 }); const insightFacts = { segment: label, donem: periodLabel, toplam_gelir_2025_M: +totals.rev25.toFixed(2), toplam_gelir_2026_M: +totals.rev26.toFixed(2), satirlar: merged.slice(0, 12).map(r => ({ ad: r.label, gelir_2025_M: +(r.rev25 || 0).toFixed(2), gelir_2026_M: +(r.rev26 || 0).toFixed(2), yoy_gelir_pct: (r.rev25 && r.rev26 != null) ? +(((r.rev26 / r.rev25) - 1) * 100).toFixed(1) : null, pax_2025: r.pax25 || 0, pax_2026: r.pax26 || 0, adr_2025: Math.round(r.adr25 || 0), adr_2026: Math.round(r.adr26 || 0), })), }; return React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } }, React.createElement(Panel, { title: `${label} Bazında Gelir · ${periodLabel}`, sub: `€ milyon · 2025 vs 2026 · ${periodLabel} · ilk ${Math.min(topN, merged.length)}` }, React.createElement(BarChart, { data: merged, height: horizontal ? 480 : 360, valueKey: 'rev25', secondaryKey: d26 ? 'rev26' : null, labelKey: 'label', format: v => '€' + v.toFixed(0) + 'M', color: 'color-mix(in srgb, var(--text-dim) 50%, transparent)', secondaryColor: 'var(--brand)', horizontal, valueLabel: !horizontal, // On phones, give each group room and scroll horizontally instead of // crushing 13 groups + value labels into ~340px (#8). minGroupWidth: (!horizontal && isMobile) ? 86 : 0, tooltipFormat: (d) => React.createElement('div', null, React.createElement('div', { style: { fontWeight: 600, marginBottom: 6, color: 'var(--paper)' } }, d.label), React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, color: 'var(--paper)', marginBottom: 2 } }, React.createElement('span', { style: { width: 10, height: 3, background: 'color-mix(in srgb, var(--text-dim) 50%, transparent)', flexShrink: 0 } }), React.createElement('span', { style: { fontWeight: 600 } }, '2025') ), React.createElement('div', { style: { color: 'var(--paper)', fontSize: 10.5, paddingLeft: 16, marginBottom: 6 } }, `Gelir: €${(d.rev25 || 0).toFixed(1)}M · ADR: €${(d.adr25 || 0).toFixed(0)} · Yolcu: ${fmtN(d.pax25 || 0)}`), d.rev26 != null && React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, color: 'var(--paper)', marginBottom: 2 } }, React.createElement('span', { style: { width: 10, height: 3, background: 'var(--brand)', flexShrink: 0 } }), React.createElement('span', { style: { fontWeight: 600 } }, '2026') ), d.rev26 != null && React.createElement('div', { style: { color: 'var(--paper)', fontSize: 10.5, paddingLeft: 16 } }, `Gelir: €${(d.rev26 || 0).toFixed(1)}M · ADR: €${(d.adr26 || 0).toFixed(0)} · Yolcu: ${fmtN(d.pax26 || 0)}`) ) }) ), React.createElement(InsightBox, { label: `${label} Bazında Gelir · ${periodLabel}`, hint: `${label} kırılımında 2025 vs 2026 ${periodLabel} gelir karşılaştırması. En büyük segmenti, en hızlı büyüyen/daralanı, YoY uçlarını ve ADR farkını vurgula.`, facts: insightFacts, }), React.createElement(Panel, { title: `${label} — Detay`, sub: `${merged.length} satır · ${periodLabel} · 2025: ${fmtEurM(totals.rev25 * 1e6)} · 2026: ${fmtEurM(totals.rev26 * 1e6)}`, flush: true }, React.createElement('div', { style: { maxHeight: 460, overflowY: 'auto', overflowX: 'auto' } }, React.createElement('table', { className: 'tbl' }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', null, '#'), React.createElement('th', null, label), React.createElement('th', { className: 'num' }, '2025 Gelir'), React.createElement('th', { className: 'num' }, '2026 Gelir'), React.createElement('th', { className: 'num' }, 'YoY Gelir'), React.createElement('th', { className: 'num' }, '2025 Yolcu'), React.createElement('th', { className: 'num' }, '2026 Yolcu'), React.createElement('th', { className: 'num' }, 'YoY Yolcu'), React.createElement('th', { className: 'num' }, '2025 ADR'), React.createElement('th', { className: 'num' }, '2026 ADR'), React.createElement('th', { className: 'num' }, 'Pay') ) ), React.createElement('tbody', null, merged.map((r, i) => { const yoyRev = r.rev26 != null && r.rev25 ? ((r.rev26 / r.rev25) - 1) * 100 : null; const yoyPax = r.pax26 != null && r.pax25 ? ((r.pax26 / r.pax25) - 1) * 100 : null; const share = (r.rev25 / totals.rev25) * 100; return React.createElement('tr', { key: i }, React.createElement('td', { className: 'text-faint mono' }, String(i+1).padStart(2, '0')), React.createElement('td', { style: { fontWeight: 500 } }, r.label), React.createElement('td', { className: 'num' }, '€' + (r.rev25||0).toFixed(1) + 'M'), React.createElement('td', { className: 'num', style: { fontWeight: 600 } }, r.rev26 != null ? '€' + r.rev26.toFixed(1) + 'M' : '—'), React.createElement('td', { className: 'num' }, yoyRev != null ? React.createElement(Delta, { value: yoyRev }) : React.createElement('span', { className: 'text-faint' }, '—') ), React.createElement('td', { className: 'num text-muted' }, fmtN(r.pax25 || 0)), React.createElement('td', { className: 'num' }, fmtN(r.pax26 || 0)), React.createElement('td', { className: 'num' }, yoyPax != null ? React.createElement(Delta, { value: yoyPax }) : React.createElement('span', { className: 'text-faint' }, '—') ), React.createElement('td', { className: 'num' }, '€' + (r.adr25 || 0).toFixed(0)), React.createElement('td', { className: 'num' }, '€' + (r.adr26 || 0).toFixed(0)), React.createElement('td', { className: 'num text-muted' }, share.toFixed(1) + '%') ); }) ) ) ) ) ); }; const HotelView = ({ period = { from: '2026-01-01', to: '2026-05-05' }, periodLabel = 'YTD' }) => { const [search, setSearch] = useState(''); const [region, setRegion] = useState('all'); const [stars, setStars] = useState('all'); const [sort, setSort] = useState('rev26'); // Re-aggregate the portfolio from the hotel×month cross-tab for the period. const weights = useMemo(() => monthWeights(period.from, period.to), [period]); const h25 = useMemo(() => aggMonthly(DATA.by_hotel_monthly_2025, 'HotelName', weights), [weights]); const h26 = useMemo(() => aggMonthly(DATA.by_hotel_monthly_2026, 'HotelName', weights), [weights]); const map26 = useMemo(() => { const m = {}; h26.forEach(h => m[h.HotelName] = h); return m; }, [h26]); const regions = useMemo(() => ['all', ...new Set(h25.map(h => h.SubRegion))], [h25]); const merged = useMemo(() => h25.map(h => { const c = map26[h.HotelName] || {}; return { ...h, rev25: h.revenue || 0, rev26: c.revenue || 0, pax25: h.pax || 0, pax26: c.pax || 0, adr25: h.adr || 0, adr26: c.adr || 0, bookings25: h.bookings || 0, bookings26: c.bookings || 0, yoyRev: h.revenue ? ((c.revenue || 0) / h.revenue - 1) * 100 : 0, yoyPax: h.pax ? ((c.pax || 0) / h.pax - 1) * 100 : 0, }; }), [h25, map26]); const filtered = useMemo(() => { return merged .filter(h => !search || (h.HotelName || '').toLowerCase().includes(search.toLowerCase())) .filter(h => region === 'all' || h.SubRegion === region) .filter(h => stars === 'all' || h.Stars === parseInt(stars)) .sort((a, b) => (b[sort] || 0) - (a[sort] || 0)); }, [merged, search, region, stars, sort]); const hotelTot = filtered.reduce((a, h) => ({ rev25: a.rev25 + h.rev25, rev26: a.rev26 + h.rev26, pax26: a.pax26 + h.pax26, }), { rev25: 0, rev26: 0, pax26: 0 }); const hotelFacts = { donem: periodLabel, otel_sayisi: filtered.length, toplam_gelir_2025_M: +(hotelTot.rev25 / 1e6).toFixed(2), toplam_gelir_2026_M: +(hotelTot.rev26 / 1e6).toFixed(2), yoy_pct: hotelTot.rev25 ? +(((hotelTot.rev26 / hotelTot.rev25) - 1) * 100).toFixed(1) : null, en_iyi_oteller: filtered.slice(0, 8).map(h => ({ otel: h.HotelName, bolge: h.SubRegion, yildiz: h.Stars, gelir_2026_M: +(h.rev26 / 1e6).toFixed(2), yoy_gelir_pct: +(h.yoyRev || 0).toFixed(1), adr_2025: Math.round(h.adr25), adr_2026: Math.round(h.adr26), marj_pct: +(h.margin_pct || 0).toFixed(1), })), }; return React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } }, React.createElement(InsightBox, { label: 'Otel Portföyü · Karşılaştırma', hint: 'Gösterilen otel portföyünün 2025 vs 2026 özeti. Lider otelleri, YoY büyüme/daralma uçlarını, ADR ve marj sinyallerini vurgula.', facts: hotelFacts, }), React.createElement(Panel, { title: 'Otel Portföyü · Karşılaştırma', sub: `${filtered.length} / ${merged.length} otel · 2025 vs 2026 · ${periodLabel}`, flush: true }, React.createElement('div', { className: 'filterbar', style: { borderTop: 'none' } }, React.createElement('input', { className: 'input', style: { width: 220 }, placeholder: 'Otel ara…', value: search, onChange: e => setSearch(e.target.value) }), React.createElement('div', { style: { display: 'flex', gap: 4, flexWrap: 'wrap' } }, regions.slice(0, 12).map(r => React.createElement('div', { key: r, className: 'filter-chip' + (region === r ? ' active' : ''), onClick: () => setRegion(r) }, r === 'all' ? 'Tüm bölgeler' : r)) ), React.createElement('div', { style: { display: 'flex', gap: 4, marginLeft: 'auto' } }, ['all', '5', '4', '3'].map(s => React.createElement('div', { key: s, className: 'filter-chip' + (stars === s ? ' active' : ''), onClick: () => setStars(s) }, s === 'all' ? 'Tüm sınıflar' : `${s}★`)) ) ), React.createElement('div', { style: { maxHeight: 540, overflowY: 'auto', overflowX: 'auto' } }, React.createElement('table', { className: 'tbl' }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', null, '#'), React.createElement('th', null, 'Otel'), React.createElement('th', null, 'Bölge'), React.createElement('th', null, 'Sınıf'), React.createElement('th', { className: 'num clickable', style: { cursor: 'pointer', color: sort==='rev25'?'var(--brand)':'inherit' }, onClick: () => setSort('rev25') }, '25 Gelir'), React.createElement('th', { className: 'num clickable', style: { cursor: 'pointer', color: sort==='rev26'?'var(--brand)':'inherit' }, onClick: () => setSort('rev26') }, '26 Gelir'), React.createElement('th', { className: 'num clickable', style: { cursor: 'pointer', color: sort==='yoyRev'?'var(--brand)':'inherit' }, onClick: () => setSort('yoyRev') }, 'YoY Gelir'), React.createElement('th', { className: 'num' }, '25 Yolcu'), React.createElement('th', { className: 'num' }, '26 Yolcu'), React.createElement('th', { className: 'num clickable', style: { cursor: 'pointer', color: sort==='yoyPax'?'var(--brand)':'inherit' }, onClick: () => setSort('yoyPax') }, 'YoY Yolcu'), React.createElement('th', { className: 'num' }, '25 ADR'), React.createElement('th', { className: 'num' }, '26 ADR'), React.createElement('th', { className: 'num' }, 'Marj') ) ), React.createElement('tbody', null, filtered.slice(0, 200).map((h, i) => React.createElement('tr', { key: h.HotelName + i, className: 'clickable', onClick: () => window.dispatchEvent(new CustomEvent('open-hotel', { detail: h })) }, React.createElement('td', { className: 'text-faint mono' }, String(i+1).padStart(2, '0')), React.createElement('td', { style: { fontWeight: 500 } }, h.HotelName), React.createElement('td', { className: 'text-muted' }, h.SubRegion), React.createElement('td', null, React.createElement(Stars, { n: h.Stars })), React.createElement('td', { className: 'num text-muted' }, fmtEurM(h.rev25)), React.createElement('td', { className: 'num', style: { fontWeight: 600 } }, fmtEurM(h.rev26)), React.createElement('td', { className: 'num' }, React.createElement(Delta, { value: h.yoyRev })), React.createElement('td', { className: 'num text-muted' }, fmtN(h.pax25)), React.createElement('td', { className: 'num' }, fmtN(h.pax26)), React.createElement('td', { className: 'num' }, React.createElement(Delta, { value: h.yoyPax })), React.createElement('td', { className: 'num text-muted' }, '€' + Math.round(h.adr25)), React.createElement('td', { className: 'num' }, '€' + Math.round(h.adr26)), React.createElement('td', { className: 'num text-pos' }, (h.margin_pct || 0).toFixed(1) + '%') )) ) ) ) ) ); }; const AirportView = ({ period = { from: '2026-01-01', to: '2026-05-05' }, periodLabel = 'YTD' }) => { const meta = DATA.by_airport || []; const metaByCode = useMemo(() => Object.fromEntries(meta.map(a => [a.code, a])), [meta]); // Airport bars re-aggregate by period; the hotel drill-down stays full-2025. const weights = useMemo(() => monthWeights(period.from, period.to), [period]); const aggA = useMemo(() => aggMonthly(DATA.by_airport_monthly_2025, 'ArrivalAirport', weights), [weights]); const airports = aggA.map(a => { const m = metaByCode[a.ArrivalAirport] || {}; return { code: a.ArrivalAirport, name: m.name || a.ArrivalAirport, city: m.city || '', country: m.country || '', revenue: a.revenue, pax: a.pax, adr: a.adr, bookings: a.bookings, roomnights: a.roomnights }; }); const byAirport = DATA.by_airport_hotels || {}; const [selected, setSelected] = useState(meta[0] ? meta[0].code : null); const chartData = airports.map(a => ({ label: a.code, name: a.name, code: a.code, rev: (a.revenue || 0) / 1e6, pax: a.pax || 0, adr: a.adr || 0, bookings: a.bookings || 0, })); const hotels = (selected && byAirport[selected]) || []; const selectedMeta = airports.find(a => a.code === selected); const totals = airports.reduce((acc, a) => ({ rev: acc.rev + (a.revenue || 0), pax: acc.pax + (a.pax || 0), }), { rev: 0, pax: 0 }); const airportFacts = { donem: periodLabel, toplam_gelir_M: +(totals.rev / 1e6).toFixed(2), toplam_pax: totals.pax, havalimanlari: chartData.map(a => ({ kod: a.code, ad: a.name, gelir_M: +(a.rev || 0).toFixed(2), pax: a.pax || 0, adr: Math.round(a.adr || 0), pay_pct: totals.rev ? +(((a.rev * 1e6) / totals.rev) * 100).toFixed(1) : null, })), }; return React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } }, React.createElement(Panel, { title: `Havalimanı Bazında Gelir · ${periodLabel}`, sub: `${airports.length} varış havalimanı · toplam ${fmtEurM(totals.rev)} · ${fmtN(totals.pax)} yolcu · seçili: ${selectedMeta ? selectedMeta.name : '—'}` }, React.createElement(BarChart, { data: chartData, height: 280, valueKey: 'rev', labelKey: 'label', format: v => '€' + v.toFixed(0) + 'M', color: 'var(--brand)', valueLabel: true, onBarClick: (d) => setSelected(d.code), tooltipFormat: (d) => React.createElement('div', null, React.createElement('div', { style: { fontWeight: 600, marginBottom: 4, color: 'var(--paper)' } }, `${d.code} · ${d.name}`), React.createElement('div', { style: { color: 'var(--paper)', fontSize: 10.5 } }, `Gelir: €${(d.rev || 0).toFixed(1)}M · ADR: €${(d.adr || 0).toFixed(0)} · Yolcu: ${fmtN(d.pax)} · Rezervasyon: ${fmtN(d.bookings)}`), React.createElement('div', { style: { color: 'var(--paper)', fontSize: 10, fontStyle: 'italic', marginTop: 4, opacity: 0.75 } }, 'Tıkla → bu havalimanından ulaşılan oteller') ) }) ), React.createElement(InsightBox, { label: `Havalimanı Bazında Gelir · ${periodLabel}`, hint: 'Varış havalimanı bazında gelir dağılımı. En büyük havalimanını, pay yüzdelerini ve ADR farklarını vurgula.', facts: airportFacts, }), React.createElement(Panel, { title: selectedMeta ? `${selectedMeta.name} (${selectedMeta.code}) — Oteller` : 'Havalimanı Detayı', sub: selectedMeta ? `${hotels.length} otel · ${fmtN(selectedMeta.pax)} yolcu · ${fmtEurM(selectedMeta.revenue)} gelir · ADR €${Math.round(selectedMeta.adr)} · oteller: tüm 2025` : 'Bir havalimanı seçin', flush: true }, !selected || hotels.length === 0 ? React.createElement('div', { style: { padding: 36, textAlign: 'center', color: 'var(--text-muted)', fontSize: 11.5 } }, 'Bir havalimanı seçin') : React.createElement('div', { style: { maxHeight: 460, overflowY: 'auto', overflowX: 'auto' } }, React.createElement('table', { className: 'tbl' }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', null, '#'), React.createElement('th', null, 'Otel'), React.createElement('th', null, 'Bölge'), React.createElement('th', null, 'Sınıf'), React.createElement('th', { className: 'num' }, 'Yolcu'), React.createElement('th', { className: 'num' }, 'Gelir'), React.createElement('th', { className: 'num' }, 'ADR'), React.createElement('th', { className: 'num' }, 'Geceleme') ) ), React.createElement('tbody', null, hotels.map((h, i) => React.createElement('tr', { key: h.hotel_id + i, className: 'clickable', onClick: () => window.dispatchEvent(new CustomEvent('open-hotel', { detail: { HotelName: h.hotel_name } })) }, React.createElement('td', { className: 'text-faint mono' }, String(i+1).padStart(2, '0')), React.createElement('td', { style: { fontWeight: 500 } }, h.hotel_name), React.createElement('td', { className: 'text-muted' }, h.city), React.createElement('td', null, React.createElement(Stars, { n: h.stars })), React.createElement('td', { className: 'num', style: { fontWeight: 600 } }, fmtN(h.pax)), React.createElement('td', { className: 'num' }, fmtEurM(h.revenue)), React.createElement('td', { className: 'num' }, '€' + Math.round(h.adr)), React.createElement('td', { className: 'num text-muted' }, fmtN(h.roomnights)) )) ) ) ) ) ); }; window.Reporting = Reporting;