summaryrefslogtreecommitdiff
path: root/lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx')
-rw-r--r--lib/dolce/dialogs/b4-bulk-upload-dialog-v2.tsx258
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>
)}