summaryrefslogtreecommitdiff
path: root/lib/evaluation-target-list/table/manual-create-evaluation-target-dialog.tsx
diff options
context:
space:
mode:
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.tsx151
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>