import { sanitizeHtml } from '@common/utils/sanitize';
import type { OilSpillReportData } from './OilSpillReportTemplate';
// ─── Report Export Helpers ──────────────────────────────
export function generateReportHTML(
templateLabel: string,
meta: { writeTime: string; author: string; jurisdiction: string },
sections: { title: string; fields: { key: string; label: string }[] }[],
getVal: (key: string) => string
) {
const rows = sections.map(section => {
const fieldRows = section.fields.map(f => {
if (f.label) {
return `
| ${f.label} | ${getVal(f.key) || '-'} |
`
}
return `| ${getVal(f.key) || '-'} |
`
}).join('')
return `${section.title}
`
}).join('')
return `${templateLabel}
해양오염방제지원시스템
${templateLabel}
작성일시: ${meta.writeTime} | 작성자: ${meta.author || '-'} | 관할: ${meta.jurisdiction}
${rows}`
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function exportAsPDF(html: string, _filename: string) {
const sanitizedHtml = sanitizeHtml(html)
const blob = new Blob([sanitizedHtml], { type: 'text/html; charset=utf-8' })
const url = URL.createObjectURL(blob)
const win = window.open(url, '_blank')
if (win) {
win.addEventListener('afterprint', () => URL.revokeObjectURL(url))
setTimeout(() => win.print(), 500)
}
setTimeout(() => URL.revokeObjectURL(url), 30000)
}
export async function exportAsHWP(
templateLabel: string,
meta: { writeTime: string; author: string; jurisdiction: string },
sections: { title: string; fields: { key: string; label: string }[] }[],
getVal: (key: string) => string,
filename: string,
images?: { step3?: string; step6?: string; sensitiveMap?: string; sensitivityMap?: string },
) {
const { exportAsHWPX } = await import('./hwpxExport');
await exportAsHWPX(templateLabel, meta, sections, getVal, filename, images);
}
export type ViewState =
| { screen: 'list' }
| { screen: 'templates' }
| { screen: 'generate' }
| { screen: 'view'; data: OilSpillReportData }
| { screen: 'edit'; data: OilSpillReportData }
export const typeColors: Record = {
'초기보고서': { bg: 'rgba(6,182,212,0.15)', text: '#06b6d4' },
'지휘부 보고': { bg: 'rgba(168,85,247,0.15)', text: '#a855f7' },
'예측보고서': { bg: 'rgba(59,130,246,0.15)', text: '#3b82f6' },
'종합보고서': { bg: 'rgba(249,115,22,0.15)', text: '#f97316' },
'유출유 보고': { bg: 'rgba(234,179,8,0.15)', text: '#eab308' },
}
export const statusColors: Record = {
'완료': { bg: 'rgba(34,197,94,0.15)', text: '#22c55e' },
'수행중': { bg: 'rgba(249,115,22,0.15)', text: '#f97316' },
'테스트': { bg: 'rgba(138,150,168,0.15)', text: '#8a96a8' },
}
export const analysisCatColors: Record = {
'유출유 확산예측': { bg: 'rgba(6,182,212,0.12)', text: '#06b6d4', icon: '🛢' },
'HNS 대기확산': { bg: 'rgba(249,115,22,0.12)', text: '#f97316', icon: '🧪' },
'긴급구난': { bg: 'rgba(239,68,68,0.12)', text: '#ef4444', icon: '🚨' },
}
export function inferAnalysisCategory(report: OilSpillReportData): string {
if (report.analysisCategory) return report.analysisCategory
const t = (report.title || '').toLowerCase()
const rt = report.reportType || ''
if (t.includes('hns') || t.includes('대기확산') || t.includes('화학') || t.includes('aloha')) return 'HNS 대기확산'
if (t.includes('구난') || t.includes('구조') || t.includes('긴급') || t.includes('salvage') || t.includes('rescue')) return '긴급구난'
if (t.includes('유출유') || t.includes('확산예측') || t.includes('민감자원') || t.includes('유출사고') || t.includes('오염') || t.includes('방제') || rt === '유출유 보고' || rt === '예측보고서') return '유출유 확산예측'
return ''
}
// ─── PDF/HWP 섹션 포맷 헬퍼 ─────────────────────────────────
const TH = 'padding:6px 8px;border:1px solid #d1d5db;background:#f0f4f8;font-weight:600;font-size:11px;'
const TD = 'padding:6px 8px;border:1px solid #d1d5db;font-size:11px;'
const TABLE = 'width:100%;border-collapse:collapse;'
function formatTideTable(tide: OilSpillReportData['tide']): string {
if (!tide?.length) return ''
const header = `| 날짜 | 조형 | 간조1 | 만조1 | 간조2 | 만조2 |
`
const rows = tide.map(t =>
`| ${t.date} | ${t.tideType} | ${t.lowTide1} | ${t.highTide1} | ${t.lowTide2} | ${t.highTide2} |
`
).join('')
return ``
}
function formatWeatherTable(weather: OilSpillReportData['weather']): string {
if (!weather?.length) return ''
const header = `| 시각 | 풍향 | 풍속 | 유향 | 유속 | 파고 |
`
const rows = weather.map(w =>
`| ${w.time} | ${w.windDir} | ${w.windSpeed} | ${w.currentDir} | ${w.currentSpeed} | ${w.waveHeight} |
`
).join('')
return ``
}
function formatSpreadTable(spread: OilSpillReportData['spread']): string {
if (!spread?.length) return ''
const header = `| 경과시간 | 풍화량 | 해상잔유량 | 연안부착량 | 면적 |
`
const rows = spread.map(s =>
`| ${s.elapsed} | ${s.weathered} | ${s.seaRemain} | ${s.coastAttach} | ${s.area} |
`
).join('')
return ``
}
function formatSensitiveTable(r: OilSpillReportData): string {
const parts: string[] = []
if (r.sensitiveMapImage) {
parts.push(
'민감자원 분포 지도
' +
`
`
)
}
if (r.aquaculture?.length) {
const h = `| 종류 | 면적 | 거리 |
`
const rows = r.aquaculture.map(a => `| ${a.type} | ${a.area} | ${a.distance} |
`).join('')
parts.push(`양식업
`)
}
if (r.beaches?.length) {
const h = `| 해수욕장명 | 거리 |
`
const rows = r.beaches.map(b => `| ${b.name} | ${b.distance} |
`).join('')
parts.push(`해수욕장
`)
}
if (r.markets?.length) {
const h = `| 수산시장명 | 거리 |
`
const rows = r.markets.map(m => `| ${m.name} | ${m.distance} |
`).join('')
parts.push(`수산시장
`)
}
if (r.esi?.length) {
const h = `| 코드 | 유형 | 길이 |
`
const rows = r.esi.map(e => `| ${e.code} | ${e.type} | ${e.length} |
`).join('')
parts.push(`ESI 해안선
`)
}
if (r.species?.length) {
const h = `| 분류 | 종명 |
`
const rows = r.species.map(s => `| ${s.category} | ${s.species} |
`).join('')
parts.push(`보호생물
`)
}
if (r.habitat?.length) {
const h = `| 유형 | 면적 |
`
const rows = r.habitat.map(h2 => `| ${h2.type} | ${h2.area} |
`).join('')
parts.push(`서식지
`)
}
if (r.sensitivityMapImage) {
parts.push(
'통합민감도 평가 지도
' +
`
`
)
}
if (r.sensitivity?.length) {
const h = `| 민감도 | 면적 |
`
const rows = r.sensitivity.map(s => `| ${s.level} | ${s.area} |
`).join('')
parts.push(`민감도 등급
`)
}
return parts.join('')
}
function formatVesselsTable(vessels: OilSpillReportData['vessels']): string {
if (!vessels?.length) return ''
const header = `| 선명 | 기관 | 거리 | 속력 | 톤수 | 회수장비 | 오일붐 |
`
const rows = vessels.map(v =>
`| ${v.name} | ${v.org} | ${v.dist} | ${v.speed} | ${v.ton} | ${v.collectorType} ${v.collectorCap} | ${v.boomType} ${v.boomLength} |
`
).join('')
return ``
}
function formatRecoveryTable(recovery: OilSpillReportData['recovery']): string {
if (!recovery?.length) return ''
const header = `| 선박명 | 회수 기간 |
`
const rows = recovery.map(r =>
`| ${r.shipName} | ${r.period} |
`
).join('')
return ``
}
function formatResultTable(result: OilSpillReportData['result']): string {
if (!result) return ''
return `
| 유출총량 | ${result.spillTotal} | 풍화총량 | ${result.weatheredTotal} |
| 회수총량 | ${result.recoveredTotal} | 해상잔유량 | ${result.seaRemainTotal} |
| 연안부착량 | ${result.coastAttachTotal} |
`
}
export function buildReportGetVal(report: OilSpillReportData) {
return (key: string): string => {
if (key === 'author') return report.author ?? ''
if (key.startsWith('incident.')) {
const f = key.split('.')[1]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (report.incident as any)[f] || ''
}
if (key === '__tide') return formatTideTable(report.tide)
if (key === '__weather') return formatWeatherTable(report.weather)
if (key === '__spreadMaps') {
const img3 = report.step3MapImage
const img6 = report.step6MapImage
if (!img3 && !img6) return ''
const cell = (label: string, src: string) =>
`${label}
` +
`

`
return `` +
(img3 ? cell('3시간 후', img3) : '') +
(img6 ? cell('6시간 후', img6) : '') +
`
`
}
if (key === '__spread') return formatSpreadTable(report.spread)
if (key === '__sensitive') return formatSensitiveTable(report)
if (key === '__vessels') return formatVesselsTable(report.vessels)
if (key === '__recovery') return formatRecoveryTable(report.recovery)
if (key === '__result') return formatResultTable(report.result)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (report as any)[key] || ''
}
}