"use client"; import React, { FC, Dispatch, SetStateAction, useState, useEffect, } from "react"; import { useToast } from "@/hooks/use-toast"; import { toast as toastMessage } from "sonner"; import prettyBytes from "pretty-bytes"; import { X, Loader2, Download, Delete, Trash2 } from "lucide-react"; import ExcelJS from "exceljs"; import { saveAs } from "file-saver"; 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 { Button } from "@/components/ui/button"; 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { getReportTempList, uploadReportTemp, getReportTempFileData, deleteReportTempFile, } from "@/lib/forms/services"; import { VendorDataReportTemps } from "@/db/schema/vendorData"; import { DataTableColumnJSON } from "./form-data-table-columns"; interface FormDataReportTempUploadDialogProps { columnsJSON: DataTableColumnJSON[]; open: boolean; setOpen: Dispatch>; packageId: number; formCode: string; formId: number; uploaderType: string; } // 최대 파일 크기 설정 (3000MB) const MAX_FILE_SIZE = 3000000; export const FormDataReportTempUploadDialog: FC< FormDataReportTempUploadDialogProps > = ({ columnsJSON, open, setOpen, packageId, formId, formCode, uploaderType, }) => { const { toast } = useToast(); const [selectedFiles, setSelectedFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [prevReportTemp, setPrevReportTemp] = useState( [] ); useEffect(() => { updateReportTempList(packageId, formId, setPrevReportTemp); }, [packageId, formId]); // 드롭존 - 파일 드랍 처리 const handleDropAccepted = (acceptedFiles: File[]) => { const newFiles = [...selectedFiles, ...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); setUploadProgress(0); 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); formData.append("uploaderType", uploaderType); await uploadReportTemp(packageId, formId, formData); successCount++; setUploadProgress(Math.round((successCount / totalFiles) * 100)); } } catch (err) { console.error(err); toast({ title: "Error", description: "파일 업로드 중 오류가 발생했습니다.", variant: "destructive", }); } finally { setIsUploading(false); setUploadProgress(0); updateReportTempList(packageId, formId, setPrevReportTemp); setOpen(false); } }; const downloadTempFile = async () => { try { const { fileName, fileType, base64 } = await getReportTempFileData(); saveAs(`data:${fileType};base64,${base64}`, fileName); toastMessage.success("Report Sample File 다운로드 완료!"); } catch (err) { console.log(err); toast({ title: "Error", description: "Sample File을 찾을 수가 없습니다.", variant: "destructive", }); } }; const downloadReportVarList = async () => { try { // Create a new workbook const workbook = new ExcelJS.Workbook(); // 데이터 시트 생성 const worksheet = workbook.addWorksheet("Data"); // 유효성 검사용 숨김 시트 생성 const validationSheet = workbook.addWorksheet("ValidationData"); validationSheet.state = "hidden"; // 시트 숨김 처리 // 1. 데이터 시트에 헤더 추가 const headers = ["Table Column Label", "Report Variable"]; worksheet.addRow(headers); // 헤더 스타일 적용 const headerRow = worksheet.getRow(1); headerRow.font = { bold: true }; headerRow.alignment = { horizontal: "center" }; headerRow.eachCell((cell) => { cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFCCCCCC" }, }; }); // 2. 데이터 행 추가 columnsJSON.forEach((row) => { const { displayLabel, label } = row; const labelConvert = label.replaceAll(" ", "_"); worksheet.addRow([displayLabel, labelConvert]); }); // 3. 컬럼 너비 자동 조정 headers.forEach((col, idx) => { const column = worksheet.getColumn(idx + 1); // 최적 너비 계산 let maxLength = col.length; columnsJSON.forEach((row) => { const valueKey = idx === 0 ? "displayLabel" : "label"; const value = row[valueKey]; if (value !== undefined && value !== null) { const valueLength = String(value).length; if (valueLength > maxLength) { maxLength = valueLength; } } }); // 너비 설정 (최소 10, 최대 50) column.width = Math.min(Math.max(maxLength + 2, 10), 50); }); const buffer = await workbook.xlsx.writeBuffer(); saveAs(new Blob([buffer]), `${formCode}_report_varible_list.xlsx`); toastMessage.success("Report Varible List File 다운로드 완료!"); } catch (err) { console.log(err); toast({ title: "Error", description: "Variable List 파일을 찾을 수가 없습니다.", variant: "destructive", }); } }; return ( Report Template Upload 사용하시고자 하는 Report Template(.docx)를 업로드 하여주시기 바랍니다.
sample_template_file.docx Download report_variable_list.xlsx Download
updateReportTempList(packageId, formId, setPrevReportTemp) } />
{({ maxSize }) => ( <>
파일을 여기에 드롭하세요 또는 클릭하여 파일을 선택하세요. 최대 크기:{" "} {maxSize ? prettyBytes(maxSize) : "무제한"}
)}
{selectedFiles.length > 0 && (
선택된 파일 ({selectedFiles.length})
{selectedFiles.length}개 파일
)} {isUploading && }
); }; interface UploadFileItemProps { selectedFiles: File[]; removeFile: (index: number) => void; isUploading: boolean; } const UploadFileItem: FC = ({ selectedFiles, removeFile, isUploading, }) => { return ( {selectedFiles.map((file, index) => ( {file.name} {prettyBytes(file.size)} removeFile(index)} disabled={isUploading} > Remove ))} ); }; const UploadProgressBox: FC<{ uploadProgress: number }> = ({ uploadProgress, }) => { return (
{uploadProgress}% 업로드 중...
); }; const generateFileName = ( packageId: number, formId: number, originalFileName: string, index: number, totalFiles: number ) => { // Get the file extension const extension = originalFileName.split(".").pop() || ""; // Base name without extension const baseName = `${packageId}_${formId}`; // For multiple files, add a suffix if (totalFiles > 1) { return `${baseName}_${index + 1}.${extension}`; } // For a single file, no suffix needed return `${baseName}.${extension}`; }; type UpdateReportTempList = ( packageId: number, formId: number, setPrevReportTemp: Dispatch> ) => void; const updateReportTempList: UpdateReportTempList = async ( packageId, formId, setPrevReportTemp ) => { const tempList = await getReportTempList(packageId, formId); setPrevReportTemp(tempList); }; interface UploadedTempFiles { prevReportTemp: VendorDataReportTemps[]; updateReportTempList: () => void; } const UploadedTempFiles: FC = ({ prevReportTemp, updateReportTempList, }) => { const { toast } = useToast(); const downloadTempFile = async (fileName: string, filePath: string) => { try { const getTempFile = await fetch(filePath); if (getTempFile.ok) { const blob = await getTempFile.blob(); saveAs(blob, fileName); toastMessage.success("Report 다운로드 완료!"); } else { const err = await getTempFile.json(); console.error("에러:", err); throw new Error(err.message); } toastMessage.success("Template File 다운로드 완료!"); } catch (err) { console.error(err) toast({ title: "Error", description: "Template File 다운로드 중 오류가 발생했습니다.", variant: "destructive", }); } }; const deleteTempFile = async (id: number) => { try { const { result, error } = await deleteReportTempFile(id); if (result) { updateReportTempList(); toastMessage.success("Template File 삭제 완료!"); } else { throw new Error(error); } } catch (err) { toast({ title: "Error", description: "Template File 삭제 중 오류가 발생했습니다.", variant: "destructive", }); } }; return ( {prevReportTemp.map((c) => { const { fileName, filePath, id } = c; return ( {fileName} { downloadTempFile(fileName, filePath); }} > Download Delete Report Templete File({fileName})을 삭제하시겠습니까? 취소 { deleteTempFile(id); }} > 삭제 ); })} ); };