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;