diff options
Diffstat (limited to 'lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx')
| -rw-r--r-- | lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx | 258 |
1 files changed, 228 insertions, 30 deletions
diff --git a/lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx b/lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx index 3207c00b..ba5673ef 100644 --- a/lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx +++ b/lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx @@ -30,10 +30,13 @@ import { } from "./b4-upload-validation-dialog"; import { fetchDwgReceiptList, - bulkUploadB4FilesV2, + prepareB4DetailDrawingsV2, type B4BulkUploadResult, type GttDwgReceiptItem, } from "../actions"; +import { uploadFilesWithProgress } from "../utils/upload-with-progress"; +import { FileUploadProgressList } from "../components/file-upload-progress-list"; +import type { FileUploadProgress } from "../hooks/use-file-upload-with-progress"; interface B4BulkUploadDialogV2Props { open: boolean; @@ -71,6 +74,7 @@ export function B4BulkUploadDialogV2({ const [isDragging, setIsDragging] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [uploadResult, setUploadResult] = useState<B4BulkUploadResult | null>(null); + const [fileProgresses, setFileProgresses] = useState<FileUploadProgress[]>([]); // B4 GTT 옵션 const drawingUsageOptions = [ @@ -96,6 +100,7 @@ export function B4BulkUploadDialogV2({ setIsDragging(false); setUploadProgress(0); setUploadResult(null); + setFileProgresses([]); } }, [open]); @@ -271,7 +276,7 @@ export function B4BulkUploadDialogV2({ } }; - // 업로드 확인 (V2: bulkUploadB4FilesV2 사용) + // 업로드 확인 (V2: prepareB4DetailDrawingsV2 + uploadFilesWithProgress 사용) const handleConfirmUpload = async (validFiles: FileValidationResult[]) => { setIsUploading(true); setCurrentStep("uploading"); @@ -280,35 +285,224 @@ export function B4BulkUploadDialogV2({ try { console.log(`[V2 Dialog] 업로드 시작: ${validFiles.length}개 파일`); - // FormData 구성 - const formData = new FormData(); - formData.append("projectNo", projectNo); - formData.append("userId", userId); - formData.append("userNm", userName); - formData.append("email", userEmail); - formData.append("vendorCode", vendorCode); - formData.append("registerKind", registerKind); - formData.append("fileCount", String(validFiles.length)); + // 0단계: 모든 파일에 대한 진행도 상태 초기화 + const initialProgresses: FileUploadProgress[] = validFiles.map((fileResult) => ({ + file: fileResult.file, + progress: 0, + status: "pending" as const, + })); + setFileProgresses(initialProgresses); + // 파일 인덱스 맵 생성 (파일명 기반) + const fileIndexMap = new Map<string, number>(); validFiles.forEach((fileResult, index) => { - formData.append(`file_${index}`, fileResult.file); + fileIndexMap.set(fileResult.file.name, index); }); - // 업로드 프로그레스 시뮬레이션 - const progressInterval = setInterval(() => { - setUploadProgress((prev) => { - if (prev >= 90) return 90; - return prev + 10; - }); - }, 500); + // 1단계: DrawingNo + RevNo별로 그룹화 + // - 동일한 Drawing/Revision에 속하는 파일들을 하나의 그룹으로 묶음 + // - 이렇게 하면 같은 리비전의 상세도면을 1번만 생성/조회함 + const uploadGroups = new Map< + string, + { + drawingNo: string; + revNo: string; + files: File[]; + fileIndices: number[]; // 전체 배열에서의 인덱스 + } + >(); + + validFiles.forEach((fileResult, index) => { + const groupKey = `${fileResult.parsed!.drawingNo}_${fileResult.parsed!.revNo}`; + if (!uploadGroups.has(groupKey)) { + uploadGroups.set(groupKey, { + drawingNo: fileResult.parsed!.drawingNo, + revNo: fileResult.parsed!.revNo, + files: [], + fileIndices: [], + }); + } + uploadGroups.get(groupKey)!.files.push(fileResult.file); + uploadGroups.get(groupKey)!.fileIndices.push(index); + }); + + console.log( + `[V2 Dialog] ${uploadGroups.size}개 리비전 그룹 생성 (${validFiles.length}개 파일)` + ); + + // 2단계: 상세도면 준비 (서버 액션) + // - 각 리비전별로 상세도면 존재 여부 확인 + // - 기존 상세도면이 있으면 uploadId 재사용 + // - 없으면 새로 생성 (1번만!) + const drawingRevisions = Array.from(uploadGroups.values()).map((group) => ({ + drawingNo: group.drawingNo, + revNo: group.revNo, + })); + + console.log(`[V2 Dialog] 상세도면 준비 요청: ${drawingRevisions.length}개 리비전`); + + const prepareResult = await prepareB4DetailDrawingsV2({ + projectNo, + userId, + userNm: userName, + email: userEmail, + vendorCode, + registerKind, + drawingRevisions, + }); + + if (!prepareResult.success || !prepareResult.detailDrawings) { + throw new Error(prepareResult.error || "상세도면 준비 실패"); + } + + const newDrawings = prepareResult.detailDrawings.filter((d) => d.isNew); + const existingDrawings = prepareResult.detailDrawings.filter((d) => !d.isNew); + + console.log( + `[V2 Dialog] 상세도면 준비 완료: 총 ${prepareResult.detailDrawings.length}개 ` + + `(기존 ${existingDrawings.length}개 재사용, 신규 ${newDrawings.length}개 생성)` + ); + + // 3단계: 각 그룹별로 파일 업로드 + // - 준비된 uploadId를 사용하여 파일 업로드 + const detailDrawingMap = new Map( + prepareResult.detailDrawings.map((d) => [`${d.drawingNo}_${d.revNo}`, d]) + ); + + let successCount = 0; + let failCount = 0; + let completedGroups = 0; + const results: B4BulkUploadResult["results"] = []; + + for (const [groupKey, group] of uploadGroups.entries()) { + try { + const detailDrawing = detailDrawingMap.get(groupKey); + if (!detailDrawing) { + throw new Error(`상세도면 정보를 찾을 수 없습니다: ${groupKey}`); + } - // V2 함수 호출 - const result = await bulkUploadB4FilesV2(formData); + console.log( + `[V2 Dialog] 그룹 ${groupKey} 업로드 시작\n` + + ` - 파일 수: ${group.files.length}개\n` + + ` - UploadId: ${detailDrawing.uploadId}\n` + + ` - 상태: ${detailDrawing.isNew ? "신규 생성" : "기존 재사용"}` + ); + + // 그룹 내 모든 파일 상태를 uploading으로 변경 + setFileProgresses((prev) => + prev.map((fp, index) => + group.fileIndices.includes(index) + ? { ...fp, status: "uploading" as const } + : fp + ) + ); + + // uploadFilesWithProgress 사용 (클라이언트 fetch) + const uploadResult = await uploadFilesWithProgress({ + uploadId: detailDrawing.uploadId, + userId: userId, + files: group.files, + callbacks: { + onProgress: (fileIndexInGroup, progress) => { + // 그룹 내 파일 인덱스를 전체 인덱스로 변환 + const globalFileIndex = group.fileIndices[fileIndexInGroup]; + + // 개별 파일 진행도 업데이트 + setFileProgresses((prev) => + prev.map((fp, index) => + index === globalFileIndex + ? { ...fp, progress, status: "uploading" as const } + : fp + ) + ); + + // 전체 진행도 계산 + const groupProgress = (completedGroups / uploadGroups.size) * 100; + const currentGroupProgress = (progress / 100) * (100 / uploadGroups.size); + setUploadProgress(Math.round(groupProgress + currentGroupProgress)); + }, + onFileComplete: (fileIndexInGroup) => { + const globalFileIndex = group.fileIndices[fileIndexInGroup]; + setFileProgresses((prev) => + prev.map((fp, index) => + index === globalFileIndex + ? { ...fp, progress: 100, status: "completed" as const } + : fp + ) + ); + }, + onFileError: (fileIndexInGroup, error) => { + const globalFileIndex = group.fileIndices[fileIndexInGroup]; + console.error(`[V2 Dialog] 파일 ${globalFileIndex} 업로드 실패:`, error); + setFileProgresses((prev) => + prev.map((fp, index) => + index === globalFileIndex + ? { ...fp, status: "error" as const, error } + : fp + ) + ); + }, + }, + }); - clearInterval(progressInterval); - setUploadProgress(100); + if (uploadResult.success) { + console.log( + `[V2 Dialog] ✓ 그룹 ${groupKey} 업로드 완료 (${group.files.length}개 파일)` + ); + successCount += group.files.length; + + // 성공 결과 추가 + group.files.forEach((file) => { + results?.push({ + drawingNo: group.drawingNo, + revNo: group.revNo, + fileName: file.name, + success: true, + }); + }); + } else { + throw new Error(uploadResult.error || "파일 업로드 실패"); + } + } catch (error) { + console.error( + `[V2 Dialog] ✗ 그룹 ${groupKey} 업로드 실패 (${group.files.length}개 파일):`, + error + ); + const errorMessage = error instanceof Error ? error.message : "알 수 없는 오류"; + failCount += group.files.length; + + // 실패 결과 추가 + group.files.forEach((file) => { + results?.push({ + drawingNo: group.drawingNo, + revNo: group.revNo, + fileName: file.name, + success: false, + error: errorMessage, + }); + }); + } - console.log("[V2 Dialog] 업로드 완료:", result); + completedGroups++; + setUploadProgress(Math.round((completedGroups / uploadGroups.size) * 100)); + } + + console.log( + `[V2 Dialog] ========================================\n` + + `[V2 Dialog] 일괄 업로드 최종 결과\n` + + `[V2 Dialog] - 총 파일 수: ${validFiles.length}개\n` + + `[V2 Dialog] - 성공: ${successCount}개\n` + + `[V2 Dialog] - 실패: ${failCount}개\n` + + `[V2 Dialog] - 처리된 리비전: ${uploadGroups.size}개\n` + + `[V2 Dialog] ========================================` + ); + + const result: B4BulkUploadResult = { + success: successCount > 0, + successCount, + failCount, + results, + }; setUploadResult(result); setCurrentStep("complete"); @@ -506,7 +700,7 @@ export function B4BulkUploadDialogV2({ {/* 4단계: 업로드 진행 중 */} {currentStep === "uploading" && ( - <div className="space-y-6 py-8"> + <div className="space-y-6 py-4"> <div className="flex flex-col items-center"> <Loader2 className="h-12 w-12 animate-spin text-primary mb-4" /> <h3 className="text-lg font-semibold mb-2">{t("bulkUpload.uploading")}</h3> @@ -514,18 +708,22 @@ export function B4BulkUploadDialogV2({ {t("bulkUpload.uploadingWait")} </p> </div> + + {/* 전체 진행도 */} <div className="space-y-2"> <div className="flex justify-between text-sm"> <span>{t("bulkUpload.uploadProgress")}</span> <span>{uploadProgress}%</span> </div> <Progress value={uploadProgress} className="h-2" /> - {uploadProgress >= 90 && uploadProgress < 100 && ( - <p className="text-xs text-muted-foreground text-center pt-2"> - {t("bulkUpload.uploadingToServer")} - </p> - )} </div> + + {/* 개별 파일 진행도 리스트 */} + {fileProgresses.length > 0 && ( + <div className="border rounded-lg p-4 max-h-96 overflow-y-auto"> + <FileUploadProgressList fileProgresses={fileProgresses} /> + </div> + )} </div> )} |
