"use client"; import * as React from "react"; 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, Check, X } from "lucide-react"; import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"; import { EvaluationTargetWithDepartments } from "@/db/schema"; import type { DataTableRowAction } from "@/types/table"; import { formatDate } from "@/lib/utils"; import { vendortypeMap } from "@/types/evaluation"; interface GetColumnsProps { setRowAction: React.Dispatch | null>>; } // ✅ 모든 헬퍼 함수들을 컴포넌트 외부로 이동 export const getStatusBadgeVariant = (status: string) => { switch (status) { case "PENDING": return "secondary"; case "CONFIRMED": return "default"; case "EXCLUDED": return "destructive"; default: return "outline"; } }; const getConsensusBadge = (consensusStatus: boolean | null) => { if (consensusStatus === null) { return 검토 중; } if (consensusStatus === true) { return 의견 일치; } return 의견 불일치; }; const getDivisionBadge = (division: string) => { return ( {division === "PLANT" ? "해양" : "조선"} ); }; export const getMaterialTypeBadge = (materialType: string) => { return {vendortypeMap[materialType] || materialType}; }; const getDomesticForeignBadge = (domesticForeign: string) => { return ( {domesticForeign === "DOMESTIC" ? "D" : "F"} ); }; const getEvaluationTargetBadge = (isTarget: boolean | null) => { if (isTarget === null) { return 미정; } return isTarget ? ( 평가 대상 ) : ( 평가 제외 ); }; const getStatusLabel = (status: string) => { const statusMap = { PENDING: "미확정", EXCLUDED: "제외", CONFIRMED: "확정" }; return statusMap[status] || status; }; // ✅ 모든 cell 렌더러 함수들을 미리 정의 (매번 새로 생성 방지) const renderEvaluationYear = ({ row }: any) => ( {row.getValue("evaluationYear")} ); const renderDivision = ({ row }: any) => getDivisionBadge(row.getValue("division")); const renderStatus = ({ row }: any) => { const status = row.getValue("status"); return ( {getStatusLabel(status)} ); }; const renderConsensusStatus = ({ row }: any) => getConsensusBadge(row.getValue("consensusStatus")); const renderVendorCode = ({ row }: any) => ( {row.getValue("vendorCode")} ); const renderVendorName = ({ row }: any) => (
("vendorName")!}> {row.getValue("vendorName") as string}
); const renderDomesticForeign = ({ row }: any) => getDomesticForeignBadge(row.getValue("domesticForeign")); const renderMaterialType = ({ row }: any) => getMaterialTypeBadge(row.getValue("materialType")); const renderReviewerName = (fieldName: string) => ({ row }: any) => { const reviewerName = row.getValue(fieldName); return reviewerName ? (
{reviewerName}
) : ( - ); }; const renderIsApproved = (fieldName: string) => ({ row }: any) => { const isApproved = row.getValue(fieldName); return getEvaluationTargetBadge(isApproved); }; const renderAdminComment = (maxWidth: string) => ({ row }: any) => { const comment = row.getValue("adminComment") ; return comment ? (
{comment}
) : ( - ); }; const renderConsolidatedComment = (maxWidth: string) => ({ row }: any) => { const comment = row.getValue("consolidatedComment"); return comment ? (
{comment}
) : ( - ); }; const renderConfirmedAt = ({ row }: any) => { const confirmedAt = row.getValue("confirmedAt"); return {confirmedAt ? formatDate(confirmedAt, "KR") : '-'}; }; const renderCreatedAt = ({ row }: any) => { const createdAt = row.getValue("createdAt"); return {formatDate(createdAt, "KR")}; }; // ✅ 헤더 렌더러들도 미리 정의 const createHeaderRenderer = (title: string) => ({ column }: any) => ( ); // ✅ 체크박스 관련 함수들도 미리 정의 const renderSelectAllCheckbox = ({ table }: any) => ( table.toggleAllPageRowsSelected(!!v)} aria-label="select all" className="translate-y-0.5" /> ); const renderRowCheckbox = ({ row }: any) => ( row.toggleSelected(!!v)} aria-label="select row" className="translate-y-0.5" /> ); // ✅ 정적 컬럼 정의 (setRowAction만 동적으로 주입) function createStaticColumns(setRowAction: GetColumnsProps['setRowAction']): ColumnDef[] { // Actions 컬럼의 클릭 핸들러를 미리 정의 const renderActionsCell = ({ row }: any) => (
); return [ // Checkbox { id: "select", header: renderSelectAllCheckbox, cell: renderRowCheckbox, size: 40, enableSorting: false, enableHiding: false, }, // 기본 정보 { accessorKey: "evaluationYear", header: createHeaderRenderer("평가년도"), cell: renderEvaluationYear, size: 100, meta: { excelHeader: "평가년도", }, }, { accessorKey: "division", header: createHeaderRenderer("구분"), cell: renderDivision, size: 80, meta: { excelHeader: "구분", }, }, { accessorKey: "status", header: createHeaderRenderer("상태"), cell: renderStatus, size: 100, meta: { excelHeader: "상태", }, }, // 벤더 정보 { id: "vendorInfo", header: "벤더 정보", columns: [ { accessorKey: "vendorCode", header: createHeaderRenderer("벤더 코드"), cell: renderVendorCode, size: 120, meta: { excelHeader: "벤더 코드", }, }, { accessorKey: "vendorName", header: createHeaderRenderer("벤더명"), cell: renderVendorName, size: 200, meta: { excelHeader: "벤더명", }, }, { accessorKey: "domesticForeign", header: createHeaderRenderer("내외자"), cell: renderDomesticForeign, size: 80, meta: { excelHeader: "내외자", }, }, { accessorKey: "materialType", header: createHeaderRenderer("자재구분"), cell: renderMaterialType, size: 120, meta: { excelHeader: "자재구분", }, }, ] }, { accessorKey: "consensusStatus", header: createHeaderRenderer("의견 일치"), cell: renderConsensusStatus, size: 100, meta: { excelHeader: "의견 일치", }, }, { id: "claim", header: "L/D, Claim", columns:[ { accessorKey: "ldClaimCount", header: ({ column }) => , cell: ({ row }) => ( {row.original.ldClaimCount} ), size: 80, meta: { excelHeader: "LD 건수", }, }, { accessorKey: "ldClaimAmount", header: ({ column }) => , cell: ({ row }) => ( {(Number(row.original.ldClaimAmount).toLocaleString())} ), size: 80, meta: { excelHeader: "LD 금액", }, }, { accessorKey: "ldClaimCurrency", header: ({ column }) => , cell: ({ row }) => ( {row.original.ldClaimCurrency} ), size: 80, meta: { excelHeader: "LD 금액 단위", }, }, ] }, // 발주 담당자 { id: "orderReviewer", header: "발주 담당자", columns: [ { accessorKey: "orderReviewerName", header: createHeaderRenderer("담당자명"), cell: renderReviewerName("orderReviewerName"), size: 120, meta: { excelHeader: "발주 담당자명", }, }, { accessorKey: "orderIsApproved", header: createHeaderRenderer("평가 대상"), cell: renderIsApproved("orderIsApproved"), size: 120, meta: { excelHeader: "발주 평가 대상", }, }, ] }, // 조달 담당자 { id: "procurementReviewer", header: "조달 담당자", columns: [ { accessorKey: "procurementReviewerName", header: createHeaderRenderer("담당자명"), cell: renderReviewerName("procurementReviewerName"), size: 120, meta: { excelHeader: "조달 담당자명", }, }, { accessorKey: "procurementIsApproved", header: createHeaderRenderer("평가 대상"), cell: renderIsApproved("procurementIsApproved"), size: 120, meta: { excelHeader: "조달 평가 대상", }, }, ] }, // 품질 담당자 { id: "qualityReviewer", header: "품질 담당자", columns: [ { accessorKey: "qualityReviewerName", header: createHeaderRenderer("담당자명"), cell: renderReviewerName("qualityReviewerName"), size: 120, meta: { excelHeader: "품질 담당자명", }, }, { accessorKey: "qualityIsApproved", header: createHeaderRenderer("평가 대상"), cell: renderIsApproved("qualityIsApproved"), size: 120, meta: { excelHeader: "품질 평가 대상", }, }, ] }, // 설계 담당자 { id: "designReviewer", header: "설계 담당자", columns: [ { accessorKey: "designReviewerName", header: createHeaderRenderer("담당자명"), cell: renderReviewerName("designReviewerName"), size: 120, }, // { // accessorKey: "designIsApproved", // header: createHeaderRenderer("평가 대상"), // cell: renderIsApproved("designIsApproved"), // size: 120, // }, ] }, // CS 담당자 { id: "csReviewer", header: "CS 담당자", columns: [ { accessorKey: "csReviewerName", header: createHeaderRenderer("담당자명"), cell: renderReviewerName("csReviewerName"), size: 120, }, // { // accessorKey: "csIsApproved", // header: createHeaderRenderer("평가 대상"), // cell: renderIsApproved("csIsApproved"), // size: 120, // }, ] }, // 의견 및 결과 { accessorKey: "adminComment", header: createHeaderRenderer("관리자 의견"), cell: renderAdminComment("max-w-[150px]"), size: 150, meta: { excelHeader: "관리자 의견", }, }, { accessorKey: "consolidatedComment", header: createHeaderRenderer("종합 의견"), cell: renderConsolidatedComment("max-w-[150px]"), size: 150, meta: { excelHeader: "종합 의견", }, }, { accessorKey: "confirmedAt", header: createHeaderRenderer("확정일"), cell: renderConfirmedAt, size: 100, meta: { excelHeader: "확정일", }, }, { accessorKey: "createdAt", header: createHeaderRenderer("생성일"), cell: renderCreatedAt, size: 100, meta: { excelHeader: "생성일", }, }, // Actions { id: "actions", enableHiding: false, size: 40, minSize: 40, cell: renderActionsCell, }, ]; } // ✅ WeakMap 캐시로 setRowAction별로 컬럼 캐싱 const columnsCache = new WeakMap[]>(); export function getEvaluationTargetsColumns({ setRowAction }: GetColumnsProps): ColumnDef[] { // 캐시 확인 if (columnsCache.has(setRowAction)) { console.log('✅ 캐시된 컬럼 사용'); return columnsCache.get(setRowAction)!; } console.log('🏗️ 새로운 컬럼 생성'); const columns = createStaticColumns(setRowAction); columnsCache.set(setRowAction, columns); return columns; }