summaryrefslogtreecommitdiff
path: root/lib/project-gtc/table/add-project-dialog.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-20 11:37:31 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-20 11:37:31 +0000
commitaa86729f9a2ab95346a2851e3837de1c367aae17 (patch)
treeb601b18b6724f2fb449c7fa9ea50cbd652a8077d /lib/project-gtc/table/add-project-dialog.tsx
parent95bbe9c583ff841220da1267630e7b2025fc36dc (diff)
(대표님) 20250620 작업사항
Diffstat (limited to 'lib/project-gtc/table/add-project-dialog.tsx')
-rw-r--r--lib/project-gtc/table/add-project-dialog.tsx296
1 files changed, 296 insertions, 0 deletions
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