diff options
Diffstat (limited to 'lib/evaluation-target-list/table/evaluation-targets-columns.tsx')
| -rw-r--r-- | lib/evaluation-target-list/table/evaluation-targets-columns.tsx | 351 |
1 files changed, 125 insertions, 226 deletions
diff --git a/lib/evaluation-target-list/table/evaluation-targets-columns.tsx b/lib/evaluation-target-list/table/evaluation-targets-columns.tsx index 93807ef9..e2163cad 100644 --- a/lib/evaluation-target-list/table/evaluation-targets-columns.tsx +++ b/lib/evaluation-target-list/table/evaluation-targets-columns.tsx @@ -4,16 +4,17 @@ import { type ColumnDef } from "@tanstack/react-table"; import { Checkbox } from "@/components/ui/checkbox"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Pencil, Eye, MessageSquare, Check, X } from "lucide-react"; +import { Pencil, Check, X } from "lucide-react"; import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"; import { EvaluationTargetWithDepartments } from "@/db/schema"; -import { EditEvaluationTargetSheet } from "./update-evaluation-target"; +import type { DataTableRowAction } from "@/types/table"; +import { formatDate } from "@/lib/utils"; interface GetColumnsProps { setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<EvaluationTargetWithDepartments> | null>>; } -// 상태별 색상 매핑 +// ✅ 모든 헬퍼 함수들을 컴포넌트 외부로 이동 (매번 재생성 방지) const getStatusBadgeVariant = (status: string) => { switch (status) { case "PENDING": @@ -27,7 +28,15 @@ const getStatusBadgeVariant = (status: string) => { } }; -// 의견 일치 여부 배지 +const getStatusText = (status: string) => { + const statusMap = { + PENDING: "검토 중", + CONFIRMED: "확정", + EXCLUDED: "제외" + }; + return statusMap[status] || status; +}; + const getConsensusBadge = (consensusStatus: boolean | null) => { if (consensusStatus === null) { return <Badge variant="outline">검토 중</Badge>; @@ -38,16 +47,14 @@ const getConsensusBadge = (consensusStatus: boolean | null) => { return <Badge variant="destructive">의견 불일치</Badge>; }; -// 구분 배지 const getDivisionBadge = (division: string) => { return ( - <Badge variant={division === "PLANT" ? "default" : "secondary"}> - {division === "PLANT" ? "해양" : "조선"} + <Badge variant={division === "OCEAN" ? "default" : "secondary"}> + {division === "OCEAN" ? "해양" : "조선"} </Badge> ); }; -// 자재구분 배지 const getMaterialTypeBadge = (materialType: string) => { const typeMap = { EQUIPMENT: "기자재", @@ -57,7 +64,6 @@ const getMaterialTypeBadge = (materialType: string) => { return <Badge variant="outline">{typeMap[materialType] || materialType}</Badge>; }; -// 내외자 배지 const getDomesticForeignBadge = (domesticForeign: string) => { return ( <Badge variant={domesticForeign === "DOMESTIC" ? "default" : "secondary"}> @@ -66,24 +72,27 @@ const getDomesticForeignBadge = (domesticForeign: string) => { ); }; -// 평가 상태 배지 -const getApprovalBadge = (isApproved: boolean | null) => { - if (isApproved === null) { - return <Badge variant="outline" className="text-xs">대기중</Badge>; - } - if (isApproved === true) { - return <Badge variant="default" className="bg-green-600 text-xs">승인</Badge>; +// ✅ 평가 대상 여부 표시 함수 +const getEvaluationTargetBadge = (isTarget: boolean | null) => { + if (isTarget === null) { + return <Badge variant="outline">미정</Badge>; } - return <Badge variant="destructive" className="text-xs">거부</Badge>; + return isTarget ? ( + <Badge variant="default" className="bg-blue-600"> + <Check className="size-3 mr-1" /> + 평가 대상 + </Badge> + ) : ( + <Badge variant="secondary"> + <X className="size-3 mr-1" /> + 평가 제외 + </Badge> + ); }; -export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): ColumnDef<EvaluationTargetWithDepartments>[] { +export function getEvaluationTargetsColumns({ setRowAction }: GetColumnsProps): ColumnDef<EvaluationTargetWithDepartments>[] { return [ - // ═══════════════════════════════════════════════════════════════ - // 기본 정보 - // ═══════════════════════════════════════════════════════════════ - - // Checkbox + // ✅ Checkbox { id: "select", header: ({ table }) => ( @@ -107,15 +116,13 @@ export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): Col enableHiding: false, }, - // ░░░ 평가년도 ░░░ + // ✅ 기본 정보 { accessorKey: "evaluationYear", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가년도" />, cell: ({ row }) => <span className="font-medium">{row.getValue("evaluationYear")}</span>, size: 100, }, - - // ░░░ 구분 ░░░ { accessorKey: "division", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="구분" />, @@ -127,24 +134,25 @@ export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): Col header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="상태" />, cell: ({ row }) => { const status = row.getValue<string>("status"); - const statusMap = { - PENDING: "검토 중", - CONFIRMED: "확정", - EXCLUDED: "제외" - }; return ( <Badge variant={getStatusBadgeVariant(status)}> - {statusMap[status] || status} + {getStatusText(status)} </Badge> ); }, size: 100, }, + { + accessorKey: "consensusStatus", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="의견 일치" />, + cell: ({ row }) => getConsensusBadge(row.getValue("consensusStatus")), + size: 100, + }, - // ░░░ 벤더 코드 ░░░ - + // ✅ 벤더 정보 그룹 { - header: "협력업체 정보", + id: "vendorInfo", + header: "벤더 정보", columns: [ { accessorKey: "vendorCode", @@ -154,8 +162,6 @@ export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): Col ), size: 120, }, - - // ░░░ 벤더명 ░░░ { accessorKey: "vendorName", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더명" />, @@ -166,267 +172,182 @@ export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): Col ), size: 200, }, - - // ░░░ 내외자 ░░░ { accessorKey: "domesticForeign", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="내외자" />, cell: ({ row }) => getDomesticForeignBadge(row.getValue("domesticForeign")), size: 80, }, - + { + accessorKey: "materialType", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재구분" />, + cell: ({ row }) => getMaterialTypeBadge(row.getValue("materialType")), + size: 120, + }, ] }, - // ░░░ 자재구분 ░░░ - { - accessorKey: "materialType", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재구분" />, - cell: ({ row }) => getMaterialTypeBadge(row.getValue("materialType")), - size: 120, - }, - - // ░░░ 상태 ░░░ - - - // ░░░ 의견 일치 여부 ░░░ - { - accessorKey: "consensusStatus", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="의견 일치" />, - cell: ({ row }) => getConsensusBadge(row.getValue("consensusStatus")), - size: 100, - }, - - // ═══════════════════════════════════════════════════════════════ - // 주문 부서 그룹 - // ═══════════════════════════════════════════════════════════════ + // ✅ 발주 담당자 { - header: "발주 평가 담당자", + id: "orderReviewer", + header: "발주 담당자", columns: [ { - accessorKey: "orderDepartmentName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, - cell: ({ row }) => { - const departmentName = row.getValue<string>("orderDepartmentName"); - return departmentName ? ( - <div className="truncate max-w-[120px]" title={departmentName}> - {departmentName} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ); - }, - size: 120, - }, - { accessorKey: "orderReviewerName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자명" />, cell: ({ row }) => { const reviewerName = row.getValue<string>("orderReviewerName"); return reviewerName ? ( - <div className="truncate max-w-[100px]" title={reviewerName}> + <div className="truncate max-w-[120px]" title={reviewerName}> {reviewerName} </div> ) : ( <span className="text-muted-foreground">-</span> ); }, - size: 100, + size: 120, }, { accessorKey: "orderIsApproved", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, - cell: ({ row }) => getApprovalBadge(row.getValue("orderIsApproved")), - size: 80, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가 대상" />, + cell: ({ row }) => { + const isApproved = row.getValue<boolean>("orderIsApproved"); + return getEvaluationTargetBadge(isApproved); + }, + size: 120, }, - ], + ] }, - // ═══════════════════════════════════════════════════════════════ - // 조달 부서 그룹 - // ═══════════════════════════════════════════════════════════════ + // ✅ 조달 담당자 { - header: "조달 평가 담당자", + id: "procurementReviewer", + header: "조달 담당자", columns: [ { - accessorKey: "procurementDepartmentName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, - cell: ({ row }) => { - const departmentName = row.getValue<string>("procurementDepartmentName"); - return departmentName ? ( - <div className="truncate max-w-[120px]" title={departmentName}> - {departmentName} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ); - }, - size: 120, - }, - { accessorKey: "procurementReviewerName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자명" />, cell: ({ row }) => { const reviewerName = row.getValue<string>("procurementReviewerName"); return reviewerName ? ( - <div className="truncate max-w-[100px]" title={reviewerName}> + <div className="truncate max-w-[120px]" title={reviewerName}> {reviewerName} </div> ) : ( <span className="text-muted-foreground">-</span> ); }, - size: 100, + size: 120, }, { accessorKey: "procurementIsApproved", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, - cell: ({ row }) => getApprovalBadge(row.getValue("procurementIsApproved")), - size: 80, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가 대상" />, + cell: ({ row }) => { + const isApproved = row.getValue<boolean>("procurementIsApproved"); + return getEvaluationTargetBadge(isApproved); + }, + size: 120, }, - ], + ] }, - // ═══════════════════════════════════════════════════════════════ - // 품질 부서 그룹 - // ═══════════════════════════════════════════════════════════════ + // ✅ 품질 담당자 { - header: "품질 평가 담당자", + id: "qualityReviewer", + header: "품질 담당자", columns: [ { - accessorKey: "qualityDepartmentName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, - cell: ({ row }) => { - const departmentName = row.getValue<string>("qualityDepartmentName"); - return departmentName ? ( - <div className="truncate max-w-[120px]" title={departmentName}> - {departmentName} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ); - }, - size: 120, - }, - { accessorKey: "qualityReviewerName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자명" />, cell: ({ row }) => { const reviewerName = row.getValue<string>("qualityReviewerName"); return reviewerName ? ( - <div className="truncate max-w-[100px]" title={reviewerName}> + <div className="truncate max-w-[120px]" title={reviewerName}> {reviewerName} </div> ) : ( <span className="text-muted-foreground">-</span> ); }, - size: 100, + size: 120, }, { accessorKey: "qualityIsApproved", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, - cell: ({ row }) => getApprovalBadge(row.getValue("qualityIsApproved")), - size: 80, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가 대상" />, + cell: ({ row }) => { + const isApproved = row.getValue<boolean>("qualityIsApproved"); + return getEvaluationTargetBadge(isApproved); + }, + size: 120, }, - ], + ] }, - // ═══════════════════════════════════════════════════════════════ - // 설계 부서 그룹 - // ═══════════════════════════════════════════════════════════════ + // ✅ 설계 담당자 { - header: "설계 평가 담당자", + id: "designReviewer", + header: "설계 담당자", columns: [ { - accessorKey: "designDepartmentName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, - cell: ({ row }) => { - const departmentName = row.getValue<string>("designDepartmentName"); - return departmentName ? ( - <div className="truncate max-w-[120px]" title={departmentName}> - {departmentName} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ); - }, - size: 120, - }, - { accessorKey: "designReviewerName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자명" />, cell: ({ row }) => { const reviewerName = row.getValue<string>("designReviewerName"); return reviewerName ? ( - <div className="truncate max-w-[100px]" title={reviewerName}> + <div className="truncate max-w-[120px]" title={reviewerName}> {reviewerName} </div> ) : ( <span className="text-muted-foreground">-</span> ); }, - size: 100, + size: 120, }, { accessorKey: "designIsApproved", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, - cell: ({ row }) => getApprovalBadge(row.getValue("designIsApproved")), - size: 80, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가 대상" />, + cell: ({ row }) => { + const isApproved = row.getValue<boolean>("designIsApproved"); + return getEvaluationTargetBadge(isApproved); + }, + size: 120, }, - ], + ] }, - // ═══════════════════════════════════════════════════════════════ - // CS 부서 그룹 - // ═══════════════════════════════════════════════════════════════ + // ✅ CS 담당자 { - header: "CS 평가 담당자", + id: "csReviewer", + header: "CS 담당자", columns: [ { - accessorKey: "csDepartmentName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, - cell: ({ row }) => { - const departmentName = row.getValue<string>("csDepartmentName"); - return departmentName ? ( - <div className="truncate max-w-[120px]" title={departmentName}> - {departmentName} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ); - }, - size: 120, - }, - { accessorKey: "csReviewerName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자명" />, cell: ({ row }) => { const reviewerName = row.getValue<string>("csReviewerName"); return reviewerName ? ( - <div className="truncate max-w-[100px]" title={reviewerName}> + <div className="truncate max-w-[120px]" title={reviewerName}> {reviewerName} </div> ) : ( <span className="text-muted-foreground">-</span> ); }, - size: 100, + size: 120, }, { accessorKey: "csIsApproved", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, - cell: ({ row }) => getApprovalBadge(row.getValue("csIsApproved")), - size: 80, + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가 대상" />, + cell: ({ row }) => { + const isApproved = row.getValue<boolean>("csIsApproved"); + return getEvaluationTargetBadge(isApproved); + }, + size: 120, }, - ], + ] }, - // ═══════════════════════════════════════════════════════════════ - // 관리 정보 - // ═══════════════════════════════════════════════════════════════ - - // ░░░ 관리자 의견 ░░░ + // ✅ 의견 및 결과 { accessorKey: "adminComment", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="관리자 의견" />, @@ -442,8 +363,6 @@ export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): Col }, size: 150, }, - - // ░░░ 종합 의견 ░░░ { accessorKey: "consolidatedComment", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="종합 의견" />, @@ -459,69 +378,49 @@ export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): Col }, size: 150, }, - - // ░░░ 확정일 ░░░ { accessorKey: "confirmedAt", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="확정일" />, cell: ({ row }) => { const confirmedAt = row.getValue<Date>("confirmedAt"); - return confirmedAt ? ( - <span className="text-sm"> - {new Intl.DateTimeFormat("ko-KR", { - year: "numeric", - month: "2-digit", - day: "2-digit", - }).format(new Date(confirmedAt))} - </span> - ) : ( - <span className="text-muted-foreground">-</span> - ); + return <span className="text-sm">{formatDate(confirmedAt, "KR")}</span>; }, size: 100, }, - - // ░░░ 생성일 ░░░ { accessorKey: "createdAt", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="생성일" />, cell: ({ row }) => { const createdAt = row.getValue<Date>("createdAt"); - return createdAt ? ( - <span className="text-sm"> - {new Intl.DateTimeFormat("ko-KR", { - year: "numeric", - month: "2-digit", - day: "2-digit", - }).format(new Date(createdAt))} - </span> - ) : ( - <span className="text-muted-foreground">-</span> - ); + return <span className="text-sm">{formatDate(createdAt, "KR")}</span>; }, size: 100, }, - // ░░░ Actions ░░░ + // ✅ Actions - 가장 안전하게 처리 { id: "actions", enableHiding: false, size: 40, minSize: 40, cell: ({ row }) => { - return ( + // ✅ 함수를 직접 정의해서 매번 새로 생성되지 않도록 처리 + const handleEdit = () => { + setRowAction({ row, type: "update" }); + }; + + return ( <div className="flex items-center gap-1"> <Button variant="ghost" size="icon" className="size-8" - onClick={() => setRowAction({ row, type: "update" })} + onClick={handleEdit} aria-label="수정" title="수정" > <Pencil className="size-4" /> </Button> - </div> ); }, |
