diff options
Diffstat (limited to 'lib/basic-contract/template/update-basicContract-sheet.tsx')
| -rw-r--r-- | lib/basic-contract/template/update-basicContract-sheet.tsx | 426 |
1 files changed, 297 insertions, 129 deletions
diff --git a/lib/basic-contract/template/update-basicContract-sheet.tsx b/lib/basic-contract/template/update-basicContract-sheet.tsx index 2c6efc9b..810e1b77 100644 --- a/lib/basic-contract/template/update-basicContract-sheet.tsx +++ b/lib/basic-contract/template/update-basicContract-sheet.tsx @@ -8,6 +8,8 @@ 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, @@ -43,23 +45,44 @@ import { 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" -// 업데이트 템플릿 스키마 정의 (유효기간 필드 추가) +// 업데이트 템플릿 스키마 정의 export const updateTemplateSchema = z.object({ + templateCode: z.string().min(1, "템플릿 코드는 필수입니다."), // readonly로 처리 templateName: z.string().min(1, "템플릿 이름은 필수입니다."), - validityPeriod: z.coerce - .number({ invalid_type_error: "유효기간은 숫자여야 합니다." }) - .int("유효기간은 정수여야 합니다.") - .min(1, "유효기간은 최소 1개월 이상이어야 합니다.") - .max(120, "유효기간은 최대 120개월(10년)을 초과할 수 없습니다.") - .default(12), - status: z.enum(["ACTIVE", "INACTIVE"], { + revision: z.coerce.number().int().min(1, "리비전은 1 이상이어야 합니다."), + 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(), + + status: z.enum(["ACTIVE", "DISPOSED"], { required_error: "상태는 필수 선택사항입니다.", }), file: z.instanceof(File, { message: "파일을 업로드해주세요." }).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<typeof updateTemplateSchema> @@ -73,15 +96,22 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem const [isUpdatePending, startUpdateTransition] = React.useTransition() const [selectedFile, setSelectedFile] = React.useState<File | null>(null) - // 템플릿 데이터 확인을 위한 로그 - console.log(template) - const form = useForm<UpdateTemplateSchema>({ resolver: zodResolver(updateTemplateSchema), defaultValues: { + templateCode: template?.templateCode ?? "", templateName: template?.templateName ?? "", - validityPeriod: template?.validityPeriod ?? 12, // 기본값 12개월 - status: (template?.status as "ACTIVE" | "INACTIVE") || "ACTIVE" + revision: template?.revision ?? 1, + 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, + status: (template?.status as "ACTIVE" | "DISPOSED") || "ACTIVE" }, mode: "onChange" }) @@ -95,26 +125,38 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem } }; + // 모든 적용 범위 선택/해제 + const handleSelectAllScopes = (checked: boolean) => { + BUSINESS_UNITS.forEach(unit => { + form.setValue(unit.key as keyof UpdateTemplateSchema, checked); + }); + }; + // 템플릿 변경 시 폼 값 업데이트 React.useEffect(() => { if (template) { form.reset({ + templateCode: template.templateCode, templateName: template.templateName, - validityPeriod: template.validityPeriod ?? 12, // 기존 값이 없으면 기본값 12개월 - status: template.status as "ACTIVE" | "INACTIVE", + revision: template.revision ?? 1, + 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, + status: template.status as "ACTIVE" | "DISPOSED", }); } }, [template, form]); - // 유효기간 선택 옵션 - const validityOptions = [ - { value: "3", label: "3개월" }, - { value: "6", label: "6개월" }, - { value: "12", label: "1년" }, - { value: "24", label: "2년" }, - { value: "36", label: "3년" }, - { value: "60", label: "5년" }, - ]; + // 현재 선택된 적용 범위 수 + const selectedScopesCount = BUSINESS_UNITS.filter(unit => + form.watch(unit.key as keyof UpdateTemplateSchema) + ).length; function onSubmit(input: UpdateTemplateSchema) { startUpdateTransition(async () => { @@ -122,8 +164,17 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem // FormData 객체 생성하여 파일과 데이터를 함께 전송 const formData = new FormData(); + formData.append("templateCode", input.templateCode); formData.append("templateName", input.templateName); - formData.append("validityPeriod", input.validityPeriod.toString()); // 유효기간 추가 + 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()); + }); + formData.append("status", input.status); if (input.file) { @@ -154,124 +205,241 @@ export function UpdateTemplateSheet({ template, onSuccess, ...props }: UpdateTem }); } + if (!template) return null; + return ( <Sheet {...props}> - <SheetContent className="flex flex-col gap-6 sm:max-w-md"> + <SheetContent className="flex flex-col gap-6 sm:max-w-[600px] overflow-y-auto"> <SheetHeader className="text-left"> <SheetTitle>템플릿 업데이트</SheetTitle> <SheetDescription> 템플릿 정보를 수정하고 변경사항을 저장하세요 </SheetDescription> </SheetHeader> + <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} - className="flex flex-col gap-4" + className="flex flex-col gap-6" > - <FormField - control={form.control} - name="templateName" - render={({ field }) => ( - <FormItem> - <FormLabel>템플릿 이름</FormLabel> - <FormControl> - <Input placeholder="템플릿 이름을 입력하세요" {...field} /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> + {/* 기본 정보 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">기본 정보</CardTitle> + <CardDescription> + 현재 적용 범위: {scopeHelpers.getScopeDisplayText(template)} + </CardDescription> + </CardHeader> + <CardContent className="space-y-4"> + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <FormField + control={form.control} + name="templateCode" + render={({ field }) => ( + <FormItem> + <FormLabel>템플릿 코드</FormLabel> + <FormControl> + <Input + {...field} + readOnly + className="bg-muted" + /> + </FormControl> + <FormDescription> + 템플릿 코드는 수정할 수 없습니다. + </FormDescription> + </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> + 템플릿 버전을 업데이트하세요. + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> + </div> + + <FormField + control={form.control} + name="templateName" + render={({ field }) => ( + <FormItem> + <FormLabel>템플릿 이름</FormLabel> + <FormControl> + <Input placeholder="템플릿 이름을 입력하세요" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> - <FormField - control={form.control} - name="validityPeriod" - render={({ field }) => ( - <FormItem> - <FormLabel>계약 유효기간</FormLabel> - <Select - value={field.value?.toString()} - onValueChange={(value) => field.onChange(parseInt(value))} - > - <FormControl> - <SelectTrigger> - <SelectValue placeholder="유효기간을 선택하세요" /> - </SelectTrigger> - </FormControl> - <SelectContent> - {validityOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} - </SelectItem> - ))} - </SelectContent> - </Select> - <FormDescription> - 계약서의 유효 기간을 설정합니다. 이 기간이 지나면 재계약이 필요합니다. - </FormDescription> - <FormMessage /> - </FormItem> - )} - /> + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <FormField + control={form.control} + name="status" + render={({ field }) => ( + <FormItem> + <FormLabel>상태</FormLabel> + <Select + defaultValue={field.value} + onValueChange={field.onChange} + > + <FormControl> + <SelectTrigger> + <SelectValue placeholder="템플릿 상태 선택" /> + </SelectTrigger> + </FormControl> + <SelectContent> + <SelectGroup> + <SelectItem value="ACTIVE">활성</SelectItem> + <SelectItem value="DISPOSED">폐기</SelectItem> + </SelectGroup> + </SelectContent> + </Select> + <FormMessage /> + </FormItem> + )} + /> + </div> + + <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> - <FormField - control={form.control} - name="status" - render={({ field }) => ( - <FormItem> - <FormLabel>상태</FormLabel> - <Select - defaultValue={field.value} - onValueChange={field.onChange} - > - <FormControl> - <SelectTrigger> - <SelectValue placeholder="템플릿 상태 선택" /> - </SelectTrigger> - </FormControl> - <SelectContent> - <SelectGroup> - <SelectItem value="ACTIVE">활성</SelectItem> - <SelectItem value="INACTIVE">비활성</SelectItem> - </SelectGroup> - </SelectContent> - </Select> - <FormMessage /> - </FormItem> - )} - /> + {/* 적용 범위 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">적용 범위</CardTitle> + <CardDescription> + 이 템플릿이 적용될 사업부를 선택하세요. ({selectedScopesCount}개 선택됨) + </CardDescription> + </CardHeader> + <CardContent className="space-y-4"> + <div className="flex items-center space-x-2"> + <Checkbox + id="select-all" + checked={selectedScopesCount === BUSINESS_UNITS.length} + onCheckedChange={handleSelectAllScopes} + /> + <label htmlFor="select-all" className="text-sm font-medium"> + 전체 선택 + </label> + </div> + + <Separator /> + + <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> + {BUSINESS_UNITS.map((unit) => ( + <FormField + key={unit.key} + control={form.control} + name={unit.key as keyof UpdateTemplateSchema} + render={({ field }) => ( + <FormItem className="flex flex-row items-start space-x-3 space-y-0"> + <FormControl> + <Checkbox + checked={field.value as boolean} + onCheckedChange={field.onChange} + /> + </FormControl> + <div className="space-y-1 leading-none"> + <FormLabel className="text-sm font-normal"> + {unit.label} + </FormLabel> + </div> + </FormItem> + )} + /> + ))} + </div> + + {form.formState.errors.shipBuildingApplicable && ( + <p className="text-sm text-destructive"> + {form.formState.errors.shipBuildingApplicable.message} + </p> + )} + </CardContent> + </Card> - <FormField - control={form.control} - name="file" - render={() => ( - <FormItem> - <FormLabel>템플릿 파일 (선택사항)</FormLabel> - <FormControl> - <Dropzone - onDrop={handleFileChange} - > - <DropzoneZone> - <DropzoneUploadIcon className="h-10 w-10 text-muted-foreground" /> - <DropzoneTitle> - {selectedFile - ? selectedFile.name - : template?.fileName - ? `현재 파일: ${template.fileName}` - : "새 파일을 드래그하세요"} - </DropzoneTitle> - <DropzoneDescription> - {selectedFile - ? `파일 크기: ${(selectedFile.size / 1024).toFixed(2)} KB` - : "또는 클릭하여 파일을 선택하세요 (선택사항)"} - </DropzoneDescription> - <DropzoneInput /> - </DropzoneZone> - </Dropzone> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> + {/* 파일 업데이트 */} + <Card> + <CardHeader> + <CardTitle className="text-lg">파일 업데이트</CardTitle> + <CardDescription> + 현재 파일: {template.fileName} + </CardDescription> + </CardHeader> + <CardContent> + <FormField + control={form.control} + name="file" + render={() => ( + <FormItem> + <FormLabel>템플릿 파일 (선택사항)</FormLabel> + <FormControl> + <Dropzone + onDrop={handleFileChange} + accept={{ + 'application/pdf': ['.pdf'] + }} + > + <DropzoneZone> + <DropzoneUploadIcon className="h-10 w-10 text-muted-foreground" /> + <DropzoneTitle> + {selectedFile + ? selectedFile.name + : "새 파일을 드래그하세요 (선택사항)"} + </DropzoneTitle> + <DropzoneDescription> + {selectedFile + ? `파일 크기: ${(selectedFile.size / (1024 * 1024)).toFixed(2)} MB` + : "또는 클릭하여 파일을 선택하세요"} + </DropzoneDescription> + <DropzoneInput /> + </DropzoneZone> + </Dropzone> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </CardContent> + </Card> <SheetFooter className="gap-2 pt-2 sm:space-x-0"> <SheetClose asChild> |
