From 37f55540833c2d5894513eca9fc8f7c6233fc2d2 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 29 May 2025 05:17:13 +0000 Subject: (대표님) 0529 14시 16분 변경사항 저장 (Vendor Data, Docu) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vendor-tbe-table/tbeFileHandler.tsx | 354 +++++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 lib/tech-vendor-rfq-response/vendor-tbe-table/tbeFileHandler.tsx (limited to 'lib/tech-vendor-rfq-response/vendor-tbe-table/tbeFileHandler.tsx') diff --git a/lib/tech-vendor-rfq-response/vendor-tbe-table/tbeFileHandler.tsx b/lib/tech-vendor-rfq-response/vendor-tbe-table/tbeFileHandler.tsx new file mode 100644 index 00000000..6c622fd1 --- /dev/null +++ b/lib/tech-vendor-rfq-response/vendor-tbe-table/tbeFileHandler.tsx @@ -0,0 +1,354 @@ +"use client"; + +import { useCallback, useState, useEffect } from "react"; +import { toast } from "sonner"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { + fetchTbeTemplateFiles, + uploadTbeResponseFile, + getTbeSubmittedFiles, + getFileFromRfqAttachmentsbyid, +} from "../../rfqs-tech/service"; +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone"; +import { + FileList, + FileListAction, + FileListDescription, + FileListIcon, + FileListInfo, + FileListItem, + FileListName, + FileListSize, +} from "@/components/ui/file-list"; +import { Download, X } from "lucide-react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { formatDateTime } from "@/lib/utils"; + +export function useTbeFileHandlers() { + // 모달 열림 여부, 현재 선택된 IDs + const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false); + const [currentTbeId, setCurrentTbeId] = useState(null); + const [currentVendorId, setCurrentVendorId] = useState(null); + const [currentRfqId, setCurrentRfqId] = useState(null); + const [currentvendorResponseId, setCurrentvendorResponseId] = useState(null); + + + + // 로딩 상태들 + const [isLoading, setIsLoading] = useState(false); + const [isFetchingFiles, setIsFetchingFiles] = useState(false); + + // 업로드할 파일, 제출된 파일 목록 + const [selectedFile, setSelectedFile] = useState(null); + const [submittedFiles, setSubmittedFiles] = useState< + Array<{ id: number; fileName: string; filePath: string; uploadedAt: Date }> + >([]); + + // =================================== + // 1) 제출된 파일 목록 가져오기 + // =================================== + const fetchSubmittedFiles = useCallback(async (vendorResponseId: number) => { + if (!vendorResponseId ) return; + + setIsFetchingFiles(true); + try { + const { files, error } = await getTbeSubmittedFiles(vendorResponseId); + if (error) { + console.error(error); + return; + } + setSubmittedFiles(files); + } catch (error) { + console.error("Failed to fetch submitted files:", error); + } finally { + setIsFetchingFiles(false); + } + }, []); + + // =================================== + // 2) TBE 템플릿 다운로드 + // =================================== + const handleDownloadTbeTemplate = useCallback( + async (tbeId: number, vendorId: number, rfqId: number) => { + setCurrentTbeId(tbeId); + setCurrentVendorId(vendorId); + setCurrentRfqId(rfqId); + setIsLoading(true); + + try { + const { files, error } = await fetchTbeTemplateFiles(tbeId); + if (error) { + toast.error(error); + return; + } + if (files.length === 0) { + toast.warning("다운로드할 템플릿 파일이 없습니다"); + return; + } + // 순차적으로 파일 다운로드 + for (const file of files) { + await downloadFile(file.id); + } + toast.success("모든 템플릿 파일이 다운로드되었습니다"); + } catch (error) { + toast.error("템플릿 파일을 다운로드하는 데 실패했습니다"); + console.error(error); + } finally { + setIsLoading(false); + } + }, + [] + ); + + // 실제 다운로드 로직 + const downloadFile = useCallback(async (fileId: number) => { + try { + const { file, error } = await getFileFromRfqAttachmentsbyid(fileId); + if (error || !file) { + throw new Error(error || "파일 정보를 가져오는 데 실패했습니다"); + } + + const link = document.createElement("a"); + link.href = `/api/rfq-download?path=${encodeURIComponent(file.filePath)}`; + link.download = file.fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + return true; + } catch (error) { + console.error(error); + return false; + } + }, []); + + // =================================== + // 3) 제출된 파일 다운로드 + // =================================== + const downloadSubmittedFile = useCallback((file: { id: number; fileName: string; filePath: string }) => { + try { + const link = document.createElement("a"); + link.href = `/api/tbe-download?path=${encodeURIComponent(file.filePath)}`; + link.download = file.fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + toast.success(`${file.fileName} 다운로드 시작`); + } catch (error) { + console.error("Failed to download file:", error); + toast.error("파일 다운로드에 실패했습니다"); + } + }, []); + + // =================================== + // 4) TBE 응답 업로드 모달 열기 + // (이 시점에서는 데이터 fetch하지 않음) + // =================================== + const handleUploadTbeResponse = useCallback((tbeId: number, vendorId: number, rfqId: number, vendorResponseId:number) => { + setCurrentTbeId(tbeId); + setCurrentVendorId(vendorId); + setCurrentRfqId(rfqId); + setCurrentvendorResponseId(vendorResponseId); + setIsUploadDialogOpen(true); + }, []); + + // =================================== + // 5) Dialog 열고 닫힐 때 상태 초기화 + // 열렸을 때 -> useEffect로 파일 목록 가져오기 + // =================================== + useEffect(() => { + if (!isUploadDialogOpen) { + // 닫힐 때는 파일 상태들 초기화 + setSelectedFile(null); + setSubmittedFiles([]); + } + }, [isUploadDialogOpen]); + + useEffect(() => { + // Dialog가 열렸고, ID들이 유효하면 + if (isUploadDialogOpen &¤tvendorResponseId) { + fetchSubmittedFiles(currentvendorResponseId); + } + }, [isUploadDialogOpen, currentvendorResponseId, fetchSubmittedFiles]); + + // =================================== + // 6) 드롭존 파일 선택 & 제거 + // =================================== + const handleFileDrop = useCallback((files: File[]) => { + if (files && files.length > 0) { + setSelectedFile(files[0]); + } + }, []); + + const handleRemoveFile = useCallback(() => { + setSelectedFile(null); + }, []); + + // =================================== + // 7) 응답 파일 업로드 + // =================================== + const handleSubmitResponse = useCallback(async () => { + if (!selectedFile || !currentTbeId || !currentVendorId || !currentRfqId ||!currentvendorResponseId) { + toast.error("업로드할 파일을 선택해주세요"); + return; + } + + setIsLoading(true); + try { + // FormData 생성 + const formData = new FormData(); + formData.append("file", selectedFile); + formData.append("rfqId", currentRfqId.toString()); + formData.append("vendorId", currentVendorId.toString()); + formData.append("evaluationId", currentTbeId.toString()); + formData.append("vendorResponseId", currentvendorResponseId.toString()); + + const result = await uploadTbeResponseFile(formData); + if (!result.success) { + throw new Error(result.error || "파일 업로드에 실패했습니다"); + } + + toast.success(result.message || "응답이 성공적으로 업로드되었습니다"); + + // 업로드 후 다시 제출된 파일 목록 가져오기 + await fetchSubmittedFiles(currentvendorResponseId); + + // 업로드 성공 시 선택 파일 초기화 + setSelectedFile(null); + + // 페이지 새로고침으로 테이블 데이터 업데이트 + window.location.reload(); + } catch (error) { + toast.error(error instanceof Error ? error.message : "응답 업로드에 실패했습니다"); + console.error(error); + } finally { + setIsLoading(false); + } + }, [selectedFile, currentTbeId, currentVendorId, currentRfqId, currentvendorResponseId,fetchSubmittedFiles]); + + // =================================== + // 8) 실제 Dialog 컴포넌트 + // =================================== + const UploadDialog = () => ( + + + + TBE 응답 파일 + 제출된 파일을 확인하거나 새 파일을 업로드하세요. + + + + + 새 파일 업로드 + 0 ? "relative" : ""} + > + 제출된 파일{" "} + {submittedFiles.length > 0 && ( + + {submittedFiles.length} + + )} + + + + {/* 업로드 탭 */} + +
+ {selectedFile ? ( + + + + + {selectedFile.name} + {selectedFile.size} + + + + 파일 제거 + + + + ) : ( + + + + + 파일을 드래그하거나 클릭하여 업로드 + TBE 응답 파일 (XLSX, XLS, DOCX, PDF 등) + + + )} + + + + +
+
+ + {/* 제출된 파일 탭 */} + + {isFetchingFiles ? ( +
+
+
+ ) : submittedFiles.length > 0 ? ( +
+ + {submittedFiles.map((file) => ( + +
+ + + {file.fileName} + + {file.uploadedAt ? formatDateTime(file.uploadedAt) : ""} + + +
+ downloadSubmittedFile(file)}> + + 파일 다운로드 + +
+ ))} +
+
+ ) : ( +
제출된 파일이 없습니다.
+ )} +
+
+
+
+ ); + + // =================================== + // 9) Hooks 내보내기 + // =================================== + return { + handleDownloadTbeTemplate, + handleUploadTbeResponse, + UploadDialog, + }; +} \ No newline at end of file -- cgit v1.2.3