wing-ops/frontend/src/tabs/hns/components/HNSLeftPanel.tsx
Nan Kyung Lee 8f98f63aa5 feat(aerial): CCTV 실시간 HLS 스트림 + HNS 분석 고도화
CCTV 실시간 영상:
- CCTVPlayer 컴포넌트 (hls.js 기반 HLS/MJPEG/MP4 재생)
- 백엔드 HLS 프록시 엔드포인트 (CORS 우회, m3u8 URL 재작성)
- KHOA 15개 + KBS 6개 실제 해안 CCTV 연동
- Vite dev proxy, 스트림 타입 자동 감지 유틸리티

HNS 분석:
- HNS 시나리오 저장/불러오기/재계산 기능
- 물질 DB 검색 및 상세 정보 연동
- 좌표/파라미터 입력 UI 개선
- Python 확산 모델 스크립트 (hns_dispersion.py)

공통:
- 3D 지도 토글, 보고서 생성 개선
- useSubMenu 훅, mapUtils 확장
- ESLint set-state-in-effect 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:21:41 +09:00

702 lines
32 KiB
TypeScript
Executable File

import { useState, useEffect, useRef } from 'react';
import { ComboBox } from '@common/components/ui/ComboBox';
import { useWeatherFetch } from '../hooks/useWeatherFetch';
import { getSubstanceToxicity } from '../utils/toxicityData';
import type { WeatherFetchResult, ReleaseType } from '../utils/dispersionTypes';
import { fetchIncidentsRaw } from '@tabs/incidents/services/incidentsApi';
import type { IncidentListItem } from '@tabs/incidents/services/incidentsApi';
/** HNS 분석 입력 파라미터 (부모에 전달) */
export interface HNSInputParams {
substance: string;
releaseType: ReleaseType;
/** 배출률 (g/s) — Plume, Dense Gas */
emissionRate: number;
/** 총 누출량 (g) — Puff */
totalRelease: number;
/** 누출 높이 (m) — 전 모델 */
releaseHeight: number;
/** 누출 지속시간 (s) — Plume */
releaseDuration: number;
/** 액체풀 반경 (m) — Dense Gas */
poolRadius: number;
algorithm: string;
criteriaModel: string;
weather: WeatherFetchResult;
/** 사고 발생일 (YYYY-MM-DD) */
accidentDate: string;
/** 사고 발생시각 (HH:mm) */
accidentTime: string;
/** 예측시간 (예: '24시간') */
predictionTime: string;
/** 사고명 (직접 입력 또는 사고 리스트 선택) */
accidentName: string;
}
interface HNSLeftPanelProps {
activeSubTab: 'analysis' | 'list';
onSubTabChange: (tab: 'analysis' | 'list') => void;
incidentCoord: { lon: number; lat: number };
onCoordChange: (coord: { lon: number; lat: number }) => void;
onMapSelectClick: () => void;
onRunPrediction: () => void;
isRunningPrediction: boolean;
onParamsChange?: (params: HNSInputParams) => void;
onReset?: () => void;
loadedParams?: Partial<HNSInputParams> | null;
}
/** 십진 좌표 → 도분초 변환 */
function toDMS(decimal: number, type: 'lat' | 'lon'): string {
const abs = Math.abs(decimal);
const d = Math.floor(abs);
const m = Math.floor((abs - d) * 60);
const s = ((abs - d - m / 60) * 3600).toFixed(2);
const dir = type === 'lat' ? (decimal >= 0 ? 'N' : 'S') : (decimal >= 0 ? 'E' : 'W');
return `${d}\u00B0 ${m}' ${s}" ${dir}`;
}
export function HNSLeftPanel({
activeSubTab,
onSubTabChange: _onSubTabChange, // eslint-disable-line @typescript-eslint/no-unused-vars
incidentCoord,
onCoordChange,
onMapSelectClick,
onRunPrediction,
isRunningPrediction,
onParamsChange,
onReset,
loadedParams,
}: HNSLeftPanelProps) {
const [incidents, setIncidents] = useState<IncidentListItem[]>([]);
const [selectedIncidentSn, setSelectedIncidentSn] = useState('');
const [accidentName, setAccidentName] = useState('');
const [accidentDate, setAccidentDate] = useState<string>(() => {
const now = new Date();
return now.toISOString().slice(0, 10);
});
const [accidentTime, setAccidentTime] = useState<string>(() => {
const now = new Date();
return now.toTimeString().slice(0, 5);
});
const [predictionTime, setPredictionTime] = useState('24시간');
const [substance, setSubstance] = useState('톨루엔 (Toluene)');
const [releaseType, setReleaseType] = useState<ReleaseType>('연속 유출');
const [emissionRate, setEmissionRate] = useState(''); // g/s (Plume, Dense Gas)
const [totalRelease, setTotalRelease] = useState(''); // g (Puff)
const [releaseHeight, setReleaseHeight] = useState('0.5'); // m
const [releaseDuration, setReleaseDuration] = useState('300'); // s (Plume)
const [poolRadius, setPoolRadius] = useState(''); // m (Dense Gas)
const [algorithm, setAlgorithm] = useState('ALOHA (EPA)');
const [criteriaModel, setCriteriaModel] = useState('AEGL');
// 불러오기 시 입력값 복원
useEffect(() => {
if (!loadedParams) return;
queueMicrotask(() => {
if (loadedParams.substance) setSubstance(loadedParams.substance);
if (loadedParams.releaseType) setReleaseType(loadedParams.releaseType);
if (loadedParams.emissionRate != null) setEmissionRate(String(loadedParams.emissionRate));
if (loadedParams.totalRelease != null) setTotalRelease(String(loadedParams.totalRelease));
if (loadedParams.releaseHeight != null) setReleaseHeight(String(loadedParams.releaseHeight));
if (loadedParams.releaseDuration != null) setReleaseDuration(String(loadedParams.releaseDuration));
if (loadedParams.poolRadius != null) setPoolRadius(String(loadedParams.poolRadius));
if (loadedParams.algorithm) setAlgorithm(loadedParams.algorithm);
if (loadedParams.criteriaModel) setCriteriaModel(loadedParams.criteriaModel);
if (loadedParams.accidentDate) setAccidentDate(loadedParams.accidentDate);
if (loadedParams.accidentTime) setAccidentTime(loadedParams.accidentTime);
if (loadedParams.predictionTime) setPredictionTime(loadedParams.predictionTime);
if (loadedParams.accidentName) setAccidentName(loadedParams.accidentName);
});
}, [loadedParams]);
// 기상정보 자동조회 (사고 발생 일시 기반)
const weather = useWeatherFetch(incidentCoord.lat, incidentCoord.lon, accidentDate, accidentTime);
// 물질 독성 정보
const tox = getSubstanceToxicity(substance);
// 물질 변경 시 기본값 동기화 (핸들러에서 직접)
const handleSubstanceChange = (value: string) => {
setSubstance(value);
const newTox = getSubstanceToxicity(value);
setEmissionRate(String(newTox.Q));
setTotalRelease(String(newTox.QTotal));
setPoolRadius(String(newTox.poolRadius));
};
// 사고 목록 불러오기 (마운트 시 1회, ref 초기화 패턴)
const incidentsPromiseRef = useRef<Promise<void> | null>(null);
if (incidentsPromiseRef.current == null) {
incidentsPromiseRef.current = fetchIncidentsRaw()
.then(data => setIncidents(data))
.catch(() => setIncidents([]));
}
// 사고 선택 시 필드 자동 채움
const handleSelectIncident = (snStr: string) => {
setSelectedIncidentSn(snStr);
const sn = parseInt(snStr);
const incident = incidents.find(i => i.acdntSn === sn);
if (!incident) return;
setAccidentName(incident.acdntNm);
if (incident.lat && incident.lng) {
onCoordChange({ lat: incident.lat, lon: incident.lng });
}
};
// 파라미터 변경 시 부모에 통지
useEffect(() => {
if (onParamsChange) {
onParamsChange({
substance,
releaseType,
emissionRate: parseFloat(emissionRate) || tox.Q,
totalRelease: parseFloat(totalRelease) || tox.QTotal,
releaseHeight: parseFloat(releaseHeight) || 0.5,
releaseDuration: parseFloat(releaseDuration) || 300,
poolRadius: parseFloat(poolRadius) || tox.poolRadius,
algorithm,
criteriaModel,
weather,
accidentDate,
accidentTime,
predictionTime,
accidentName,
});
}
}, [substance, releaseType, emissionRate, totalRelease, releaseHeight, releaseDuration, poolRadius, algorithm, criteriaModel, weather, onParamsChange, tox.Q, tox.QTotal, tox.poolRadius, accidentDate, accidentTime, predictionTime, accidentName]);
const handleReset = () => {
setSelectedIncidentSn('');
setAccidentName('');
const now = new Date();
setAccidentDate(now.toISOString().slice(0, 10));
setAccidentTime(now.toTimeString().slice(0, 5));
setPredictionTime('24시간');
setSubstance('톨루엔 (Toluene)');
setReleaseType('연속 유출');
const defaultTox = getSubstanceToxicity('톨루엔 (Toluene)');
setEmissionRate(String(defaultTox.Q));
setTotalRelease(String(defaultTox.QTotal));
setReleaseHeight('0.5');
setReleaseDuration('300');
setPoolRadius(String(defaultTox.poolRadius));
setAlgorithm('ALOHA (EPA)');
setCriteriaModel('AEGL');
onCoordChange({ lon: 129.3542, lat: 35.4215 });
onReset?.();
};
return (
<div className="w-80 min-w-[320px] flex flex-col h-full bg-bg-1 border-r border-border overflow-hidden">
{/* Scrollable Content */}
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent bg-bg-0">
{activeSubTab === 'analysis' && (
<div className="p-4">
{/* Header */}
<div className="flex items-center justify-between mb-[14px]">
<div className="flex items-center gap-2.5">
<div
className="w-9 h-9 rounded-md flex items-center justify-center text-[18px]"
style={{
background: 'linear-gradient(135deg, rgba(249,115,22,0.15), rgba(168,85,247,0.1))',
border: '1px solid rgba(249,115,22,0.25)',
}}
>🧪</div>
<div>
<div className="text-[13px] font-bold text-text-2 font-korean">
HNS
</div>
<div className="text-[10px] text-text-3">
ALOHA/CAMEO
</div>
</div>
</div>
</div>
{/* Single Column Layout */}
<div className="flex flex-col gap-3">
{/* 사고 기본정보 */}
<div>
<div className="text-[13px] font-bold text-text-2 font-korean mb-3 flex items-center gap-1.5">
📋
</div>
<div className="flex flex-col gap-[6px]">
{/* 사고명 직접 입력 */}
<input
className="prd-i w-full"
value={accidentName}
onChange={(e) => setAccidentName(e.target.value)}
placeholder="사고명 직접 입력"
/>
{/* 또는 사고 리스트에서 선택 */}
<ComboBox
className="prd-i"
value={selectedIncidentSn}
onChange={handleSelectIncident}
placeholder="또는 사고 리스트에서 선택"
options={incidents.map(inc => ({
value: String(inc.acdntSn),
label: `${inc.acdntNm} (${new Date(inc.occrnDtm).toLocaleDateString('ko-KR')})`,
}))}
/>
{/* 사고 발생 일시 */}
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> </label>
<div className="grid grid-cols-2 gap-1">
<input
className="prd-i"
type="date"
value={accidentDate}
onChange={(e) => setAccidentDate(e.target.value)}
/>
<input
className="prd-i"
type="time"
value={accidentTime}
onChange={(e) => setAccidentTime(e.target.value)}
/>
</div>
</div>
{/* 좌표 + 지도 버튼 */}
<div className="grid items-center gap-1" style={{ gridTemplateColumns: '1fr 1fr auto' }}>
<input
className="prd-i flex-1 font-mono"
type="number"
step="0.0001"
value={incidentCoord.lat.toFixed(4)}
onChange={(e) => onCoordChange({ ...incidentCoord, lat: parseFloat(e.target.value) || 0 })}
/>
<input
className="prd-i flex-1 font-mono"
type="number"
step="0.0001"
value={incidentCoord.lon.toFixed(4)}
onChange={(e) => onCoordChange({ ...incidentCoord, lon: parseFloat(e.target.value) || 0 })}
/>
<button className="prd-map-btn" onClick={onMapSelectClick}>
📍
</button>
</div>
{/* DMS 표시 */}
<div className="text-[9px] text-text-3 font-mono border border-border bg-bg-0"
style={{ padding: '4px 8px', borderRadius: 'var(--rS)' }}>
{toDMS(incidentCoord.lat, 'lat')} / {toDMS(incidentCoord.lon, 'lon')}
</div>
{/* 유출형태 + 물질명 */}
<div className="grid grid-cols-2 gap-1">
<ComboBox
className="prd-i"
value={releaseType}
onChange={(v) => setReleaseType(v as ReleaseType)}
options={[
{ value: '연속 유출', label: '연속' },
{ value: '순간 유출', label: '순간' },
{ value: '풀(Pool) 증발', label: '풀 증발' },
]}
/>
<ComboBox
className="prd-i"
value={substance}
onChange={handleSubstanceChange}
options={[
{ value: '톨루엔 (Toluene)', label: '톨루엔' },
{ value: '벤젠 (Benzene)', label: '벤젠' },
{ value: '자일렌 (Xylene)', label: '자일렌' },
{ value: '스티렌 (Styrene)', label: '스티렌' },
{ value: '메탄올 (Methanol)', label: '메탄올' },
{ value: '아세톤 (Acetone)', label: '아세톤' },
{ value: '염소 (Chlorine)', label: '염소' },
{ value: '암모니아 (Ammonia)', label: '암모니아' },
{ value: '염화수소 (HCl)', label: '염화수소' },
{ value: '황화수소 (H2S)', label: '황화수소' },
]}
/>
</div>
{/* 유출량 + 단위 + 예측시간 */}
<div className="grid items-center gap-1" style={{ gridTemplateColumns: '1fr 65px 1fr' }}>
<input
className="prd-i font-mono"
type="number"
value={releaseType === '순간 유출' ? totalRelease : emissionRate}
onChange={(e) => releaseType === '순간 유출' ? setTotalRelease(e.target.value) : setEmissionRate(e.target.value)}
placeholder={releaseType === '순간 유출' ? 'g' : 'g/s'}
/>
<ComboBox
className="prd-i"
value={releaseType === '순간 유출' ? 'g' : 'g/s'}
onChange={() => {}}
options={
releaseType === '순간 유출'
? [{ value: 'g', label: 'g' }, { value: 'kg', label: 'kg' }]
: [{ value: 'g/s', label: 'g/s' }, { value: 'kg/s', label: 'kg/s' }]
}
/>
<ComboBox
className="prd-i"
value={predictionTime}
onChange={setPredictionTime}
options={[
{ value: '6시간', label: '6시간' },
{ value: '12시간', label: '12시간' },
{ value: '24시간', label: '24시간' },
{ value: '48시간', label: '48시간' },
]}
/>
</div>
{/* 기상 정보 (자동조회) — 내부적으로 사용, UI 숨김 */}
{/* 알고리즘 선택 */}
<div className="grid grid-cols-2 gap-1">
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> </label>
<ComboBox
className="prd-i"
value={algorithm}
onChange={setAlgorithm}
options={[
{ value: 'ALOHA (EPA)', label: 'ALOHA (EPA)' },
{ value: 'CAMEO', label: 'CAMEO' },
{ value: 'Gaussian Plume', label: 'Gaussian Plume' },
{ value: 'AERMOD', label: 'AERMOD' }
]}
/>
</div>
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> </label>
<ComboBox
className="prd-i"
value={criteriaModel}
onChange={setCriteriaModel}
options={[
{ value: 'AEGL', label: 'AEGL' },
{ value: 'ERPG', label: 'ERPG' },
{ value: 'TEEL', label: 'TEEL' },
{ value: 'PAC', label: 'PAC' }
]}
/>
</div>
</div>
</div>
</div>
{/* 모델 파라미터 & 물질 정보 */}
<div>
<div className="text-[13px] font-bold text-text-2 font-korean mb-3 flex items-center gap-1.5">
🧪 {releaseType === '연속 유출' ? 'Plume' : releaseType === '순간 유출' ? 'Puff' : 'Dense Gas'}
</div>
<div className="flex flex-col gap-[6px]">
{/* 모델별 입력 파라미터 */}
<div
className="p-[10px] rounded-md"
style={{ background: 'rgba(6,182,212,0.04)', border: '1px solid rgba(6,182,212,0.15)' }}
>
{/* 연속 유출 (Plume): 배출률, 누출지속시간, 누출높이 */}
{releaseType === '연속 유출' && (
<div className="flex flex-col gap-[6px]">
<div className="grid grid-cols-2 gap-1">
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> (g/s)</label>
<input
className="prd-i w-full font-mono"
type="number"
value={emissionRate}
onChange={(e) => setEmissionRate(e.target.value)}
step="0.1"
min="0"
/>
</div>
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> (s)</label>
<input
className="prd-i w-full font-mono"
type="number"
value={releaseDuration}
onChange={(e) => setReleaseDuration(e.target.value)}
step="10"
min="1"
/>
</div>
</div>
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> (m)</label>
<input
className="prd-i w-full font-mono"
type="number"
value={releaseHeight}
onChange={(e) => setReleaseHeight(e.target.value)}
step="0.1"
min="0"
/>
</div>
</div>
)}
{/* 순간 유출 (Puff): 총 누출량, 누출높이 */}
{releaseType === '순간 유출' && (
<div className="flex flex-col gap-[6px]">
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> (g)</label>
<input
className="prd-i w-full font-mono"
type="number"
value={totalRelease}
onChange={(e) => setTotalRelease(e.target.value)}
step="100"
min="0"
/>
</div>
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> (m)</label>
<input
className="prd-i w-full font-mono"
type="number"
value={releaseHeight}
onChange={(e) => setReleaseHeight(e.target.value)}
step="0.1"
min="0"
/>
</div>
</div>
)}
{/* 풀(Pool) 증발 (Dense Gas): 배출률, 풀반경, 누출높이 */}
{releaseType === '풀(Pool) 증발' && (
<div className="flex flex-col gap-[6px]">
<div className="grid grid-cols-2 gap-1">
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> (g/s)</label>
<input
className="prd-i w-full font-mono"
type="number"
value={emissionRate}
onChange={(e) => setEmissionRate(e.target.value)}
step="0.1"
min="0"
/>
</div>
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> (m)</label>
<input
className="prd-i w-full font-mono"
type="number"
value={poolRadius}
onChange={(e) => setPoolRadius(e.target.value)}
step="0.5"
min="0.1"
/>
</div>
</div>
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> (m)</label>
<input
className="prd-i w-full font-mono"
type="number"
value={releaseHeight}
onChange={(e) => setReleaseHeight(e.target.value)}
step="0.1"
min="0"
/>
</div>
</div>
)}
{/* 모델 설명 */}
<div className="text-[9px] text-text-3 mt-1 leading-[1.4]">
{releaseType === '연속 유출' && '정상상태 연속 배출. 바람 방향으로 플룸이 형성됩니다.'}
{releaseType === '순간 유출' && '한 번에 전량 방출. 시간에 따라 구름이 이동하며 확산됩니다.'}
{releaseType === '풀(Pool) 증발' && '고밀도 가스가 지표면을 따라 확산됩니다 (Britter-McQuaid 모델).'}
</div>
</div>
{/* 물질 위험 특성 */}
<div
className="p-2 rounded-sm mt-0.5"
style={{ background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)' }}
>
<div className="text-[10px] font-bold text-status-orange mb-1">
</div>
<div className="grid grid-cols-2 gap-[3px] text-[9px]">
<div className="flex justify-between">
<span className="text-text-3"></span>
<span className="font-mono">{tox.mw} g/mol</span>
</div>
<div className="flex justify-between">
<span className="text-text-3"></span>
<span className="font-mono">{tox.densityGas} kg/m³</span>
</div>
<div className="flex justify-between">
<span className="text-text-3"></span>
<span className="font-mono">{tox.vaporPressure} mmHg</span>
</div>
<div className="flex justify-between">
<span className="text-text-3">IDLH</span>
<span className="text-status-red font-semibold font-mono">{tox.idlh} ppm</span>
</div>
</div>
</div>
{/* AEGL 등급 범례 */}
<div
className="p-2 rounded-sm"
style={{ background: 'rgba(168,85,247,0.05)', border: '1px solid rgba(168,85,247,0.12)' }}
>
<div className="text-[10px] font-bold text-primary-purple mb-1">
📊 (AEGL)
</div>
<div className="flex flex-col gap-0.5 text-[9px]">
<div className="flex items-center gap-1">
<div className="w-2 h-2 rounded-[2px]" style={{ background: 'rgba(239,68,68,0.7)' }}></div>
<span className="text-text-3">AEGL-3 () {tox.aegl3} ppm</span>
</div>
<div className="flex items-center gap-1">
<div className="w-2 h-2 rounded-[2px]" style={{ background: 'rgba(249,115,22,0.7)' }}></div>
<span className="text-text-3">AEGL-2 () {tox.aegl2} ppm</span>
</div>
<div className="flex items-center gap-1">
<div className="w-2 h-2 rounded-[2px]" style={{ background: 'rgba(234,179,8,0.7)' }}></div>
<span className="text-text-3">AEGL-1 () {tox.aegl1} ppm</span>
</div>
</div>
</div>
</div>
</div>
</div>
{/* 실행 버튼 */}
<div className="flex flex-col gap-1 mt-2">
<button
className="prd-btn pri"
style={{ padding: '7px', fontSize: '11px' }}
onClick={onRunPrediction}
disabled={isRunningPrediction}
>
{isRunningPrediction ? '⏳ 실행 중...' : '🧪 대기확산 예측 실행'}
</button>
<button
className="prd-btn sec"
style={{ padding: '7px', fontSize: '11px' }}
onClick={handleReset}
>
</button>
</div>
</div>
)}
{activeSubTab === 'list' && (
<div className="p-4">
{/* Header */}
<div className="flex items-center gap-2.5 mb-[14px]">
<div
className="w-9 h-9 rounded-md flex items-center justify-center text-[18px]"
style={{
background: 'linear-gradient(135deg, rgba(6,182,212,0.15), rgba(168,85,247,0.1))',
border: '1px solid rgba(6,182,212,0.25)',
}}
>📋</div>
<div>
<div className="text-[13px] font-bold text-text-2 font-korean">
</div>
<div className="text-[10px] text-text-3">
</div>
</div>
</div>
{/* 필터 섹션 */}
<div className="bg-bg-3 border border-border rounded-md p-[14px] mb-3">
<div className="text-[13px] font-bold text-text-2 font-korean mb-3 flex items-center gap-1.5">
🔍
</div>
<div className="flex flex-col gap-2">
{/* 기간 선택 */}
<div>
<label className="text-[10px] text-text-3 block mb-0.5"></label>
<ComboBox
value="최근 7일"
onChange={() => {}}
options={[
{ value: '오늘', label: '오늘' },
{ value: '최근 7일', label: '최근 7일' },
{ value: '최근 30일', label: '최근 30일' },
{ value: '전체', label: '전체' }
]}
/>
</div>
{/* 물질 분류 */}
<div>
<label className="text-[10px] text-text-3 block mb-0.5"> </label>
<ComboBox
value="전체"
onChange={() => {}}
options={[
{ value: '전체', label: '전체' },
{ value: '유독성 액체', label: '유독성 액체' },
{ value: '유독성 기체', label: '유독성 기체' },
{ value: '인화성 액체', label: '인화성 액체' },
{ value: '인화성 기체', label: '인화성 기체' }
]}
/>
</div>
{/* 위험도 */}
<div>
<label className="text-[10px] text-text-3 block mb-0.5"></label>
<ComboBox
value="전체"
onChange={() => {}}
options={[
{ value: '전체', label: '전체' },
{ value: 'AEGL-3', label: 'AEGL-3' },
{ value: 'AEGL-2', label: 'AEGL-2' },
{ value: 'AEGL-1', label: 'AEGL-1' }
]}
/>
</div>
</div>
</div>
{/* 통계 요약 */}
<div className="bg-bg-3 border border-border rounded-md p-[14px]">
<div className="text-[13px] font-bold text-text-2 font-korean mb-3 flex items-center gap-1.5">
📊
</div>
<div className="flex flex-col gap-2">
<div className="flex justify-between items-center p-2 bg-bg-0 rounded">
<span className="text-[10px] text-text-3"> </span>
<span className="text-sm font-bold text-primary-cyan font-mono">8</span>
</div>
<div className="flex justify-between items-center p-2 bg-bg-0 rounded">
<span className="text-[10px] text-text-3"> (AEGL-3)</span>
<span className="text-sm font-bold text-status-red font-mono">3</span>
</div>
<div className="flex justify-between items-center p-2 bg-bg-0 rounded">
<span className="text-[10px] text-text-3"> (AEGL-2)</span>
<span className="text-sm font-bold text-status-orange font-mono">5</span>
</div>
</div>
</div>
</div>
)}
</div>
</div>
);
}