import { useState } from 'react' import type { BoomLine, BoomLineCoord, AlgorithmSettings, ContainmentResult } from '@common/types/boomLine' import { generateAIBoomLines, runContainmentAnalysis, computePolylineLength, computeBearing } from '@common/utils/geo' interface OilBoomSectionProps { expanded: boolean onToggle: () => void boomLines: BoomLine[] onBoomLinesChange: (lines: BoomLine[]) => void oilTrajectory: Array<{ lat: number; lon: number; time: number; particle?: number }> incidentCoord: { lon: number; lat: number } algorithmSettings: AlgorithmSettings onAlgorithmSettingsChange: (settings: AlgorithmSettings) => void isDrawingBoom: boolean onDrawingBoomChange: (drawing: boolean) => void drawingPoints: BoomLineCoord[] onDrawingPointsChange: (points: BoomLineCoord[]) => void containmentResult: ContainmentResult | null onContainmentResultChange: (result: ContainmentResult | null) => void } const OilBoomSection = ({ expanded, onToggle, boomLines, onBoomLinesChange, oilTrajectory, incidentCoord, algorithmSettings, onAlgorithmSettingsChange, isDrawingBoom, onDrawingBoomChange, drawingPoints, onDrawingPointsChange, containmentResult, onContainmentResultChange, }: OilBoomSectionProps) => { const [boomPlacementTab, setBoomPlacementTab] = useState<'ai' | 'manual' | 'simulation'>('simulation') return (

๐Ÿ›ก ์˜ค์ผํŽœ์Šค ๋ฐฐ์น˜ ๊ฐ€์ด๋“œ

{expanded ? 'โ–ผ' : 'โ–ถ'}
{expanded && (
{/* Tab Buttons + Reset */}
{[ { id: 'ai' as const, label: 'AI ์ž๋™ ์ถ”์ฒœ' }, { id: 'manual' as const, label: '์ˆ˜๋™ ๋ฐฐ์น˜' }, { id: 'simulation' as const, label: '์‹œ๋ฎฌ๋ ˆ์ด์…˜' } ].map(tab => ( ))}
{/* Key Metrics (๋™์ ) */}
{[ { value: String(boomLines.length), label: '๋ฐฐ์น˜ ๋ผ์ธ', color: 'var(--orange)' }, { value: boomLines.length > 0 ? `${(boomLines.reduce((s, l) => s + l.length, 0) / 1000).toFixed(1)}km` : '0km', label: '์ด ๊ธธ์ด', color: 'var(--cyan)' }, { value: boomLines.length > 0 ? `${Math.round(boomLines.reduce((s, l) => s + l.efficiency, 0) / boomLines.length)}%` : 'โ€”', label: 'ํ‰๊ท  ํšจ์œจ', color: 'var(--orange)' } ].map((metric, idx) => (
{metric.value}
{metric.label}
))}
{/* ===== AI ์ž๋™ ์ถ”์ฒœ ํƒญ ===== */} {boomPlacementTab === 'ai' && ( <>
0 ? 'var(--green)' : 'var(--t3)' }} /> 0 ? 'var(--green)' : 'var(--t3)' }} className="text-[10px] font-bold"> {oilTrajectory.length > 0 ? 'ํ™•์‚ฐ ๋ฐ์ดํ„ฐ ์ค€๋น„ ์™„๋ฃŒ' : 'ํ™•์‚ฐ ์˜ˆ์ธก์„ ๋จผ์ € ์‹คํ–‰ํ•˜์„ธ์š”'}

ํ™•์‚ฐ ์˜ˆ์ธก ๊ธฐ๋ฐ˜ ์ตœ์  ๋ฐฐ์น˜์•ˆ

{oilTrajectory.length > 0 ? 'ํ™•์‚ฐ ๊ถค์ ์„ ๋ถ„์„ํ•˜์—ฌ ํ•ด๋ฅ˜ ์ง๊ต ๋ฐฉํ–ฅ 1์ฐจ ๋ฐฉ์–ด์„ , Uํ˜• ํฌ์œ„ 2์ฐจ ๋ฐฉ์–ด์„ , ์—ฐ์•ˆ ๋ณดํ˜ธ 3์ฐจ ๋ฐฉ์–ด์„ ์„ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.' : '์ƒ๋‹จ์—์„œ ํ™•์‚ฐ ์˜ˆ์ธก์„ ์‹คํ–‰ํ•œ ๋’ค AI ๋ฐฐ์น˜๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.' }

{/* ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค์ • */}

๐Ÿ“Š ๋ฐฐ์น˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค์ •

{[ { label: 'ํ•ด๋ฅ˜ ์ง๊ต ๋ณด์ •', key: 'currentOrthogonalCorrection' as const, unit: 'ยฐ', value: algorithmSettings.currentOrthogonalCorrection }, { label: '์•ˆ์ „ ๋งˆ์ง„ (๋„๋‹ฌ์‹œ๊ฐ„)', key: 'safetyMarginMinutes' as const, unit: '๋ถ„', value: algorithmSettings.safetyMarginMinutes }, { label: '์ตœ์†Œ ์ฐจ๋‹จ ํšจ์œจ', key: 'minContainmentEfficiency' as const, unit: '%', value: algorithmSettings.minContainmentEfficiency }, { label: 'ํŒŒ๊ณ  ๋ณด์ • ๊ณ„์ˆ˜', key: 'waveHeightCorrectionFactor' as const, unit: 'x', value: algorithmSettings.waveHeightCorrectionFactor }, ].map((setting) => (
โ— {setting.label}
{ const val = parseFloat(e.target.value) || 0 onAlgorithmSettingsChange({ ...algorithmSettings, [setting.key]: val }) }} className="boom-setting-input" step={setting.key === 'waveHeightCorrectionFactor' ? 0.1 : 1} /> {setting.unit}
))}
)} {/* ===== ์ˆ˜๋™ ๋ฐฐ์น˜ ํƒญ ===== */} {boomPlacementTab === 'manual' && ( <> {/* ๋“œ๋กœ์ž‰ ์ปจํŠธ๋กค */}
{!isDrawingBoom ? ( ) : ( <> )}
{/* ๋“œ๋กœ์ž‰ ์‹ค์‹œ๊ฐ„ ์ •๋ณด */} {isDrawingBoom && drawingPoints.length > 0 && (
ํฌ์ธํŠธ: {drawingPoints.length} ๊ธธ์ด: {computePolylineLength(drawingPoints).toFixed(0)}m {drawingPoints.length >= 2 && ( ๋ฐฉ์œ„๊ฐ: {computeBearing(drawingPoints[0], drawingPoints[drawingPoints.length - 1]).toFixed(0)}ยฐ )}
)} {/* ๋ฐฐ์น˜๋œ ๋ผ์ธ ๋ชฉ๋ก */} {boomLines.length === 0 ? (

๋ฐฐ์น˜๋œ ์˜ค์ผํŽœ์Šค ๋ผ์ธ์ด ์—†์Šต๋‹ˆ๋‹ค.

) : ( boomLines.map((line, idx) => (
{ const updated = [...boomLines] updated[idx] = { ...updated[idx], name: e.target.value } onBoomLinesChange(updated) }} className="flex-1 text-[11px] font-bold bg-transparent border-none outline-none" />
๊ธธ์ด
{line.length.toFixed(0)}m
๊ฐ๋„
{line.angle.toFixed(0)}ยฐ
์šฐ์„ ์ˆœ์œ„
)) )} )} {/* ===== ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํƒญ ===== */} {boomPlacementTab === 'simulation' && ( <> {/* ์ „์ œ์กฐ๊ฑด ์ฒดํฌ */}
0 ? 'var(--green)' : 'var(--red)' }} /> 0 ? 'var(--green)' : 'var(--t3)' }}> ํ™•์‚ฐ ๊ถค์  ๋ฐ์ดํ„ฐ {oilTrajectory.length > 0 ? `(${oilTrajectory.length}๊ฐœ ์ž…์ž)` : '์—†์Œ'}
0 ? 'var(--green)' : 'var(--red)' }} /> 0 ? 'var(--green)' : 'var(--t3)' }}> ์˜ค์ผํŽœ์Šค ๋ผ์ธ {boomLines.length > 0 ? `(${boomLines.length}๊ฐœ ๋ฐฐ์น˜)` : '์—†์Œ'}
{/* ์‹คํ–‰ ๋ฒ„ํŠผ */} {/* ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒฐ๊ณผ */} {containmentResult && containmentResult.totalParticles > 0 && (
{/* ์ „์ฒด ํšจ์œจ */}
{containmentResult.overallEfficiency}%
์ „์ฒด ์ฐจ๋‹จ ํšจ์œจ
{/* ์ฐจ๋‹จ/ํ†ต๊ณผ ์นด์šดํŠธ */}
{containmentResult.blockedParticles}
์ฐจ๋‹จ ์ž…์ž
{containmentResult.passedParticles}
ํ†ต๊ณผ ์ž…์ž
{/* ํšจ์œจ ๋ฐ” */}
= 80 ? 'var(--green)' : containmentResult.overallEfficiency >= 50 ? 'var(--orange)' : 'var(--red)' }} />
{/* ๋ผ์ธ๋ณ„ ๋ถ„์„ */}

