import { useState, useEffect } from 'react'; import { createEmptyReport, } from './OilSpillReportTemplate'; import { consumeReportGenCategory } from '@common/hooks/useSubMenu'; import { saveReport } from '../services/reportsApi'; import { CATEGORIES, sampleOilData, sampleHnsData, sampleRescueData, type ReportCategory, type ReportSection, } from './reportTypes'; import { exportAsPDF } from './reportUtils'; interface ReportGeneratorProps { onSave: () => void; } function ReportGenerator({ onSave }: ReportGeneratorProps) { const [activeCat, setActiveCat] = useState(() => { const hint = consumeReportGenCategory() return (hint === 0 || hint === 1 || hint === 2) ? hint : 0 }) const [selectedTemplate, setSelectedTemplate] = useState(0) const [sectionsMap, setSectionsMap] = useState>(() => ({ 0: CATEGORIES[0].sections.map(s => ({ ...s })), 1: CATEGORIES[1].sections.map(s => ({ ...s })), 2: CATEGORIES[2].sections.map(s => ({ ...s })), })) // 외부에서 카테고리 힌트가 변경되면 반영 useEffect(() => { const hint = consumeReportGenCategory() if (hint === 0 || hint === 1 || hint === 2) { // eslint-disable-next-line react-hooks/set-state-in-effect setActiveCat(hint) setSelectedTemplate(0) } }, []) const cat = CATEGORIES[activeCat] const sections = sectionsMap[activeCat] const activeSections = sections.filter(s => s.checked) const toggleSection = (id: string) => { setSectionsMap(prev => ({ ...prev, [activeCat]: prev[activeCat].map(s => s.id === id ? { ...s, checked: !s.checked } : s), })) } const handleSave = async () => { const report = createEmptyReport() report.reportType = activeCat === 0 ? '예측보고서' : activeCat === 1 ? '종합보고서' : '초기보고서' report.analysisCategory = activeCat === 0 ? '유출유 확산예측' : activeCat === 1 ? 'HNS 대기확산' : '긴급구난' report.title = cat.reportName report.status = '완료' report.author = '시스템 자동생성' if (activeCat === 0) { report.incident.pollutant = sampleOilData.pollution.oilType report.incident.spillAmount = sampleOilData.pollution.spillAmount } try { await saveReport(report) onSave() } catch (err) { console.error('[reports] 저장 오류:', err) alert('보고서 저장 중 오류가 발생했습니다.') } } const handleDownload = () => { const sectionHTML = activeSections.map(sec => { return `

${sec.icon} ${sec.title}

${sec.desc}

` }).join('') const html = `${cat.reportName}

해양환경 위기대응 통합지원시스템

${cat.reportName}

${sectionHTML}` exportAsPDF(html, cat.reportName) } return (
{/* Header */}

보고서 생성

보고서 유형을 선택하고 포함할 섹션을 구성하여 보고서를 생성합니다.

{/* 3 카테고리 카드 */}
{CATEGORIES.map((c, i) => { const isActive = activeCat === i return ( ) })}
{/* Left Sidebar - Template + Sections */}
{/* 템플릿 선택 */}

📄 보고서 템플릿

{cat.templates.map((tmpl, i) => ( ))}
{/* 섹션 체크 */}

📋 포함 섹션

{sections.map(sec => ( ))}
{/* Right - Report Preview */}
{/* Preview Header */}

📄 보고서 미리보기 {cat.templates[selectedTemplate].label}

{/* Preview Content */}
{/* Report Header */}

해양환경 위기대응 통합지원시스템

{cat.reportName}

{cat.templates[selectedTemplate].label}

{/* Dynamic Sections */} {activeSections.map(sec => (

{sec.icon} {sec.title}

{/* ── 유출유 확산예측 섹션들 ── */} {sec.id === 'oil-spread' && ( <>
[확산예측 지도 - 범위 조절 작업]
{[ { label: 'KOSPS', value: sampleOilData.spread.kosps, color: '#06b6d4' }, { label: 'OpenDrift', value: sampleOilData.spread.openDrift, color: '#ef4444' }, { label: 'POSEIDON', value: sampleOilData.spread.poseidon, color: '#f97316' }, ].map((m, i) => (

{m.label}

{m.value}

))}
)} {sec.id === 'oil-pollution' && ( {[ ['유출량', sampleOilData.pollution.spillAmount, '풍화량', sampleOilData.pollution.weathered], ['해상잔유량', sampleOilData.pollution.seaRemain, '오염해역면적', sampleOilData.pollution.pollutionArea], ['연안부착량', sampleOilData.pollution.coastAttach, '오염해안길이', sampleOilData.pollution.coastLength], ].map((row, i) => ( ))}
{row[0]} {row[1]} {row[2]} {row[3]}
)} {sec.id === 'oil-sensitive' && ( <>

반경 10 NM 기준

{sampleOilData.sensitive.map((item, i) => ( {item.label} ))}
)} {sec.id === 'oil-coastal' && (

최초 부착시간: {sampleOilData.coastal.firstTime} {' / '} 부착 해안길이: {sampleOilData.coastal.coastLength}

)} {sec.id === 'oil-defense' && (

방제자원 배치 계획에 따른 전략을 수립합니다.

[방제자원 배치 지도]
)} {sec.id === 'oil-tide' && (

고조: {sampleOilData.tide.highTide1} {' / '}저조: {sampleOilData.tide.lowTide} {' / '}고조: {sampleOilData.tide.highTide2}

)} {/* ── HNS 대기확산 섹션들 ── */} {sec.id === 'hns-atm' && ( <>
[대기확산 예측 지도]
{[ { label: 'ALOHA', value: sampleHnsData.atm.aloha, color: '#f97316' }, { label: 'WRF-Chem', value: sampleHnsData.atm.wrfChem, color: '#22c55e' }, ].map((m, i) => (

{m.label} 최대 확산거리

{m.value}

))}
)} {sec.id === 'hns-hazard' && (
{[ { label: 'ERPG-2 구역', value: sampleHnsData.hazard.erpg2, color: '#f97316', desc: '건강 영향' }, { label: 'ERPG-3 구역', value: sampleHnsData.hazard.erpg3, color: '#ef4444', desc: '생명 위협' }, { label: '대피 권고 범위', value: sampleHnsData.hazard.evacuation, color: '#a855f7', desc: '안전거리' }, ].map((h, i) => (

{h.label}

{h.value}

{h.desc}

))}
)} {sec.id === 'hns-substance' && (
{[ { k: '물질명', v: sampleHnsData.substance.name }, { k: 'UN번호', v: sampleHnsData.substance.un }, { k: 'CAS번호', v: sampleHnsData.substance.cas }, { k: '위험등급', v: sampleHnsData.substance.class }, ].map((r, i) => (
{r.k} {r.v}
))}
독성기준 {sampleHnsData.substance.toxicity}
)} {sec.id === 'hns-ppe' && (
{sampleHnsData.ppe.map((item, i) => ( 🛡 {item} ))}
)} {sec.id === 'hns-facility' && (
{[ { label: '인근 학교', value: `${sampleHnsData.facility.schools}개소`, icon: '🏫' }, { label: '의료시설', value: `${sampleHnsData.facility.hospitals}개소`, icon: '🏥' }, { label: '주변 인구', value: sampleHnsData.facility.population, icon: '👥' }, ].map((f, i) => (
{f.icon}

{f.value}

{f.label}

))}
)} {sec.id === 'hns-3d' && (
[3D 농도 분포 시각화]
)} {sec.id === 'hns-weather' && (
{[ { label: '풍향', value: 'NE 42°', icon: '🌬' }, { label: '풍속', value: '5.2 m/s', icon: '💨' }, { label: '대기안정도', value: 'D (중립)', icon: '🌡' }, { label: '기온', value: '8.5°C', icon: '☀️' }, ].map((w, i) => (
{w.icon}

{w.value}

{w.label}

))}
)} {/* ── 긴급구난 섹션들 ── */} {sec.id === 'rescue-safety' && (
{[ { label: 'GM (복원력)', value: sampleRescueData.safety.gm, color: '#f97316' }, { label: '경사각 (Heel)', value: sampleRescueData.safety.heel, color: '#ef4444' }, { label: '트림 (Trim)', value: sampleRescueData.safety.trim, color: '#06b6d4' }, { label: '안전 상태', value: sampleRescueData.safety.status, color: '#f97316' }, ].map((s, i) => (

{s.label}

{s.value}

))}
)} {sec.id === 'rescue-timeline' && (
{[ { time: '06:28', event: '충돌 발생 — ORIENTAL GLORY ↔ HAI FENG 168', color: '#ef4444' }, { time: '06:30', event: 'No.1P 탱크 파공, 벙커C유 유출 개시', color: '#f97316' }, { time: '06:35', event: 'VHF Ch.16 조난통신, 해경 출동 요청', color: '#eab308' }, { time: '07:15', event: '해경 3009함 현장 도착, 방제 개시', color: '#06b6d4' }, ].map((e, i) => (
{e.time} {e.event}
))}
)} {sec.id === 'rescue-casualty' && (
{[ { label: '총원', value: sampleRescueData.casualty.total, color: 'var(--t1)' }, { label: '구조완료', value: sampleRescueData.casualty.rescued, color: '#22c55e' }, { label: '실종', value: sampleRescueData.casualty.missing, color: '#ef4444' }, { label: '부상', value: sampleRescueData.casualty.injured, color: '#f97316' }, ].map((c, i) => (

{c.label}

{c.value}

))}
)} {sec.id === 'rescue-resource' && ( {sampleRescueData.resources.map((r, i) => ( ))}
유형 선박/장비명 도착예정 상태
{r.type} {r.name} {r.eta} {r.status}
)} {sec.id === 'rescue-grounding' && (
{[ { label: '좌초 위험도', value: sampleRescueData.grounding.risk, color: '#ef4444' }, { label: '최근 천해', value: sampleRescueData.grounding.nearestShallow, color: '#f97316' }, { label: '현재 수심', value: sampleRescueData.grounding.depth, color: '#06b6d4' }, ].map((g, i) => (

{g.label}

{g.value}

))}
)} {sec.id === 'rescue-weather' && (
{[ { label: '파고', value: '1.5 m', icon: '🌊' }, { label: '풍속', value: '5.2 m/s', icon: '🌬' }, { label: '조류', value: '1.2 kts NE', icon: '🌀' }, { label: '시정', value: '8 km', icon: '👁' }, ].map((w, i) => (
{w.icon}

{w.value}

{w.label}

))}
)}
))} {activeSections.length === 0 && (
📋

왼쪽에서 보고서에 포함할 섹션을 선택하세요

)}
) } export default ReportGenerator;