From 26365ef08588d53b8c5d9c7cfaefb244536e6743 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Mon, 24 Nov 2025 10:41:46 +0900
Subject: (김준회) 돌체 재개발 - 2차: 업로드 개선, 다운로드 오류 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/dolce/dialogs/add-detail-drawing-dialog.tsx | 129 +++++++++++++++---------
 1 file changed, 82 insertions(+), 47 deletions(-)

(limited to 'lib/dolce/dialogs/add-detail-drawing-dialog.tsx')

diff --git a/lib/dolce/dialogs/add-detail-drawing-dialog.tsx b/lib/dolce/dialogs/add-detail-drawing-dialog.tsx
index 290a226b..34d06368 100644
--- a/lib/dolce/dialogs/add-detail-drawing-dialog.tsx
+++ b/lib/dolce/dialogs/add-detail-drawing-dialog.tsx
@@ -1,7 +1,6 @@
 "use client";
 
-import { useState, useCallback } from "react";
-import { useDropzone } from "react-dropzone";
+import { useState } from "react";
 import {
   Dialog,
   DialogContent,
@@ -22,8 +21,11 @@ import {
 import { Alert, AlertDescription } from "@/components/ui/alert";
 import { Upload, X, FileIcon, Info } from "lucide-react";
 import { toast } from "sonner";
-import { UnifiedDwgReceiptItem, editDetailDwgReceipt, uploadFilesToDetailDrawing } from "../actions";
+import { UnifiedDwgReceiptItem, editDetailDwgReceipt } from "../actions";
 import { v4 as uuidv4 } from "uuid";
+import { useFileUploadWithProgress } from "../hooks/use-file-upload-with-progress";
+import { uploadFilesWithProgress } from "../utils/upload-with-progress";
+import { FileUploadProgressList } from "../components/file-upload-progress-list";
 
 interface AddDetailDrawingDialogProps {
   open: boolean;
@@ -80,30 +82,26 @@ export function AddDetailDrawingDialog({
   const [drawingUsage, setDrawingUsage] = useState<string>("");
   const [registerKind, setRegisterKind] = useState<string>("");
   const [revision, setRevision] = useState<string>("");
-  const [files, setFiles] = useState<File[]>([]);
   const [isSubmitting, setIsSubmitting] = useState(false);
 
-  // 파일 드롭 핸들러
-  const onDrop = useCallback((acceptedFiles: File[]) => {
-    setFiles((prev) => [...prev, ...acceptedFiles]);
-  }, []);
-
-  const { getRootProps, getInputProps, isDragActive } = useDropzone({
-    onDrop,
-    multiple: true,
-  });
-
-  // 파일 제거
-  const removeFile = (index: number) => {
-    setFiles((prev) => prev.filter((_, i) => i !== index));
-  };
+  // 파일 업로드 훅 사용 (진행도 추적)
+  const {
+    fileProgresses,
+    files,
+    removeFile,
+    clearFiles,
+    updateFileProgress,
+    getRootProps,
+    getInputProps,
+    isDragActive,
+  } = useFileUploadWithProgress();
 
   // 폼 초기화
   const resetForm = () => {
     setDrawingUsage("");
     setRegisterKind("");
     setRevision("");
-    setFiles([]);
+    clearFiles();
   };
 
   // 제출
@@ -169,16 +167,27 @@ export function AddDetailDrawingDialog({
         if (files.length > 0) {
           toast.info(`${files.length}개 파일 업로드를 진행합니다...`);
           
-          const formData = new FormData();
-          formData.append("uploadId", uploadId);
-          formData.append("userId", userId);
-          formData.append("fileCount", String(files.length));
-
-          files.forEach((file, index) => {
-            formData.append(`file_${index}`, file);
+          // 모든 파일 상태를 uploading으로 변경
+          files.forEach((_, index) => {
+            updateFileProgress(index, 0, "uploading");
           });
 
-          const uploadResult = await uploadFilesToDetailDrawing(formData);
+          const uploadResult = await uploadFilesWithProgress({
+            uploadId,
+            userId,
+            files,
+            callbacks: {
+              onProgress: (fileIndex, progress) => {
+                updateFileProgress(fileIndex, progress, "uploading");
+              },
+              onFileComplete: (fileIndex) => {
+                updateFileProgress(fileIndex, 100, "completed");
+              },
+              onFileError: (fileIndex, error) => {
+                updateFileProgress(fileIndex, 0, "error", error);
+              },
+            },
+          });
           
           if (uploadResult.success) {
             toast.success(`상세도면 추가 및 ${uploadResult.uploadedCount}개 파일 업로드 완료`);
@@ -189,8 +198,10 @@ export function AddDetailDrawingDialog({
           toast.success("상세도면이 추가되었습니다");
         }
         
+        // API 호출 성공 시 무조건 다이얼로그 닫기 (파일 업로드 성공 여부와 무관)
         resetForm();
         onComplete();
+        onOpenChange(false);
       } else {
         toast.error("상세도면 추가에 실패했습니다");
       }
@@ -318,7 +329,7 @@ export function AddDetailDrawingDialog({
                       파일을 드래그하거나 클릭하여 선택
                     </p>
                     <p className="text-xs text-muted-foreground">
-                      여러 파일을 한 번에 업로드할 수 있습니다
+                      여러 파일을 한 번에 업로드할 수 있습니다 (최대 1GB/파일)
                     </p>
                   </div>
                 </div>
@@ -337,25 +348,49 @@ export function AddDetailDrawingDialog({
             {/* 선택된 파일 목록 */}
             {files.length > 0 && (
               <div className="space-y-2 mt-4">
-                {files.map((file, index) => (
-                  <div
-                    key={index}
-                    className="flex items-center gap-2 p-2 border rounded-lg"
-                  >
-                    <FileIcon className="h-4 w-4 text-muted-foreground" />
-                    <span className="flex-1 text-sm truncate">{file.name}</span>
-                    <span className="text-xs text-muted-foreground">
-                      {(file.size / 1024).toFixed(2)} KB
-                    </span>
-                    <Button
-                      variant="ghost"
-                      size="icon"
-                      onClick={() => removeFile(index)}
-                    >
-                      <X className="h-4 w-4" />
-                    </Button>
-                  </div>
-                ))}
+                {isSubmitting ? (
+                  // 업로드 중: 진행도 표시
+                  <FileUploadProgressList fileProgresses={fileProgresses} />
+                ) : (
+                  // 대기 중: 삭제 버튼 표시
+                  <>
+                    <div className="flex items-center justify-between mb-2">
+                      <h4 className="text-sm font-medium">
+                        선택된 파일 ({files.length}개)
+                      </h4>
+                      <Button
+                        variant="ghost"
+                        size="sm"
+                        onClick={clearFiles}
+                      >
+                        전체 제거
+                      </Button>
+                    </div>
+                    <div className="max-h-48 overflow-auto space-y-2">
+                      {files.map((file, index) => (
+                        <div
+                          key={index}
+                          className="flex items-center gap-2 p-2 border rounded-lg bg-muted/50"
+                        >
+                          <FileIcon className="h-4 w-4 text-muted-foreground shrink-0" />
+                          <div className="flex-1 min-w-0">
+                            <p className="text-sm truncate">{file.name}</p>
+                            <p className="text-xs text-muted-foreground">
+                              {(file.size / 1024 / 1024).toFixed(2)} MB
+                            </p>
+                          </div>
+                          <Button
+                            variant="ghost"
+                            size="sm"
+                            onClick={() => removeFile(index)}
+                          >
+                            <X className="h-4 w-4" />
+                          </Button>
+                        </div>
+                      ))}
+                    </div>
+                  </>
+                )}
               </div>
             )}
           </div>
-- 
cgit v1.2.3

