From aa86729f9a2ab95346a2851e3837de1c367aae17 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 20 Jun 2025 11:37:31 +0000
Subject: (대표님) 20250620 작업사항
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/project-gtc/table/add-project-dialog.tsx | 296 +++++++++++++++++++++++++++
 1 file changed, 296 insertions(+)
 create mode 100644 lib/project-gtc/table/add-project-dialog.tsx

(limited to 'lib/project-gtc/table/add-project-dialog.tsx')

diff --git a/lib/project-gtc/table/add-project-dialog.tsx b/lib/project-gtc/table/add-project-dialog.tsx
new file mode 100644
index 00000000..616ab950
--- /dev/null
+++ b/lib/project-gtc/table/add-project-dialog.tsx
@@ -0,0 +1,296 @@
+"use client"
+
+import * as React from "react"
+import { useForm } from "react-hook-form"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { z } from "zod"
+import { toast } from "sonner"
+import { Upload, X } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+} from "@/components/ui/dialog"
+import {
+  Form,
+  FormControl,
+  FormField,
+  FormItem,
+  FormLabel,
+  FormMessage,
+} from "@/components/ui/form"
+import { ProjectSelector } from "@/components/ProjectSelector"
+import { uploadProjectGtcFile, getProjectsWithGtcFiles } from "../service"
+import { type Project } from "@/lib/rfqs/service"
+
+const addProjectSchema = z.object({
+  projectId: z.number().min(1, "프로젝트 선택은 필수입니다."),
+  gtcFile: z.instanceof(File, { message: "GTC 파일은 필수입니다." }).optional(),
+})
+
+type AddProjectFormValues = z.infer<typeof addProjectSchema>
+
+interface AddProjectDialogProps {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  onSuccess?: () => void
+}
+
+export function AddProjectDialog({
+  open,
+  onOpenChange,
+  onSuccess,
+}: AddProjectDialogProps) {
+  const [isLoading, setIsLoading] = React.useState(false)
+  const [selectedProject, setSelectedProject] = React.useState<Project | null>(null)
+  const [selectedFile, setSelectedFile] = React.useState<File | null>(null)
+  const [excludedProjectIds, setExcludedProjectIds] = React.useState<number[]>([])
+
+  const form = useForm<AddProjectFormValues>({
+    resolver: zodResolver(addProjectSchema),
+    defaultValues: {
+      projectId: 0,
+      gtcFile: undefined,
+    },
+  })
+
+  // 이미 GTC 파일이 등록된 프로젝트 ID 목록 로드
+  React.useEffect(() => {
+    async function loadExcludedProjects() {
+      try {
+        const excludedIds = await getProjectsWithGtcFiles();
+        setExcludedProjectIds(excludedIds);
+      } catch (error) {
+        console.error("제외할 프로젝트 목록 로드 오류:", error);
+      }
+    }
+    
+    if (open) {
+      loadExcludedProjects();
+    }
+  }, [open]);
+
+  // 프로젝트 선택 시 폼에 자동으로 채우기
+  const handleProjectSelect = (project: Project) => {
+    // 이미 GTC 파일이 등록된 프로젝트인지 확인
+    if (excludedProjectIds.includes(project.id)) {
+      toast.error("이미 GTC 파일이 등록된 프로젝트입니다.");
+      // 선택된 프로젝트 정보 초기화
+      setSelectedProject(null);
+      form.setValue("projectId", 0);
+      return;
+    }
+    
+    setSelectedProject(project)
+    form.setValue("projectId", project.id)
+  }
+
+  // 파일 선택 처리
+  const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
+    const file = event.target.files?.[0]
+    if (file) {
+      // PDF 파일만 허용
+      if (file.type !== 'application/pdf') {
+        toast.error("PDF 파일만 업로드 가능합니다.")
+        return
+      }
+
+      setSelectedFile(file)
+      form.setValue("gtcFile", file)
+    }
+  }
+
+  // 파일 제거
+  const handleRemoveFile = () => {
+    setSelectedFile(null)
+    form.setValue("gtcFile", undefined)
+    // input 요소의 value도 초기화
+    const fileInput = document.getElementById('gtc-file-input') as HTMLInputElement
+    if (fileInput) {
+      fileInput.value = ''
+    }
+  }
+
+  const onSubmit = async (data: AddProjectFormValues) => {
+    // 프로젝트가 선택되지 않았으면 에러
+    if (!selectedProject) {
+      toast.error("프로젝트를 선택해주세요.")
+      return
+    }
+
+    // 이미 GTC 파일이 등록된 프로젝트인지 다시 한번 확인
+    if (excludedProjectIds.includes(selectedProject.id)) {
+      toast.error("이미 GTC 파일이 등록된 프로젝트입니다.")
+      return
+    }
+
+    // GTC 파일이 없으면 에러
+    if (!data.gtcFile) {
+      toast.error("GTC 파일은 필수입니다.")
+      return
+    }
+
+    setIsLoading(true)
+    try {
+      // GTC 파일 업로드
+      const fileResult = await uploadProjectGtcFile(selectedProject.id, data.gtcFile)
+      
+      if (!fileResult.success) {
+        toast.error(fileResult.error || "GTC 파일 업로드에 실패했습니다.")
+        return
+      }
+
+      toast.success("GTC 파일이 성공적으로 업로드되었습니다.")
+      form.reset()
+      setSelectedProject(null)
+      setSelectedFile(null)
+      onOpenChange(false)
+      onSuccess?.()
+    } catch (error) {
+      console.error("GTC 파일 업로드 오류:", error)
+      toast.error("GTC 파일 업로드 중 오류가 발생했습니다.")
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  const handleOpenChange = (newOpen: boolean) => {
+    if (!newOpen) {
+      form.reset()
+      setSelectedProject(null)
+      setSelectedFile(null)
+    }
+    onOpenChange(newOpen)
+  }
+
+  return (
+    <Dialog open={open} onOpenChange={handleOpenChange}>
+      <DialogContent className="sm:max-w-[500px]">
+        <DialogHeader>
+          <DialogTitle>GTC 파일 추가</DialogTitle>
+          <DialogDescription>
+            기존 프로젝트를 선택하고 GTC 파일을 업로드합니다. (이미 GTC 파일이 등록된 프로젝트는 제외됩니다)
+          </DialogDescription>
+        </DialogHeader>
+
+        <Form {...form}>
+          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
+            {/* 프로젝트 선택 (필수) */}
+            <FormField
+              control={form.control}
+              name="projectId"
+              render={() => (
+                <FormItem>
+                  <FormLabel>프로젝트 선택 *</FormLabel>
+                  <FormControl>
+                    <ProjectSelector
+                      selectedProjectId={selectedProject?.id}
+                      onProjectSelect={handleProjectSelect}
+                      placeholder="프로젝트를 선택하세요..."
+                    />
+                  </FormControl>
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+
+            {/* 선택된 프로젝트 정보 표시 (읽기 전용) */}
+            {selectedProject && (
+              <div className="p-4 bg-muted rounded-lg space-y-2">
+                <h4 className="font-medium text-sm">선택된 프로젝트 정보</h4>
+                <div className="space-y-1 text-sm">
+                  <div className="flex justify-between">
+                    <span className="text-muted-foreground">프로젝트 코드:</span>
+                    <span className="font-medium">{selectedProject.projectCode}</span>
+                  </div>
+                  <div className="flex justify-between">
+                    <span className="text-muted-foreground">프로젝트명:</span>
+                    <span className="font-medium">{selectedProject.projectName}</span>
+                  </div>
+                </div>
+              </div>
+            )}
+
+            {/* GTC 파일 업로드 */}
+            <FormField
+              control={form.control}
+              name="gtcFile"
+              render={() => (
+                <FormItem>
+                  <FormLabel>GTC 파일 *</FormLabel>
+                  <div className="space-y-2">
+                    {!selectedFile ? (
+                      <div className="flex items-center justify-center w-full">
+                        <label
+                          htmlFor="gtc-file-input"
+                          className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100"
+                        >
+                          <div className="flex flex-col items-center justify-center pt-5 pb-6">
+                            <Upload className="w-8 h-8 mb-4 text-gray-500" />
+                            <p className="mb-2 text-sm text-gray-500">
+                              <span className="font-semibold">클릭하여 파일 선택</span> 또는 드래그 앤 드롭
+                            </p>
+                            <p className="text-xs text-gray-500">
+                              PDF 파일만
+                            </p>
+                          </div>
+                          <input
+                            id="gtc-file-input"
+                            type="file"
+                            className="hidden"
+                            accept=".pdf"
+                            onChange={handleFileSelect}
+                            disabled={isLoading}
+                          />
+                        </label>
+                      </div>
+                    ) : (
+                      <div className="flex items-center justify-between p-3 border rounded-lg bg-gray-50">
+                        <div className="flex items-center space-x-2">
+                          <Upload className="w-4 h-4 text-gray-500" />
+                          <span className="text-sm font-medium">{selectedFile.name}</span>
+                          <span className="text-xs text-gray-500">
+                            ({(selectedFile.size / 1024 / 1024).toFixed(2)} MB)
+                          </span>
+                        </div>
+                        <Button
+                          type="button"
+                          variant="ghost"
+                          size="sm"
+                          onClick={handleRemoveFile}
+                          disabled={isLoading}
+                        >
+                          <X className="w-4 h-4" />
+                        </Button>
+                      </div>
+                    )}
+                  </div>
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+
+            <DialogFooter>
+              <Button
+                type="button"
+                variant="outline"
+                onClick={() => handleOpenChange(false)}
+                disabled={isLoading}
+              >
+                취소
+              </Button>
+              <Button type="submit" disabled={isLoading || !selectedProject}>
+                {isLoading ? "업로드 중..." : "GTC 파일 업로드"}
+              </Button>
+            </DialogFooter>
+          </form>
+        </Form>
+      </DialogContent>
+    </Dialog>
+  )
+} 
\ No newline at end of file
-- 
cgit v1.2.3

