"use client"; import * as React from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import * as z from "zod"; import { toast } from "sonner"; import { v4 as uuidv4 } from "uuid"; import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription, } from "@/components/ui/form"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import { Dropzone, DropzoneZone, DropzoneUploadIcon, DropzoneTitle, DropzoneDescription, DropzoneInput } from "@/components/ui/dropzone"; import { Progress } from "@/components/ui/progress"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Plus, X, FileText, AlertCircle } from "lucide-react"; import { useRouter } from "next/navigation"; import { createProjectDocTemplate } from "@/lib/project-doc-templates/service"; import { ProjectSelector } from "@/components/ProjectSelector"; import type { TemplateVariable } from "@/db/schema/project-doc-templates"; import type { Project } from "@/lib/rfqs/service"; // 기본 변수들 (읽기 전용) const DEFAULT_VARIABLES_DISPLAY: TemplateVariable[] = [ { name: "document_number", displayName: "문서번호", type: "text", required: true, description: "문서 고유 번호" }, { name: "project_code", displayName: "프로젝트 코드", type: "text", required: true, description: "프로젝트 식별 코드" }, { name: "project_name", displayName: "프로젝트명", type: "text", required: true, description: "프로젝트 이름" }, ]; const templateFormSchema = z.object({ templateName: z.string().min(1, "템플릿 이름을 입력해주세요."), templateCode: z.string().optional(), description: z.string().optional(), projectId: z.number({ required_error: "프로젝트를 선택해주세요.", }), customVariables: z.array(z.object({ name: z.string().min(1, "변수명을 입력해주세요."), displayName: z.string().min(1, "표시명을 입력해주세요."), type: z.enum(["text", "number", "date", "select"]), required: z.boolean(), defaultValue: z.string().optional(), description: z.string().optional(), })).default([]), file: z.instanceof(File, { message: "파일을 업로드해주세요.", }), }) .refine((data) => { if (data.file && data.file.size > 100 * 1024 * 1024) return false; return true; }, { message: "파일 크기는 100MB 이하여야 합니다.", path: ["file"], }) .refine((data) => { if (data.file) { const validTypes = [ 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ]; return validTypes.includes(data.file.type); } return true; }, { message: "워드 파일(.doc, .docx)만 업로드 가능합니다.", path: ["file"], }); type TemplateFormValues = z.infer; export function AddProjectDocTemplateDialog() { const [open, setOpen] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false); const [selectedFile, setSelectedFile] = React.useState(null); const [uploadProgress, setUploadProgress] = React.useState(0); const [showProgress, setShowProgress] = React.useState(false); const [selectedProject, setSelectedProject] = React.useState(null); const router = useRouter(); const form = useForm({ resolver: zodResolver(templateFormSchema), defaultValues: { templateName: "", templateCode: "", description: "", customVariables: [], }, mode: "onChange", }); // 프로젝트 선택 시 처리 const handleProjectSelect = (project: Project) => { setSelectedProject(project); form.setValue("projectId", project.id); // 템플릿 이름 자동 설정 (원하면) if (!form.getValues("templateName")) { form.setValue("templateName", `${project.projectCode} 벤더문서 커버 템플릿`); } }; const handleFileChange = (files: File[]) => { if (files.length > 0) { const file = files[0]; setSelectedFile(file); form.setValue("file", file); } }; // 사용자 정의 변수 추가 const addCustomVariable = () => { const currentVars = form.getValues("customVariables"); form.setValue("customVariables", [ ...currentVars, { name: "", displayName: "", type: "text", required: false, defaultValue: "", description: "", }, ]); }; // 사용자 정의 변수 제거 const removeCustomVariable = (index: number) => { const currentVars = form.getValues("customVariables"); form.setValue("customVariables", currentVars.filter((_, i) => i !== index)); }; // 청크 업로드 const CHUNK_SIZE = 1 * 1024 * 1024; const uploadFileInChunks = async (file: File, fileId: string) => { const totalChunks = Math.ceil(file.size / CHUNK_SIZE); setShowProgress(true); setUploadProgress(0); for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { const start = chunkIndex * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end); const formData = new FormData(); formData.append('chunk', chunk); formData.append('filename', file.name); formData.append('chunkIndex', chunkIndex.toString()); formData.append('totalChunks', totalChunks.toString()); formData.append('fileId', fileId); const response = await fetch('/api/upload/project-doc-template/chunk', { method: 'POST', body: formData, }); if (!response.ok) { throw new Error(`청크 업로드 실패: ${response.statusText}`); } const progress = Math.round(((chunkIndex + 1) / totalChunks) * 100); setUploadProgress(progress); const result = await response.json(); if (chunkIndex === totalChunks - 1) { return result; } } }; async function onSubmit(formData: TemplateFormValues) { setIsLoading(true); try { // 파일 업로드 const fileId = uuidv4(); const uploadResult = await uploadFileInChunks(formData.file, fileId); if (!uploadResult?.success) { throw new Error("파일 업로드에 실패했습니다."); } // 템플릿 생성 (고정값들 적용) const result = await createProjectDocTemplate({ templateName: formData.templateName, templateCode: formData.templateCode, description: formData.description, projectId: formData.projectId, templateType: "PROJECT", // 고정 documentType: "VENDOR_DOC_COVER", // 벤더문서 커버로 고정 filePath: uploadResult.filePath, fileName: uploadResult.fileName, fileSize: formData.file.size, mimeType: formData.file.type, variables: formData.customVariables, isPublic: false, // 고정 requiresApproval: false, // 고정 }); if (!result.success) { throw new Error(result.error || "템플릿 생성에 실패했습니다."); } toast.success("템플릿이 성공적으로 추가되었습니다."); form.reset(); setSelectedFile(null); setSelectedProject(null); setOpen(false); setShowProgress(false); router.refresh(); } catch (error) { console.error("Submit error:", error); toast.error(error instanceof Error ? error.message : "템플릿 추가 중 오류가 발생했습니다."); } finally { setIsLoading(false); } } const customVariables = form.watch("customVariables"); // 다이얼로그 닫을 때 폼 초기화 React.useEffect(() => { if (!open) { form.reset(); setSelectedFile(null); setSelectedProject(null); setShowProgress(false); setUploadProgress(0); } }, [open, form]); return ( {/* 헤더 - 고정 */} 프로젝트 벤더문서 커버 템플릿 추가 프로젝트별 벤더문서 커버 템플릿을 등록합니다. 기본 변수(document_number, project_code, project_name)는 자동으로 포함됩니다. {/* 본문 - 스크롤 영역 */}
{/* 프로젝트 선택 및 기본 정보 */} 기본 정보 {/* 프로젝트 선택 - 필수 */} ( 프로젝트 * 템플릿을 적용할 프로젝트를 선택하세요. )} /> {selectedProject && (

선택된 프로젝트: {selectedProject.projectCode} - {selectedProject.projectName}

)}
( 템플릿 이름 * )} /> ( 템플릿 코드 비워두면 자동으로 생성됩니다. )} />
( 설명