summaryrefslogtreecommitdiff
path: root/lib/evaluation-target-list/table/evaluation-targets-columns.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-20 11:37:31 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-20 11:37:31 +0000
commitaa86729f9a2ab95346a2851e3837de1c367aae17 (patch)
treeb601b18b6724f2fb449c7fa9ea50cbd652a8077d /lib/evaluation-target-list/table/evaluation-targets-columns.tsx
parent95bbe9c583ff841220da1267630e7b2025fc36dc (diff)
(대표님) 20250620 작업사항
Diffstat (limited to 'lib/evaluation-target-list/table/evaluation-targets-columns.tsx')
-rw-r--r--lib/evaluation-target-list/table/evaluation-targets-columns.tsx459
1 files changed, 322 insertions, 137 deletions
diff --git a/lib/evaluation-target-list/table/evaluation-targets-columns.tsx b/lib/evaluation-target-list/table/evaluation-targets-columns.tsx
index b1e19434..93807ef9 100644
--- a/lib/evaluation-target-list/table/evaluation-targets-columns.tsx
+++ b/lib/evaluation-target-list/table/evaluation-targets-columns.tsx
@@ -7,6 +7,11 @@ import { Button } from "@/components/ui/button";
import { Pencil, Eye, MessageSquare, 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";
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<EvaluationTargetWithDepartments> | null>>;
+}
// 상태별 색상 매핑
const getStatusBadgeVariant = (status: string) => {
@@ -36,8 +41,8 @@ const getConsensusBadge = (consensusStatus: boolean | null) => {
// 구분 배지
const getDivisionBadge = (division: string) => {
return (
- <Badge variant={division === "OCEAN" ? "default" : "secondary"}>
- {division === "OCEAN" ? "해양" : "조선"}
+ <Badge variant={division === "PLANT" ? "default" : "secondary"}>
+ {division === "PLANT" ? "해양" : "조선"}
</Badge>
);
};
@@ -46,7 +51,7 @@ const getDivisionBadge = (division: string) => {
const getMaterialTypeBadge = (materialType: string) => {
const typeMap = {
EQUIPMENT: "기자재",
- BULK: "벌크",
+ BULK: "벌크",
EQUIPMENT_BULK: "기자재/벌크"
};
return <Badge variant="outline">{typeMap[materialType] || materialType}</Badge>;
@@ -61,8 +66,23 @@ const getDomesticForeignBadge = (domesticForeign: string) => {
);
};
-export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDepartments>[] {
+// 평가 상태 배지
+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>;
+ }
+ return <Badge variant="destructive" className="text-xs">거부</Badge>;
+};
+
+export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): ColumnDef<EvaluationTargetWithDepartments>[] {
return [
+ // ═══════════════════════════════════════════════════════════════
+ // 기본 정보
+ // ═══════════════════════════════════════════════════════════════
+
// Checkbox
{
id: "select",
@@ -102,46 +122,6 @@ export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDep
cell: ({ row }) => getDivisionBadge(row.getValue("division")),
size: 80,
},
-
- // ░░░ 벤더 코드 ░░░
- {
- accessorKey: "vendorCode",
- header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더 코드" />,
- cell: ({ row }) => (
- <span className="font-mono text-sm">{row.getValue("vendorCode")}</span>
- ),
- size: 120,
- },
-
- // ░░░ 벤더명 ░░░
- {
- accessorKey: "vendorName",
- header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더명" />,
- cell: ({ row }) => (
- <div className="truncate max-w-[200px]" title={row.getValue<string>("vendorName")!}>
- {row.getValue("vendorName") as string}
- </div>
- ),
- 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: "status",
header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="상태" />,
@@ -161,6 +141,54 @@ export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDep
size: 100,
},
+ // ░░░ 벤더 코드 ░░░
+
+ {
+ header: "협력업체 정보",
+ columns: [
+ {
+ accessorKey: "vendorCode",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더 코드" />,
+ cell: ({ row }) => (
+ <span className="font-mono text-sm">{row.getValue("vendorCode")}</span>
+ ),
+ size: 120,
+ },
+
+ // ░░░ 벤더명 ░░░
+ {
+ accessorKey: "vendorName",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더명" />,
+ cell: ({ row }) => (
+ <div className="truncate max-w-[200px]" title={row.getValue<string>("vendorName")!}>
+ {row.getValue("vendorName") as string}
+ </div>
+ ),
+ 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: "consensusStatus",
@@ -169,56 +197,235 @@ export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDep
size: 100,
},
- // ░░░ 담당자 현황 ░░░
+ // ═══════════════════════════════════════════════════════════════
+ // 주문 부서 그룹
+ // ═══════════════════════════════════════════════════════════════
{
- id: "reviewers",
- header: "담당자 현황",
- cell: ({ row }) => {
- const reviewers = row.original.reviewers || [];
- const totalReviewers = reviewers.length;
- const completedReviews = reviewers.filter(r => r.review?.isApproved !== null).length;
- const approvedReviews = reviewers.filter(r => r.review?.isApproved === true).length;
-
- return (
- <div className="flex items-center gap-2">
- <div className="text-xs">
- <span className="text-green-600 font-medium">{approvedReviews}</span>
- <span className="text-muted-foreground">/{completedReviews}</span>
- <span className="text-muted-foreground">/{totalReviewers}</span>
- </div>
- {totalReviewers > 0 && (
- <div className="flex gap-1">
- {reviewers.slice(0, 3).map((reviewer, idx) => (
- <div
- key={idx}
- className={`w-2 h-2 rounded-full ${
- reviewer.review?.isApproved === true
- ? "bg-green-500"
- : reviewer.review?.isApproved === false
- ? "bg-red-500"
- : "bg-gray-300"
- }`}
- title={`${reviewer.departmentCode}: ${
- reviewer.review?.isApproved === true
- ? "승인"
- : reviewer.review?.isApproved === false
- ? "거부"
- : "대기중"
- }`}
- />
- ))}
- {totalReviewers > 3 && (
- <span className="text-xs text-muted-foreground">+{totalReviewers - 3}</span>
- )}
+ 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>
- )}
- </div>
- );
- },
- size: 120,
- enableSorting: false,
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ );
+ },
+ size: 120,
+ },
+ {
+ accessorKey: "orderReviewerName",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />,
+ cell: ({ row }) => {
+ const reviewerName = row.getValue<string>("orderReviewerName");
+ return reviewerName ? (
+ <div className="truncate max-w-[100px]" title={reviewerName}>
+ {reviewerName}
+ </div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ );
+ },
+ size: 100,
+ },
+ {
+ accessorKey: "orderIsApproved",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />,
+ cell: ({ row }) => getApprovalBadge(row.getValue("orderIsApproved")),
+ size: 80,
+ },
+ ],
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 조달 부서 그룹
+ // ═══════════════════════════════════════════════════════════════
+ {
+ 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="담당자" />,
+ cell: ({ row }) => {
+ const reviewerName = row.getValue<string>("procurementReviewerName");
+ return reviewerName ? (
+ <div className="truncate max-w-[100px]" title={reviewerName}>
+ {reviewerName}
+ </div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ );
+ },
+ size: 100,
+ },
+ {
+ accessorKey: "procurementIsApproved",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />,
+ cell: ({ row }) => getApprovalBadge(row.getValue("procurementIsApproved")),
+ size: 80,
+ },
+ ],
},
+ // ═══════════════════════════════════════════════════════════════
+ // 품질 부서 그룹
+ // ═══════════════════════════════════════════════════════════════
+ {
+ 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="담당자" />,
+ cell: ({ row }) => {
+ const reviewerName = row.getValue<string>("qualityReviewerName");
+ return reviewerName ? (
+ <div className="truncate max-w-[100px]" title={reviewerName}>
+ {reviewerName}
+ </div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ );
+ },
+ size: 100,
+ },
+ {
+ accessorKey: "qualityIsApproved",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />,
+ cell: ({ row }) => getApprovalBadge(row.getValue("qualityIsApproved")),
+ size: 80,
+ },
+ ],
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 설계 부서 그룹
+ // ═══════════════════════════════════════════════════════════════
+ {
+ 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="담당자" />,
+ cell: ({ row }) => {
+ const reviewerName = row.getValue<string>("designReviewerName");
+ return reviewerName ? (
+ <div className="truncate max-w-[100px]" title={reviewerName}>
+ {reviewerName}
+ </div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ );
+ },
+ size: 100,
+ },
+ {
+ accessorKey: "designIsApproved",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />,
+ cell: ({ row }) => getApprovalBadge(row.getValue("designIsApproved")),
+ size: 80,
+ },
+ ],
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // CS 부서 그룹
+ // ═══════════════════════════════════════════════════════════════
+ {
+ 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="담당자" />,
+ cell: ({ row }) => {
+ const reviewerName = row.getValue<string>("csReviewerName");
+ return reviewerName ? (
+ <div className="truncate max-w-[100px]" title={reviewerName}>
+ {reviewerName}
+ </div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ );
+ },
+ size: 100,
+ },
+ {
+ accessorKey: "csIsApproved",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />,
+ cell: ({ row }) => getApprovalBadge(row.getValue("csIsApproved")),
+ size: 80,
+ },
+ ],
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 관리 정보
+ // ═══════════════════════════════════════════════════════════════
+
// ░░░ 관리자 의견 ░░░
{
accessorKey: "adminComment",
@@ -274,69 +481,47 @@ export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDep
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>
+ );
+ },
+ size: 100,
+ },
+
// ░░░ Actions ░░░
{
id: "actions",
enableHiding: false,
- size: 120,
- minSize: 120,
+ size: 40,
+ minSize: 40,
cell: ({ row }) => {
- const record = row.original;
- const [openDetail, setOpenDetail] = React.useState(false);
- const [openEdit, setOpenEdit] = React.useState(false);
- const [openRequest, setOpenRequest] = React.useState(false);
-
- return (
+ return (
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
className="size-8"
- onClick={() => setOpenDetail(true)}
- aria-label="상세보기"
- title="상세보기"
- >
- <Eye className="size-4" />
- </Button>
-
- <Button
- variant="ghost"
- size="icon"
- className="size-8"
- onClick={() => setOpenEdit(true)}
+ onClick={() => setRowAction({ row, type: "update" })}
aria-label="수정"
title="수정"
>
<Pencil className="size-4" />
</Button>
- <Button
- variant="ghost"
- size="icon"
- className="size-8"
- onClick={() => setOpenRequest(true)}
- aria-label="의견요청"
- title="의견요청"
- >
- <MessageSquare className="size-4" />
- </Button>
-
- {/* TODO: 실제 다이얼로그 컴포넌트들로 교체 */}
- {openDetail && (
- <div onClick={() => setOpenDetail(false)}>
- {/* <EvaluationTargetDetailDialog /> */}
- </div>
- )}
- {openEdit && (
- <div onClick={() => setOpenEdit(false)}>
- {/* <EditEvaluationTargetDialog /> */}
- </div>
- )}
- {openRequest && (
- <div onClick={() => setOpenRequest(false)}>
- {/* <RequestReviewDialog /> */}
- </div>
- )}
</div>
);
},