import { Panel } from './Panel'; import { GULF_INVESTMENTS } from '@/config/gulf-fdi'; import type { GulfInvestment, GulfInvestmentSector, GulfInvestorCountry, GulfInvestingEntity, GulfInvestmentStatus, } from '@/types'; import { escapeHtml } from '@/utils/sanitize'; interface InvestmentFilters { investingCountry: GulfInvestorCountry | 'ALL'; sector: GulfInvestmentSector | 'ALL'; entity: GulfInvestingEntity | 'ALL'; status: GulfInvestmentStatus | 'ALL'; search: string; } const SECTOR_LABELS: Record = { ports: 'Ports', pipelines: 'Pipelines', energy: 'Energy', datacenters: 'Data Centers', airports: 'Airports', railways: 'Railways', telecoms: 'Telecoms', water: 'Water', logistics: 'Logistics', mining: 'Mining', 'real-estate': 'Real Estate', manufacturing: 'Manufacturing', }; const STATUS_COLORS: Record = { 'operational': '#22c55e', 'under-construction': '#f59e0b', 'announced': '#60a5fa', 'rumoured': '#a78bfa', 'cancelled': '#ef4444', 'divested': '#6b7280', }; const FLAG: Record = { SA: 'πŸ‡ΈπŸ‡¦', UAE: 'πŸ‡¦πŸ‡ͺ', }; function formatUSD(usd?: number): string { if (usd === undefined) return 'Undisclosed'; if (usd >= 100000) return `$${(usd / 1000).toFixed(0)}B`; if (usd >= 1000) return `$${(usd / 1000).toFixed(1)}B`; return `$${usd.toLocaleString()}M`; } export class InvestmentsPanel extends Panel { private filters: InvestmentFilters = { investingCountry: 'ALL', sector: 'ALL', entity: 'ALL', status: 'ALL', search: '', }; private sortKey: keyof GulfInvestment = 'assetName'; private sortAsc = true; private onInvestmentClick?: (inv: GulfInvestment) => void; constructor(onInvestmentClick?: (inv: GulfInvestment) => void) { super({ id: 'gcc-investments', title: 'GCC Investments', showCount: true, infoTooltip: 'Database of Saudi Arabia and UAE foreign direct investments in global critical infrastructure. Click a row to fly to the investment on the map.', }); this.onInvestmentClick = onInvestmentClick; this.render(); } private getFiltered(): GulfInvestment[] { const { investingCountry, sector, entity, status, search } = this.filters; const q = search.toLowerCase(); return GULF_INVESTMENTS .filter(inv => { if (investingCountry !== 'ALL' && inv.investingCountry !== investingCountry) return false; if (sector !== 'ALL' && inv.sector !== sector) return false; if (entity !== 'ALL' && inv.investingEntity !== entity) return false; if (status !== 'ALL' && inv.status !== status) return false; if (q && !inv.assetName.toLowerCase().includes(q) && !inv.targetCountry.toLowerCase().includes(q) && !inv.description.toLowerCase().includes(q) && !inv.investingEntity.toLowerCase().includes(q)) return false; return true; }) .sort((a, b) => { const key = this.sortKey; const av = a[key] ?? ''; const bv = b[key] ?? ''; const cmp = av < bv ? -1 : av > bv ? 1 : 0; return this.sortAsc ? cmp : -cmp; }); } private render(): void { const filtered = this.getFiltered(); // Build unique entity list for dropdown const entities = Array.from(new Set(GULF_INVESTMENTS.map(i => i.investingEntity))).sort(); const sectors = Array.from(new Set(GULF_INVESTMENTS.map(i => i.sector))).sort(); const sortArrow = (key: keyof GulfInvestment) => this.sortKey === key ? (this.sortAsc ? ' ↑' : ' ↓') : ''; const rows = filtered.map(inv => { const statusColor = STATUS_COLORS[inv.status] || '#6b7280'; const flag = FLAG[inv.investingCountry] || ''; const sector = SECTOR_LABELS[inv.sector] || inv.sector; return ` ${flag} ${escapeHtml(inv.assetName)}
${escapeHtml(inv.investingEntity)}
${escapeHtml(inv.targetCountry)} ${escapeHtml(sector)} ${escapeHtml(inv.status)} ${escapeHtml(formatUSD(inv.investmentUSD))} ${inv.yearAnnounced ?? inv.yearOperational ?? 'β€”'} `; }).join(''); const html = `
${rows || ''}
Asset${sortArrow('assetName')} Country${sortArrow('targetCountry')} Sector${sortArrow('sector')} Status${sortArrow('status')} Investment${sortArrow('investmentUSD')} Year${sortArrow('yearAnnounced')}
No investments match filters
`; this.setContent(html); if (this.countEl) this.countEl.textContent = String(filtered.length); this.attachListeners(); } private attachListeners(): void { const content = this.content; // Search input const searchEl = content.querySelector('.fdi-search'); searchEl?.addEventListener('input', () => { this.filters.search = searchEl.value; this.render(); }); // Filter dropdowns content.querySelectorAll('.fdi-filter').forEach(sel => { sel.addEventListener('change', () => { const key = sel.dataset.filter as keyof InvestmentFilters; (this.filters as unknown as Record)[key] = sel.value; this.render(); }); }); // Sort headers content.querySelectorAll('.fdi-sort').forEach(th => { th.addEventListener('click', () => { const key = th.dataset.sort as keyof GulfInvestment; if (this.sortKey === key) { this.sortAsc = !this.sortAsc; } else { this.sortKey = key; this.sortAsc = true; } this.render(); }); }); // Row click β†’ fly to map content.querySelectorAll('.fdi-row').forEach(row => { row.addEventListener('click', () => { const inv = GULF_INVESTMENTS.find(i => i.id === row.dataset.id); if (inv && this.onInvestmentClick) { this.onInvestmentClick(inv); } }); }); } }