import { useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; // Aircraft category colors (matches AircraftLayer military fixed colors) const AC_CAT_COLORS: Record = { fighter: '#ff4444', military: '#ff6600', surveillance: '#ffcc00', tanker: '#00ccff', cargo: '#a78bfa', civilian: '#FFD700', unknown: '#7CFC00', }; // Altitude color legend (matches AircraftLayer gradient) const ALT_LEGEND: [string, string][] = [ ['Ground', '#555555'], ['< 2,000ft', '#00c000'], ['2,000ft', '#55EC55'], ['4,000ft', '#7CFC00'], ['6,000ft', '#BFFF00'], ['10,000ft', '#FFFF00'], ['20,000ft', '#FFD700'], ['30,000ft', '#FF8C00'], ['40,000ft', '#FF4500'], ['50,000ft+', '#BA55D3'], ]; // Military color legend const MIL_LEGEND: [string, string][] = [ ['Fighter', '#ff4444'], ['Military', '#ff6600'], ['ISR / Surveillance', '#ffcc00'], ['Tanker', '#00ccff'], ]; // Ship MT category color (matches ShipLayer MT_TYPE_COLORS) const MT_CAT_COLORS: Record = { cargo: 'var(--kcg-ship-cargo)', tanker: 'var(--kcg-ship-tanker)', passenger: 'var(--kcg-ship-passenger)', fishing: 'var(--kcg-ship-fishing)', fishing_gear: '#f97316', military: 'var(--kcg-ship-military)', tug_special: 'var(--kcg-ship-tug)', high_speed: 'var(--kcg-ship-highspeed)', pleasure: 'var(--kcg-ship-pleasure)', other: 'var(--kcg-ship-other)', unspecified: 'var(--kcg-ship-unknown)', unknown: 'var(--kcg-ship-unknown)', }; // Ship type color legend (MarineTraffic style) const SHIP_TYPE_LEGEND: [string, string][] = [ ['cargo', 'var(--kcg-ship-cargo)'], ['tanker', 'var(--kcg-ship-tanker)'], ['passenger', 'var(--kcg-ship-passenger)'], ['fishing', 'var(--kcg-ship-fishing)'], ['fishing_gear', '#f97316'], ['pleasure', 'var(--kcg-ship-pleasure)'], ['military', 'var(--kcg-ship-military)'], ['tug_special', 'var(--kcg-ship-tug)'], ['other', 'var(--kcg-ship-other)'], ['unspecified', 'var(--kcg-ship-unknown)'], ]; const AC_CATEGORIES = ['fighter', 'military', 'surveillance', 'tanker', 'cargo', 'civilian', 'unknown'] as const; const MT_CATEGORIES = ['cargo', 'tanker', 'passenger', 'fishing', 'fishing_gear', 'military', 'tug_special', 'high_speed', 'pleasure', 'other', 'unspecified'] as const; // Nationality categories for Korea tab const NAT_CATEGORIES = ['KR', 'CN', 'KP', 'JP', 'unclassified'] as const; // Fishing vessel nationality categories const FISHING_NAT_CATEGORIES = ['CN', 'KR', 'JP', 'other'] as const; const FISHING_NAT_LABELS: Record = { CN: 'πŸ‡¨πŸ‡³ 쀑ꡭ어선', KR: 'πŸ‡°πŸ‡· ν•œκ΅­μ–΄μ„ ', JP: 'πŸ‡―πŸ‡΅ 일본어선', other: '🏳️ 기타어선', }; const FISHING_NAT_COLORS: Record = { CN: '#ef4444', KR: '#3b82f6', JP: '#f472b6', other: '#6b7280', }; const NAT_LABELS: Record = { KR: 'πŸ‡°πŸ‡· ν•œκ΅­', CN: 'πŸ‡¨πŸ‡³ 쀑ꡭ', KP: 'πŸ‡°πŸ‡΅ λΆν•œ', JP: 'πŸ‡―πŸ‡΅ 일본', unclassified: '🏳️ λ―ΈλΆ„λ₯˜', }; const NAT_COLORS: Record = { KR: '#3b82f6', CN: '#ef4444', KP: '#f97316', JP: '#f472b6', unclassified: '#6b7280', }; interface ExtraLayer { key: string; label: string; color: string; count?: number; group?: string; } const GROUP_META: Record = { '항곡망': { label: '항곡망', color: '#22d3ee' }, 'ꡭ가기관망': { label: 'ꡭ가기관망', color: '#f59e0b' }, 'ν•΄μ–‘μ•ˆμ „': { label: 'ν•΄μ–‘μ•ˆμ „', color: '#3b82f6' }, }; interface LayerPanelProps { layers: Record; onToggle: (key: string) => void; aircraftByCategory: Record; aircraftTotal: number; shipsByMtCategory: Record; shipTotal: number; satelliteCount: number; extraLayers?: ExtraLayer[]; hiddenAcCategories: Set; hiddenShipCategories: Set; onAcCategoryToggle: (cat: string) => void; onShipCategoryToggle: (cat: string) => void; shipsByNationality?: Record; hiddenNationalities?: Set; onNationalityToggle?: (nat: string) => void; fishingByNationality?: Record; hiddenFishingNats?: Set; onFishingNatToggle?: (nat: string) => void; } export function LayerPanel({ layers, onToggle, aircraftByCategory, aircraftTotal, shipsByMtCategory, shipTotal, satelliteCount, extraLayers, hiddenAcCategories, hiddenShipCategories, onAcCategoryToggle, onShipCategoryToggle, shipsByNationality, hiddenNationalities, onNationalityToggle, fishingByNationality, hiddenFishingNats, onFishingNatToggle, }: LayerPanelProps) { const { t } = useTranslation(['common', 'ships']); const [expanded, setExpanded] = useState>(new Set(['ships'])); const [legendOpen, setLegendOpen] = useState>(new Set()); const toggleExpand = useCallback((key: string) => { setExpanded(prev => { const next = new Set(prev); if (next.has(key)) { next.delete(key); } else { next.add(key); } return next; }); }, []); const toggleLegend = useCallback((key: string) => { setLegendOpen(prev => { const next = new Set(prev); if (next.has(key)) { next.delete(key); } else { next.add(key); } return next; }); }, []); const militaryCount = Object.entries(aircraftByCategory) .filter(([cat]) => cat !== 'civilian' && cat !== 'unknown') .reduce((sum, [, c]) => sum + c, 0); return (

LAYERS

{/* ═══ μ„ λ°• (μ΅œμƒμœ„) ═══ */} {/* Ships tree */} onToggle('ships')} onExpand={() => toggleExpand('ships')} /> {layers.ships && expanded.has('ships') && (
{MT_CATEGORIES.map(cat => { const count = shipsByMtCategory[cat] || 0; if (count === 0) return null; // 어선은 ꡭ적별 ν•˜μœ„ λΆ„λ₯˜ ν‘œμ‹œ if (cat === 'fishing' && fishingByNationality && hiddenFishingNats && onFishingNatToggle) { const isFishingExpanded = expanded.has('fishing-sub'); return (
{ e.stopPropagation(); toggleExpand('fishing-sub'); }} > {isFishingExpanded ? 'β–Ό' : 'β–Ά'}
{isFishingExpanded && !hiddenShipCategories.has('fishing') && (
{FISHING_NAT_CATEGORIES.map(nat => { const fCount = fishingByNationality[nat] || 0; if (fCount === 0) return null; return (
)}
); } return (
)} {/* Nationality tree (Korea tab only) */} {shipsByNationality && hiddenNationalities && onNationalityToggle && ( <> a + b, 0)})`} color="#8b5cf6" active expandable isExpanded={expanded.has('nationality')} onToggle={() => toggleExpand('nationality')} onExpand={() => toggleExpand('nationality')} /> {expanded.has('nationality') && (
{NAT_CATEGORIES.map(nat => { const count = shipsByNationality[nat] || 0; if (count === 0) return null; return (
)} )} {/* ═══ 항곡망 κ·Έλ£Ή ═══ */} toggleExpand('group-항곡망')} onExpand={() => toggleExpand('group-항곡망')} /> {expanded.has('group-항곡망') && (
{/* Aircraft tree */} onToggle('aircraft')} onExpand={() => toggleExpand('aircraft')} /> {layers.aircraft && expanded.has('aircraft') && (
{AC_CATEGORIES.map(cat => { const count = aircraftByCategory[cat] || 0; if (count === 0) return null; return (
)} {/* Satellites */} onToggle('satellites')} />
)} {/* Extra layers β€” grouped */} {extraLayers && (() => { const grouped: Record = {}; const ungrouped: ExtraLayer[] = []; for (const el of extraLayers) { if (el.group) { if (!grouped[el.group]) grouped[el.group] = []; grouped[el.group].push(el); } else { ungrouped.push(el); } } return ( <> {/* Grouped layers */} {Object.entries(grouped).map(([groupName, items]) => { const meta = GROUP_META[groupName] || { label: groupName, color: '#888' }; const isGroupExpanded = expanded.has(`group-${groupName}`); return (
toggleExpand(`group-${groupName}`)} onExpand={() => toggleExpand(`group-${groupName}`)} /> {isGroupExpanded && (
{items.map(el => ( onToggle(el.key)} /> ))}
)}
); })} {/* Ungrouped layers */} {ungrouped.map(el => ( onToggle(el.key)} /> ))} ); })()}
{/* Military only filter */} onToggle('militaryOnly')} />
); } /* ── Sub-components ─────────────────────────────────── */ function LayerTreeItem({ layerKey, label, color, active, expandable, isExpanded, onToggle, onExpand, }: { layerKey: string; label: string; color: string; active: boolean; expandable?: boolean; isExpanded?: boolean; onToggle: () => void; onExpand?: () => void; }) { return (
{expandable ? ( { e.stopPropagation(); onExpand?.(); }} > {'\u25B6'} ) : ( )}
); } function CategoryToggle({ label, color, count, hidden, onClick, }: { label: string; color: string; count: number; hidden: boolean; onClick: () => void; }) { return (
{label} {count}
); }