"use client" import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { Loader } from "lucide-react" import { useForm } from "react-hook-form" import { toast } from "sonner" import * as z from "zod" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Switch } from "@/components/ui/switch" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription, } from "@/components/ui/form" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet" import { Dropzone, DropzoneZone, DropzoneUploadIcon, DropzoneTitle, DropzoneDescription, DropzoneInput } from "@/components/ui/dropzone" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Separator } from "@/components/ui/separator" import { Badge } from "@/components/ui/badge" import { updateTemplate } from "../service" import { BasicContractTemplate } from "@/db/schema" import { BUSINESS_UNITS, scopeHelpers } from "@/config/basicContractColumnsConfig" // 템플릿 이름 옵션 정의 const TEMPLATE_NAME_OPTIONS = [ "준법서약 (한글)", "준법서약 (영문)", "기술자료 요구서", "비밀유지 계약서", "표준하도급기본 계약서", "General GTC", "안전보건관리 약정서", "동반성장", "윤리규범 준수 서약서", "기술자료 동의서", "내국신용장 미개설 합의서", "직납자재 하도급대급등 연동제 의향서" ] as const; // 업데이트 템플릿 스키마 정의 (리비전 필드 제거, 워드파일만 허용) export const updateTemplateSchema = z.object({ templateName: z.enum(TEMPLATE_NAME_OPTIONS, { required_error: "템플릿 이름을 선택해주세요.", }), legalReviewRequired: z.boolean(), // 적용 범위 shipBuildingApplicable: z.boolean(), windApplicable: z.boolean(), pcApplicable: z.boolean(), nbApplicable: z.boolean(), rcApplicable: z.boolean(), gyApplicable: z.boolean(), sysApplicable: z.boolean(), infraApplicable: z.boolean(), file: z .instanceof(File, { message: "파일을 업로드해주세요." }) .refine((file) => file.size <= 100 * 1024 * 1024, { message: "파일 크기는 100MB 이하여야 합니다.", }) .refine( (file) => file.type === 'application/msword' || file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', { message: "워드 파일(.doc, .docx)만 업로드 가능합니다." } ) .optional(), }).refine((data) => { // 적어도 하나의 적용 범위는 선택되어야 함 const hasAnyScope = BUSINESS_UNITS.some(unit => data[unit.key as keyof typeof data] as boolean ); return hasAnyScope; }, { message: "적어도 하나의 적용 범위를 선택해야 합니다.", path: ["shipBuildingApplicable"], }); export type UpdateTemplateSchema = z.infer interface UpdateTemplateSheetProps extends React.ComponentPropsWithRef { template: BasicContractTemplate | null onSuccess?: () => void } export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTemplateSheetProps) { const [isUpdatePending, startUpdateTransition] = React.useTransition() const [selectedFile, setSelectedFile] = React.useState(null) const form = useForm({ resolver: zodResolver(updateTemplateSchema), defaultValues: { templateName: template?.templateName as typeof TEMPLATE_NAME_OPTIONS[number] ?? "준법서약 (한글)", legalReviewRequired: template?.legalReviewRequired ?? false, shipBuildingApplicable: template?.shipBuildingApplicable ?? false, windApplicable: template?.windApplicable ?? false, pcApplicable: template?.pcApplicable ?? false, nbApplicable: template?.nbApplicable ?? false, rcApplicable: template?.rcApplicable ?? false, gyApplicable: template?.gyApplicable ?? false, sysApplicable: template?.sysApplicable ?? false, infraApplicable: template?.infraApplicable ?? false, }, mode: "onChange" }) // 파일 선택 핸들러 const handleFileChange = (files: File[]) => { if (files.length > 0) { const file = files[0]; setSelectedFile(file); form.setValue("file", file); } }; // 모든 적용 범위 선택/해제 const handleSelectAllScopes = (checked: boolean | "indeterminate") => { const value = checked === true; BUSINESS_UNITS.forEach(unit => { form.setValue(unit.key as keyof UpdateTemplateSchema, value); }); }; // 템플릿 변경 시 폼 값 업데이트 React.useEffect(() => { if (template) { form.reset({ templateName: template.templateName as typeof TEMPLATE_NAME_OPTIONS[number], legalReviewRequired: template.legalReviewRequired ?? false, shipBuildingApplicable: template.shipBuildingApplicable ?? false, windApplicable: template.windApplicable ?? false, pcApplicable: template.pcApplicable ?? false, nbApplicable: template.nbApplicable ?? false, rcApplicable: template.rcApplicable ?? false, gyApplicable: template.gyApplicable ?? false, sysApplicable: template.sysApplicable ?? false, infraApplicable: template.infraApplicable ?? false, }); } }, [template, form]); // 현재 선택된 적용 범위 수 const selectedScopesCount = BUSINESS_UNITS.filter(unit => form.watch(unit.key as keyof UpdateTemplateSchema) ).length; function onSubmit(input: UpdateTemplateSchema) { startUpdateTransition(async () => { if (!template) return // FormData 객체 생성하여 파일과 데이터를 함께 전송 const formData = new FormData(); formData.append("templateName", input.templateName); formData.append("legalReviewRequired", input.legalReviewRequired.toString()); // 적용 범위 추가 BUSINESS_UNITS.forEach(unit => { const value = input[unit.key as keyof UpdateTemplateSchema] as boolean; formData.append(unit.key, value.toString()); }); if (input.file) { formData.append("file", input.file); } try { // 서비스 함수 호출 const { error } = await updateTemplate({ id: template.id, formData, }); if (error) { toast.error(error); return; } form.reset(); setSelectedFile(null); props.onOpenChange?.(false); toast.success("템플릿이 성공적으로 업데이트되었습니다."); onSuccess?.(); } catch (error) { console.error("Update error:", error); toast.error("템플릿 업데이트 중 오류가 발생했습니다."); } }); } if (!template) return null; const scopeSelected = BUSINESS_UNITS.some( (unit) => form.watch(unit.key as keyof UpdateTemplateSchema) ); const isDisabled = isUpdatePending || !form.watch("templateName") || !scopeSelected; return ( {/* 고정된 헤더 */} 템플릿 업데이트 템플릿 정보를 수정하고 변경사항을 저장하세요 * 표시된 항목은 필수 입력사항입니다. {/* 스크롤 가능한 컨텐츠 영역 */}
{/* 기본 정보 */} 기본 정보 현재 리비전: v{template.revision}
현재 적용 범위: {scopeHelpers.getScopeDisplayText(template)}
( 템플릿 이름 * 미리 정의된 템플릿 중에서 선택 )} />
(
법무검토 필요 법무팀 검토가 필요한 템플릿인지 설정
)} />
{/* 적용 범위 */} 적용 범위 * 이 템플릿이 적용될 사업부를 선택하세요. ({selectedScopesCount}개 선택됨)
{BUSINESS_UNITS.map((unit) => ( (
{unit.label}
)} /> ))}
{form.formState.errors.shipBuildingApplicable && (

{form.formState.errors.shipBuildingApplicable.message}

)}
{/* 파일 업데이트 */} 파일 업데이트 현재 파일: {template.fileName} ( 템플릿 파일 (선택사항) {selectedFile ? selectedFile.name : "새 워드 파일을 드래그하세요 (선택사항)"} {selectedFile ? `파일 크기: ${(selectedFile.size / (1024 * 1024)).toFixed(2)} MB` : "또는 클릭하여 워드 파일(.doc, .docx)을 선택하세요 (최대 100MB)"} )} />
{/* 고정된 푸터 */}
) }