diff --git a/frontend/src/common/components/auth/LoginPage.tsx b/frontend/src/common/components/auth/LoginPage.tsx index d6bf42f..c1f86a5 100644 --- a/frontend/src/common/components/auth/LoginPage.tsx +++ b/frontend/src/common/components/auth/LoginPage.tsx @@ -312,7 +312,7 @@ export function LoginPage() { {/* end form card */} {/* Footer */} -
+
WING V2.0 | 해양경찰청 기동방제과 위기대응 통합시스템
© 2026 Korea Coast Guard. All rights reserved. diff --git a/frontend/src/tabs/aerial/components/SatelliteRequest.tsx b/frontend/src/tabs/aerial/components/SatelliteRequest.tsx index 66e6134..613e408 100644 --- a/frontend/src/tabs/aerial/components/SatelliteRequest.tsx +++ b/frontend/src/tabs/aerial/components/SatelliteRequest.tsx @@ -737,7 +737,7 @@ export function SatelliteRequest() {
{p.sat} {p.time} - {p.res} + {p.res} {p.cloud}
{p.note && ( diff --git a/frontend/src/tabs/assets/components/ShipInsurance.tsx b/frontend/src/tabs/assets/components/ShipInsurance.tsx index 307750e..8f787b2 100644 --- a/frontend/src/tabs/assets/components/ShipInsurance.tsx +++ b/frontend/src/tabs/assets/components/ShipInsurance.tsx @@ -128,7 +128,7 @@ function ShipInsurance() {
- setConfigKeyType(e.target.value)} className="prd-i w-full border-border"> @@ -137,7 +137,7 @@ function ShipInsurance() {
- setConfigRespType(e.target.value)} className="prd-i w-full border-border"> @@ -163,7 +163,7 @@ function ShipInsurance() {
- setSearchType(e.target.value)} className="prd-i min-w-[120px] border-border"> @@ -177,7 +177,7 @@ function ShipInsurance() {
- setInsTypeFilter(e.target.value)} className="prd-i min-w-[140px] border-border"> diff --git a/frontend/src/tabs/hns/components/HNSScenarioView.tsx b/frontend/src/tabs/hns/components/HNSScenarioView.tsx index add2203..3cfe7e8 100755 --- a/frontend/src/tabs/hns/components/HNSScenarioView.tsx +++ b/frontend/src/tabs/hns/components/HNSScenarioView.tsx @@ -388,7 +388,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
{scenario.actions.map((action, i) => ( -
+
{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() {
α = 3%
과대 확산
α = 2.5%
다소 빠름
α = 2% ✓
최적 일치
-
θ = 20° ✓
최적 편향각
+
θ = 20° ✓
최적 편향각
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 ( +
+
{title}
+ {children} +
+ ); +} + +function InfoRow({ label, value, valueColor }: { label: string; value: string; valueColor?: string }) { + return ( +
+ {label} + + {value} + +
+ ); +}