openhtmltopdf renderer with bundled NanumGothic for Korean text; report authoring page gains PDF download wired to the new endpoint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
117 lines
4.3 KiB
TypeScript
117 lines
4.3 KiB
TypeScript
/*
|
|
* M6 서류·마일스톤(SCR-22/23) 실배선 API 계약.
|
|
* 공용 클라이언트(../../api/client)만 사용 — client.ts·endpoints.ts·types.ts 무수정.
|
|
* 정본: 백엔드 DocumentController 시그니처
|
|
* - GET /api/events/{eventId}/milestones → ApiResponse<MilestoneRow[]>
|
|
* - GET /api/events/{eventId}/documents → ApiResponse<RequiredDocRow[]>
|
|
* - GET /api/events/{eventId}/documents/review → ApiResponse<DocReview>
|
|
* - POST /api/events/{eventId}/documents/{docType} → ApiResponse<RequiredDocRow> (action: save|submit)
|
|
* - POST /api/events/{eventId}/documents/{docType}/pdf → ApiResponse<GeneratedDoc> (G-05 서류 PDF)
|
|
* - POST /api/events/{eventId}/document-summary/pdf → ApiResponse<GeneratedDoc> (G-05 현황 요약 PDF)
|
|
* - GET /api/events/{eventId}/documents/files/{fileId}→ application/pdf (인증 다운로드)
|
|
* ★ HWP 렌더는 스코프 밖(후속) — HWP 버튼만 disabled 유지. PDF 는 실구현.
|
|
*/
|
|
import { api, getAccessToken, ApiRequestError } from '../../api/client';
|
|
|
|
export type MilestoneState = 'done' | 'active' | 'todo';
|
|
export interface MilestoneRow {
|
|
label: string;
|
|
sub: string;
|
|
state: MilestoneState;
|
|
dueDate: string | null;
|
|
}
|
|
|
|
export type DocStatus = 'pending' | 'draft' | 'submitted' | 'approved' | 'rejected';
|
|
export interface RequiredDocRow {
|
|
docType: string;
|
|
name: string;
|
|
status: DocStatus;
|
|
dueDate: string | null;
|
|
dday: number | null;
|
|
sortOrder: number;
|
|
}
|
|
|
|
export interface ReviewIssueRow {
|
|
tone: 'warn' | 'info';
|
|
title: string;
|
|
description: string;
|
|
}
|
|
export interface DocReview {
|
|
issues: ReviewIssueRow[];
|
|
progressPct: number;
|
|
}
|
|
|
|
/** save→임시저장(draft), submit→제출(submitted). */
|
|
export type DocAction = 'save' | 'submit';
|
|
|
|
/** SCR-23 웹폼 → PDF 생성 페이로드(백엔드 ReportPdfRequest 와 1:1, 모두 선택). */
|
|
export interface ReportPdfPayload {
|
|
docTitle?: string;
|
|
eventName?: string;
|
|
eventDate?: string;
|
|
venue?: string;
|
|
visitors?: string;
|
|
safetyManager?: string;
|
|
safetyPhone?: string;
|
|
guardCount?: string;
|
|
fireStation?: string;
|
|
policeStation?: string;
|
|
medical?: string;
|
|
hazardous?: string;
|
|
}
|
|
|
|
/** PDF 생성 결과 — downloadUrl 을 인증 fetch 하여 Blob 다운로드. */
|
|
export interface GeneratedDoc {
|
|
documentId: string;
|
|
fileName: string;
|
|
downloadUrl: string;
|
|
}
|
|
|
|
const base = (eventId: string) => `/api/events/${encodeURIComponent(eventId)}`;
|
|
|
|
export const docsApi = {
|
|
milestones: (eventId: string) => api.get<MilestoneRow[]>(`${base(eventId)}/milestones`),
|
|
documents: (eventId: string) => api.get<RequiredDocRow[]>(`${base(eventId)}/documents`),
|
|
review: (eventId: string) => api.get<DocReview>(`${base(eventId)}/documents/review`),
|
|
transition: (eventId: string, docType: string, action: DocAction) =>
|
|
api.post<RequiredDocRow>(
|
|
`${base(eventId)}/documents/${encodeURIComponent(docType)}`,
|
|
{ action },
|
|
),
|
|
|
|
/** 서류(안전관리 계획서) PDF 생성 — 저장 후 다운로드 메타 반환. */
|
|
generateReportPdf: (eventId: string, docType: string, payload: ReportPdfPayload) =>
|
|
api.post<GeneratedDoc>(
|
|
`${base(eventId)}/documents/${encodeURIComponent(docType)}/pdf`,
|
|
payload,
|
|
),
|
|
|
|
/** 서류·마일스톤 현황 요약 PDF 생성(서버 저장 데이터로 조립). */
|
|
generateSummaryPdf: (eventId: string) =>
|
|
api.post<GeneratedDoc>(`${base(eventId)}/document-summary/pdf`, {}),
|
|
};
|
|
|
|
/**
|
|
* 인증 다운로드 — 공용 client.request 는 JSON 봉투를 강제 파싱하므로 PDF Blob 은 여기서 직접 fetch 한다
|
|
* (client.ts 무수정 원칙). Authorization 헤더를 부여해 브라우저 다운로드를 트리거한다.
|
|
*/
|
|
export async function downloadDoc(doc: GeneratedDoc): Promise<void> {
|
|
const token = getAccessToken();
|
|
const res = await fetch(doc.downloadUrl, {
|
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
});
|
|
if (!res.ok) {
|
|
throw new ApiRequestError('UNKNOWN', 'PDF 다운로드에 실패했습니다.', res.status);
|
|
}
|
|
const blob = await res.blob();
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = doc.fileName;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
// 다음 틱에 해제(다운로드 시작 보장).
|
|
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
}
|