import { useState, useEffect, useRef, type CSSProperties, type ReactElement } from 'react'; import { List } from 'react-window'; import type { ScatSegment } from './scatTypes'; import type { ApiZoneItem } from '../services/scatApi'; import { esiColor, sensColor, statusColor, esiLevel } from './scatConstants'; interface ScatLeftPanelProps { segments: ScatSegment[]; zones: ApiZoneItem[]; jurisdictions: string[]; offices: string[]; selectedOffice: string; onOfficeChange: (v: string) => void; selectedSeg: ScatSegment; onSelectSeg: (s: ScatSegment) => void; onOpenPopup: (sn: number) => void; jurisdictionFilter: string; onJurisdictionChange: (v: string) => void; areaFilter: string; onAreaChange: (v: string) => void; phaseFilter: string; onPhaseChange: (v: string) => void; statusFilter: string; onStatusChange: (v: string) => void; searchTerm: string; onSearchChange: (v: string) => void; } interface SegRowData { filtered: ScatSegment[]; selectedId: number; onSelectSeg: (s: ScatSegment) => void; onOpenPopup: (sn: number) => void; } function SegRow( props: { ariaAttributes: { 'aria-posinset': number; 'aria-setsize': number; role: 'listitem' }; index: number; style: CSSProperties; } & SegRowData, ): ReactElement | null { const { index, style, filtered, selectedId, onSelectSeg, onOpenPopup } = props; const seg = filtered[index]; if (!seg) return null; const lvl = esiLevel(seg.esiNum); const borderColor = lvl === 'h' ? 'border-l-status-red' : lvl === 'm' ? 'border-l-status-orange' : 'border-l-status-green'; const isSelected = selectedId === seg.id; return (
{ onSelectSeg(seg); onOpenPopup(seg.id); }} className={`bg-bg-card border border-stroke rounded-sm p-2.5 px-3 cursor-pointer transition-all border-l-4 ${borderColor} ${ isSelected ? 'border-status-green bg-[color-mix(in_srgb,var(--color-success)_5%,transparent)]' : 'hover:border-stroke-light hover:bg-bg-surface-hover' }`} >
📍 {seg.code} {seg.area} ESI {seg.esi}
유형 {seg.type}
길이 {seg.length}
민감 {seg.sensitivity}
현황 {seg.status}
); } function ScatLeftPanel({ segments, // eslint-disable-next-line @typescript-eslint/no-unused-vars zones, jurisdictions, offices, selectedOffice, onOfficeChange, selectedSeg, onSelectSeg, onOpenPopup, jurisdictionFilter, onJurisdictionChange, areaFilter, // eslint-disable-next-line @typescript-eslint/no-unused-vars onAreaChange, phaseFilter, onPhaseChange, statusFilter, onStatusChange, searchTerm, onSearchChange, }: ScatLeftPanelProps) { const filtered = segments.filter((s) => { if (areaFilter && !s.area.includes(areaFilter)) return false; if (statusFilter !== '전체' && s.status !== statusFilter) return false; if (searchTerm && !s.code.includes(searchTerm) && !s.name.includes(searchTerm)) return false; return true; }); const listContainerRef = useRef(null); const [listHeight, setListHeight] = useState(400); useEffect(() => { const el = listContainerRef.current; if (!el) return; const ro = new ResizeObserver((entries) => { for (const entry of entries) { setListHeight(entry.contentRect.height); } }); ro.observe(el); return () => ro.disconnect(); }, []); return (
{/* Filters */}
해안 조사 구역
{/*
*/}
onSearchChange(e.target.value)} className="prd-i flex-1" />
{/* Segment List */}
해안 구간 목록 총 {filtered.length}개 구간
rowCount={filtered.length} rowHeight={88} overscanCount={5} style={{ height: listHeight, scrollbarWidth: 'thin', scrollbarColor: 'var(--stroke-default) transparent', }} rowComponent={SegRow} rowProps={{ filtered, selectedId: selectedSeg.id, onSelectSeg, onOpenPopup, }} />
); } export default ScatLeftPanel;