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 /components/form-data/form-data-report-batch-dialog.tsx | |
| parent | e0b2367d88dd80eece67390574e60c9eacdee14d (diff) | |
report batch download 개발 중
Diffstat (limited to 'components/form-data/form-data-report-batch-dialog.tsx')
| -rw-r--r-- | components/form-data/form-data-report-batch-dialog.tsx | 307 |
1 files changed, 307 insertions, 0 deletions
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 }; + }) + ); +}; |
