diff options
| author | rlaks5757 <rlaks5757@gmail.com> | 2025-03-27 11:53:12 +0900 |
|---|---|---|
| committer | rlaks5757 <rlaks5757@gmail.com> | 2025-03-27 11:53:12 +0900 |
| commit | 23db698279eb8ea5f73f678ce6deb93267c4705e (patch) | |
| tree | 932095e15eedbcf726b407470b98e6e332256a8b | |
| parent | e0b2367d88dd80eece67390574e60c9eacdee14d (diff) | |
report batch download 개발 중
| -rw-r--r-- | .env.development | 4 | ||||
| -rw-r--r-- | .env.production | 4 | ||||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | components/form-data/form-data-report-batch-dialog.tsx | 307 | ||||
| -rw-r--r-- | components/form-data/form-data-report-dialog.tsx | 2 | ||||
| -rw-r--r-- | components/form-data/form-data-table.tsx | 23 | ||||
| -rw-r--r-- | db/migrations/meta/_journal.json | 2 | ||||
| -rw-r--r-- | package-lock.json | 445 | ||||
| -rw-r--r-- | package.json | 3 | ||||
| -rw-r--r-- | pages/api/pdftron/createVendorDataReports.ts | 36 |
10 files changed, 822 insertions, 5 deletions
diff --git a/.env.development b/.env.development index 435884dd..183c6f93 100644 --- a/.env.development +++ b/.env.development @@ -10,7 +10,9 @@ NEXT_PUBLIC_MUI_KEY=da30586e1f20b93856a9783012fc9258Tz04ODI0MyxFPTE3NDQ0NTM2Nzgw NEXT_PUBLIC_URL=http://3.36.56.124:3000 NEXT_PUBLIC_BASE_URL=http://3.36.56.124:3001 - NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY=demo:1739264618684:616161d7030000000091db1c97c6f386d41d3506ab5b507381ef2ee2bd +# PDFTRON KEYS +NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY=demo:1739264618684:616161d7030000000091db1c97c6f386d41d3506ab5b507381ef2ee2bd +NEXT_PUBLIC_PDFTRON_SERVER_KEY=demo:1740034881027:6175a0fc0300000000f155d153480e5ba091f17922a109cbd7cf6e40b3 # 기간계 시스템 연동 설정 ERP_API_URL=https://erp.example.com/api/vendors diff --git a/.env.production b/.env.production index 997653c5..1ada78ec 100644 --- a/.env.production +++ b/.env.production @@ -10,7 +10,9 @@ NEXT_PUBLIC_MUI_KEY=da30586e1f20b93856a9783012fc9258Tz04ODI0MyxFPTE3NDQ0NTM2Nzgw NEXT_PUBLIC_URL=https://evcp.dtsolution.io NEXT_PUBLIC_BASE_URL=https://evcp.dtsolution.io - NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY=demo:1739264618684:616161d7030000000091db1c97c6f386d41d3506ab5b507381ef2ee2bd +# PDFTRON KEYS +NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY=demo:1739264618684:616161d7030000000091db1c97c6f386d41d3506ab5b507381ef2ee2bd +NEXT_PUBLIC_PDFTRON_SERVER_KEY=demo:1740034881027:6175a0fc0300000000f155d153480e5ba091f17922a109cbd7cf6e40b3 # 기간계 시스템 연동 설정 ERP_API_URL=https://erp.example.com/api/vendors @@ -51,6 +51,7 @@ next-env.d.ts /public/uploads /public/vendors /public/profiles +/public/vendorFormData # 직접 참조가 불가능해 복사가 필요했던 라이브러리 (pdftrone) # node_modules/@pdftron/public 경로에서 core 및 ui 경로를 복사해 사용 diff --git a/components/form-data/form-data-report-batch-dialog.tsx b/components/form-data/form-data-report-batch-dialog.tsx new file mode 100644 index 00000000..e3fd7ea2 --- /dev/null +++ b/components/form-data/form-data-report-batch-dialog.tsx @@ -0,0 +1,307 @@ +"use client"; + +import React, { + FC, + Dispatch, + SetStateAction, + useState, + useEffect, + useRef, +} from "react"; +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 { ScrollArea } from "@/components/ui/scroll-area"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone"; +import { + FileList, + FileListAction, + FileListDescription, + FileListHeader, + FileListIcon, + FileListInfo, + FileListItem, + FileListName, +} from "@/components/ui/file-list"; +import { Button } from "@/components/ui/button"; +import { getReportTempList } from "@/lib/forms/services"; +import { DataTableColumnJSON } from "./form-data-table-columns"; + +const MAX_FILE_SIZE = 3000000; + +type ReportData = { + [key: string]: any; +}; + +interface tempFile { + fileName: string; + filePath: string; +} + +interface FormDataReportBatchDialogProps { + open: boolean; + setOpen: Dispatch<SetStateAction<boolean>>; + columnsJSON: DataTableColumnJSON[]; + reportData: ReportData[]; + packageId: number; + formId: number; + formCode: string; +} + +export const FormDataReportBatchDialog: FC<FormDataReportBatchDialogProps> = ({ + open, + setOpen, + columnsJSON, + reportData, + packageId, + formId, + formCode, +}) => { + const { toast } = useToast(); + const [tempList, setTempList] = useState<tempFile[]>([]); + const [selectTemp, setSelectTemp] = useState<string>(""); + const [selectedFiles, setSelectedFiles] = useState<File[]>([]); + const [isUploading, setIsUploading] = useState(false); + + useEffect(() => { + updateReportTempList(packageId, formId, setTempList); + }, [packageId, formId]); + + const onClose = () => { + if (isUploading) { + return; + } + setOpen(false); + }; + + // 드롭존 - 파일 드랍 처리 + const handleDropAccepted = (acceptedFiles: File[]) => { + const newFiles = [...acceptedFiles]; + setSelectedFiles(newFiles); + }; + + // 드롭존 - 파일 거부(에러) 처리 + const handleDropRejected = (fileRejections: any[]) => { + fileRejections.forEach((rejection) => { + toast({ + variant: "destructive", + title: "File Error", + description: `${rejection.file.name}: ${ + rejection.errors[0]?.message || "Upload failed" + }`, + }); + }); + }; + + // 파일 제거 핸들러 + const removeFile = (index: number) => { + const updatedFiles = [...selectedFiles]; + updatedFiles.splice(index, 1); + setSelectedFiles(updatedFiles); + }; + + const submitData = async () => { + setIsUploading(true); + + try { + const totalFiles = selectedFiles.length; + let successCount = 0; + + for (let i = 0; i < totalFiles; i++) { + const file = selectedFiles[i]; + + const formData = new FormData(); + formData.append("file", file); + formData.append("customFileName", file.name); + + // await uploadReportTemp(packageId, formId, formData); + + successCount++; + } + } catch (err) { + console.error(err); + toast({ + title: "Error", + description: "파일 업로드 중 오류가 발생했습니다.", + variant: "destructive", + }); + } finally { + setIsUploading(false); + setOpen(false); + } + }; + + return ( + <Dialog open={open} onOpenChange={onClose}> + <DialogContent className="w-[600px]" style={{ maxWidth: "none" }}> + <DialogHeader> + <DialogTitle>Batch Report Download</DialogTitle> + <DialogDescription> + Report Template을 선택하신 후 갑지를 업로드하여 주시기 바랍니다. + </DialogDescription> + </DialogHeader> + <div className="h-[60px]"> + <Label>Report Template Select</Label> + <Select value={selectTemp} onValueChange={setSelectTemp}> + <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> + <Label>Report Cover Page Upload</Label> + <Dropzone + maxSize={MAX_FILE_SIZE} + multiple={false} + accept={{ accept: [".docx"] }} + onDropAccepted={handleDropAccepted} + onDropRejected={handleDropRejected} + disabled={isUploading} + > + {({ maxSize }) => ( + <> + <DropzoneZone className="flex justify-center"> + <DropzoneInput /> + <div className="flex items-center gap-6"> + <DropzoneUploadIcon /> + <div className="grid gap-0.5"> + <DropzoneTitle>파일을 여기에 드롭하세요</DropzoneTitle> + <DropzoneDescription> + 또는 클릭하여 파일을 선택하세요. 최대 크기:{" "} + {maxSize ? prettyBytes(maxSize) : "무제한"} + </DropzoneDescription> + </div> + </div> + </DropzoneZone> + <Label className="text-xs text-muted-foreground"> + 여러 파일을 선택할 수 있습니다. + </Label> + </> + )} + </Dropzone> + </div> + + {selectedFiles.length > 0 && ( + <div className="grid gap-2"> + <div className="flex items-center justify-between"> + <h6 className="text-sm font-semibold"> + 선택된 파일 ({selectedFiles.length}) + </h6> + <Badge variant="secondary">{selectedFiles.length}개 파일</Badge> + </div> + <ScrollArea> + <UploadFileItem + selectedFiles={selectedFiles} + removeFile={removeFile} + isUploading={isUploading} + /> + </ScrollArea> + </div> + )} + + <DialogFooter> + <Button + disabled={selectedFiles.length === 0 || selectTemp.length === 0} + onClick={submitData} + > + 다운로드 + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ); +}; + +interface UploadFileItemProps { + selectedFiles: File[]; + removeFile: (index: number) => void; + isUploading: boolean; +} + +const UploadFileItem: FC<UploadFileItemProps> = ({ + selectedFiles, + removeFile, + isUploading, +}) => { + return ( + <FileList className="max-h-[200px] gap-3"> + {selectedFiles.map((file, index) => ( + <FileListItem key={index} className="p-3"> + <FileListHeader> + <FileListIcon /> + <FileListInfo> + <FileListName>{file.name}</FileListName> + <FileListDescription> + {prettyBytes(file.size)} + </FileListDescription> + </FileListInfo> + <FileListAction + onClick={() => removeFile(index)} + disabled={isUploading} + > + <X className="h-4 w-4" /> + <span className="sr-only">Remove</span> + </FileListAction> + </FileListHeader> + </FileListItem> + ))} + </FileList> + ); +}; + +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 }; + }) + ); +}; diff --git a/components/form-data/form-data-report-dialog.tsx b/components/form-data/form-data-report-dialog.tsx index 5ddc5e0c..deb0873b 100644 --- a/components/form-data/form-data-report-dialog.tsx +++ b/components/form-data/form-data-report-dialog.tsx @@ -348,7 +348,7 @@ const importReportData: ImportReportData = async ( const objKey = label.split(" ").join("_"); - reportValueMapping[objKey] = reportValue[key]; + reportValueMapping[objKey] = reportValue?.[key] ?? ""; }); const doc = await createDocument(reportFileBlob, { diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx index e3c5af8f..9feaf3b2 100644 --- a/components/form-data/form-data-table.tsx +++ b/components/form-data/form-data-table.tsx @@ -23,6 +23,7 @@ import ExcelJS from "exceljs"; import { saveAs } from "file-saver"; import { FormDataReportTempUploadDialog } from "./form-data-report-temp-upload-dialog"; import { FormDataReportDialog } from "./form-data-report-dialog"; +import { FormDataReportBatchDialog } from "./form-data-report-batch-dialog"; interface GenericData { [key: string]: any; @@ -57,6 +58,7 @@ export default function DynamicTable({ const [isSaving, setIsSaving] = React.useState(false); const [tempUpDialog, setTempUpDialog] = React.useState(false); const [reportData, setReportData] = React.useState<GenericData[]>([]); + const [batchDownDialog, setBatchDownDialog] = React.useState(false); // Reference to the table instance const tableRef = React.useRef(null); @@ -527,6 +529,13 @@ export default function DynamicTable({ <Button variant="default" size="sm" + onClick={() => setBatchDownDialog(true)} + > + Report Batch + </Button> + <Button + variant="default" + size="sm" onClick={() => setTempUpDialog(true)} > Temp Upload @@ -621,6 +630,20 @@ export default function DynamicTable({ formId={formId} /> )} + + {batchDownDialog && ( + <FormDataReportBatchDialog + open={batchDownDialog} + setOpen={setBatchDownDialog} + columnsJSON={columnsJSON} + reportData={tableData} + packageId={contractItemId} + formCode={formCode} + formId={formId} + /> + )} </> ); } + + diff --git a/db/migrations/meta/_journal.json b/db/migrations/meta/_journal.json index a84b0a28..f2725ef7 100644 --- a/db/migrations/meta/_journal.json +++ b/db/migrations/meta/_journal.json @@ -659,6 +659,6 @@ "when": 1742834014376, "tag": "0093_young_the_hunter", "breakpoints": true - } + } ] }
\ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e0a4ffee..d41582f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@mui/material": "^6.2.1", "@mui/x-data-grid-premium": "^7.23.3", "@mui/x-tree-view": "^7.23.6", + "@pdftron/pdfnet-node": "^11.3.0", "@pdftron/webviewer": "^11.3.0", "@radix-ui/primitive": "^1.1.1", "@radix-ui/react-accordion": "^1.2.2", @@ -52,6 +53,7 @@ "@t3-oss/env-nextjs": "^0.11.1", "@tanstack/react-table": "^8.20.6", "@types/docusign-esign": "^5.19.8", + "@types/formidable": "^3.4.5", "accept-language": "^3.0.20", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -63,6 +65,7 @@ "embla-carousel-react": "^8.5.1", "exceljs": "^4.4.0", "file-saver": "^2.0.5", + "formidable": "^3.5.2", "handlebars": "^4.7.8", "i18n-iso-countries": "^7.13.0", "i18next": "^24.1.2", @@ -2122,6 +2125,60 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@mui/core-downloads-tracker": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.2.1.tgz", @@ -2741,6 +2798,20 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/@pdftron/pdfnet-node": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@pdftron/pdfnet-node/-/pdfnet-node-11.3.0.tgz", + "integrity": "sha512-FhZD9Z6S/m6VUV23fFc2wtCOt50taV78rbM01+dotlb1FXu87el470EZ1SnRbdcCWegk16IfGeLIGKCuSn+oOw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "underscore": "^1.13.6", + "xhr2": "^0.2.1" + }, + "engines": { + "node": ">=8 <=22" + } + }, "node_modules/@pdftron/webviewer": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/@pdftron/webviewer/-/webviewer-11.3.0.tgz", @@ -4303,6 +4374,14 @@ "integrity": "sha512-xrCYOdHh5zA3LUrn6CvspYwlzSWxPso11Lx32WnAG6KvLCRecKZ/Rh21PLXUkzUFsQmrGcx/traJAFjR6dVS5Q==", "license": "MIT" }, + "node_modules/@types/formidable": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.5.tgz", + "integrity": "sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", @@ -4706,6 +4785,11 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/accept-language": { "version": "3.0.20", "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.20.tgz", @@ -4751,6 +4835,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4814,6 +4909,11 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, "node_modules/archiver": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", @@ -4910,6 +5010,19 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -5102,6 +5215,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -5544,6 +5662,14 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -5630,6 +5756,14 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -5678,6 +5812,11 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -6099,12 +6238,16 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -6115,6 +6258,15 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diacritics": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", @@ -7456,12 +7608,47 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", + "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^2.0.0", + "once": "^1.4.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -7537,6 +7724,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/get-intrinsic": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", @@ -7809,6 +8058,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -7827,6 +8081,14 @@ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "license": "MIT" }, + "node_modules/hexoid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", + "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", + "engines": { + "node": ">=8" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -7845,6 +8107,18 @@ "void-elements": "3.1.0" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/i18n-iso-countries": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.13.0.tgz", @@ -9020,6 +9294,28 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -9109,6 +9405,29 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", @@ -9391,6 +9710,20 @@ "node": ">=6.0.0" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9400,6 +9733,18 @@ "node": ">=0.10.0" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nuqs": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-2.2.3.tgz", @@ -10970,6 +11315,11 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -11663,6 +12013,22 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -11679,6 +12045,25 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -12434,6 +12819,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -12742,6 +13132,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -12852,6 +13287,14 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xhr2": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", + "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", diff --git a/package.json b/package.json index c5306242..af0efae7 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@mui/material": "^6.2.1", "@mui/x-data-grid-premium": "^7.23.3", "@mui/x-tree-view": "^7.23.6", + "@pdftron/pdfnet-node": "^11.3.0", "@pdftron/webviewer": "^11.3.0", "@radix-ui/primitive": "^1.1.1", "@radix-ui/react-accordion": "^1.2.2", @@ -54,6 +55,7 @@ "@t3-oss/env-nextjs": "^0.11.1", "@tanstack/react-table": "^8.20.6", "@types/docusign-esign": "^5.19.8", + "@types/formidable": "^3.4.5", "accept-language": "^3.0.20", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -65,6 +67,7 @@ "embla-carousel-react": "^8.5.1", "exceljs": "^4.4.0", "file-saver": "^2.0.5", + "formidable": "^3.5.2", "handlebars": "^4.7.8", "i18n-iso-countries": "^7.13.0", "i18next": "^24.1.2", diff --git a/pages/api/pdftron/createVendorDataReports.ts b/pages/api/pdftron/createVendorDataReports.ts new file mode 100644 index 00000000..47f6055d --- /dev/null +++ b/pages/api/pdftron/createVendorDataReports.ts @@ -0,0 +1,36 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import formidable from "formidable"; + +export const config = { + api: { + bodyParser: false, // ✅ 이게 false면 안 됨! + }, +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== "POST") { + return res.status(405).end(); + } + + const form = formidable({ multiples: true }); + + form.parse(req, async (err, fields, files) => { + if (err) { + console.error(err); + return res.status(500).json({ error: "Error parsing form" }); + } + + try { + const additionalData = JSON.parse((fields?.additionalData ?? "") as string); + console.log("📦 additionalData:", additionalData); + console.log("📎 files:", files.files); // files.files는 array or single file + + return res.status(200).json({ success: true }); + } catch (e) { + return res.status(400).json({ error: "Invalid additionalData" }); + } + }); +} |
