diff options
Diffstat (limited to 'lib/general-contract-template/template/update-generalContract-sheet.tsx')
| -rw-r--r-- | lib/general-contract-template/template/update-generalContract-sheet.tsx | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/lib/general-contract-template/template/update-generalContract-sheet.tsx b/lib/general-contract-template/template/update-generalContract-sheet.tsx new file mode 100644 index 00000000..9949d127 --- /dev/null +++ b/lib/general-contract-template/template/update-generalContract-sheet.tsx @@ -0,0 +1,314 @@ +"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 { Switch } from "@/components/ui/switch" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormDescription, +} from "@/components/ui/form" +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 { Input } from "@/components/ui/input" +import { updateTemplate } from "../service" +import { GeneralContractTemplate } from "@/db/schema" + +// 업데이트 스키마: 계약종류, 계약문서명, 법무검토, 파일(선택) +export const updateTemplateSchema = 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(), + 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(), +}); + +export type UpdateTemplateSchema = z.infer<typeof updateTemplateSchema> + +interface UpdateTemplateSheetProps + extends React.ComponentPropsWithRef<typeof Sheet> { + template: GeneralContractTemplate | null + onSuccess?: () => void +} + +export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTemplateSheetProps) { + const [isUpdatePending, startUpdateTransition] = React.useTransition() + const [selectedFile, setSelectedFile] = React.useState<File | null>(null) + + const form = useForm<UpdateTemplateSchema>({ + resolver: zodResolver(updateTemplateSchema), + defaultValues: { + contractTemplateType: template?.contractTemplateType ?? "", + contractTemplateName: template?.contractTemplateName ?? "", + legalReviewRequired: template?.legalReviewRequired ?? false, + }, + mode: "onChange" + }) + + // 파일 선택 핸들러 + const handleFileChange = (files: File[]) => { + if (files.length > 0) { + const file = files[0]; + setSelectedFile(file); + form.setValue("file", file); + } + }; + + // 템플릿 변경 시 폼 값 업데이트 + React.useEffect(() => { + if (template) { + form.reset({ + contractTemplateType: template.contractTemplateType ?? "", + contractTemplateName: template.contractTemplateName ?? "", + legalReviewRequired: template.legalReviewRequired ?? false, + }); + } + }, [template, form]); + + function onSubmit(input: UpdateTemplateSchema) { + startUpdateTransition(async () => { + if (!template) return + + // FormData 객체 생성하여 파일과 데이터를 함께 전송 + const formData = new FormData(); + formData.append("contractTemplateType", input.contractTemplateType); + formData.append("contractTemplateName", input.contractTemplateName); + formData.append("legalReviewRequired", input.legalReviewRequired.toString()); + // basic-contract와 동일하게 리비전은 서버에서 증가 처리 + + 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; + + return ( + <Sheet {...props}> + <SheetContent className="sm:max-w-[500px] h-[100vh] flex flex-col p-0"> + {/* 고정된 헤더 */} + <SheetHeader className="p-6 pb-4 border-b"> + <SheetTitle>일반계약 템플릿 수정</SheetTitle> + <SheetDescription>계약 기본정보를 수정하고 파일을 교체할 수 있습니다</SheetDescription> + </SheetHeader> + + {/* 스크롤 가능한 컨텐츠 영역 */} + <div className="flex-1 overflow-y-auto px-6"> + <Form {...form}> + <form + onSubmit={form.handleSubmit(onSubmit)} + className="space-y-6 py-4" + > + {/* 1. 계약 종류 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">계약 종류</CardTitle> + </CardHeader> + <CardContent> + <FormField + control={form.control} + name="contractTemplateType" + render={({ field }) => ( + <FormItem> + <FormLabel>계약 종류</FormLabel> + <Input + placeholder="2자리 영문 (예: LO)" + value={field.value} + onChange={(e) => field.onChange(e.target.value.toUpperCase().slice(0, 2))} + maxLength={2} + /> + <FormMessage /> + </FormItem> + )} + /> + </CardContent> + </Card> + + {/* 2. 계약 문서명 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">계약 문서명</CardTitle> + </CardHeader> + <CardContent> + <FormField + control={form.control} + name="contractTemplateName" + render={({ field }) => ( + <FormItem> + <FormLabel>계약 문서명</FormLabel> + <Input + placeholder="계약문서명을 입력하세요" + value={field.value} + onChange={field.onChange} + /> + <FormMessage /> + </FormItem> + )} + /> + </CardContent> + </Card> + + {/* 3. 법무 검토 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">법무 검토</CardTitle> + </CardHeader> + <CardContent> + <FormField + control={form.control} + name="legalReviewRequired" + render={({ field }) => ( + <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm"> + <div className="space-y-0.5"> + <FormLabel>법무검토 필요</FormLabel> + <FormDescription>법무팀 검토가 필요한 템플릿인지 설정</FormDescription> + </div> + <FormControl> + <Switch + checked={field.value} + onCheckedChange={field.onChange} + /> + </FormControl> + </FormItem> + )} + /> + </CardContent> + </Card> + + {/* 4. 파일 업데이트 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">파일 업데이트</CardTitle> + <CardDescription> + 새로운 템플릿 파일을 업로드하세요 + </CardDescription> + </CardHeader> + <CardContent> + <FormField + control={form.control} + name="file" + render={() => ( + <FormItem> + <FormLabel>새 템플릿 파일 (선택사항)</FormLabel> + <FormControl> + <Dropzone + onDrop={handleFileChange} + accept={{ + 'application/msword': ['.doc'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'] + }} + > + <DropzoneZone> + <DropzoneUploadIcon className="h-10 w-10 text-muted-foreground" /> + <DropzoneTitle> + {selectedFile + ? selectedFile.name + : "새 워드 파일을 드래그하세요"} + </DropzoneTitle> + <DropzoneDescription> + {selectedFile + ? `파일 크기: ${(selectedFile.size / (1024 * 1024)).toFixed(2)} MB` + : "또는 클릭하여 워드 파일(.doc, .docx)을 선택하세요 (최대 100MB)"} + </DropzoneDescription> + <DropzoneInput /> + </DropzoneZone> + </Dropzone> + </FormControl> + <FormDescription> + 파일을 업로드하지 않으면 기존 파일이 유지됩니다 + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> + </CardContent> + </Card> + </form> + </Form> + </div> + + {/* 고정된 푸터 */} + <SheetFooter className="p-6 pt-4 border-t"> + <SheetClose asChild> + <Button type="button" variant="outline"> + 취소 + </Button> + </SheetClose> + <Button + type="button" + onClick={form.handleSubmit(onSubmit)} + disabled={isUpdatePending} + > + {isUpdatePending && ( + <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" /> + )} + 업데이트 + </Button> + </SheetFooter> + </SheetContent> + </Sheet> + ) +}
\ No newline at end of file |
