import { useState, useRef, useEffect } from 'react' import type { Layer } from '../../../data/layerDatabase' const PRESET_COLORS = [ '#ef4444','#f97316','#eab308','#22c55e','#06b6d4', '#3b82f6','#8b5cf6','#a855f7','#ec4899','#f43f5e', '#64748b','#ffffff', ] interface LayerTreeProps { layers: (Layer & { children?: Layer[] })[] enabledLayers: Set onToggleLayer: (layerId: string, enabled: boolean) => void layerColors?: Record onColorChange?: (layerId: string, color: string) => void } export function LayerTree({ layers, enabledLayers, onToggleLayer, layerColors = {}, onColorChange }: LayerTreeProps) { const allLeafIds = getAllLeafIds(layers) const allEnabled = allLeafIds.length > 0 && allLeafIds.every(id => enabledLayers.has(id)) const handleToggleAll = () => { const newState = !allEnabled getAllNodeIds(layers).forEach(id => onToggleLayer(id, newState)) } return (
전체 레이어
{layers.map(layer => ( ))}
) } function getAllLeafIds(layers: (Layer & { children?: Layer[] })[]): string[] { const ids: string[] = [] for (const l of layers) { if (l.children && l.children.length > 0) { ids.push(...getAllLeafIds(l.children)) } else { ids.push(l.id) } } return ids } function getAllNodeIds(layers: (Layer & { children?: Layer[] })[]): string[] { const ids: string[] = [] for (const l of layers) { ids.push(l.id) if (l.children && l.children.length > 0) { ids.push(...getAllNodeIds(l.children)) } } return ids } interface LayerNodeProps { layer: Layer & { children?: Layer[] } enabledLayers: Set onToggleLayer: (layerId: string, enabled: boolean) => void layerColors: Record onColorChange?: (layerId: string, color: string) => void depth: number } function LayerNode({ layer, enabledLayers, onToggleLayer, layerColors, onColorChange, depth }: LayerNodeProps) { const [expanded, setExpanded] = useState(depth < 1) const hasChildren = layer.children && layer.children.length > 0 const isEnabled = enabledLayers.has(layer.id) const getAllDescendantIds = (node: Layer & { children?: Layer[] }): string[] => { const ids: string[] = [] if (node.children) { for (const child of node.children) { ids.push(child.id) ids.push(...getAllDescendantIds(child)) } } return ids } const handleSwitchClick = (e: React.MouseEvent) => { e.stopPropagation() const newState = !isEnabled onToggleLayer(layer.id, newState) if (hasChildren) { getAllDescendantIds(layer).forEach(id => onToggleLayer(id, newState)) } } const handleHeaderClick = () => { if (hasChildren) setExpanded(!expanded) } // depth 0 — 대분류 (lyr-g1 / lyr-h1) if (depth === 0) { return (
{hasChildren ? ( ) : ( )}
{layer.icon && {layer.icon}} {layer.name} {layer.count !== undefined && ( {layer.count.toLocaleString()} )}
{hasChildren && (
{layer.children!.map(child => ( ))}
)}
) } // depth 1 — 중분류 (lyr-g2 / lyr-h2) 또는 leaf if (depth === 1) { // 자식 없는 depth 1 → leaf로 렌더링 (해양관측·기상 하위 등) if (!hasChildren) { return (
{ if (!(e.target as HTMLElement).closest('.lyr-csw, .lyr-cpop')) handleSwitchClick(e) }}>
{layer.icon && {layer.icon}} {layer.name} {layer.count !== undefined && {layer.count.toLocaleString()}} {onColorChange && ( onColorChange(layer.id, c)} /> )}
) } return (
{hasChildren ? ( ) : ( )}
{layer.icon && {layer.icon}} {layer.name} {layer.count !== undefined && ( {layer.count.toLocaleString()} )}
{layer.children!.map(child => ( ))}
) } // depth 2+ leaf — 색상 스와치 포함 if (!hasChildren) { return (
{ if (!(e.target as HTMLElement).closest('.lyr-csw, .lyr-cpop')) handleSwitchClick(e) }}>
{layer.icon && {layer.icon}} {layer.name} {layer.count !== undefined && {layer.count.toLocaleString()}} {onColorChange && ( onColorChange(layer.id, c)} /> )}
) } // depth 2+ with children return (
{layer.icon && {layer.icon}} {layer.name} {layer.count !== undefined && {layer.count.toLocaleString()}}
{expanded && (
{layer.children!.map(child => ( ))}
)}
) } // 색상 스와치 + 피커 function ColorSwatch({ color, onChange }: { color?: string; onChange: (c: string) => void }) { const [open, setOpen] = useState(false) const ref = useRef(null) useEffect(() => { if (!open) return const handler = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false) } document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, [open]) return (
{ e.stopPropagation(); setOpen(!open) }} /> {open && (
e.stopPropagation()}>
{PRESET_COLORS.map(pc => (
{ onChange(pc); setOpen(false) }} /> ))}
{ onChange(e.target.value); setOpen(false) }} />
)}
) }