summaryrefslogtreecommitdiff
path: root/lib/evaluation-criteria/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/evaluation-criteria/table')
-rw-r--r--lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx221
-rw-r--r--lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx226
-rw-r--r--lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx12
-rw-r--r--lib/evaluation-criteria/table/reg-eval-criteria-table.tsx83
-rw-r--r--lib/evaluation-criteria/table/reg-eval-criteria-update-sheet.tsx574
5 files changed, 760 insertions, 356 deletions
diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx
index bdf583bc..88c8107b 100644
--- a/lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx
+++ b/lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx
@@ -10,14 +10,16 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
+ DropdownMenuShortcut,
+ DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import { Ellipsis } from 'lucide-react';
+import { Ellipsis, Eye, Edit, Trash2 } from 'lucide-react';
import {
REG_EVAL_CRITERIA_CATEGORY,
REG_EVAL_CRITERIA_CATEGORY2,
REG_EVAL_CRITERIA_ITEM,
- type RegEvalCriteriaView,
+ type RegEvalCriteria, // RegEvalCriteriaView 대신 RegEvalCriteria 사용
} from '@/db/schema';
import { type ColumnDef } from '@tanstack/react-table';
import { type DataTableRowAction } from '@/types/table';
@@ -26,16 +28,16 @@ import { type DataTableRowAction } from '@/types/table';
/* TYPES */
interface GetColumnsProps {
- setRowAction: Dispatch<SetStateAction<DataTableRowAction<RegEvalCriteriaView> | null>>,
+ setRowAction: Dispatch<SetStateAction<DataTableRowAction<RegEvalCriteria> | null>>,
};
// ----------------------------------------------------------------------------------------------------
/* FUNCTION FOR GETTING COLUMNS SETTING */
-function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<RegEvalCriteriaView>[] {
+function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<RegEvalCriteria>[] {
// [1] SELECT COLUMN - CHECKBOX
- const selectColumn: ColumnDef<RegEvalCriteriaView> = {
+ const selectColumn: ColumnDef<RegEvalCriteria> = {
id: 'select',
header: ({ table }) => (
<Checkbox
@@ -58,11 +60,11 @@ function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<RegEvalCriteri
),
enableSorting: false,
enableHiding: false,
- size:40,
+ size: 40,
};
// [2] CRITERIA COLUMNS
- const criteriaColumns: ColumnDef<RegEvalCriteriaView>[] = [
+ const criteriaColumns: ColumnDef<RegEvalCriteria>[] = [
{
accessorKey: 'category',
header: ({ column }) => (
@@ -146,149 +148,129 @@ function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<RegEvalCriteri
{
accessorKey: 'range',
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="범위" />
+ <DataTableColumnHeaderSimple column={column} title="평가명" />
),
cell: ({ row }) => (
- <div className="font-regular">
+ <div className="font-medium">
{row.getValue('range') || '-'}
</div>
),
enableSorting: true,
enableHiding: false,
meta: {
- excelHeader: 'Range',
+ excelHeader: 'Evaluation Name',
type: 'text',
},
},
{
- accessorKey: 'detail',
+ accessorKey: 'scoreType',
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="평가내용" />
- ),
- cell: ({ row }) => (
- <div className="font-bold">
- {row.getValue('detail')}
- </div>
- ),
- enableSorting: true,
- enableHiding: false,
- meta: {
- excelHeader: 'Detail',
- type: 'text',
- },
- },
- ];
-
- // [3] SCORE COLUMNS
- const scoreEquipColumns: ColumnDef<RegEvalCriteriaView>[] = [
- {
- accessorKey: 'scoreEquipShip',
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="조선" />
+ <DataTableColumnHeaderSimple column={column} title="점수유형" />
),
cell: ({ row }) => {
- const value = row.getValue<string>('scoreEquipShip');
- const displayValue = typeof value === 'string'
- ? parseFloat(parseFloat(value).toFixed(2)).toString()
- : '-';
+ const value = row.getValue<string>('scoreType');
return (
- <div className="font-bold">
- {displayValue}
- </div>
+ <Badge variant={value === 'fixed' ? 'default' : 'secondary'}>
+ {value === 'fixed' ? '고정점수' : '변동점수'}
+ </Badge>
);
},
enableSorting: true,
enableHiding: false,
meta: {
- excelHeader: 'Equipment-Shipbuilding Score',
- group: 'Equipment Score',
- type: 'number',
+ excelHeader: 'Score Type',
+ type: 'select',
},
},
+ ];
+
+ // [3] VARIABLE SCORE COLUMNS (변동점수 관련 컬럼들)
+ const variableScoreColumns: ColumnDef<RegEvalCriteria>[] = [
{
- accessorKey: 'scoreEquipMarine',
+ accessorKey: 'variableScoreMin',
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="해양" />
+ <DataTableColumnHeaderSimple column={column} title="최소점수" />
),
cell: ({ row }) => {
- const value = row.getValue<string>('scoreEquipMarine');
+ const value = row.getValue<string>('variableScoreMin');
+ const scoreType = row.getValue<string>('scoreType');
+ if (scoreType !== 'variable') return <div className="text-gray-400">-</div>;
+
const displayValue = typeof value === 'string'
? parseFloat(parseFloat(value).toFixed(2)).toString()
: '-';
return (
- <div className="font-bold">
+ <div className="font-regular text-center">
{displayValue}
</div>
);
},
enableSorting: true,
- enableHiding: false,
+ enableHiding: true,
meta: {
- excelHeader: 'Equipment-Marine Engineering Score',
- group: 'Equipment Score',
+ excelHeader: 'Min Score',
type: 'number',
},
},
- ];
- const scoreBulkColumns: ColumnDef<RegEvalCriteriaView>[] = [
{
- accessorKey: 'scoreBulkShip',
+ accessorKey: 'variableScoreMax',
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="조선" />
+ <DataTableColumnHeaderSimple column={column} title="최대점수" />
),
cell: ({ row }) => {
- const value = row.getValue<string>('scoreBulkShip');
+ const value = row.getValue<string>('variableScoreMax');
+ const scoreType = row.getValue<string>('scoreType');
+ if (scoreType !== 'variable') return <div className="text-gray-400">-</div>;
+
const displayValue = typeof value === 'string'
? parseFloat(parseFloat(value).toFixed(2)).toString()
: '-';
return (
- <div className="font-bold">
+ <div className="font-regular text-center">
{displayValue}
</div>
);
},
enableSorting: true,
- enableHiding: false,
+ enableHiding: true,
meta: {
- excelHeader: 'Bulk-Shipbuilding Score',
- group: 'Bulk Score',
+ excelHeader: 'Max Score',
type: 'number',
},
},
{
- accessorKey: 'scoreBulkMarine',
+ accessorKey: 'variableScoreUnit',
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="해양" />
+ <DataTableColumnHeaderSimple column={column} title="점수단위" />
),
cell: ({ row }) => {
- const value = row.getValue<string>('scoreBulkMarine');
- const displayValue = typeof value === 'string'
- ? parseFloat(parseFloat(value).toFixed(2)).toString()
- : '-';
+ const value = row.getValue<string>('variableScoreUnit');
+ const scoreType = row.getValue<string>('scoreType');
+ if (scoreType !== 'variable') return <div className="text-gray-400">-</div>;
+
return (
- <div className="font-bold">
- {displayValue}
+ <div className="font-regular text-center">
+ {value || '-'}
</div>
);
},
enableSorting: true,
- enableHiding: false,
+ enableHiding: true,
meta: {
- excelHeader: 'Bulk-Marine Engineering Score',
- group: 'Bulk Score',
- type: 'number',
+ excelHeader: 'Score Unit',
+ type: 'text',
},
},
];
// [4] REMARKS COLUMN
- const remarksColumn: ColumnDef<RegEvalCriteriaView> = {
+ const remarksColumn: ColumnDef<RegEvalCriteria> = {
accessorKey: 'remarks',
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="비고" />
),
cell: ({ row }) => (
- <div className="font-regular">
+ <div className="font-regular max-w-[150px] truncate" title={row.getValue('remarks')}>
{row.getValue('remarks') || '-'}
</div>
),
@@ -300,8 +282,8 @@ function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<RegEvalCriteri
},
};
- // [5] HIDDEN ID COLUMN
- const hiddenColumns: ColumnDef<RegEvalCriteriaView>[] = [
+ // [5] HIDDEN ID COLUMNS
+ const hiddenColumns: ColumnDef<RegEvalCriteria>[] = [
{
accessorKey: 'id',
header: ({ column }) => (
@@ -321,66 +303,86 @@ function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<RegEvalCriteri
},
},
{
- accessorKey: 'criteriaId',
+ accessorKey: 'createdAt',
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="기준 ID" />
- ),
- cell: ({ row }) => (
- <div className="font-regular">
- {row.getValue('criteriaId')}
- </div>
+ <DataTableColumnHeaderSimple column={column} title="생성일시" />
),
+ cell: ({ row }) => {
+ const date = row.getValue<Date>('createdAt');
+ return (
+ <div className="font-regular">
+ {date ? new Date(date).toLocaleDateString('ko-KR') : '-'}
+ </div>
+ );
+ },
enableSorting: true,
enableHiding: true,
meta: {
- excelHeader: 'Criteria ID',
+ excelHeader: 'Created At',
group: 'Meta Data',
- type: 'criteriaId',
+ type: 'date',
},
},
{
- accessorKey: 'orderIndex',
+ accessorKey: 'updatedAt',
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="정렬 순서" />
- ),
- cell: ({ row }) => (
- <div className="font-regular">
- {row.getValue('orderIndex')}
- </div>
+ <DataTableColumnHeaderSimple column={column} title="수정일시" />
),
+ cell: ({ row }) => {
+ const date = row.getValue<Date>('updatedAt');
+ return (
+ <div className="font-regular">
+ {date ? new Date(date).toLocaleDateString('ko-KR') : '-'}
+ </div>
+ );
+ },
enableSorting: true,
enableHiding: true,
meta: {
- excelHeader: 'Order Index',
+ excelHeader: 'Updated At',
group: 'Meta Data',
- type: 'number',
+ type: 'date',
},
},
];
- // [6] ACTIONS COLUMN - DROPDOWN MENU
- const actionsColumn: ColumnDef<RegEvalCriteriaView> = {
+ // [6] ACTIONS COLUMN - DROPDOWN MENU WITH VIEW ACTION
+ const actionsColumn: ColumnDef<RegEvalCriteria> = {
id: 'actions',
enableHiding: false,
cell: function Cell({ row }) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
- <Button variant="ghost" size="icon">
- <Ellipsis className="size-4" aria-hidden="true" />
+ <Button
+ aria-label="Open menu"
+ variant="ghost"
+ className="flex size-8 p-0 data-[state=open]:bg-muted"
+ >
+ <Ellipsis className="size-4" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
- <DropdownMenuContent align="end">
+ <DropdownMenuContent align="end" className="w-40">
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "view" })}
+ >
+ <Eye className="mr-2 size-4" />
+ 상세보기
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
<DropdownMenuItem
- onClick={() => setRowAction({ row, type: 'update' })}
+ onSelect={() => setRowAction({ row, type: "update" })}
>
- Modify Criteria
+ <Edit className="mr-2 size-4" />
+ 수정하기
</DropdownMenuItem>
+ <DropdownMenuSeparator />
<DropdownMenuItem
- onClick={() => setRowAction({ row, type: 'delete' })}
- className="text-destructive"
+ onSelect={() => setRowAction({ row, type: "delete" })}
>
- Delete Criteria
+ <Trash2 className="mr-2 size-4" />
+ 삭제하기
+ <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@@ -392,18 +394,9 @@ function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<RegEvalCriteri
return [
selectColumn,
...criteriaColumns,
- {
- id: 'scoreEquip',
- header: '기자재',
- columns: scoreEquipColumns,
- },
- {
- id: 'scoreBulk',
- header: '벌크',
- columns: scoreBulkColumns,
- },
- ...hiddenColumns,
+ ...variableScoreColumns,
remarksColumn,
+ ...hiddenColumns,
actionsColumn,
];
};
diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx
new file mode 100644
index 00000000..60ca173b
--- /dev/null
+++ b/lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx
@@ -0,0 +1,226 @@
+'use client';
+
+/* IMPORT */
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { Separator } from '@/components/ui/separator';
+import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { useEffect, useState } from 'react';
+import { Loader2, X } from 'lucide-react';
+import {
+ REG_EVAL_CRITERIA_CATEGORY,
+ REG_EVAL_CRITERIA_CATEGORY2,
+ REG_EVAL_CRITERIA_ITEM,
+ type RegEvalCriteriaView,
+ type RegEvalCriteriaDetails,
+} from '@/db/schema';
+import { getRegEvalCriteriaDetails } from '../service'; // 서버 액션 import
+
+// ----------------------------------------------------------------------------------------------------
+
+/* TYPES */
+interface RegEvalCriteriaDetailsSheetProps {
+ criteriaViewData: RegEvalCriteriaView;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/* CRITERIA DETAILS SHEET COMPONENT */
+export function RegEvalCriteriaDetailsSheet({
+ criteriaViewData,
+ open,
+ onOpenChange
+}: RegEvalCriteriaDetailsSheetProps) {
+ const [details, setDetails] = useState<RegEvalCriteriaDetails[]>([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+
+ // 상세 항목들 가져오기
+ useEffect(() => {
+ if (criteriaViewData?.id && open) {
+ setLoading(true);
+ setError(null);
+
+ getRegEvalCriteriaDetails(criteriaViewData.id)
+ .then((fetchedDetails) => {
+ setDetails(fetchedDetails || []);
+ })
+ .catch((err) => {
+ console.error('Failed to fetch criteria details:', err);
+ setError('상세 정보를 불러오는데 실패했습니다.');
+ setDetails([]);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+ }, [criteriaViewData?.id, open]);
+
+ // 라벨 변환 함수들
+ const getCategoryLabel = (value: string) =>
+ REG_EVAL_CRITERIA_CATEGORY.find(item => item.value === value)?.label ?? value;
+
+ const getCategory2Label = (value: string) =>
+ REG_EVAL_CRITERIA_CATEGORY2.find(item => item.value === value)?.label ?? value;
+
+ const getItemLabel = (value: string) =>
+ REG_EVAL_CRITERIA_ITEM.find(item => item.value === value)?.label ?? value;
+
+ // 점수 표시 함수
+ const formatScore = (score: string | null | undefined) => {
+ if (!score) return '-';
+ const numericScore = typeof score === 'string' ? parseFloat(score) : score;
+ return isNaN(numericScore) ? '-' : parseFloat(numericScore.toFixed(2)).toString();
+ };
+
+ return (
+ <Sheet open={open} onOpenChange={onOpenChange}>
+ <SheetContent className="w-[800px] sm:w-[900px] sm:max-w-[90vw]" style={{width:900}}>
+ <SheetHeader>
+ <SheetTitle className="flex items-center justify-between">
+ <span>평가 기준 상세보기</span>
+ </SheetTitle>
+ </SheetHeader>
+
+ <ScrollArea className="h-[calc(100vh-120px)] pr-4">
+ <div className="space-y-6 py-4">
+ {/* 기본 정보 카드 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">기본 정보</CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">평가부문</p>
+ <Badge variant="default" className="mt-1">
+ {getCategoryLabel(criteriaViewData.category)}
+ </Badge>
+ </div>
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">점수구분</p>
+ <Badge variant="secondary" className="mt-1">
+ {getCategory2Label(criteriaViewData.category2)}
+ </Badge>
+ </div>
+ </div>
+
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">항목</p>
+ <Badge variant="outline" className="mt-1">
+ {getItemLabel(criteriaViewData.item)}
+ </Badge>
+ </div>
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">구분</p>
+ <p className="text-sm mt-1">{criteriaViewData.classification}</p>
+ </div>
+ </div>
+
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">평가명 (범위)</p>
+ <p className="text-sm mt-1 font-medium">{criteriaViewData.range || '-'}</p>
+ </div>
+
+ {criteriaViewData.remarks && (
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">비고</p>
+ <p className="text-sm mt-1">{criteriaViewData.remarks}</p>
+ </div>
+ )}
+ </CardContent>
+ </Card>
+
+ <Separator />
+
+ {/* 평가 옵션 및 점수 카드 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">평가 옵션 및 점수</CardTitle>
+ <p className="text-sm text-muted-foreground">
+ 각 평가 옵션에 따른 점수를 확인할 수 있습니다.
+ </p>
+ </CardHeader>
+ <CardContent>
+ {loading ? (
+ <div className="flex justify-center items-center py-8">
+ <Loader2 className="h-4 w-4 animate-spin mr-2" />
+ <span className="text-sm text-muted-foreground">로딩 중...</span>
+ </div>
+ ) : error ? (
+ <div className="flex justify-center items-center py-8">
+ <div className="text-sm text-destructive">{error}</div>
+ </div>
+ ) : details.length === 0 ? (
+ <div className="flex justify-center items-center py-8">
+ <div className="text-sm text-muted-foreground">등록된 평가 옵션이 없습니다.</div>
+ </div>
+ ) : (
+ <div className="border rounded-lg">
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead className="w-12">#</TableHead>
+ <TableHead className="min-w-[200px]">평가 옵션</TableHead>
+ <TableHead className="text-center w-24">기자재-조선</TableHead>
+ <TableHead className="text-center w-24">기자재-해양</TableHead>
+ <TableHead className="text-center w-24">벌크-조선</TableHead>
+ <TableHead className="text-center w-24">벌크-해양</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {details.map((detail, index) => (
+ <TableRow key={detail.id}>
+ <TableCell className="font-medium">
+ {(detail.orderIndex ?? index) + 1}
+ </TableCell>
+ <TableCell className="font-medium">
+ {detail.detail}
+ </TableCell>
+ <TableCell className="text-center">
+ <Badge variant="outline" className="font-mono">
+ {formatScore(detail.scoreEquipShip)}
+ </Badge>
+ </TableCell>
+ <TableCell className="text-center">
+ <Badge variant="outline" className="font-mono">
+ {formatScore(detail.scoreEquipMarine)}
+ </Badge>
+ </TableCell>
+ <TableCell className="text-center">
+ <Badge variant="outline" className="font-mono">
+ {formatScore(detail.scoreBulkShip)}
+ </Badge>
+ </TableCell>
+ <TableCell className="text-center">
+ <Badge variant="outline" className="font-mono">
+ {formatScore(detail.scoreBulkMarine)}
+ </Badge>
+ </TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </div>
+ )}
+ </CardContent>
+ </Card>
+
+ </div>
+ </ScrollArea>
+ </SheetContent>
+ </Sheet>
+ );
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/* EXPORT */
+export default RegEvalCriteriaDetailsSheet; \ No newline at end of file
diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx
index d33e7d29..3362d810 100644
--- a/lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx
+++ b/lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx
@@ -60,7 +60,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
/* TYPES */
const regEvalCriteriaFormSchema = z.object({
category: z.string().min(1, '평가부문은 필수 항목입니다.'),
- scoreCategory: z.string().min(1, '점수구분은 필수 항목입니다.'),
+ category2: z.string().min(1, '점수구분은 필수 항목입니다.'),
item: z.string().min(1, '항목은 필수 항목입니다.'),
classification: z.string().min(1, '구분은 필수 항목입니다.'),
range: z.string().nullable().optional(),
@@ -245,7 +245,7 @@ function RegEvalCriteriaFormSheet({
resolver: zodResolver(regEvalCriteriaFormSchema),
defaultValues: {
category: '',
- scoreCategory: '',
+ category2: '',
item: '',
classification: '',
range: '',
@@ -276,7 +276,7 @@ function RegEvalCriteriaFormSheet({
if (targetData) {
form.reset({
category: targetData.category,
- scoreCategory: targetData.scoreCategory,
+ category2: targetData.category2,
item: targetData.item,
classification: targetData.classification,
range: targetData.range,
@@ -303,7 +303,7 @@ function RegEvalCriteriaFormSheet({
} else if (open && !isUpdateMode) {
form.reset({
category: '',
- scoreCategory: '',
+ category2: '',
item: '',
classification: '',
range: '',
@@ -327,7 +327,7 @@ function RegEvalCriteriaFormSheet({
try {
const criteriaData = {
category: data.category,
- scoreCategory: data.scoreCategory,
+ category2: data.category2,
item: data.item,
classification: data.classification,
range: data.range,
@@ -414,7 +414,7 @@ function RegEvalCriteriaFormSheet({
/>
<FormField
control={form.control}
- name="scoreCategory"
+ name="category2"
render={({ field }) => (
<FormItem>
<FormLabel>점수구분</FormLabel>
diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-table.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-table.tsx
index d73eb5bd..e2d614e0 100644
--- a/lib/evaluation-criteria/table/reg-eval-criteria-table.tsx
+++ b/lib/evaluation-criteria/table/reg-eval-criteria-table.tsx
@@ -3,17 +3,18 @@
/* IMPORT */
import { DataTable } from '@/components/data-table/data-table';
import { DataTableAdvancedToolbar } from '@/components/data-table/data-table-advanced-toolbar';
-import getColumns from './reg-eval-criteria-columns';
+import getColumns from './reg-eval-criteria-columns'; // 새로운 컬럼 파일 사용
import { getRegEvalCriteria } from '../service';
import {
REG_EVAL_CRITERIA_CATEGORY,
REG_EVAL_CRITERIA_CATEGORY2,
REG_EVAL_CRITERIA_ITEM,
- type RegEvalCriteriaView
+ type RegEvalCriteria // RegEvalCriteriaView 대신 RegEvalCriteria 사용
} from '@/db/schema';
import RegEvalCriteriaCreateDialog from './reg-eval-criteria-create-dialog';
import RegEvalCriteriaDeleteDialog from './reg-eval-criteria-delete-dialog';
import RegEvalCriteriaUpdateSheet from './reg-eval-criteria-update-sheet';
+import RegEvalCriteriaDetailsSheet from './reg-eval-criteria-details-sheet'; // 새로 추가
import RegEvalCriteriaTableToolbarActions from './reg-eval-criteria-table-toolbar-actions';
import {
type DataTableFilterField,
@@ -38,7 +39,7 @@ interface RegEvalCriteriaTableProps {
/* TABLE COMPONENT */
function RegEvalCriteriaTable({ promises }: RegEvalCriteriaTableProps) {
const router = useRouter();
- const [rowAction, setRowAction] = useState<DataTableRowAction<RegEvalCriteriaView> | null>(null);
+ const [rowAction, setRowAction] = useState<DataTableRowAction<RegEvalCriteria> | null>(null);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState<boolean>(false);
const [promiseData] = use(promises);
const tableData = promiseData;
@@ -48,7 +49,7 @@ function RegEvalCriteriaTable({ promises }: RegEvalCriteriaTableProps) {
[setRowAction],
);
- const filterFields: DataTableFilterField<RegEvalCriteriaView>[] = [
+ const filterFields: DataTableFilterField<RegEvalCriteria>[] = [
{
id: 'category',
label: '평가부문',
@@ -65,7 +66,7 @@ function RegEvalCriteriaTable({ promises }: RegEvalCriteriaTableProps) {
placeholder: '항목 선택...',
},
]
- const advancedFilterFields: DataTableAdvancedFilterField<RegEvalCriteriaView>[] = [
+ const advancedFilterFields: DataTableAdvancedFilterField<RegEvalCriteria>[] = [
{
id: 'category',
label: '평가부문',
@@ -85,12 +86,13 @@ function RegEvalCriteriaTable({ promises }: RegEvalCriteriaTableProps) {
options: REG_EVAL_CRITERIA_ITEM,
},
{ id: 'classification', label: '구분', type: 'text' },
- { id: 'range', label: '범위', type: 'text' },
- { id: 'detail', label: '평가내용', type: 'text' },
- { id: 'scoreEquipShip', label: '조선', type: 'number' },
- { id: 'scoreEquipMarine', label: '해양', type: 'number' },
- { id: 'scoreBulkShip', label: '조선', type: 'number' },
- { id: 'scoreBulkMarine', label: '해양', type: 'number' },
+ { id: 'range', label: '평가명', type: 'text' },
+ { id: 'scoreType', label: '점수유형', type: 'select',
+ options: [
+ { label: '고정점수', value: 'fixed' },
+ { label: '변동점수', value: 'variable' }
+ ]
+ },
{ id: 'remarks', label: '비고', type: 'text' },
];
@@ -104,14 +106,18 @@ function RegEvalCriteriaTable({ promises }: RegEvalCriteriaTableProps) {
enableAdvancedFilter: true,
initialState: {
sorting: [
- { id: 'criteriaId', desc: false },
- { id: 'orderIndex', desc: false },
+ { id: 'id', desc: false },
],
columnPinning: { left: ['select'], right: ['actions'] },
columnVisibility: {
id: false,
- criteriaId: false,
- orderIndex: false,
+ createdAt: false,
+ updatedAt: false,
+ createdBy: false,
+ updatedBy: false,
+ variableScoreMin: false,
+ variableScoreMax: false,
+ variableScoreUnit: false,
},
},
getRowId: (originalRow) => String(originalRow.id),
@@ -119,42 +125,52 @@ function RegEvalCriteriaTable({ promises }: RegEvalCriteriaTableProps) {
clearOnDefault: true,
});
- const emptyCriteriaViewData: RegEvalCriteriaView = {
- id: null,
+ const emptyCriteriaData: RegEvalCriteria = {
+ id: 0,
category: '',
category2: '',
item: '',
classification: '',
range: null,
remarks: null,
- criteriaId: null,
- detail: '',
- orderIndex: null,
- scoreEquipShip: null,
- scoreEquipMarine: null,
- scoreBulkShip: null,
- scoreBulkMarine: null,
+ scoreType: 'fixed',
+ variableScoreMin: null,
+ variableScoreMax: null,
+ variableScoreUnit: null,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ createdBy: 0,
+ updatedBy: 0,
};
const refreshData = useCallback(() => {
router.refresh();
}, [router]);
+
const handleCreateCriteria = () => {
setIsCreateDialogOpen(true);
};
+
const handleCreateSuccess = useCallback(() => {
setIsCreateDialogOpen(false);
refreshData();
}, [refreshData]);
+
const handleModifySuccess = useCallback(() => {
setRowAction(null);
refreshData();
}, [refreshData]);
+
const handleDeleteSuccess = useCallback(() => {
setRowAction(null);
refreshData();
}, [refreshData]);
+ // 상세보기 핸들러 추가
+ const handleDetailsClose = useCallback(() => {
+ setRowAction(null);
+ }, []);
+
return (
<>
<DataTable table={table}>
@@ -170,21 +186,34 @@ function RegEvalCriteriaTable({ promises }: RegEvalCriteriaTableProps) {
/>
</DataTableAdvancedToolbar>
</DataTable>
+
+ {/* 생성 다이얼로그 */}
<RegEvalCriteriaCreateDialog
open={isCreateDialogOpen}
onOpenChange={setIsCreateDialogOpen}
onSuccess={handleCreateSuccess}
/>
+
+ {/* 상세보기 시트 - 새로 추가 */}
+ <RegEvalCriteriaDetailsSheet
+ open={rowAction?.type === 'view'}
+ onOpenChange={handleDetailsClose}
+ criteriaViewData={rowAction?.row.original ?? emptyCriteriaData}
+ />
+
+ {/* 수정 시트 */}
<RegEvalCriteriaUpdateSheet
open={rowAction?.type === 'update'}
onOpenChange={() => setRowAction(null)}
- criteriaViewData={rowAction?.row.original ?? emptyCriteriaViewData}
+ criteriaData={rowAction?.row.original ?? emptyCriteriaData}
onSuccess={handleModifySuccess}
/>
+
+ {/* 삭제 다이얼로그 */}
<RegEvalCriteriaDeleteDialog
open={rowAction?.type === 'delete'}
onOpenChange={() => setRowAction(null)}
- criteriaViewData={rowAction?.row.original ?? emptyCriteriaViewData}
+ criteriaViewData={rowAction?.row.original ?? emptyCriteriaData}
onSuccess={handleDeleteSuccess}
/>
</>
@@ -194,4 +223,4 @@ function RegEvalCriteriaTable({ promises }: RegEvalCriteriaTableProps) {
// ----------------------------------------------------------------------------------------------------
/* EXPORT */
-export default RegEvalCriteriaTable;
+export default RegEvalCriteriaTable; \ No newline at end of file
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>