import { useState, useEffect, useCallback } from 'react' import type { Dispatch, SetStateAction } from 'react' import { fetchHnsAnalyses, type HnsAnalysisItem } from '../services/hnsApi' interface HNSAnalysisListTableProps { onTabChange: Dispatch> onSelectAnalysis?: (sn: number, localRsltData?: Record) => void } const RISK_LABEL: Record = { CRITICAL: '심각', HIGH: '위험', MEDIUM: '경고', LOW: '관찰', } const RISK_STYLE: Record = { CRITICAL: { bg: 'rgba(239,68,68,0.2)', color: 'var(--red)' }, HIGH: { bg: 'rgba(239,68,68,0.15)', color: 'var(--red)' }, MEDIUM: { bg: 'rgba(249,115,22,0.15)', color: 'var(--orange)' }, LOW: { bg: 'rgba(34,197,94,0.15)', color: 'var(--green)' }, } function formatDate(dtm: string | null, mode: 'full' | 'date') { if (!dtm) return '—' const d = new Date(dtm) if (mode === 'date') return d.toISOString().slice(0, 10) return d.toISOString().slice(0, 16).replace('T', ' ') } function substanceTag(sbstNm: string | null): string { if (!sbstNm) return '—' const match = sbstNm.match(/\(([^)]+)\)/) if (match) return match[1] return sbstNm.length > 6 ? sbstNm.slice(0, 6) : sbstNm } export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnalysisListTableProps) { const [analyses, setAnalyses] = useState([]) const [loading, setLoading] = useState(true) const loadData = useCallback(async () => { setLoading(true) try { const items = await fetchHnsAnalyses() setAnalyses(items) } catch (err) { console.error('[hns] 분석 목록 조회 실패, localStorage fallback:', err) // DB 실패 시 localStorage에서 불러오기 try { const localRaw = localStorage.getItem('hns_saved_analyses') if (localRaw) { const localItems = JSON.parse(localRaw) as Record[] const mapped: HnsAnalysisItem[] = localItems.map((entry) => { const rslt = entry.rsltData as Record | null const inputP = rslt?.inputParams as Record | null const coord = rslt?.coord as { lon: number; lat: number } | null const weather = rslt?.weather as Record | null return { hnsAnlysSn: (entry.id as number) || 0, anlysNm: (entry.anlysNm as string) || '로컬 저장 분석', acdntDtm: (entry.acdntDtm as string) || (inputP?.accidentDate && inputP?.accidentTime ? `${inputP.accidentDate as string}T${inputP.accidentTime as string}:00` : (inputP?.accidentDate as string)) || null, locNm: coord ? `${coord.lat.toFixed(4)} / ${coord.lon.toFixed(4)}` : null, lon: coord?.lon ?? null, lat: coord?.lat ?? null, sbstNm: (entry.sbstNm as string) || null, spilQty: (entry.spilQty as number) ?? null, spilUnitCd: (entry.spilUnitCd as string) || null, fcstHr: (entry.fcstHr as number) ?? null, algoCd: (inputP?.algorithm as string) || null, critMdlCd: (inputP?.criteriaModel as string) || null, windSpd: (weather?.windSpeed as number) ?? null, windDir: weather?.windDirection != null ? String(weather.windDirection) : null, execSttsCd: 'COMPLETED', riskCd: (entry.riskCd as string) || null, analystNm: (entry.analystNm as string) || null, rsltData: rslt ?? null, regDtm: (entry.regDtm as string) || new Date().toISOString(), _isLocal: true, } as HnsAnalysisItem & { _isLocal?: boolean } }) setAnalyses(mapped) } } catch { // localStorage 파싱 실패 무시 } } finally { setLoading(false) } }, []) useEffect(() => { loadData() }, [loadData]) return (
{/* Header */}
📋 HNS 대기확산 분석 목록
총 {analyses.length}건
{/* Table */}
{loading ? (
로딩 중...
) : ( {analyses.map((item, index) => { const rslt = item.rsltData as Record | null const isLocal = !!(item as HnsAnalysisItem & { _isLocal?: boolean })._isLocal const riskLabel = RISK_LABEL[item.riskCd || ''] || item.riskCd || '—' const riskStyle = RISK_STYLE[item.riskCd || ''] || { bg: 'rgba(100,100,100,0.1)', color: 'var(--t3)' } const aegl3 = rslt?.aegl3 as boolean | undefined const aegl2 = rslt?.aegl2 as boolean | undefined const aegl1 = rslt?.aegl1 as boolean | undefined const damageRadius = (rslt?.damageRadius as string) || '—' const amount = item.spilQty != null ? `${item.spilQty} ${item.spilUnitCd || 'KL'}` : '—' return ( onSelectAnalysis?.(item.hnsAnlysSn, isLocal && rslt ? rslt : undefined)} style={{ transition: 'background 0.15s', background: index % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)' }} onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg2)'} onMouseLeave={(e) => e.currentTarget.style.background = index % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)'} > ) })}
번호 분석명 물질 사고일시 분석날짜 사고지점 유출량 알고리즘 예측시간
AEGL-3 생명위협
AEGL-2 건강피해
AEGL-1 불쾌감
위험등급 피해반경 분석자
{item.hnsAnlysSn} {item.anlysNm} {substanceTag(item.sbstNm)} {formatDate(item.acdntDtm, 'full')} {formatDate(item.regDtm, 'date')} {item.locNm || '—'} {amount} {item.algoCd || '—'} {item.fcstHr ? `${item.fcstHr}H` : '—'}
{riskLabel} {damageRadius} {item.analystNm || '—'}
)} {!loading && analyses.length === 0 && (
분석 데이터가 없습니다.
)}
) }