diff options
Diffstat (limited to 'lib/basic-contract/template/update-basicContract-sheet.tsx')
| -rw-r--r-- | lib/basic-contract/template/update-basicContract-sheet.tsx | 106 |
1 files changed, 43 insertions, 63 deletions
diff --git a/lib/basic-contract/template/update-basicContract-sheet.tsx b/lib/basic-contract/template/update-basicContract-sheet.tsx index 88783461..66037601 100644 --- a/lib/basic-contract/template/update-basicContract-sheet.tsx +++ b/lib/basic-contract/template/update-basicContract-sheet.tsx @@ -36,7 +36,6 @@ import { SheetHeader, SheetTitle, } from "@/components/ui/sheet" -import { Input } from "@/components/ui/input" import { Dropzone, DropzoneZone, @@ -47,14 +46,16 @@ import { } 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", @@ -66,14 +67,13 @@ const TEMPLATE_NAME_OPTIONS = [ "직납자재 하도급대급등 연동제 의향서" ] as const; -// 업데이트 템플릿 스키마 정의 (templateCode, status 제거, 워드파일만 허용) +// 업데이트 템플릿 스키마 정의 (리비전 필드 제거, 워드파일만 허용) export const updateTemplateSchema = z.object({ templateName: z.enum(TEMPLATE_NAME_OPTIONS, { required_error: "템플릿 이름을 선택해주세요.", }), - revision: z.coerce.number().int().min(1, "리비전은 1 이상이어야 합니다."), legalReviewRequired: z.boolean(), - + // 적용 범위 shipBuildingApplicable: z.boolean(), windApplicable: z.boolean(), @@ -83,22 +83,22 @@ export const updateTemplateSchema = z.object({ 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) => + file.type === 'application/msword' || file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', { message: "워드 파일(.doc, .docx)만 업로드 가능합니다." } ) .optional(), }).refine((data) => { // 적어도 하나의 적용 범위는 선택되어야 함 - const hasAnyScope = BUSINESS_UNITS.some(unit => + const hasAnyScope = BUSINESS_UNITS.some(unit => data[unit.key as keyof typeof data] as boolean ); return hasAnyScope; @@ -122,8 +122,7 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem const form = useForm<UpdateTemplateSchema>({ resolver: zodResolver(updateTemplateSchema), defaultValues: { - templateName: template?.templateName as typeof TEMPLATE_NAME_OPTIONS[number] ?? "준법서약", - revision: template?.revision ?? 1, + templateName: template?.templateName as typeof TEMPLATE_NAME_OPTIONS[number] ?? "준법서약 (한글)", legalReviewRequired: template?.legalReviewRequired ?? false, shipBuildingApplicable: template?.shipBuildingApplicable ?? false, windApplicable: template?.windApplicable ?? false, @@ -147,9 +146,10 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem }; // 모든 적용 범위 선택/해제 - const handleSelectAllScopes = (checked: boolean) => { + const handleSelectAllScopes = (checked: boolean | "indeterminate") => { + const value = checked === true; BUSINESS_UNITS.forEach(unit => { - form.setValue(unit.key as keyof UpdateTemplateSchema, checked); + form.setValue(unit.key as keyof UpdateTemplateSchema, value); }); }; @@ -158,7 +158,6 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem if (template) { form.reset({ templateName: template.templateName as typeof TEMPLATE_NAME_OPTIONS[number], - revision: template.revision ?? 1, legalReviewRequired: template.legalReviewRequired ?? false, shipBuildingApplicable: template.shipBuildingApplicable ?? false, windApplicable: template.windApplicable ?? false, @@ -173,7 +172,7 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem }, [template, form]); // 현재 선택된 적용 범위 수 - const selectedScopesCount = BUSINESS_UNITS.filter(unit => + const selectedScopesCount = BUSINESS_UNITS.filter(unit => form.watch(unit.key as keyof UpdateTemplateSchema) ).length; @@ -181,22 +180,21 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem startUpdateTransition(async () => { if (!template) return - // FormData 객체 생성하여 파일과 데이터를 함께 전송 (templateCode, status 제거) + // FormData 객체 생성하여 파일과 데이터를 함께 전송 const formData = new FormData(); formData.append("templateName", input.templateName); - formData.append("revision", input.revision.toString()); 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({ @@ -223,6 +221,15 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem 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 ( <Sheet {...props}> <SheetContent className="sm:max-w-[600px] h-[100vh] flex flex-col p-0"> @@ -234,7 +241,7 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem <span className="text-red-500 mt-1 block text-sm">* 표시된 항목은 필수 입력사항입니다.</span> </SheetDescription> </SheetHeader> - + {/* 스크롤 가능한 컨텐츠 영역 */} <div className="flex-1 overflow-y-auto px-6"> <Form {...form}> @@ -247,11 +254,13 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem <CardHeader> <CardTitle className="text-lg">기본 정보</CardTitle> <CardDescription> + 현재 리비전: <Badge variant="outline">v{template.revision}</Badge> + <br /> 현재 적용 범위: {scopeHelpers.getScopeDisplayText(template)} </CardDescription> </CardHeader> <CardContent className="space-y-4"> - <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <div className="grid grid-cols-1 gap-4"> <FormField control={form.control} name="templateName" @@ -283,32 +292,6 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem </FormItem> )} /> - - <FormField - control={form.control} - name="revision" - render={({ field }) => ( - <FormItem> - <FormLabel>리비전</FormLabel> - <FormControl> - <Input - type="number" - min="1" - {...field} - onChange={(e) => field.onChange(parseInt(e.target.value) || 1)} - /> - </FormControl> - <FormDescription> - 템플릿 버전을 업데이트하세요. - <br /> - <span className="text-xs text-muted-foreground"> - 동일한 템플릿 이름의 리비전은 중복될 수 없습니다. - </span> - </FormDescription> - <FormMessage /> - </FormItem> - )} - /> </div> <FormField @@ -346,7 +329,7 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem </CardHeader> <CardContent className="space-y-4"> <div className="flex items-center space-x-2"> - <Checkbox + <Checkbox id="select-all" checked={selectedScopesCount === BUSINESS_UNITS.length} onCheckedChange={handleSelectAllScopes} @@ -355,9 +338,9 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem 전체 선택 </label> </div> - + <Separator /> - + <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> {BUSINESS_UNITS.map((unit) => ( <FormField @@ -382,7 +365,7 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem /> ))} </div> - + {form.formState.errors.shipBuildingApplicable && ( <p className="text-sm text-destructive"> {form.formState.errors.shipBuildingApplicable.message} @@ -417,13 +400,13 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem <DropzoneZone> <DropzoneUploadIcon className="h-10 w-10 text-muted-foreground" /> <DropzoneTitle> - {selectedFile - ? selectedFile.name + {selectedFile + ? selectedFile.name : "새 워드 파일을 드래그하세요 (선택사항)"} </DropzoneTitle> <DropzoneDescription> - {selectedFile - ? `파일 크기: ${(selectedFile.size / (1024 * 1024)).toFixed(2)} MB` + {selectedFile + ? `파일 크기: ${(selectedFile.size / (1024 * 1024)).toFixed(2)} MB` : "또는 클릭하여 워드 파일(.doc, .docx)을 선택하세요 (최대 100MB)"} </DropzoneDescription> <DropzoneInput /> @@ -447,16 +430,13 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem 취소 </Button> </SheetClose> - <Button + <Button type="button" onClick={form.handleSubmit(onSubmit)} - disabled={isUpdatePending || !form.formState.isValid} + disabled={isDisabled} > {isUpdatePending && ( - <Loader - className="mr-2 size-4 animate-spin" - aria-hidden="true" - /> + <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" /> )} 저장 </Button> |
