From 8547034e6d82e4d1184f35af2dbff67180d89dc8 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Wed, 26 Nov 2025 18:09:18 +0900
Subject: (김준회) dolce: 동기화 기능 추가, 로컬 다운로드, 삭제 추가, 동기화
 dialog 개선 등
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../dialogs/upload-files-to-detail-dialog-v2.tsx   | 247 +++++++++++++++++++++
 1 file changed, 247 insertions(+)
 create mode 100644 lib/dolce-v2/dialogs/upload-files-to-detail-dialog-v2.tsx

(limited to 'lib/dolce-v2/dialogs/upload-files-to-detail-dialog-v2.tsx')

diff --git a/lib/dolce-v2/dialogs/upload-files-to-detail-dialog-v2.tsx b/lib/dolce-v2/dialogs/upload-files-to-detail-dialog-v2.tsx
new file mode 100644
index 00000000..c59f6d78
--- /dev/null
+++ b/lib/dolce-v2/dialogs/upload-files-to-detail-dialog-v2.tsx
@@ -0,0 +1,247 @@
+"use client";
+
+import * as React from "react";
+import { useState } from "react";
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Upload, FolderOpen, Loader2, X, FileText, AlertCircle } from "lucide-react";
+import { toast } from "sonner";
+import { useTranslation } from "@/i18n/client";
+import { useFileUploadWithProgress } from "@/lib/dolce/hooks/use-file-upload-with-progress";
+import { uploadFilesToDetailDrawingV2 } from "@/lib/dolce-v2/actions";
+
+interface UploadFilesToDetailDialogV2Props {
+  open: boolean;
+  onOpenChange: (open: boolean) => void;
+  uploadId: string;
+  drawingNo: string;
+  revNo: string;
+  // [추가] 메타데이터 저장을 위한 추가 정보
+  drawingName?: string;
+  discipline?: string;
+  registerKind?: string;
+  
+  userId: string;
+  projectNo?: string; // V2에서는 projectNo 필요 (Sync List 조회 인덱스용)
+  vendorCode?: string; // V2: 동기화 필터링용
+  onUploadComplete?: () => void;
+  lng: string;
+}
+
+export function UploadFilesToDetailDialogV2({
+  open,
+  onOpenChange,
+  uploadId,
+  drawingNo,
+  revNo,
+  drawingName,
+  discipline,
+  registerKind,
+  userId,
+  projectNo,
+  vendorCode,
+  onUploadComplete,
+  lng,
+}: UploadFilesToDetailDialogV2Props) {
+  const { t } = useTranslation(lng, "dolce");
+  const [isUploading, setIsUploading] = useState(false);
+
+  // 파일 업로드 훅 사용 (UI용)
+  const {
+    files: selectedFiles,
+    removeFile,
+    clearFiles,
+    getRootProps,
+    getInputProps,
+    isDragActive,
+  } = useFileUploadWithProgress();
+
+  // 다이얼로그 닫을 때 초기화
+  React.useEffect(() => {
+    if (!open) {
+      clearFiles();
+    }
+  }, [open, clearFiles]);
+
+  // 업로드 처리
+  const handleUpload = async () => {
+    if (selectedFiles.length === 0) {
+      toast.error(t("uploadFilesDialog.selectFilesError"));
+      return;
+    }
+
+    setIsUploading(true);
+
+    try {
+      const formData = new FormData();
+      formData.append("uploadId", uploadId);
+      formData.append("userId", userId);
+      formData.append("fileCount", String(selectedFiles.length));
+      if (projectNo) formData.append("projectNo", projectNo);
+      if (vendorCode) formData.append("vendorCode", vendorCode);
+      
+      // 메타데이터 추가
+      formData.append("drawingNo", drawingNo);
+      formData.append("revNo", revNo);
+      if (drawingName) formData.append("drawingName", drawingName);
+      if (discipline) formData.append("discipline", discipline);
+      if (registerKind) formData.append("registerKind", registerKind);
+      
+      selectedFiles.forEach((file, index) => {
+          formData.append(`file_${index}`, file);
+      });
+
+      const result = await uploadFilesToDetailDrawingV2(formData);
+
+      if (result.success) {
+        toast.success(t("uploadFilesDialog.uploadSuccess", { count: selectedFiles.length }));
+        onOpenChange(false);
+        onUploadComplete?.();
+      } else {
+        toast.error(result.error || t("uploadFilesDialog.uploadError"));
+      }
+    } catch (error) {
+      console.error("업로드 실패:", error);
+      toast.error(
+        error instanceof Error ? error.message : t("uploadFilesDialog.uploadErrorMessage")
+      );
+    } finally {
+      setIsUploading(false);
+    }
+  };
+
+  return (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="max-w-2xl">
+        <DialogHeader>
+          <DialogTitle>{t("uploadFilesDialog.title")}</DialogTitle>
+          <DialogDescription>
+            {t("uploadFilesDialog.description", { drawingNo, revNo })}
+          </DialogDescription>
+        </DialogHeader>
+
+        <div className="space-y-4">
+          {/* 안내 메시지 */}
+          <Alert>
+            <AlertCircle className="h-4 w-4" />
+            <AlertDescription>
+              {t("uploadFilesDialog.alertMessage")}
+            </AlertDescription>
+          </Alert>
+
+          {/* 파일 선택 영역 */}
+          <div
+            {...getRootProps()}
+            className={`border-2 border-dashed rounded-lg p-8 transition-all duration-200 cursor-pointer ${
+              isDragActive
+                ? "border-primary bg-primary/5 scale-[1.02]"
+                : "border-muted-foreground/30 hover:border-muted-foreground/50"
+            }`}
+          >
+            <input {...getInputProps()} />
+            <div className="flex flex-col items-center justify-center">
+              <FolderOpen
+                className={`h-12 w-12 mb-3 transition-colors ${
+                  isDragActive ? "text-primary" : "text-muted-foreground"
+                }`}
+              />
+              <p
+                className={`text-sm transition-colors ${
+                  isDragActive
+                    ? "text-primary font-medium"
+                    : "text-muted-foreground"
+                }`}
+              >
+                {isDragActive
+                  ? t("uploadFilesDialog.dropHereText")
+                  : t("uploadFilesDialog.dragDropText")}
+              </p>
+              <p className="text-xs text-muted-foreground mt-1">
+                {t("uploadFilesDialog.fileInfo")}
+              </p>
+            </div>
+          </div>
+
+          {/* 선택된 파일 목록 */}
+          {selectedFiles.length > 0 && (
+            <div className="border rounded-lg p-4">
+                <>
+                  <div className="flex items-center justify-between mb-3">
+                    <h4 className="text-sm font-medium">
+                      {t("uploadFilesDialog.selectedFiles", { count: selectedFiles.length })}
+                    </h4>
+                    <Button
+                      variant="ghost"
+                      size="sm"
+                      onClick={clearFiles}
+                    >
+                      {t("uploadFilesDialog.removeAll")}
+                    </Button>
+                  </div>
+                  <div className="max-h-60 overflow-y-auto space-y-2">
+                    {selectedFiles.map((file, index) => (
+                      <div
+                        key={index}
+                        className="flex items-center justify-between p-2 rounded bg-muted/50"
+                      >
+                        <div className="flex items-center gap-2 flex-1 min-w-0">
+                          <FileText 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>
+                        </div>
+                        <Button
+                          variant="ghost"
+                          size="sm"
+                          onClick={() => removeFile(index)}
+                        >
+                          <X className="h-4 w-4" />
+                        </Button>
+                      </div>
+                    ))}
+                  </div>
+                </>
+            </div>
+          )}
+        </div>
+
+        <DialogFooter>
+          <Button
+            variant="outline"
+            onClick={() => onOpenChange(false)}
+            disabled={isUploading}
+          >
+            {t("uploadFilesDialog.cancelButton")}
+          </Button>
+          <Button
+            onClick={handleUpload}
+            disabled={selectedFiles.length === 0 || isUploading}
+          >
+            {isUploading ? (
+              <>
+                <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+                {t("uploadFilesDialog.uploadingButton")}
+              </>
+            ) : (
+              <>
+                <Upload className="mr-2 h-4 w-4" />
+                {t("uploadFilesDialog.uploadButton", { count: selectedFiles.length })}
+              </>
+            )}
+          </Button>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  );
+}
-- 
cgit v1.2.3

