"use client" import * as React from "react" import { type DataTableRowAction } from "@/types/table" import { type ColumnDef } from "@tanstack/react-table" import { Ellipsis } from "lucide-react" import { toast } from "sonner" import { getErrorMessage } from "@/lib/handle-error" import { formatDate } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { useRouter } from "next/navigation" import { TechVendor, techVendors } from "@/db/schema/techVendors" import { modifyTechVendor } from "../service" import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" import { techVendorColumnsConfig } from "@/config/techVendorColumnsConfig" import { Separator } from "@/components/ui/separator" import { getVendorStatusIcon } from "../utils" // 타입 정의 추가 type StatusType = (typeof techVendors.status.enumValues)[number]; type BadgeVariantType = "default" | "secondary" | "destructive" | "outline"; type StatusConfig = { variant: BadgeVariantType; className: string; }; type StatusDisplayMap = { [key in StatusType]: string; }; type NextRouter = ReturnType; interface GetColumnsProps { setRowAction: React.Dispatch | null>>; router: NextRouter; } /** * tanstack table 컬럼 정의 (중첩 헤더 버전) */ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef[] { // ---------------------------------------------------------------- // 1) select 컬럼 (체크박스) // ---------------------------------------------------------------- const selectColumn: ColumnDef = { id: "select", header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} aria-label="Select all" className="translate-y-0.5" /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label="Select row" className="translate-y-0.5" /> ), size: 40, enableSorting: false, enableHiding: false, } // ---------------------------------------------------------------- // 2) actions 컬럼 (Dropdown 메뉴) // ---------------------------------------------------------------- const actionsColumn: ColumnDef = { id: "actions", enableHiding: false, cell: function Cell({ row }) { const [isUpdatePending, startUpdateTransition] = React.useTransition() return ( setRowAction({ row, type: "update" })} > 레코드 편집 { // 1) 만약 rowAction을 열고 싶다면 // setRowAction({ row, type: "update" }) // 2) 자세히 보기 페이지로 클라이언트 라우팅 router.push(`/evcp/tech-vendors/${row.original.id}/info`); }} > 상세보기 { // 새창으로 열기 위해 window.open() 사용 window.open(`/evcp/tech-vendors/${row.original.id}/info`, '_blank'); }} > 상세보기(새창) Status { startUpdateTransition(() => { toast.promise( modifyTechVendor({ id: String(row.original.id), status: value as TechVendor["status"], vendorName: row.original.vendorName, // Required field from UpdateVendorSchema }), { loading: "Updating...", success: "Label updated", error: (err) => getErrorMessage(err), } ) }) }} > {techVendors.status.enumValues.map((status) => ( {status} ))} ) }, size: 40, } // ---------------------------------------------------------------- // 3) 일반 컬럼들을 "그룹"별로 묶어 중첩 columns 생성 // ---------------------------------------------------------------- // 3-1) groupMap: { [groupName]: ColumnDef[] } const groupMap: Record[]> = {} techVendorColumnsConfig.forEach((cfg) => { // 만약 group가 없으면 "_noGroup" 처리 const groupName = cfg.group || "_noGroup" if (!groupMap[groupName]) { groupMap[groupName] = [] } // child column 정의 const childCol: ColumnDef = { accessorKey: cfg.id, enableResizing: true, header: ({ column }) => ( ), meta: { excelHeader: cfg.excelHeader, group: cfg.group, type: cfg.type, }, cell: ({ row, cell }) => { // Status 컬럼 렌더링 개선 - 아이콘과 더 선명한 배경색 사용 if (cfg.id === "status") { const statusVal = row.original.status; if (!statusVal) return null; // Status badge variant mapping - 더 뚜렷한 색상으로 변경 const getStatusConfig = (status: StatusType): StatusConfig & { iconColor: string } => { switch (status) { case "ACTIVE": return { variant: "default", className: "bg-emerald-100 text-emerald-800 border-emerald-300 font-semibold", iconColor: "text-emerald-600" }; case "INACTIVE": return { variant: "default", className: "bg-gray-100 text-gray-800 border-gray-300", iconColor: "text-gray-600" }; case "BLACKLISTED": return { variant: "destructive", className: "bg-slate-800 text-white border-slate-900", iconColor: "text-white" }; case "PENDING_REVIEW": return { variant: "default", className: "bg-gray-100 text-gray-800 border-gray-300", iconColor: "text-gray-600" }; default: return { variant: "default", className: "bg-gray-100 text-gray-800 border-gray-300", iconColor: "text-gray-600" }; } }; // 상태 표시 텍스트 const getStatusDisplay = (status: StatusType): string => { const statusMap: StatusDisplayMap = { "ACTIVE": "활성 상태", "INACTIVE": "비활성 상태", "BLACKLISTED": "거래 금지", "PENDING_REVIEW": "비교 견적", }; return statusMap[status] || status; }; const statusConfig = getStatusConfig(statusVal); const displayText = getStatusDisplay(statusVal); const StatusIcon = getVendorStatusIcon(statusVal); return (
{displayText}
); } // 날짜 컬럼 포맷팅 if (cfg.type === "date" && cell.getValue()) { return formatDate(cell.getValue() as Date); } return cell.getValue(); }, }; groupMap[groupName].push(childCol); }); // 3-2) groupMap -> columns (그룹별 -> 중첩 헤더 ColumnDef[] 배열 변환) const columns: ColumnDef[] = [ selectColumn, // 1) 체크박스 ]; // 3-3) 그룹이 있는 컬럼들은 중첩 헤더로, 없는 것들은 그냥 컬럼으로 Object.entries(groupMap).forEach(([groupName, childColumns]) => { if (groupName === "_noGroup") { // 그룹이 없는 컬럼들은 그냥 추가 columns.push(...childColumns); } else { // 그룹이 있는 컬럼들은 헤더 아래 자식으로 중첩 columns.push({ id: groupName, header: groupName, // 그룹명을 헤더로 columns: childColumns, // 그룹에 속한 컬럼들을 자식으로 }); } }); columns.push(actionsColumn); // 마지막에 액션 컬럼 추가 return columns; }