๋ผ์ธ๋ณ„ ์ฐจ๋‹จ ๋ถ„์„

{containmentResult.perLineResults.map((r) => (
{r.boomLineName} = 50 ? 'var(--green)' : 'var(--orange)', marginLeft: '8px' }} className="font-bold font-mono"> {r.blocked}์ฐจ๋‹จ / {r.efficiency}%
))}
)} )} {/* ๋ฐฐ์น˜๋œ ๋ฐฉ์–ด์„  ์นด๋“œ (AI/์ˆ˜๋™ ๊ณตํ†ต ํ‘œ์‹œ) */} {boomPlacementTab !== 'simulation' && boomLines.length > 0 && boomPlacementTab === 'ai' && ( <> {boomLines.map((line, idx) => { const priorityColor = line.priority === 'CRITICAL' ? 'var(--red)' : line.priority === 'HIGH' ? 'var(--orange)' : 'var(--yellow)' const priorityLabel = line.priority === 'CRITICAL' ? '๊ธด๊ธ‰' : line.priority === 'HIGH' ? '์ค‘์š”' : '๋ณดํ†ต' return (
๐Ÿ›ก {idx + 1}์ฐจ ๋ฐฉ์–ด์„  ({line.type}) {priorityLabel}
๊ธธ์ด
{line.length.toFixed(0)}m
๊ฐ๋„
{line.angle.toFixed(0)}ยฐ
= 80 ? 'var(--green)' : 'var(--orange)' }} /> = 80 ? 'var(--green)' : 'var(--orange)' }} className="text-[9px] font-semibold"> ์ฐจ๋‹จ ํšจ์œจ {line.efficiency}%
) })} )}
)}
) } export default OilBoomSection