diff options
Diffstat (limited to 'lib/evaluation-criteria/table/reg-eval-criteria-update-sheet.tsx')
| -rw-r--r-- | lib/evaluation-criteria/table/reg-eval-criteria-update-sheet.tsx | 574 |
1 files changed, 365 insertions, 209 deletions
diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-update-sheet.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-update-sheet.tsx index 7f40b318..bbf4f36d 100644 --- a/lib/evaluation-criteria/table/reg-eval-criteria-update-sheet.tsx +++ b/lib/evaluation-criteria/table/reg-eval-criteria-update-sheet.tsx @@ -30,9 +30,8 @@ import { REG_EVAL_CRITERIA_CATEGORY2, REG_EVAL_CRITERIA_ITEM, type RegEvalCriteriaDetails, - type RegEvalCriteriaView, + type RegEvalCriteria, // RegEvalCriteriaView 대신 RegEvalCriteria 사용 } from '@/db/schema'; -import { ScrollArea } from '@/components/ui/scroll-area'; import { Select, SelectContent, @@ -47,6 +46,7 @@ import { SheetHeader, SheetTitle, } from '@/components/ui/sheet'; +import { Separator } from '@/components/ui/separator'; import { Textarea } from '@/components/ui/textarea'; import { toast } from 'sonner'; import { useForm, useFieldArray } from 'react-hook-form'; @@ -64,6 +64,11 @@ const regEvalCriteriaFormSchema = z.object({ classification: z.string().min(1, '구분은 필수 항목입니다.'), range: z.string().nullable().optional(), remarks: z.string().nullable().optional(), + // 새로운 필드들 추가 + scoreType: z.enum(['fixed', 'variable']).default('fixed'), + variableScoreMin: z.coerce.number().nullable().optional(), + variableScoreMax: z.coerce.number().nullable().optional(), + variableScoreUnit: z.string().nullable().optional(), criteriaDetails: z.array( z.object({ id: z.number().optional(), @@ -75,18 +80,22 @@ const regEvalCriteriaFormSchema = z.object({ }) ).min(1, '최소 1개의 평가 내용이 필요합니다.'), }); + type RegEvalCriteriaFormData = z.infer<typeof regEvalCriteriaFormSchema>; + interface CriteriaDetailFormProps { index: number form: any onRemove: () => void canRemove: boolean disabled?: boolean + scoreType: 'fixed' | 'variable' } + interface RegEvalCriteriaUpdateSheetProps { open: boolean, onOpenChange: (open: boolean) => void, - criteriaViewData: RegEvalCriteriaView, + criteriaData: RegEvalCriteria, // criteriaViewData → criteriaData로 변경 onSuccess: () => void, }; @@ -99,13 +108,14 @@ function CriteriaDetailForm({ onRemove, canRemove, disabled = false, + scoreType, }: CriteriaDetailFormProps) { return ( <Card> <CardHeader> <div className="flex items-center justify-between"> - <CardTitle className="text-lg">Detail Item - {index + 1}</CardTitle> + <CardTitle className="text-lg">평가 옵션 {index + 1}</CardTitle> {canRemove && ( <Button type="button" @@ -133,10 +143,10 @@ function CriteriaDetailForm({ name={`criteriaDetails.${index}.detail`} render={({ field }) => ( <FormItem> - <FormLabel>평가내용</FormLabel> + <FormLabel>평가 옵션 내용</FormLabel> <FormControl> <Textarea - placeholder="평가내용을 입력하세요." + placeholder="평가 옵션 내용을 입력하세요. (예: 우수, 보통, 미흡)" {...field} disabled={disabled} /> @@ -145,86 +155,102 @@ function CriteriaDetailForm({ </FormItem> )} /> - <FormField - control={form.control} - name={`criteriaDetails.${index}.scoreEquipShip`} - render={({ field }) => ( - <FormItem> - <FormLabel>배점/기자재/조선</FormLabel> - <FormControl> - <Input - type="number" - step="0.1" - placeholder="배점/기자재/조선" - {...field} - value={field.value ?? 0} - disabled={disabled} - /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name={`criteriaDetails.${index}.scoreEquipMarine`} - render={({ field }) => ( - <FormItem> - <FormLabel>배점/기자재/해양</FormLabel> - <FormControl> - <Input - type="number" - step="0.1" - placeholder="배점/기자재/해양" - {...field} - value={field.value ?? 0} - disabled={disabled} - /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name={`criteriaDetails.${index}.scoreBulkShip`} - render={({ field }) => ( - <FormItem> - <FormLabel>배점/벌크/조선</FormLabel> - <FormControl> - <Input - type="number" - step="0.1" - placeholder="배점/벌크/조선" - {...field} - value={field.value ?? 0} - disabled={disabled} - /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name={`criteriaDetails.${index}.scoreBulkMarine`} - render={({ field }) => ( - <FormItem> - <FormLabel>배점/벌크/해양</FormLabel> - <FormControl> - <Input - type="number" - step="0.1" - placeholder="배점/벌크/해양" - {...field} - value={field.value ?? 0} - disabled={disabled} - /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> + + {/* 고정점수인 경우에만 점수 입력 필드들 표시 - 한 줄에 4개 */} + {scoreType === 'fixed' && ( + <div className="grid grid-cols-4 gap-4"> + <FormField + control={form.control} + name={`criteriaDetails.${index}.scoreEquipShip`} + render={({ field }) => ( + <FormItem> + <FormLabel>기자재-조선</FormLabel> + <FormControl> + <Input + type="number" + step="0.01" + placeholder="0.00" + {...field} + value={field.value ?? ''} + disabled={disabled} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name={`criteriaDetails.${index}.scoreEquipMarine`} + render={({ field }) => ( + <FormItem> + <FormLabel>기자재-해양</FormLabel> + <FormControl> + <Input + type="number" + step="0.01" + placeholder="0.00" + {...field} + value={field.value ?? ''} + disabled={disabled} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name={`criteriaDetails.${index}.scoreBulkShip`} + render={({ field }) => ( + <FormItem> + <FormLabel>벌크-조선</FormLabel> + <FormControl> + <Input + type="number" + step="0.01" + placeholder="0.00" + {...field} + value={field.value ?? ''} + disabled={disabled} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name={`criteriaDetails.${index}.scoreBulkMarine`} + render={({ field }) => ( + <FormItem> + <FormLabel>벌크-해양</FormLabel> + <FormControl> + <Input + type="number" + step="0.01" + placeholder="0.00" + {...field} + value={field.value ?? ''} + disabled={disabled} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + )} + + {/* 변동점수인 경우 안내 메시지 */} + {scoreType === 'variable' && ( + <div className="p-4 bg-muted rounded-lg"> + <p className="text-sm text-muted-foreground"> + 변동점수 유형에서는 개별 점수를 입력하지 않습니다. + 기본 정보에서 설정한 최소/최대 점수 범위가 적용됩니다. + </p> + </div> + )} </CardContent> </Card> ) @@ -234,9 +260,10 @@ function CriteriaDetailForm({ function RegEvalCriteriaUpdateSheet({ open, onOpenChange, - criteriaViewData, + criteriaData, onSuccess, }: RegEvalCriteriaUpdateSheetProps) { + const [isPending, startTransition] = useTransition(); const form = useForm<RegEvalCriteriaFormData>({ resolver: zodResolver(regEvalCriteriaFormSchema), @@ -247,6 +274,10 @@ function RegEvalCriteriaUpdateSheet({ classification: '', range: '', remarks: '', + scoreType: 'fixed', + variableScoreMin: null, + variableScoreMax: null, + variableScoreUnit: '', criteriaDetails: [ { id: undefined, @@ -265,11 +296,14 @@ function RegEvalCriteriaUpdateSheet({ name: 'criteriaDetails', }); + // 현재 점수 유형 감시 + const scoreType = form.watch('scoreType'); + useEffect(() => { - if (open && criteriaViewData) { + if (open && criteriaData?.id) { startTransition(async () => { try { - const targetData = await getRegEvalCriteriaWithDetails(criteriaViewData.criteriaId!); + const targetData = await getRegEvalCriteriaWithDetails(criteriaData.id); if (targetData) { form.reset({ category: targetData.category, @@ -278,6 +312,12 @@ function RegEvalCriteriaUpdateSheet({ classification: targetData.classification, range: targetData.range, remarks: targetData.remarks, + scoreType: targetData.scoreType || 'fixed', + variableScoreMin: targetData.variableScoreMin + ? Number(targetData.variableScoreMin) : null, + variableScoreMax: targetData.variableScoreMax + ? Number(targetData.variableScoreMax) : null, + variableScoreUnit: targetData.variableScoreUnit, criteriaDetails: targetData.criteriaDetails?.map((detailItem: RegEvalCriteriaDetails) => ({ id: detailItem.id, detail: detailItem.detail, @@ -298,19 +338,27 @@ function RegEvalCriteriaUpdateSheet({ } }); } - }, [open, criteriaViewData, form]); + }, [open, criteriaData, form]); const onSubmit = async (data: RegEvalCriteriaFormData) => { + startTransition(async () => { try { - const criteriaData = { + const criteriaDataToUpdate = { category: data.category, category2: data.category2, item: data.item, classification: data.classification, range: data.range, remarks: data.remarks, + scoreType: data.scoreType, + variableScoreMin: data.variableScoreMin != null + ? String(data.variableScoreMin) : null, + variableScoreMax: data.variableScoreMax != null + ? String(data.variableScoreMax) : null, + variableScoreUnit: data.variableScoreUnit, }; + const detailList = data.criteriaDetails.map((detailItem) => ({ id: detailItem.id, detail: detailItem.detail, @@ -323,14 +371,15 @@ function RegEvalCriteriaUpdateSheet({ scoreBulkMarine: detailItem.scoreBulkMarine != null ? String(detailItem.scoreBulkMarine) : null, })); - await modifyRegEvalCriteriaWithDetails(criteriaViewData.criteriaId!, criteriaData, detailList); - toast.success('평가 기준표가 수정되었습니다.'); + + await modifyRegEvalCriteriaWithDetails(criteriaData.id, criteriaDataToUpdate, detailList); + toast.success('평가 기준이 수정되었습니다.'); onSuccess(); onOpenChange(false); } catch (error) { console.error('Error in Saving Regular Evaluation Criteria:', error); toast.error( - error instanceof Error ? error.message : '평가 기준표 저장 중 오류가 발생했습니다.' + error instanceof Error ? error.message : '평가 기준 저장 중 오류가 발생했습니다.' ); } }) @@ -342,125 +391,225 @@ function RegEvalCriteriaUpdateSheet({ return ( <Sheet open={open} onOpenChange={onOpenChange}> - <SheetContent className="w-[900px] sm:max-w-[900px] overflow-y-auto"> - <SheetHeader className="mb-4"> + <SheetContent className="w-[900px] sm:max-w-[900px] flex flex-col" style={{width:900, height: '100vh'}}> + {/* 고정 헤더 */} + <SheetHeader className="flex-shrink-0 pb-4 border-b"> <SheetTitle className="font-bold"> - 협력업체 평가 기준표 수정 + 평가 기준 수정 </SheetTitle> <SheetDescription> - 협력업체 평가 기준표의 정보를 수정합니다. + 평가 기준의 정보를 수정합니다. </SheetDescription> </SheetHeader> + <Form {...form}> - <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> - <ScrollArea className="h-[calc(100vh-200px)] pr-4"> - <div className="space-y-6"> + <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col flex-1 min-h-0"> + {/* 스크롤 가능한 메인 콘텐츠 영역 */} + <div className="flex-1 overflow-y-auto py-4 min-h-0"> + <div className="space-y-6 pr-4"> <Card> <CardHeader> - <CardTitle>Criterion Info</CardTitle> + <CardTitle>기본 정보</CardTitle> </CardHeader> <CardContent className="space-y-4"> - <FormField - control={form.control} - name="category" - render={({ field }) => ( - <FormItem> - <FormLabel>평가부문</FormLabel> - <FormControl> - <Select onValueChange={field.onChange} value={field.value || ""}> - <SelectTrigger> - <SelectValue placeholder="선택" /> - </SelectTrigger> - <SelectContent> - {REG_EVAL_CRITERIA_CATEGORY.map((option) => ( - <SelectItem key={option.value} value={option.value}> - {option.label} - </SelectItem> - ))} - </SelectContent> - </Select> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name="category2" - render={({ field }) => ( - <FormItem> - <FormLabel>점수구분</FormLabel> - <FormControl> - <Select onValueChange={field.onChange} value={field.value || ""}> - <SelectTrigger> - <SelectValue placeholder="선택" /> - </SelectTrigger> - <SelectContent> - {REG_EVAL_CRITERIA_CATEGORY2.map((option) => ( - <SelectItem key={option.value} value={option.value}> - {option.label} - </SelectItem> - ))} - </SelectContent> - </Select> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name="item" - render={({ field }) => ( - <FormItem> - <FormLabel>항목</FormLabel> - <FormControl> - <Select onValueChange={field.onChange} value={field.value || ""}> - <SelectTrigger> - <SelectValue placeholder="선택" /> - </SelectTrigger> - <SelectContent> - {REG_EVAL_CRITERIA_ITEM.map((option) => ( - <SelectItem key={option.value} value={option.value}> - {option.label} - </SelectItem> - ))} - </SelectContent> - </Select> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name="classification" - render={({ field }) => ( - <FormItem> - <FormLabel>구분</FormLabel> - <FormControl> - <Input placeholder="구분을 입력하세요." {...field} /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name="range" - render={({ field }) => ( - <FormItem> - <FormLabel>범위</FormLabel> - <FormControl> - <Input - placeholder="범위를 입력하세요." {...field} - value={field.value ?? ''} + <div className="grid grid-cols-2 gap-4"> + <FormField + control={form.control} + name="category" + render={({ field }) => ( + <FormItem> + <FormLabel>평가부문</FormLabel> + <FormControl> + <Select onValueChange={field.onChange} value={field.value || ""}> + <SelectTrigger> + <SelectValue placeholder="선택" /> + </SelectTrigger> + <SelectContent> + {REG_EVAL_CRITERIA_CATEGORY.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="category2" + render={({ field }) => ( + <FormItem> + <FormLabel>점수구분</FormLabel> + <FormControl> + <Select onValueChange={field.onChange} value={field.value || ""}> + <SelectTrigger> + <SelectValue placeholder="선택" /> + </SelectTrigger> + <SelectContent> + {REG_EVAL_CRITERIA_CATEGORY2.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + + <div className="grid grid-cols-2 gap-4"> + <FormField + control={form.control} + name="item" + render={({ field }) => ( + <FormItem> + <FormLabel>항목</FormLabel> + <FormControl> + <Select onValueChange={field.onChange} value={field.value || ""}> + <SelectTrigger> + <SelectValue placeholder="선택" /> + </SelectTrigger> + <SelectContent> + {REG_EVAL_CRITERIA_ITEM.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="scoreType" + render={({ field }) => ( + <FormItem> + <FormLabel>점수유형</FormLabel> + <FormControl> + <Select onValueChange={field.onChange} value={field.value}> + <SelectTrigger> + <SelectValue placeholder="선택" /> + </SelectTrigger> + <SelectContent> + <SelectItem value="fixed">고정점수</SelectItem> + <SelectItem value="variable">변동점수</SelectItem> + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + + <div className="grid grid-cols-2 gap-4"> + <FormField + control={form.control} + name="classification" + render={({ field }) => ( + <FormItem> + <FormLabel>구분</FormLabel> + <FormControl> + <Input placeholder="구분을 입력하세요." {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="range" + render={({ field }) => ( + <FormItem> + <FormLabel>평가명</FormLabel> + <FormControl> + <Input + placeholder="평가명을 입력하세요." {...field} + value={field.value ?? ''} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + + {/* 변동점수 설정 */} + {scoreType === 'variable' && ( + <> + <Separator /> + <div className="space-y-4"> + <h4 className="font-medium">변동점수 설정</h4> + <div className="grid grid-cols-3 gap-4"> + <FormField + control={form.control} + name="variableScoreMin" + render={({ field }) => ( + <FormItem> + <FormLabel>최소점수</FormLabel> + <FormControl> + <Input + type="number" + step="0.01" + placeholder="0.00" + {...field} + value={field.value ?? ''} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> + <FormField + control={form.control} + name="variableScoreMax" + render={({ field }) => ( + <FormItem> + <FormLabel>최대점수</FormLabel> + <FormControl> + <Input + type="number" + step="0.01" + placeholder="0.00" + {...field} + value={field.value ?? ''} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="variableScoreUnit" + render={({ field }) => ( + <FormItem> + <FormLabel>점수단위</FormLabel> + <FormControl> + <Input + placeholder="예: 점, %" + {...field} + value={field.value ?? ''} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + </div> + </> + )} + <FormField control={form.control} name="remarks" @@ -480,13 +629,17 @@ function RegEvalCriteriaUpdateSheet({ /> </CardContent> </Card> + <Card> <CardHeader> <div className="flex items-center justify-between"> <div> - <CardTitle>Evaluation Criteria Item</CardTitle> + <CardTitle>평가 옵션</CardTitle> <CardDescription> - Set Evaluation Criteria Item. + {scoreType === 'fixed' + ? '각 평가 옵션별 점수를 설정하세요.' + : '평가 옵션을 설정하세요. (점수는 변동점수 설정을 따릅니다.)' + } </CardDescription> </div> <Button @@ -507,7 +660,7 @@ function RegEvalCriteriaUpdateSheet({ disabled={isPending} > <Plus className="w-4 h-4 mr-2" /> - New Item + 옵션 추가 </Button> </div> </CardHeader> @@ -521,24 +674,27 @@ function RegEvalCriteriaUpdateSheet({ onRemove={() => remove(index)} canRemove={fields.length > 1} disabled={isPending} + scoreType={scoreType} /> ))} </div> </CardContent> </Card> </div> - </ScrollArea> - <div className="flex justify-end gap-2 pt-4 border-t"> + </div> + + {/* 고정 푸터 */} + <div className="flex-shrink-0 flex justify-end gap-2 bg-background"> <Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={isPending} > - Cancel + 취소 </Button> <Button type="submit" disabled={isPending}> - {isPending ? 'Saving...' : 'Modify'} + {isPending ? '저장 중...' : '수정'} </Button> </div> </form> |
