summaryrefslogtreecommitdiff
path: root/components/form-data/form-data-report-dialog.tsx
diff options
context:
space:
mode:
authorrlaks5757 <rlaks5757@gmail.com>2025-03-26 16:51:54 +0900
committerrlaks5757 <rlaks5757@gmail.com>2025-03-26 16:51:54 +0900
commite0b2367d88dd80eece67390574e60c9eacdee14d (patch)
tree5c7ec4b950f7249a40145564c6d701ed4f260c98 /components/form-data/form-data-report-dialog.tsx
parent2ca4c91514feadb5edd0c9411670c7d9964d21e3 (diff)
po, vendor-data-form-report
Diffstat (limited to 'components/form-data/form-data-report-dialog.tsx')
-rw-r--r--components/form-data/form-data-report-dialog.tsx457
1 files changed, 457 insertions, 0 deletions
diff --git a/components/form-data/form-data-report-dialog.tsx b/components/form-data/form-data-report-dialog.tsx
new file mode 100644
index 00000000..5ddc5e0c
--- /dev/null
+++ b/components/form-data/form-data-report-dialog.tsx
@@ -0,0 +1,457 @@
+"use client";
+
+import React, {
+ FC,
+ Dispatch,
+ SetStateAction,
+ useState,
+ useEffect,
+ useRef,
+} from "react";
+import { WebViewerInstance, Core } from "@pdftron/webviewer";
+import { useToast } from "@/hooks/use-toast";
+import prettyBytes from "pretty-bytes";
+import { X, Loader2 } from "lucide-react";
+import { Badge } from "@/components/ui/badge";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { Label } from "@/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Button } from "@/components/ui/button";
+import { getReportTempList } from "@/lib/forms/services";
+import { DataTableColumnJSON } from "./form-data-table-columns";
+
+type ReportData = {
+ [key: string]: any;
+};
+
+interface tempFile {
+ fileName: string;
+ filePath: string;
+}
+
+interface FormDataReportDialogProps {
+ columnsJSON: DataTableColumnJSON[];
+ reportData: ReportData[];
+ setReportData: Dispatch<SetStateAction<ReportData[]>>;
+ packageId: number;
+ formId: number;
+ formCode: string;
+}
+
+export const FormDataReportDialog: FC<FormDataReportDialogProps> = ({
+ columnsJSON,
+ reportData,
+ setReportData,
+ packageId,
+ formId,
+}) => {
+ const [tempList, setTempList] = useState<tempFile[]>([]);
+ const [selectTemp, setSelectTemp] = useState<string>("");
+ const [instance, setInstance] = useState<null | WebViewerInstance>(null);
+ const [fileLoading, setFileLoading] = useState<boolean>(true);
+
+ useEffect(() => {
+ updateReportTempList(packageId, formId, setTempList);
+ }, [packageId, formId]);
+
+ const onClose = async (value: boolean) => {
+ if (fileLoading) {
+ return;
+ }
+ if (!value) {
+ setTimeout(() => cleanupHtmlStyle(), 1000);
+ setReportData([]);
+ }
+ };
+
+ const downloadFileData = async () => {
+ if (instance) {
+ const { UI, Core } = instance;
+ const { documentViewer } = Core;
+
+ const doc = documentViewer.getDocument();
+ const fileName = doc.getFilename();
+ const fileData = await doc.getFileData({
+ includeAnnotations: true, // 사용자가 추가한 폼 필드 및 입력 포함
+ // officeOptions: {
+ // outputFormat: "docx",
+ // },
+ });
+
+ const blob = new Blob([fileData], {
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ });
+
+ const link = document.createElement("a");
+ link.href = URL.createObjectURL(blob);
+ link.download = fileName;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ // const allTabs = UI.TabManager.getAllTabs() as {
+ // id: number;
+ // src: Core.Document;
+ // }[];
+
+ // for (const tab of allTabs) {
+ // // await UI.TabManager.setActiveTab(tab.id);
+ // await activateTabAndWaitForLoad(instance, tab.id);
+ // const tabDoc = tab.src;
+ // const fileName = tabDoc.getFilename();
+
+ // const fileData = await tabDoc.getFileData({
+ // includeAnnotations: true,
+ // });
+
+ // console.log({ fileData });
+
+ // const blob = new Blob([fileData], {
+ // type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ // });
+
+ // // 다운로드
+ // // const link = document.createElement("a");
+ // // link.href = URL.createObjectURL(blob);
+ // // link.download = fileName;
+ // // document.body.appendChild(link);
+ // // link.click();
+ // // document.body.removeChild(link);
+ // }
+ }
+ };
+
+ return (
+ <Dialog open={reportData.length > 0} onOpenChange={onClose}>
+ <DialogContent className="w-[70vw]" style={{ maxWidth: "none" }}>
+ <DialogHeader>
+ <DialogTitle>Report</DialogTitle>
+ <DialogDescription>
+ 사용하시고자 하는 Report Template를 선택하여 주시기 바랍니다.
+ </DialogDescription>
+ </DialogHeader>
+ <div className="h-[60px]">
+ <Label>Report Template Select</Label>
+ <Select
+ value={selectTemp}
+ onValueChange={setSelectTemp}
+ disabled={instance === null}
+ >
+ <SelectTrigger className="w-[100%]">
+ <SelectValue placeholder="사용하시고자하는 Report Template를 선택하여 주시기 바랍니다." />
+ </SelectTrigger>
+ <SelectContent>
+ {tempList.map((c) => {
+ const { fileName, filePath } = c;
+
+ return (
+ <SelectItem key={filePath} value={filePath}>
+ {fileName}
+ </SelectItem>
+ );
+ })}
+ </SelectContent>
+ </Select>
+ </div>
+ <div className="h-[calc(70vh-60px)]">
+ <ReportWebViewer
+ columnsJSON={columnsJSON}
+ reportTempPath={selectTemp}
+ reportDatas={reportData}
+ instance={instance}
+ setInstance={setInstance}
+ setFileLoading={setFileLoading}
+ />
+ </div>
+
+ <DialogFooter>
+ <Button onClick={downloadFileData} disabled={selectTemp.length === 0}>
+ 다운로드
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ );
+};
+
+interface ReportWebViewerProps {
+ columnsJSON: DataTableColumnJSON[];
+ reportTempPath: string;
+ reportDatas: ReportData[];
+ instance: null | WebViewerInstance;
+ setInstance: Dispatch<SetStateAction<WebViewerInstance | null>>;
+ setFileLoading: Dispatch<SetStateAction<boolean>>;
+}
+
+const ReportWebViewer: FC<ReportWebViewerProps> = ({
+ columnsJSON,
+ reportTempPath,
+ reportDatas,
+ instance,
+ setInstance,
+ setFileLoading,
+}) => {
+ const [viwerLoading, setViewerLoading] = useState<boolean>(true);
+ const viewer = useRef<HTMLDivElement>(null);
+ const initialized = React.useRef(false);
+ const isCancelled = React.useRef(false); // 초기화 중단용 flag
+
+ useEffect(() => {
+ if (!initialized.current) {
+ initialized.current = true;
+ isCancelled.current = false; // 다시 열릴 때는 false로 리셋
+
+ requestAnimationFrame(() => {
+ if (viewer.current) {
+ import("@pdftron/webviewer").then(({ default: WebViewer }) => {
+ console.log(isCancelled.current);
+ if (isCancelled.current) {
+ console.log("📛 WebViewer 초기화 취소됨 (Dialog 닫힘)");
+
+ return;
+ }
+
+ WebViewer(
+ {
+ path: "/pdftronWeb",
+ licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY,
+ fullAPI: true,
+ },
+ viewer.current as HTMLDivElement
+ ).then(async (instance: WebViewerInstance) => {
+ setInstance(instance);
+ // //Tab 메뉴 사용 필요시 활성화
+ // instance.UI.enableFeatures([instance.UI.Feature.MultiTab]);
+ // instance.UI.disableElements([
+ // "addTabButton",
+ // "multiTabsEmptyPage",
+ // ]);
+ setViewerLoading(false);
+ });
+ });
+ }
+ });
+ }
+
+ return () => {
+ // cleanup 시에는 중단 flag 세움
+ if (instance) {
+ instance.UI.dispose();
+ }
+ setTimeout(() => cleanupHtmlStyle(), 500);
+ };
+ }, []);
+
+ useEffect(() => {
+ importReportData(
+ columnsJSON,
+ instance,
+ reportDatas,
+ reportTempPath,
+ setFileLoading
+ );
+ }, [reportTempPath, reportDatas, instance, columnsJSON]);
+
+ return (
+ <div ref={viewer} className="h-[100%]">
+ {viwerLoading && (
+ <div className="flex flex-col items-center justify-center py-12">
+ <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
+ <p className="text-sm text-muted-foreground">문서 뷰어 로딩 중...</p>
+ </div>
+ )}
+ </div>
+ );
+};
+
+const cleanupHtmlStyle = () => {
+ const htmlElement = document.documentElement;
+
+ // 기존 style 속성 가져오기
+ const originalStyle = htmlElement.getAttribute("style") || "";
+
+ // "color-scheme: light" 또는 "color-scheme: dark" 찾기
+ const colorSchemeStyle = originalStyle
+ .split(";")
+ .map((s) => s.trim())
+ .find((s) => s.startsWith("color-scheme:"));
+
+ // 새로운 스타일 적용 (color-scheme만 유지)
+ if (colorSchemeStyle) {
+ htmlElement.setAttribute("style", colorSchemeStyle + ";");
+ } else {
+ htmlElement.removeAttribute("style"); // color-scheme도 없으면 style 속성 자체 삭제
+ }
+
+ console.log("html style 삭제");
+};
+
+const stringifyAllValues = (obj: any): any => {
+ if (Array.isArray(obj)) {
+ return obj.map((item) => stringifyAllValues(item));
+ } else if (typeof obj === "object" && obj !== null) {
+ const result: any = {};
+ for (const key in obj) {
+ result[key] = stringifyAllValues(obj[key]);
+ }
+ return result;
+ } else {
+ return obj !== null && obj !== undefined ? String(obj) : "";
+ }
+};
+
+type ImportReportData = (
+ columnsJSON: DataTableColumnJSON[],
+ instance: null | WebViewerInstance,
+ reportDatas: ReportData[],
+ reportTempPath: string,
+ setFileLoading: Dispatch<SetStateAction<boolean>>
+) => void;
+
+const importReportData: ImportReportData = async (
+ columnsJSON,
+ instance,
+ reportDatas,
+ reportTempPath,
+ setFileLoading
+) => {
+ setFileLoading(true);
+ try {
+ if (instance && reportDatas.length > 0 && reportTempPath.length > 0) {
+ const { UI, Core } = instance;
+ const { documentViewer, createDocument } = Core;
+
+ const getFileData = await fetch(reportTempPath);
+ const reportFileBlob = await getFileData.blob();
+
+ const reportData = reportDatas[0];
+ const reportValue = stringifyAllValues(reportData);
+
+ const reportValueMapping: { [key: string]: any } = {};
+
+ columnsJSON.forEach((c) => {
+ const { key, label } = c;
+
+ const objKey = label.split(" ").join("_");
+
+ reportValueMapping[objKey] = reportValue[key];
+ });
+
+ const doc = await createDocument(reportFileBlob, {
+ extension: "docx",
+ });
+
+ await doc.applyTemplateValues(reportValueMapping);
+
+ documentViewer.loadDocument(doc, {
+ extension: "docx",
+ enableOfficeEditing: true,
+ officeOptions: {
+ formatOptions: {
+ applyPageBreaksToSheet: true,
+ },
+ },
+ });
+ }
+ } catch (err) {
+ } finally {
+ setFileLoading(false);
+ }
+};
+
+const importReportDataTab: ImportReportData = async (
+ columnJSON,
+ instance,
+ reportDatas,
+ reportTempPath,
+ setFileLoading
+) => {
+ setFileLoading(true);
+ try {
+ if (instance && reportDatas.length > 0 && reportTempPath.length > 0) {
+ const { UI, Core } = instance;
+ const { createDocument } = Core;
+
+ const getFileData = await fetch(reportTempPath);
+ const reportFileBlob = await getFileData.blob();
+
+ const prevTab = UI.TabManager.getAllTabs();
+
+ (prevTab as object[] as { id: number }[]).forEach((c) => {
+ const { id } = c;
+ UI.TabManager.deleteTab(id);
+ });
+
+ const fileOptions = reportDatas.map((c) => {
+ const { tagNumber } = c;
+
+ const options = {
+ filename: `${tagNumber}_report.docx`,
+ };
+
+ return { options, reportData: c };
+ });
+
+ const tabIds = [];
+
+ for (const fileOption of fileOptions) {
+ let doc = await createDocument(reportFileBlob, {
+ ...fileOption.options,
+ extension: "docx",
+ });
+
+ await doc.applyTemplateValues(
+ stringifyAllValues(fileOption.reportData)
+ );
+
+ const tab = await UI.TabManager.addTab(doc, {
+ ...fileOption.options,
+ });
+
+ tabIds.push(tab); // 탭 ID 저장
+ }
+
+ if (tabIds.length > 0) {
+ await UI.TabManager.setActiveTab(tabIds[0]);
+ }
+ }
+ } catch (err) {
+ } finally {
+ setFileLoading(false);
+ }
+};
+
+type UpdateReportTempList = (
+ packageId: number,
+ formId: number,
+ setPrevReportTemp: Dispatch<SetStateAction<tempFile[]>>
+) => void;
+
+const updateReportTempList: UpdateReportTempList = async (
+ packageId,
+ formId,
+ setTempList
+) => {
+ const tempList = await getReportTempList(packageId, formId);
+
+ setTempList(
+ tempList.map((c) => {
+ const { fileName, filePath } = c;
+ return { fileName, filePath };
+ })
+ );
+};