- 11개 탭 디렉토리 생성: tabs/{prediction,hns,rescue,weather,incidents,aerial,board,reports,assets,scat,admin}/
- 51개 컴포넌트를 역할 기반(views/, analysis/, layout/) → 탭 기반(tabs/) 구조로 이동
- weather 탭에 전용 hooks/, services/ 포함
- incidents 탭에 전용 services/ 포함
- 공통 지도 컴포넌트(MapView, BacktrackReplay)를 common/components/map/으로 이동
- 각 탭에 index.ts 생성하여 View 컴포넌트 re-export
- App.tsx import를 @tabs/ alias 사용으로 변경
- 전체 import 경로 수정 (탭 내부 상대경로, 외부 @common/ alias)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
566 lines
32 KiB
TypeScript
Executable File
566 lines
32 KiB
TypeScript
Executable File
import { useState } from 'react'
|
|
import { ComboBox } from '@common/components/ui/ComboBox'
|
|
|
|
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
|
|
}
|
|
|
|
export function HNSLeftPanel({
|
|
activeSubTab,
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
onSubTabChange,
|
|
incidentCoord,
|
|
onCoordChange,
|
|
onMapSelectClick,
|
|
onRunPrediction,
|
|
isRunningPrediction
|
|
}: HNSLeftPanelProps) {
|
|
const [accidentName, setAccidentName] = useState('울산 온산항 톨루엔 누출')
|
|
const [predictionTime, setPredictionTime] = useState('24시간')
|
|
const [locationName, setLocationName] = useState('울산 온산항 제3부두')
|
|
const [materialCategory, setMaterialCategory] = useState('인화성 액체')
|
|
const [substance, setSubstance] = useState('톨루엔 (Toluene)')
|
|
const [unNumber] = useState('UN 1294')
|
|
const [casNumber] = useState('108-88-3')
|
|
const [amount, setAmount] = useState('12.0')
|
|
const [unit, setUnit] = useState('kL')
|
|
const [releaseType, setReleaseType] = useState('연속 유출')
|
|
const [algorithm, setAlgorithm] = useState('ALOHA (EPA)')
|
|
const [criteriaModel, setCriteriaModel] = useState('AEGL')
|
|
|
|
const handleReset = () => {
|
|
setAccidentName('울산 온산항 톨루엔 누출')
|
|
setPredictionTime('24시간')
|
|
setLocationName('울산 온산항 제3부두')
|
|
setMaterialCategory('인화성 액체')
|
|
setSubstance('톨루엔 (Toluene)')
|
|
setAmount('12.0')
|
|
setUnit('kL')
|
|
setReleaseType('연속 유출')
|
|
setAlgorithm('ALOHA (EPA)')
|
|
setCriteriaModel('AEGL')
|
|
onCoordChange({ lon: 129.3542, lat: 35.4215 })
|
|
}
|
|
|
|
return (
|
|
<div className="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" style={{ background: 'var(--bg0)' }}>
|
|
{activeSubTab === 'analysis' && (
|
|
<div style={{ padding: '16px' }}>
|
|
{/* Header */}
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '14px' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
|
<div style={{
|
|
width: '36px',
|
|
height: '36px',
|
|
borderRadius: '8px',
|
|
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)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
fontSize: '18px'
|
|
}}>🧪</div>
|
|
<div>
|
|
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>
|
|
HNS 대기확산 예측
|
|
</div>
|
|
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
|
ALOHA/CAMEO 기반 대기확산 시뮬레이션
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Single Column Layout */}
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
|
|
{/* 사고 기본정보 */}
|
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
|
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
📋 사고 기본정보
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
{/* 사고명 */}
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>사고명</label>
|
|
<input
|
|
className="hns-inp"
|
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
|
value={accidentName}
|
|
onChange={(e) => setAccidentName(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* 사고일시 + 예측시간 */}
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>사고일시</label>
|
|
<input
|
|
className="hns-inp"
|
|
type="datetime-local"
|
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
|
defaultValue="2025-02-11T05:02"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>예측시간</label>
|
|
<ComboBox
|
|
className="hns-inp"
|
|
value={predictionTime}
|
|
onChange={setPredictionTime}
|
|
options={[
|
|
{ value: '6시간', label: '6시간' },
|
|
{ value: '12시간', label: '12시간' },
|
|
{ value: '24시간', label: '24시간' },
|
|
{ value: '48시간', label: '48시간' },
|
|
{ value: '72시간', label: '72시간' }
|
|
]}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 사고지점 */}
|
|
<div style={{ padding: '10px', background: 'var(--bg0)', border: '1px solid rgba(6,182,212,0.2)', borderRadius: '8px' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 0 }}>📍 사고지점</label>
|
|
<button
|
|
onClick={onMapSelectClick}
|
|
style={{
|
|
padding: '4px 10px',
|
|
borderRadius: '4px',
|
|
border: '1px solid rgba(6,182,212,0.3)',
|
|
background: 'rgba(6,182,212,0.08)',
|
|
color: 'var(--cyan)',
|
|
fontSize: '8px',
|
|
fontWeight: 700,
|
|
cursor: 'pointer',
|
|
fontFamily: 'var(--fK)',
|
|
transition: '0.15s'
|
|
}}
|
|
>
|
|
🗺 지도에서 클릭
|
|
</button>
|
|
</div>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', marginBottom: '6px' }}>
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>위도</label>
|
|
<input
|
|
className="hns-inp"
|
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
|
value={incidentCoord.lat.toFixed(4)}
|
|
onChange={(e) => onCoordChange({ ...incidentCoord, lat: parseFloat(e.target.value) || 0 })}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>경도</label>
|
|
<input
|
|
className="hns-inp"
|
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
|
value={incidentCoord.lon.toFixed(4)}
|
|
onChange={(e) => onCoordChange({ ...incidentCoord, lon: parseFloat(e.target.value) || 0 })}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>상세 위치</label>
|
|
<input
|
|
className="hns-inp"
|
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
|
value={locationName}
|
|
onChange={(e) => setLocationName(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 기상 정보 */}
|
|
<div style={{ padding: '10px', background: 'linear-gradient(135deg, rgba(168,85,247,0.04), rgba(6,182,212,0.03))', border: '1px solid rgba(168,85,247,0.15)', borderRadius: '8px' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
|
<div style={{ width: '6px', height: '6px', borderRadius: '50%', background: 'var(--green)' }}></div>
|
|
<span className="hns-lbl" style={{ fontSize: '8px', color: 'var(--purple)', fontFamily: 'var(--fK)', marginBottom: 0 }}>🌬 기상정보 (자동조회)</span>
|
|
</div>
|
|
<span style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>KMA API · 울산 AWS</span>
|
|
</div>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '6px', marginBottom: '6px' }}>
|
|
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
|
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>5.2</div>
|
|
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>풍속(m/s)</div>
|
|
</div>
|
|
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
|
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>SW 225°</div>
|
|
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>풍향</div>
|
|
</div>
|
|
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
|
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--orange)' }}>8.5°C</div>
|
|
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>기온</div>
|
|
</div>
|
|
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
|
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--blue)' }}>62%</div>
|
|
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>습도</div>
|
|
</div>
|
|
</div>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 6px', background: 'var(--bg0)', borderRadius: '3px', fontSize: '8px', fontFamily: 'var(--fK)' }}>
|
|
<span style={{ color: 'var(--t3)' }}>대기안정도</span>
|
|
<span style={{ fontWeight: 600, color: 'var(--t1)' }}>D (중립)</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 6px', background: 'var(--bg0)', borderRadius: '3px', fontSize: '8px', fontFamily: 'var(--fK)' }}>
|
|
<span style={{ color: 'var(--t3)' }}>지표 조도</span>
|
|
<span style={{ fontWeight: 600, color: 'var(--t1)' }}>해안</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 알고리즘 선택 */}
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>예측 알고리즘</label>
|
|
<ComboBox
|
|
className="hns-inp"
|
|
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="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>확산 등급 기준</label>
|
|
<ComboBox
|
|
className="hns-inp"
|
|
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 style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
|
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
🧪 물질 정보
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
{/* 물질 분류 */}
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>물질 분류</label>
|
|
<ComboBox
|
|
className="hns-inp"
|
|
value={materialCategory}
|
|
onChange={setMaterialCategory}
|
|
options={[
|
|
{ value: '유독성 액체', label: '유독성 액체' },
|
|
{ value: '유독성 기체', label: '유독성 기체' },
|
|
{ value: '인화성 액체', label: '인화성 액체' },
|
|
{ value: '인화성 기체', label: '인화성 기체' },
|
|
{ value: '부식성 물질', label: '부식성 물질' },
|
|
{ value: '친환경 연료', label: '친환경 연료' }
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
{/* 물질명 */}
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>물질명</label>
|
|
<ComboBox
|
|
className="hns-inp"
|
|
value={substance}
|
|
onChange={setSubstance}
|
|
options={[
|
|
{ value: '톨루엔 (Toluene)', label: '톨루엔 (Toluene)' },
|
|
{ value: '벤젠 (Benzene)', label: '벤젠 (Benzene)' },
|
|
{ value: '자일렌 (Xylene)', label: '자일렌 (Xylene)' },
|
|
{ value: '스티렌 (Styrene)', label: '스티렌 (Styrene)' },
|
|
{ value: '메탄올 (Methanol)', label: '메탄올 (Methanol)' },
|
|
{ value: '아세톤 (Acetone)', label: '아세톤 (Acetone)' }
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
{/* UN번호 / CAS번호 */}
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>UN번호 / CAS번호</label>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
|
<input
|
|
className="hns-inp"
|
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
|
value={unNumber}
|
|
readOnly
|
|
/>
|
|
<input
|
|
className="hns-inp"
|
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
|
value={casNumber}
|
|
readOnly
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 유출량 + 단위 */}
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>유출량</label>
|
|
<input
|
|
className="hns-inp"
|
|
type="number"
|
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
|
value={amount}
|
|
onChange={(e) => setAmount(e.target.value)}
|
|
step="0.1"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>단위</label>
|
|
<ComboBox
|
|
className="hns-inp"
|
|
value={unit}
|
|
onChange={setUnit}
|
|
options={[
|
|
{ value: 'kL', label: 'kL' },
|
|
{ value: '톤', label: '톤' },
|
|
{ value: 'kg', label: 'kg' },
|
|
{ value: '배럴', label: '배럴' }
|
|
]}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 유출 형태 */}
|
|
<div>
|
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>유출 형태</label>
|
|
<ComboBox
|
|
className="hns-inp"
|
|
value={releaseType}
|
|
onChange={setReleaseType}
|
|
options={[
|
|
{ value: '연속 유출', label: '연속 유출' },
|
|
{ value: '순간 유출', label: '순간 유출' },
|
|
{ value: '풀(Pool) 증발', label: '풀(Pool) 증발' }
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
{/* 물질 위험 특성 */}
|
|
<div style={{ padding: '8px', background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)', borderRadius: '6px', marginTop: '2px' }}>
|
|
<div style={{ fontSize: '8px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: '4px' }}>
|
|
⚠ 물질 위험 특성
|
|
</div>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '3px', fontSize: '8px', fontFamily: 'var(--fK)' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span style={{ color: 'var(--t3)' }}>인화점</span>
|
|
<span style={{ color: 'var(--red)', fontWeight: 600, fontFamily: 'var(--fM)' }}>4°C</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span style={{ color: 'var(--t3)' }}>비중</span>
|
|
<span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>0.867</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span style={{ color: 'var(--t3)' }}>증기압</span>
|
|
<span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>22 mmHg</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span style={{ color: 'var(--t3)' }}>IDLH</span>
|
|
<span style={{ color: 'var(--red)', fontWeight: 600, fontFamily: 'var(--fM)' }}>500 ppm</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span style={{ color: 'var(--t3)' }}>TWA</span>
|
|
<span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>50 ppm</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span style={{ color: 'var(--t3)' }}>AEGL-2(1h)</span>
|
|
<span style={{ color: 'var(--orange)', fontWeight: 600, fontFamily: 'var(--fM)' }}>150 ppm</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* AEGL 등급 범례 */}
|
|
<div style={{ padding: '8px', background: 'rgba(168,85,247,0.05)', border: '1px solid rgba(168,85,247,0.12)', borderRadius: '6px' }}>
|
|
<div style={{ fontSize: '8px', fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fK)', marginBottom: '4px' }}>
|
|
📊 확산 등급 기준 (AEGL)
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px', fontSize: '8px', fontFamily: 'var(--fK)' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
|
<div style={{ width: '8px', height: '8px', borderRadius: '2px', background: 'rgba(239,68,68,0.7)' }}></div>
|
|
<span style={{ color: 'var(--t3)' }}>AEGL-3 (생명위협) — 500 ppm</span>
|
|
</div>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
|
<div style={{ width: '8px', height: '8px', borderRadius: '2px', background: 'rgba(249,115,22,0.7)' }}></div>
|
|
<span style={{ color: 'var(--t3)' }}>AEGL-2 (건강피해) — 150 ppm</span>
|
|
</div>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
|
<div style={{ width: '8px', height: '8px', borderRadius: '2px', background: 'rgba(234,179,8,0.7)' }}></div>
|
|
<span style={{ color: 'var(--t3)' }}>AEGL-1 (불쾌감) — 37 ppm</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 실행 버튼 */}
|
|
<div style={{ display: 'flex', gap: '8px', marginTop: '14px', justifyContent: 'center' }}>
|
|
<button
|
|
onClick={onRunPrediction}
|
|
disabled={isRunningPrediction}
|
|
style={{
|
|
padding: '12px 40px',
|
|
background: isRunningPrediction
|
|
? 'var(--t4)'
|
|
: 'linear-gradient(135deg, var(--orange), var(--red))',
|
|
border: 'none',
|
|
borderRadius: '8px',
|
|
color: '#fff',
|
|
fontSize: '13px',
|
|
fontWeight: 700,
|
|
cursor: isRunningPrediction ? 'not-allowed' : 'pointer',
|
|
fontFamily: 'var(--fK)',
|
|
transition: '0.2s',
|
|
boxShadow: isRunningPrediction ? 'none' : '0 4px 16px rgba(249,115,22,0.25)'
|
|
}}
|
|
>
|
|
🧪 {isRunningPrediction ? '예측 실행 중...' : '대기확산 예측 실행'}
|
|
</button>
|
|
<button
|
|
onClick={handleReset}
|
|
style={{
|
|
padding: '12px 24px',
|
|
background: 'var(--bg3)',
|
|
border: '1px solid var(--bd)',
|
|
borderRadius: '8px',
|
|
color: 'var(--t2)',
|
|
fontSize: '12px',
|
|
fontWeight: 600,
|
|
cursor: 'pointer',
|
|
fontFamily: 'var(--fK)'
|
|
}}
|
|
>
|
|
🔄 초기화
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeSubTab === 'list' && (
|
|
<div style={{ padding: '16px' }}>
|
|
{/* Header */}
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '14px' }}>
|
|
<div style={{
|
|
width: '36px',
|
|
height: '36px',
|
|
borderRadius: '8px',
|
|
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)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
fontSize: '18px'
|
|
}}>📋</div>
|
|
<div>
|
|
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>
|
|
분석 목록
|
|
</div>
|
|
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
|
저장된 대기확산 예측 결과
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 필터 섹션 */}
|
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px', marginBottom: '12px' }}>
|
|
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
🔍 필터
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
{/* 기간 선택 */}
|
|
<div>
|
|
<label style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>기간</label>
|
|
<ComboBox
|
|
value="최근 7일"
|
|
onChange={() => {}}
|
|
options={[
|
|
{ value: '오늘', label: '오늘' },
|
|
{ value: '최근 7일', label: '최근 7일' },
|
|
{ value: '최근 30일', label: '최근 30일' },
|
|
{ value: '전체', label: '전체' }
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
{/* 물질 분류 */}
|
|
<div>
|
|
<label style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>물질 분류</label>
|
|
<ComboBox
|
|
value="전체"
|
|
onChange={() => {}}
|
|
options={[
|
|
{ value: '전체', label: '전체' },
|
|
{ value: '유독성 액체', label: '유독성 액체' },
|
|
{ value: '유독성 기체', label: '유독성 기체' },
|
|
{ value: '인화성 액체', label: '인화성 액체' },
|
|
{ value: '인화성 기체', label: '인화성 기체' }
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
{/* 위험도 */}
|
|
<div>
|
|
<label style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>위험도</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 style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
|
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fK)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
📊 통계
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
|
<span style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>전체 분석</span>
|
|
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>8건</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
|
<span style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>고위험 (AEGL-3)</span>
|
|
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fM)' }}>3건</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
|
<span style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>중위험 (AEGL-2)</span>
|
|
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>5건</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|