wing-ops/frontend/src/tabs/reports/components/reportUtils.ts
Nan Kyung Lee 374a487878 feat(reports): HWP 저장을 실제 HWPX 포맷으로 변경
기존 HTML Blob → .doc 저장 방식을 OWPML 표준 HWPX(ZIP+XML) 포맷으로 교체.
JSZip으로 HWPX 파일을 순수 브라우저에서 생성하여 한글에서 직접 열 수 있도록 구현.

- hwpxExport.ts 신규: HWPX ZIP 패키징 (mimetype, header.xml, section0.xml 등)
- reportUtils.ts: exportAsHWP → dynamic import로 HWPX 위임
- ReportsView.tsx, TemplateFormEditor.tsx: 구조화 데이터 직접 전달

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 10:07:06 +09:00

89 lines
4.7 KiB
TypeScript

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 `<tr><td style="background:#f0f4f8;padding:8px 12px;border:1px solid #d1d5db;font-weight:600;width:200px;font-size:12px;">${f.label}</td><td style="padding:8px 12px;border:1px solid #d1d5db;font-size:12px;">${getVal(f.key) || '-'}</td></tr>`
}
return `<tr><td colspan="2" style="padding:8px 12px;border:1px solid #d1d5db;font-size:12px;white-space:pre-wrap;">${getVal(f.key) || '-'}</td></tr>`
}).join('')
return `<h3 style="color:#0891b2;font-size:14px;margin:20px 0 8px;">${section.title}</h3><table style="width:100%;border-collapse:collapse;">${fieldRows}</table>`
}).join('')
return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${templateLabel}</title>
<style>@page{size:A4;margin:20mm}body{font-family:'맑은 고딕','Malgun Gothic',sans-serif;color:#1a1a1a;max-width:800px;margin:0 auto;padding:40px}</style>
</head><body>
<div style="text-align:center;margin-bottom:30px">
<h1 style="font-size:20px;margin:0">해양오염방제지원시스템</h1>
<h2 style="font-size:16px;color:#0891b2;margin:8px 0">${templateLabel}</h2>
<p style="font-size:11px;color:#666">작성일시: ${meta.writeTime} | 작성자: ${meta.author || '-'} | 관할: ${meta.jurisdiction}</p>
</div>${rows}</body></html>`
}
// 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,
) {
const { exportAsHWPX } = await import('./hwpxExport');
await exportAsHWPX(templateLabel, meta, sections, getVal, filename);
}
export type ViewState =
| { screen: 'list' }
| { screen: 'templates' }
| { screen: 'generate' }
| { screen: 'view'; data: OilSpillReportData }
| { screen: 'edit'; data: OilSpillReportData }
export const typeColors: Record<string, { bg: string; text: string }> = {
'초기보고서': { 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<string, { bg: string; text: string }> = {
'완료': { 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<string, { bg: string; text: string; icon: string }> = {
'유출유 확산예측': { 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 ''
}