summaryrefslogtreecommitdiff
path: root/lib/basic-contract/template/update-basicContract-sheet.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract/template/update-basicContract-sheet.tsx')
-rw-r--r--lib/basic-contract/template/update-basicContract-sheet.tsx426
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>