summaryrefslogtreecommitdiff
path: root/lib/evaluation-criteria/table/reg-eval-criteria-update-sheet.tsx
diff options
context:
space:
mode:
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.tsx574
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>