From cc2c3def63f47063d4fa8b01f9f61eafdd52805c Mon Sep 17 00:00:00 2001
From: rlaks5757 <rlaks5757@gmail.com>
Date: Tue, 1 Apr 2025 11:58:20 +0900
Subject: template-upload-dialog component 세분화
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../form-data-report-temp-upload-dialog.tsx        | 544 +--------------------
 1 file changed, 15 insertions(+), 529 deletions(-)

(limited to 'components/form-data/form-data-report-temp-upload-dialog.tsx')

diff --git a/components/form-data/form-data-report-temp-upload-dialog.tsx b/components/form-data/form-data-report-temp-upload-dialog.tsx
index 32273415..51fe5aca 100644
--- a/components/form-data/form-data-report-temp-upload-dialog.tsx
+++ b/components/form-data/form-data-report-temp-upload-dialog.tsx
@@ -1,59 +1,14 @@
 "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, BookDown } from "lucide-react";
-import ExcelJS from "exceljs";
-import { saveAs } from "file-saver";
-import { Badge } from "@/components/ui/badge";
+import React, { FC, Dispatch, SetStateAction, useState } from "react";
 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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
 import {
   Tooltip,
@@ -61,13 +16,10 @@ import {
   TooltipProvider,
   TooltipTrigger,
 } from "@/components/ui/tooltip";
-import {
-  getReportTempList,
-  uploadReportTemp,
-  getReportTempFileData,
-  deleteReportTempFile,
-} from "@/lib/forms/services";
-import { VendorDataReportTemps } from "@/db/schema/vendorData";
+import { TempDownloadBtn } from "./temp-download-btn";
+import { VarListDownloadBtn } from "./var-list-download-btn";
+import { FormDataReportTempUploadTab } from "./form-data-report-temp-upload-tab";
+import { FormDataReportTempUploadedListTab } from "./form-data-report-temp-uploaded-list-tab";
 import { DataTableColumnJSON } from "./form-data-table-columns";
 
 interface FormDataReportTempUploadDialogProps {
@@ -80,9 +32,6 @@ interface FormDataReportTempUploadDialogProps {
   uploaderType: string;
 }
 
-// 최대 파일 크기 설정 (3000MB)
-const MAX_FILE_SIZE = 3000000;
-
 export const FormDataReportTempUploadDialog: FC<
   FormDataReportTempUploadDialogProps
 > = ({
@@ -94,98 +43,8 @@ export const FormDataReportTempUploadDialog: FC<
   formCode,
   uploaderType,
 }) => {
-  const { toast } = useToast();
   const [tabValue, setTabValue] = useState<"upload" | "uploaded">("upload");
 
-  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 (
     <Dialog open={open} onOpenChange={setOpen}>
       <DialogContent className="w-[600px]" style={{ maxWidth: "none" }}>
@@ -213,14 +72,7 @@ export const FormDataReportTempUploadDialog: FC<
               <TooltipProvider>
                 <Tooltip>
                   <TooltipTrigger asChild>
-                    <Button
-                      variant="ghost"
-                      className="relative p-2"
-                      aria-label="Template Sample Download"
-                      onClick={downloadTempFile}
-                    >
-                      <Download />
-                    </Button>
+                    <TempDownloadBtn />
                   </TooltipTrigger>
                   <TooltipContent>
                     <Label>Template Sample Download</Label>
@@ -228,14 +80,10 @@ export const FormDataReportTempUploadDialog: FC<
                 </Tooltip>
                 <Tooltip>
                   <TooltipTrigger asChild>
-                    <Button
-                      variant="ghost"
-                      className="relative p-2"
-                      aria-label="Variable List Download"
-                      onClick={downloadReportVarList}
-                    >
-                      <BookDown />
-                    </Button>
+                    <VarListDownloadBtn
+                      columnsJSON={columnsJSON}
+                      formCode={formCode}
+                    />
                   </TooltipTrigger>
                   <TooltipContent>
                     <Label>Variable List Download</Label>
@@ -245,382 +93,20 @@ export const FormDataReportTempUploadDialog: FC<
             </div>
           </div>
           <TabsContent value="upload">
-            <TempUploadTab
+            <FormDataReportTempUploadTab
               packageId={packageId}
               formId={formId}
               uploaderType={uploaderType}
             />
           </TabsContent>
           <TabsContent value="uploaded">
-            <TempUploadedTab packageId={packageId} formId={formId} />
+            <FormDataReportTempUploadedListTab
+              packageId={packageId}
+              formId={formId}
+            />
           </TabsContent>
         </Tabs>
       </DialogContent>
     </Dialog>
   );
 };
-
-interface TempUploadTabProps {
-  packageId: number;
-  formId: number;
-  uploaderType: string;
-}
-
-const TempUploadTab: FC<TempUploadTabProps> = ({
-  packageId,
-  formId,
-  uploaderType,
-}) => {
-  const { toast } = useToast();
-  const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
-  const [isUploading, setIsUploading] = useState(false);
-  const [uploadProgress, setUploadProgress] = useState(0);
-
-  // 드롭존 - 파일 드랍 처리
-  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);
-    }
-  };
-
-  return (
-    <>
-      <div>
-        <Label>Vendor Document Template File Upload(.docx)</Label>
-        <Dropzone
-          maxSize={MAX_FILE_SIZE}
-          multiple={true}
-          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>
-      )}
-
-      {isUploading && <UploadProgressBox uploadProgress={uploadProgress} />}
-      <DialogFooter>
-        <Button disabled={selectedFiles.length === 0} onClick={submitData}>
-          업로드
-        </Button>
-      </DialogFooter>
-    </>
-  );
-};
-
-interface UploadFileItemProps {
-  selectedFiles: File[];
-  removeFile: (index: number) => void;
-  isUploading: boolean;
-}
-
-const UploadFileItem: FC<UploadFileItemProps> = ({
-  selectedFiles,
-  removeFile,
-  isUploading,
-}) => {
-  return (
-    <FileList className="max-h-[150px] 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>
-  );
-};
-
-const UploadProgressBox: FC<{ uploadProgress: number }> = ({
-  uploadProgress,
-}) => {
-  return (
-    <div className="flex flex-col gap-1 mt-2">
-      <div className="flex items-center gap-2">
-        <Loader2 className="h-4 w-4 animate-spin" />
-        <span className="text-sm">{uploadProgress}% 업로드 중...</span>
-      </div>
-      <div className="h-2 w-full bg-muted rounded-full overflow-hidden">
-        <div
-          className="h-full bg-primary rounded-full transition-all"
-          style={{ width: `${uploadProgress}%` }}
-        />
-      </div>
-    </div>
-  );
-};
-
-type UpdateReportTempList = (
-  packageId: number,
-  formId: number,
-  setPrevReportTemp: Dispatch<SetStateAction<VendorDataReportTemps[]>>
-) => Promise<void>;
-
-const updateReportTempList: UpdateReportTempList = async (
-  packageId,
-  formId,
-  setPrevReportTemp
-) => {
-  const tempList = await getReportTempList(packageId, formId);
-  setPrevReportTemp(tempList);
-};
-
-interface UploadedTempFiles {
-  prevReportTemp: VendorDataReportTemps[];
-  updateReportTempList: () => void;
-  isLoading: boolean;
-}
-
-const UploadedTempFiles: FC<UploadedTempFiles> = ({
-  prevReportTemp,
-  updateReportTempList,
-  isLoading,
-}) => {
-  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",
-      });
-    }
-  };
-
-  if (isLoading) {
-    return (
-      <div className="min-h-[157px]">
-        <Label>로딩 중...</Label>
-      </div>
-    );
-  }
-
-  return (
-    <ScrollArea className="min-h-[157px] max-h-[337px] overflow-auto">
-      <FileList className="gap-3">
-        {prevReportTemp.map((c) => {
-          const { fileName, filePath, id } = c;
-
-          return (
-            <AlertDialog key={id}>
-              <FileListItem className="p-3">
-                <FileListHeader>
-                  <FileListIcon />
-                  <FileListInfo>
-                    <FileListName>{fileName}</FileListName>
-                  </FileListInfo>
-                  <FileListAction
-                    onClick={() => {
-                      downloadTempFile(fileName, filePath);
-                    }}
-                  >
-                    <Download className="h-4 w-4" />
-                    <span className="sr-only">Download</span>
-                  </FileListAction>
-                  <AlertDialogTrigger asChild>
-                    <FileListAction>
-                      <Trash2 className="h-4 w-4" />
-                      <span className="sr-only">Delete</span>
-                    </FileListAction>
-                  </AlertDialogTrigger>
-                  <AlertDialogContent>
-                    <AlertDialogHeader>
-                      <AlertDialogTitle>
-                        Report Templete File({fileName})을 삭제하시겠습니까?
-                      </AlertDialogTitle>
-                      <AlertDialogDescription />
-                    </AlertDialogHeader>
-                    <AlertDialogFooter>
-                      <AlertDialogCancel>취소</AlertDialogCancel>
-                      <AlertDialogAction
-                        onClick={() => {
-                          deleteTempFile(id);
-                        }}
-                      >
-                        삭제
-                      </AlertDialogAction>
-                    </AlertDialogFooter>
-                  </AlertDialogContent>
-                </FileListHeader>
-              </FileListItem>
-            </AlertDialog>
-          );
-        })}
-      </FileList>
-    </ScrollArea>
-  );
-};
-
-interface TempUploadedTabProps {
-  packageId: number;
-  formId: number;
-}
-
-const TempUploadedTab: FC<TempUploadedTabProps> = ({ packageId, formId }) => {
-  const [prevReportTemp, setPrevReportTemp] = useState<VendorDataReportTemps[]>(
-    []
-  );
-  const [isLoading, setIsLoading] = useState(true);
-
-  useEffect(() => {
-    const getTempFiles = async () => {
-      await updateReportTempList(packageId, formId, setPrevReportTemp);
-      setIsLoading(false);
-    };
-
-    getTempFiles();
-  }, [packageId, formId]);
-  return (
-    <div>
-      <Label>Uploaded Template File List</Label>
-      <UploadedTempFiles
-        prevReportTemp={prevReportTemp}
-        updateReportTempList={() =>
-          updateReportTempList(packageId, formId, setPrevReportTemp)
-        }
-        isLoading={isLoading}
-      />
-    </div>
-  );
-};
-- 
cgit v1.2.3

