diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-20 11:37:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-20 11:37:31 +0000 |
| commit | aa86729f9a2ab95346a2851e3837de1c367aae17 (patch) | |
| tree | b601b18b6724f2fb449c7fa9ea50cbd652a8077d /lib/evaluation-target-list/table/manual-create-evaluation-target-dialog.tsx | |
| parent | 95bbe9c583ff841220da1267630e7b2025fc36dc (diff) | |
(대표님) 20250620 작업사항
Diffstat (limited to 'lib/evaluation-target-list/table/manual-create-evaluation-target-dialog.tsx')
| -rw-r--r-- | lib/evaluation-target-list/table/manual-create-evaluation-target-dialog.tsx | 151 |
1 files changed, 88 insertions, 63 deletions
diff --git a/lib/evaluation-target-list/table/manual-create-evaluation-target-dialog.tsx b/lib/evaluation-target-list/table/manual-create-evaluation-target-dialog.tsx index 5704cba1..af369ea6 100644 --- a/lib/evaluation-target-list/table/manual-create-evaluation-target-dialog.tsx +++ b/lib/evaluation-target-list/table/manual-create-evaluation-target-dialog.tsx @@ -60,26 +60,7 @@ import { import { EVALUATION_TARGET_FILTER_OPTIONS, getDefaultEvaluationYear } from "../validation" import { useSession } from "next-auth/react" -// 폼 스키마 정의 -const createEvaluationTargetSchema = z.object({ - evaluationYear: z.number().min(2020).max(2030), - division: z.enum(["OCEAN", "SHIPYARD"]), - vendorId: z.number().min(1, "벤더를 선택해주세요"), - materialType: z.enum(["EQUIPMENT", "BULK", "EQUIPMENT_BULK"]), - adminComment: z.string().optional(), - // L/D 클레임 정보 - ldClaimCount: z.number().min(0).optional(), - ldClaimAmount: z.number().min(0).optional(), - ldClaimCurrency: z.enum(["KRW", "USD", "EUR", "JPY"]).optional(), - reviewers: z.array( - z.object({ - departmentCode: z.string(), - reviewerUserId: z.number().min(1, "담당자를 선택해주세요"), - }) - ).min(1, "최소 1명의 담당자를 지정해주세요"), -}) -type CreateEvaluationTargetFormValues = z.infer<typeof createEvaluationTargetSchema> interface ManualCreateEvaluationTargetDialogProps { open: boolean @@ -114,12 +95,49 @@ export function ManualCreateEvaluationTargetDialog({ // 부서 정보 상태 const [departments, setDepartments] = React.useState<Array<{ code: string, name: string, key: string }>>([]) + // 폼 스키마 정의 +const createEvaluationTargetSchema = z.object({ + evaluationYear: z.number().min(2020).max(2030), + division: z.enum(["PLANT", "SHIP"]), + vendorId: z.number().min(0), // 0도 허용, 클라이언트에서 검증 + materialType: z.enum(["EQUIPMENT", "BULK", "EQUIPMENT_BULK"]), + adminComment: z.string().optional(), + // L/D 클레임 정보 + ldClaimCount: z.number().min(0).optional(), + ldClaimAmount: z.number().min(0).optional(), + ldClaimCurrency: z.enum(["KRW", "USD", "EUR", "JPY"]).optional(), + reviewers: z.array( + z.object({ + departmentCode: z.string(), + reviewerUserId: z.number(), // min(1) 제거, 나중에 클라이언트에서 필터링 + }) + ), +}).refine((data) => { + // 벤더가 선택되어야 함 + if (data.vendorId === 0) { + return false; + } + // 최소 1명의 담당자가 지정되어야 함 (reviewerUserId > 0) + const validReviewers = data.reviewers + .filter(r => r.reviewerUserId > 0) + .map((r, i) => ({ + departmentCode: r.departmentCode || departments[i]?.code, // 없으면 보충 + reviewerUserId: r.reviewerUserId, + })); + return validReviewers.length > 0; +}, { + message: "벤더를 선택하고 최소 1명의 담당자를 지정해주세요.", + path: ["vendorId"] +}) +type CreateEvaluationTargetFormValues = z.infer<typeof createEvaluationTargetSchema> + + const form = useForm<CreateEvaluationTargetFormValues>({ resolver: zodResolver(createEvaluationTargetSchema), defaultValues: { evaluationYear: getDefaultEvaluationYear(), - division: "OCEAN", - vendorId: 0, + division: "SHIP", + vendorId: 0, // 임시로 0, 나중에 검증에서 체크 materialType: "EQUIPMENT", adminComment: "", ldClaimCount: 0, @@ -180,17 +198,12 @@ export function ManualCreateEvaluationTargetDialog({ // 부서 정보가 로드되면 reviewers 기본값 설정 React.useEffect(() => { if (departments.length > 0 && open) { - const currentReviewers = form.getValues("reviewers") - - // 이미 설정되어 있으면 다시 설정하지 않음 - if (currentReviewers.length === 0) { - const defaultReviewers = departments.map(dept => ({ - departmentCode: dept.code, - reviewerUserId: 0, - })) - form.setValue('reviewers', defaultReviewers) - } - } + const defaultReviewers = departments.map(dept => ({ + departmentCode: dept.code, // ✅ 반드시 포함 + reviewerUserId: 0, + })); + form.setValue("reviewers", defaultReviewers, { shouldValidate: false }); + } }, [departments, open]) // form 의존성 제거하고 조건 추가 console.log(departments) @@ -234,7 +247,7 @@ export function ManualCreateEvaluationTargetDialog({ // 폼과 상태 초기화 form.reset({ evaluationYear: getDefaultEvaluationYear(), - division: "OCEAN", + division: "SHIP", vendorId: 0, materialType: "EQUIPMENT", adminComment: "", @@ -269,7 +282,7 @@ export function ManualCreateEvaluationTargetDialog({ if (!open) { form.reset({ evaluationYear: getDefaultEvaluationYear(), - division: "OCEAN", + division: "SHIP", vendorId: 0, materialType: "EQUIPMENT", adminComment: "", @@ -326,24 +339,29 @@ export function ManualCreateEvaluationTargetDialog({ return ( <Dialog open={open} onOpenChange={handleOpenChange}> - <DialogContent className="max-w-lg flex flex-col h-[90vh]"> + <DialogContent className="max-w-lg h-[90vh] p-0 flex flex-col"> {/* 고정 헤더 */} - <DialogHeader className="flex-shrink-0"> - <DialogTitle>평가 대상 수동 생성</DialogTitle> - <DialogDescription> - 새로운 평가 대상을 수동으로 생성하고 담당자를 지정합니다. - </DialogDescription> - </DialogHeader> + <div className="flex-shrink-0 p-6 border-b"> + <DialogHeader> + <DialogTitle>평가 대상 수동 생성</DialogTitle> + <DialogDescription> + 새로운 평가 대상을 수동으로 생성하고 담당자를 지정합니다. + </DialogDescription> + </DialogHeader> + </div> {/* Form을 전체 콘텐츠를 감싸도록 수정 */} <Form {...form}> <form - onSubmit={form.handleSubmit(onSubmit)} - className="flex flex-col flex-1" + onSubmit={form.handleSubmit( + onSubmit, + (errors) => console.log('❌ validation errors:', errors) + )} + className="flex flex-col flex-1 min-h-0" id="evaluation-target-form" > {/* 스크롤 가능한 콘텐츠 영역 */} - <div className="flex-1 overflow-y-auto"> + <div className="flex-1 overflow-y-auto p-6"> <div className="space-y-6"> {/* 기본 정보 */} <Card> @@ -693,9 +711,14 @@ export function ManualCreateEvaluationTargetDialog({ <CommandItem value="선택 안함" onSelect={() => { - field.onChange(0) - setReviewerOpens(prev => ({...prev, [department.code]: false})) - }} + // reviewers[index] 전체를 갱신 + form.setValue( + `reviewers.${index}`, + { departmentCode: department.code, reviewerUserId: reviewer.id }, + { shouldValidate: true } + ); + setReviewerOpens(prev => ({ ...prev, [department.code]: false })); + }} > <Check className={cn( @@ -747,22 +770,24 @@ export function ManualCreateEvaluationTargetDialog({ </div> {/* 고정 버튼 영역 */} - <div className="flex-shrink-0 flex justify-end gap-3 pt-4 border-t"> - <Button - type="button" - variant="outline" - onClick={() => handleOpenChange(false)} - disabled={isSubmitting} - > - 취소 - </Button> - <Button - type="submit" - disabled={isSubmitting} - > - {isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} - 생성 - </Button> + <div className="flex-shrink-0 border-t bg-background p-6"> + <div className="flex justify-end gap-3"> + <Button + type="button" + variant="outline" + onClick={() => handleOpenChange(false)} + disabled={isSubmitting} + > + 취소 + </Button> + <Button + type="submit" + disabled={isSubmitting} + > + {isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} + 생성 + </Button> + </div> </div> </form> </Form> |
