"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"; // 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 { Switch } from "@/components/ui/switch"; import { Input } from "@/components/ui/input"; 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 { useRouter } from "next/navigation"; import { createTemplateFromUpload } from "../actions"; // (불필요한 템플릿/프로젝트 로딩 로직 제거) const templateFormSchema = z.object({ contractTemplateType: z.string().min(2, "계약 종류는 2자리 영문입니다.").max(2, "계약 종류는 2자리 영문입니다.").regex(/^[A-Za-z]{2}$/, "영문 2자리로 입력하세요."), contractTemplateName: z.string().min(1, "계약 문서명을 입력하세요."), legalReviewRequired: z.boolean().default(false), 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 isValidType = data.file.type === 'application/msword' || data.file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; return isValidType; } return true; }, { message: "워드 파일(.doc, .docx)만 업로드 가능합니다.", path: ["file"], }); type TemplateFormValues = z.infer; export function AddGeneralContractTemplateDialog() { 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 router = useRouter(); // 기본값 const defaultValues: Partial = { contractTemplateType: "", contractTemplateName: "", legalReviewRequired: false, }; const form = useForm({ resolver: zodResolver(templateFormSchema), defaultValues, mode: "onChange", }); // (불필요한 데이터 로딩 제거) const handleFileChange = (files: File[]) => { if (files.length > 0) { const file = files[0]; setSelectedFile(file); form.setValue("file", file); } }; // (프로젝트/템플릿 관련 핸들러 제거) // 청크 업로드 설정 (basic과 동일 패턴) 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/generalContract/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 { // 파일 업로드 (청크 업로드 → 마지막 청크에서 filePath 반환) const { v4: uuidv4 } = await import('uuid'); const fileId = uuidv4(); const uploadResult = await uploadFileInChunks(formData.file, fileId); if (!uploadResult?.success) { throw new Error("파일 업로드에 실패했습니다."); } // 업로드 완료 후 DB 저장 API 호출 (basic과 동일 플로우) const saveResponse = await fetch('/api/upload/generalContract/complete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contractTemplateType: formData.contractTemplateType, contractTemplateName: formData.contractTemplateName, legalReviewRequired: formData.legalReviewRequired, revision: 1, status: 'ACTIVE', fileName: uploadResult.fileName, filePath: uploadResult.filePath, }), }); const saveResult = await saveResponse.json(); if (!saveResult?.success) { throw new Error(saveResult?.error || '템플릿 정보 저장에 실패했습니다.'); } toast.success('템플릿이 성공적으로 추가되었습니다.'); form.reset(); setSelectedFile(null); setOpen(false); setShowProgress(false); router.refresh(); } catch (error) { console.error("Submit error:", error); toast.error(error instanceof Error ? error.message : "템플릿 추가 중 오류가 발생했습니다."); } finally { setIsLoading(false); } } React.useEffect(() => { if (!open) { form.reset(); setSelectedFile(null); setShowProgress(false); setUploadProgress(0); } }, [open, form]); function handleDialogOpenChange(nextOpen: boolean) { if (!nextOpen) { form.reset(); } setOpen(nextOpen); } // (이전 필드 watch 제거) const isSubmitDisabled = isLoading || !form.watch("contractTemplateType") || !form.watch("contractTemplateName") || !form.watch("file"); return ( 신규등록 - 일반계약 표준양식 계약 종류, 계약 문서명, 법무 검토, 첨부파일을 입력하세요. * 표시된 항목은 필수 입력사항입니다.
계약 종류 ( 계약 종류 * field.onChange(e.target.value.toUpperCase().slice(0, 2))} maxLength={2} /> )} /> 계약 문서명 ( 계약 문서명 * )} /> 법무 검토 (
법무검토 필요 법무팀 검토가 필요한 템플릿인지 설정
)} />
파일 업로드 템플릿 파일을 업로드하세요 ( 템플릿 파일 * {selectedFile ? selectedFile.name : "워드 파일을 여기에 드래그하세요"} {selectedFile ? `파일 크기: ${(selectedFile.size / (1024 * 1024)).toFixed(2)} MB` : "또는 클릭하여 워드 파일(.doc, .docx)을 선택하세요 (최대 100MB)"} )} /> {showProgress && (
업로드 진행률 {uploadProgress}%
)}
); }