+
•
{action}
diff --git a/frontend/src/tabs/hns/components/HNSSubstanceView.tsx b/frontend/src/tabs/hns/components/HNSSubstanceView.tsx
index 37b9e04..620d2e3 100755
--- a/frontend/src/tabs/hns/components/HNSSubstanceView.tsx
+++ b/frontend/src/tabs/hns/components/HNSSubstanceView.tsx
@@ -39,6 +39,7 @@ const substances: HNSSubstance[] = [
{ name: 'LPG', nameEn: 'LPG (Propane/Butane)', formula: 'C₃H₈/C₄H₁₀', unNumber: 'UN1075', casNumber: '68476-85-7', imdgClass: 'Class 2.1', sebc: 'G (가스)', flashPoint: '-104°C', boilingPoint: '-42°C', specificGravity: '0.50', vaporPressure: '8,460 mmHg', solubility: '0.01%', idlh: '2,100 ppm', twa: '1,000 ppm', aegl1: '-', aegl2: '17,000 ppm', aegl3: '33,000 ppm', category: 'flammable_gas', color: '#f97316' },
{ name: '에틸렌', nameEn: 'Ethylene', formula: 'C₂H₄', unNumber: 'UN1962', casNumber: '74-85-1', imdgClass: 'Class 2.1', sebc: 'G (가스)', flashPoint: '-', boilingPoint: '-104°C', specificGravity: '0.57', vaporPressure: '-', solubility: '0.01%', idlh: '-', twa: '-', aegl1: '-', aegl2: '-', aegl3: '-', category: 'flammable_gas', color: '#a855f7' },
{ name: '1,2-디클로로에탄', nameEn: '1,2-Dichloroethane (EDC)', formula: 'C₂H₄Cl₂', unNumber: 'UN1184', casNumber: '107-06-2', imdgClass: 'Class 3', sebc: 'S (침강)', flashPoint: '13°C', boilingPoint: '83°C', specificGravity: '1.253', vaporPressure: '87 mmHg', solubility: '0.87%', idlh: '50 ppm', twa: '1 ppm', aegl1: '-', aegl2: '20 ppm', aegl3: '200 ppm', category: 'toxic_liquid', color: '#ef4444' },
+ { name: '페놀', nameEn: 'Phenol', formula: 'C₆H₅OH', unNumber: 'UN2312', casNumber: '108-95-2', imdgClass: 'Class 6.1', sebc: 'S/SD (침강/용해)', flashPoint: '79°C', boilingPoint: '182°C', specificGravity: '1.07', vaporPressure: '0.35 mmHg', solubility: '8.4%', idlh: '250 ppm', twa: '5 ppm', aegl1: '19 ppm', aegl2: '29 ppm', aegl3: '57 ppm', category: 'toxic_liquid', color: '#22c55e' },
]
const categories = [
@@ -518,7 +519,7 @@ ${styles}
- | NH₃ 암모니아 |
+ NH₃ 암모니아 |
30 |
160 |
1,100 |
@@ -528,7 +529,7 @@ ${styles}
G/GD |
- | CH₃OH 메탄올 |
+ CH₃OH 메탄올 |
530 |
2,100 |
14,000 |
@@ -538,7 +539,7 @@ ${styles}
ED |
- | H₂ 수소 |
+ H₂ 수소 |
- |
- |
- |
@@ -548,7 +549,7 @@ ${styles}
G |
- | CH₄ LNG |
+ CH₄ LNG |
- |
- |
- |
@@ -558,7 +559,7 @@ ${styles}
G |
- | C₆H₅OH 페놀 |
+ C₆H₅OH 페놀 |
19 |
29 |
57 |
@@ -568,7 +569,7 @@ ${styles}
S/SD |
- | C₇H₈ 톨루엔 |
+ C₇H₈ 톨루엔 |
67 |
560 |
3,700 |
@@ -795,15 +796,15 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
🔥 화재 시
-
격리거리: {s.responseDistanceFire} 이상
+
격리거리: {s.responseDistanceFire} 이상
💨 유출 시 (비화재)
-
주간 방호활동거리: {s.responseDistanceSpillDay}
야간 방호활동거리: {s.responseDistanceSpillNight}
+
주간 방호활동거리: {s.responseDistanceSpillDay}
야간 방호활동거리: {s.responseDistanceSpillNight}
🌊 해상 유출 시
-
{s.marineResponse}
+
{s.marineResponse}
@@ -898,15 +899,15 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
🔥 화재 대응
-
{s.emsFire}
+
{s.emsFire}
💧 유출 대응
-
{s.emsSpill}
+
{s.emsSpill}
🏥 응급조치
-
{s.emsFirstAid}
+
{s.emsFirstAid}
@@ -941,9 +942,9 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
const srcBg = c.source === '적부도' ? 'rgba(249,115,22,.1)' : c.source === '용선자' ? 'rgba(6,182,212,.1)' : 'rgba(34,197,94,.1)'
return (
- | {c.code} |
+ {c.code} |
{c.name} |
- {c.company} |
+ {c.company} |
{c.source} |
)
@@ -973,8 +974,8 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
const freqBg = p.frequency === '높음' ? 'rgba(239,68,68,.1)' : p.frequency === '중간' ? 'rgba(249,115,22,.1)' : 'rgba(34,197,94,.1)'
return (
- | {p.port} |
- {p.portCode} |
+ {p.port} |
+ {p.portCode} |
{p.lastImport} |
{p.frequency} |
diff --git a/frontend/src/tabs/incidents/components/IncidentsLeftPanel.tsx b/frontend/src/tabs/incidents/components/IncidentsLeftPanel.tsx
index b506b8d..905c6be 100755
--- a/frontend/src/tabs/incidents/components/IncidentsLeftPanel.tsx
+++ b/frontend/src/tabs/incidents/components/IncidentsLeftPanel.tsx
@@ -507,7 +507,7 @@ WeatherPopup.displayName = 'WeatherPopup'
function WxCell({ icon, label, value }: { icon: string; label: string; value: string }) {
return (
-
+
{icon}
{label}
diff --git a/frontend/src/tabs/incidents/components/IncidentsView.tsx b/frontend/src/tabs/incidents/components/IncidentsView.tsx
index 4e146d7..29c41b2 100755
--- a/frontend/src/tabs/incidents/components/IncidentsView.tsx
+++ b/frontend/src/tabs/incidents/components/IncidentsView.tsx
@@ -339,7 +339,7 @@ export function IncidentsView() {
{incidentPopup.incident.name}
-
+
상태: {getStatusLabel(incidentPopup.incident.status)}
일시: {incidentPopup.incident.date} {incidentPopup.incident.time}
diff --git a/frontend/src/tabs/prediction/components/OilBoomSection.tsx b/frontend/src/tabs/prediction/components/OilBoomSection.tsx
index 1513887..39f72f8 100644
--- a/frontend/src/tabs/prediction/components/OilBoomSection.tsx
+++ b/frontend/src/tabs/prediction/components/OilBoomSection.tsx
@@ -149,7 +149,7 @@ const OilBoomSection = ({
확산 예측 기반 최적 배치안
-
+
{oilTrajectory.length > 0
? '확산 궤적을 분석하여 해류 직교 방향 1차 방어선, U형 포위 2차 방어선, 연안 보호 3차 방어선을 자동 생성합니다.'
: '상단에서 확산 예측을 실행한 뒤 AI 배치를 적용할 수 있습니다.'
diff --git a/frontend/src/tabs/prediction/components/OilSpillTheoryView.tsx b/frontend/src/tabs/prediction/components/OilSpillTheoryView.tsx
index 701582b..bb7ccd4 100755
--- a/frontend/src/tabs/prediction/components/OilSpillTheoryView.tsx
+++ b/frontend/src/tabs/prediction/components/OilSpillTheoryView.tsx
@@ -584,7 +584,7 @@ function KospsPanel() {
전자해도(ENC) 무작위 수심자료를 계산격자로 보간할 때 Akima(1978) 2차원 5차다항식(Bivariate Quintic Polynomial)을 사용합니다. 삼각망(TIN)을 구성하여 각 꼭지점의 편미분 연속성 조건으로 21개 계수를 결정합니다.
-
+
z(x,y) = Σ Σ qᵢⱼ xⁱ yʲ (i≤5, i+j≤5)
@@ -1068,7 +1068,7 @@ function OpenDriftPanel() {
2024
-
Numerical Model Test of Spilled Oil Transport Near the Korean Coasts Using Various Input Parametric Models
+
Numerical Model Test of Spilled Oil Transport Near the Korean Coasts Using Various Input Parametric Models
Hai Van Dang, Suchan Joo, Junhyeok Lim, Jinhwan Hur, Sungwon Shin | Hanyang University ERICA | Journal of Ocean Engineering and Technology, 2024
@@ -1103,7 +1103,7 @@ function OpenDriftPanel() {
1998
-
한국 동남해역에서의 유출유 확산예측모델 (Oil Spill Behavior Forecasting Model in South-eastern Coastal Area of Korea)
+
한국 동남해역에서의 유출유 확산예측모델 (Oil Spill Behavior Forecasting Model in South-eastern Coastal Area of Korea)
류청로, 김종규, 설동관, 강동욱 | 부경대학교 해양공학과 | 한국해양환경공학회지 Vol.1 No.2, pp.52–59, 1998
@@ -1135,7 +1135,7 @@ function OpenDriftPanel() {
2008
-
태안 기름유출사고의 유출유 확산특성 분석 (Analysis of Oil Spill Dispersion in Taean Coastal Zone)
+
태안 기름유출사고의 유출유 확산특성 분석 (Analysis of Oil Spill Dispersion in Taean Coastal Zone)
정태성, 조형진 | 한남대학교 토목환경공학과 | 한국해안·해양공학회 학술발표논문집 제17권 pp.60–63, 2008
@@ -1159,7 +1159,7 @@ function OpenDriftPanel() {
-
+
diff --git a/frontend/src/tabs/reports/components/ReportGenerator.tsx b/frontend/src/tabs/reports/components/ReportGenerator.tsx
index 48527aa..9f9cd3e 100644
--- a/frontend/src/tabs/reports/components/ReportGenerator.tsx
+++ b/frontend/src/tabs/reports/components/ReportGenerator.tsx
@@ -249,7 +249,7 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
>
)}
{sec.id === 'oil-pollution' && (
-
+
{[
@@ -447,7 +447,7 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
)}
{sec.id === 'rescue-resource' && (
-
+
| 유형 |
diff --git a/frontend/src/tabs/reports/components/ReportsView.tsx b/frontend/src/tabs/reports/components/ReportsView.tsx
index b1b67dc..52af3a5 100755
--- a/frontend/src/tabs/reports/components/ReportsView.tsx
+++ b/frontend/src/tabs/reports/components/ReportsView.tsx
@@ -117,7 +117,7 @@ export function ReportsView() {
) : (
-
+
diff --git a/frontend/src/tabs/reports/components/TemplateFormEditor.tsx b/frontend/src/tabs/reports/components/TemplateFormEditor.tsx
index c18edcb..9ac4401 100644
--- a/frontend/src/tabs/reports/components/TemplateFormEditor.tsx
+++ b/frontend/src/tabs/reports/components/TemplateFormEditor.tsx
@@ -128,7 +128,7 @@ function TemplateFormEditor({ onSave, onBack }: TemplateFormEditorProps) {
{template.sections.map((section, sIdx) => (
-
{section.title}
+
{section.title}
@@ -235,7 +235,7 @@ function TemplateFormEditor({ onSave, onBack }: TemplateFormEditorProps) {
{/* Report Title */}
해양오염방제지원시스템
-
+
{formData['incident.name'] || template.label}
diff --git a/frontend/src/tabs/scat/components/PreScatView.tsx b/frontend/src/tabs/scat/components/PreScatView.tsx
index a85cef1..372e922 100755
--- a/frontend/src/tabs/scat/components/PreScatView.tsx
+++ b/frontend/src/tabs/scat/components/PreScatView.tsx
@@ -6,6 +6,7 @@ import ScatLeftPanel from './ScatLeftPanel';
import ScatMap from './ScatMap';
import ScatTimeline from './ScatTimeline';
import ScatPopup from './ScatPopup';
+import ScatRightPanel from './ScatRightPanel';
// ═══ Main PreScatView ═══
@@ -21,6 +22,8 @@ export function PreScatView() {
const [statusFilter, setStatusFilter] = useState('전체');
const [searchTerm, setSearchTerm] = useState('');
const [popupData, setPopupData] = useState(null);
+ const [panelDetail, setPanelDetail] = useState(null);
+ const [panelLoading, setPanelLoading] = useState(false);
const [timelineIdx, setTimelineIdx] = useState(6);
// API에서 구역 및 구간 데이터 로딩
@@ -51,6 +54,21 @@ export function PreScatView() {
};
}, []);
+ // 선택 구간 변경 시 우측 패널 상세 로딩
+ useEffect(() => {
+ if (!selectedSeg) {
+ setPanelDetail(null);
+ return;
+ }
+ let cancelled = false;
+ setPanelLoading(true);
+ fetchSectionDetail(selectedSeg.id)
+ .then(detail => { if (!cancelled) setPanelDetail(detail); })
+ .catch(err => console.error('[SCAT] 패널 상세 로딩 오류:', err))
+ .finally(() => { if (!cancelled) setPanelLoading(false); });
+ return () => { cancelled = true; };
+ }, [selectedSeg]);
+
// 관할 기반 세그먼트 필터링
const filteredSegments = segments.filter((s) => {
if (jurisdictionFilter === '서귀포해양경비안전서') return s.jurisdiction === '서귀포';
@@ -144,6 +162,11 @@ export function PreScatView() {
/>
+
+
{popupData && (
)}
diff --git a/frontend/src/tabs/scat/components/ScatRightPanel.tsx b/frontend/src/tabs/scat/components/ScatRightPanel.tsx
new file mode 100644
index 0000000..6275676
--- /dev/null
+++ b/frontend/src/tabs/scat/components/ScatRightPanel.tsx
@@ -0,0 +1,227 @@
+import { useState } from 'react';
+import type { ScatDetail } from './scatTypes';
+import { sensColor, statusColor } from './scatConstants';
+
+interface ScatRightPanelProps {
+ detail: ScatDetail | null;
+ loading: boolean;
+ onOpenReport?: () => void;
+ onNewSurvey?: () => void;
+}
+
+const tabs = [
+ { id: 0, label: '구간 상세', icon: '📋' },
+ { id: 1, label: '현장 사진', icon: '📷' },
+ { id: 2, label: '방제 권고', icon: '🛡️' },
+] as const;
+
+export default function ScatRightPanel({ detail, loading, onOpenReport, onNewSurvey }: ScatRightPanelProps) {
+ const [activeTab, setActiveTab] = useState(0);
+
+ if (!detail && !loading) {
+ return (
+
+
🏖️
+
+ 좌측 목록에서 구간을
선택하면 상세 정보가
여기에 표시됩니다.
+
+
+ );
+ }
+
+ return (
+
+ {/* 헤더 */}
+
+ {detail ? (
+
+
+ {detail.esi}
+
+
+
{detail.name}
+
{detail.code}
+
+
+ ) : (
+
로딩 중...
+ )}
+
+
+ {/* 탭 바 */}
+
+ {tabs.map(tab => (
+
+ ))}
+
+
+ {/* 스크롤 영역 */}
+
+ {loading ? (
+
+ 데이터 로딩 중...
+
+ ) : detail ? (
+ <>
+ {activeTab === 0 &&
}
+ {activeTab === 1 &&
}
+ {activeTab === 2 &&
}
+ >
+ ) : null}
+
+
+ {/* 하단 버튼 */}
+
+
+
+
+
+ );
+}
+
+/* ═══ 탭 0: 구간 상세 ═══ */
+function DetailTab({ detail }: { detail: ScatDetail }) {
+ return (
+
+ {/* 기본 정보 */}
+
+
+ {/* 접근성 */}
+
+
+ {/* 민감 자원 */}
+ {detail.sensitive && detail.sensitive.length > 0 && (
+
+
+ {detail.sensitive.map((s, i) => (
+
+ {s.t}
+ {s.v}
+
+ ))}
+
+
+ )}
+
+ );
+}
+
+/* ═══ 탭 1: 현장 사진 ═══ */
+function PhotoTab() {
+ return (
+
+
📷
+
+ 현장 사진 기능은
추후 업데이트 예정입니다.
+
+
+ 사진 업로드 API 연동 예정
+
+
+ );
+}
+
+/* ═══ 탭 2: 방제 권고 ═══ */
+function CleanupTab({ detail }: { detail: ScatDetail }) {
+ return (
+
+ {/* 방제 방법 */}
+
+ {detail.cleanup && detail.cleanup.length > 0 ? (
+
+ {detail.cleanup.map((method, i) => (
+
+ {method}
+
+ ))}
+
+ ) : (
+ 등록된 방제 방법 없음
+ )}
+
+
+ {/* 종료 기준 */}
+
+ {detail.endCriteria && detail.endCriteria.length > 0 ? (
+
+ {detail.endCriteria.map((c, i) => (
+
+ ✓
+ {c}
+
+ ))}
+
+ ) : (
+ 등록된 종료 기준 없음
+ )}
+
+
+ {/* 참고사항 */}
+
+ {detail.notes && detail.notes.length > 0 ? (
+
+ {detail.notes.map((note, i) => (
+
+ {note}
+
+ ))}
+
+ ) : (
+ 등록된 참고사항 없음
+ )}
+
+
+ );
+}
+
+/* ═══ 공통 UI ═══ */
+function Section({ title, children }: { title: string; children: React.ReactNode }) {
+ return (
+
+ );
+}
+
+function InfoRow({ label, value, valueColor }: { label: string; value: string; valueColor?: string }) {
+ return (
+
+ {label}
+
+ {value}
+
+
+ );
+}