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.tsx106
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>