"use client" import * as React from "react" import { ColumnDef } from "@tanstack/react-table" import { Checkbox } from "@/components/ui/checkbox" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Edit, Ellipsis, AlertTriangle, FileEdit, Eye, FileText } from "lucide-react" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" import { formatDate } from "@/lib/utils" // Import types import { type DataTableRowAction } from "@/types/table" import { vendorInvestigationsColumnsConfig, VendorInvestigationsViewWithContacts } from "@/config/vendorInvestigationsColumnsConfig" import { useRouter } from "next/navigation" // Props for the column generator function interface GetVendorInvestigationsColumnsProps { setRowAction?: React.Dispatch< React.SetStateAction< DataTableRowAction | null > > openVendorDetailsModal?: (vendorId: number) => void openSupplementRequestDialog?: (investigationId: number, investigationMethod: string, vendorName: string) => void } // Helper function for investigation method variants function getMethodVariant(method: string): "default" | "secondary" | "outline" | "destructive" { switch (method) { case "PURCHASE_SELF_EVAL": return "secondary" case "DOCUMENT_EVAL": return "outline" case "PRODUCT_INSPECTION": return "default" case "SITE_VISIT_EVAL": return "destructive" default: return "outline" } } export function getColumns({ setRowAction, openVendorDetailsModal, openSupplementRequestDialog, }: GetVendorInvestigationsColumnsProps): ColumnDef< VendorInvestigationsViewWithContacts >[] { // -------------------------------------------- // 1) Select (checkbox) column // -------------------------------------------- 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 column // -------------------------------------------- const actionsColumn: ColumnDef = { id: "actions", enableHiding: false, cell: ({ row }) => { const router = useRouter() const isCanceled = row.original.investigationStatus === "CANCELED" const isCompleted = row.original.investigationStatus === "COMPLETED" const canReviewPQ = !isCanceled && row.original.investigationStatus === "PLANNED" && !!row.original.pqSubmissionId const reviewUrl = `/evcp/pq_new/${row.original.vendorId}/${row.original.pqSubmissionId}` const canRequestSupplement = (row.original.investigationMethod === "PRODUCT_INSPECTION" || row.original.investigationMethod === "SITE_VISIT_EVAL") && row.original.investigationStatus === "COMPLETED" && (row.original.evaluationResult === "SUPPLEMENT" || row.original.evaluationResult === "SUPPLEMENT_REINSPECT" || row.original.evaluationResult === "SUPPLEMENT_DOCUMENT") return ( { if (!canReviewPQ) return if (router) { router.push(reviewUrl) } else if (typeof window !== "undefined") { window.location.href = reviewUrl } }} disabled={!canReviewPQ} > 검토 { if (!isCanceled && row.original.investigationStatus === "QM_REVIEW_CONFIRMED") { (setRowAction as any)?.({ type: "update-progress", row }) } }} disabled={isCanceled || row.original.investigationStatus !== "QM_REVIEW_CONFIRMED"} > 실사 진행 관리 { if (isCanceled || (row.original.investigationStatus !== "IN_PROGRESS" && row.original.investigationStatus !== "SUPPLEMENT_REQUIRED")) return // 구매자체평가일 경우 결과입력 비활성화 if (row.original.investigationMethod === "PURCHASE_SELF_EVAL") { return } // 방문/제품 평가 시: 벤더 회신 여부 확인 후 열기 (없으면 토스트) if ( row.original.investigationMethod === "PRODUCT_INSPECTION" || row.original.investigationMethod === "SITE_VISIT_EVAL" ) { try { const { getSiteVisitRequestAction } = await import("@/lib/site-visit/service") const req = await getSiteVisitRequestAction(row.original.investigationId) const canProceed = req.success && req.data && req.data.status === "VENDOR_SUBMITTED" if (!canProceed) { const { toast } = await import("sonner") toast.error("협력업체 방문실사 정보 회신 전에는 결과 입력이 불가합니다.") return } } catch {} } ;(setRowAction as any)?.({ type: "update-result", row }) }} disabled={ isCanceled || (row.original.investigationStatus !== "IN_PROGRESS" && row.original.investigationStatus !== "SUPPLEMENT_REQUIRED") || row.original.investigationMethod === "PURCHASE_SELF_EVAL" } > 실사 결과 입력 {/* 실사 실시 확정 정보 버튼 - 제품검사평가 또는 방문실사평가인 경우 */} {(row.original.investigationMethod === "PRODUCT_INSPECTION" || row.original.investigationMethod === "SITE_VISIT_EVAL") && ( { (setRowAction as any)?.({ type: "vendor-info-view", row }) }} > 실사 실시 확정 정보 )} {canRequestSupplement && ( <> { openSupplementRequestDialog?.( row.original.investigationId, row.original.investigationMethod || "", row.original.vendorName ) }} className="text-amber-600 focus:text-amber-600" > 보완 요청 )} ) }, size: 40, } // -------------------------------------------- // 3) Vendor Name with click handler // -------------------------------------------- const vendorNameColumn: ColumnDef = { accessorKey: "vendorName", header: ({ column }) => ( ), cell: ({ row }) => { const vendorId = row.original.vendorId const vendorName = row.getValue("vendorName") as string return ( ) }, meta: { excelHeader: "협력사명", group: "협력업체", }, } // -------------------------------------------- // 4) Build grouped columns from config // -------------------------------------------- const groupMap: Record[]> = {} vendorInvestigationsColumnsConfig.forEach((cfg) => { // Skip vendorName as we have a custom column for it if (cfg.id === "vendorName") { return } const groupName = cfg.group || "_noGroup" if (!groupMap[groupName]) { groupMap[groupName] = [] } const childCol: ColumnDef = { accessorKey: cfg.id, header: ({ column }) => ( ), meta: { excelHeader: cfg.excelHeader, group: cfg.group, type: cfg.type, }, cell: ({ row, column }) => { const value = row.getValue(column.id) // Handle date fields if ( column.id === "scheduledStartAt" || column.id === "scheduledEndAt" || column.id === "forecastedAt" || column.id === "requestedAt" || column.id === "confirmedAt" || column.id === "completedAt" || column.id === "createdAt" || column.id === "updatedAt" ) { if (!value) return "" return formatDate(new Date(value as string), "KR") } // Handle status fields with badges if (column.id === "investigationStatus") { if (!value) return "" return ( {formatStatus(value as string)} ) } // Handle investigation method if (column.id === "investigationMethod") { if (!value) return "" return ( {formatEnumValue(value as string)} ) } // Handle evaluation result if (column.id === "evaluationResult") { if (!value) return "" return ( {formatEnumValue(value as string)} ) } // Handle pqItems if (column.id === "pqItems") { if (!value) return - const items = typeof value === 'string' ? JSON.parse(value as string) : value; if (Array.isArray(items) && items.length > 0) { const firstItem = items[0]; return (
{firstItem.itemCode} - {firstItem.itemName} {items.length > 1 && ( 외 {items.length - 1}건 )}
); } } // Handle IDs for pqSubmissionId (keeping for reference) if (column.id === "pqSubmissionId") { return value ? `#${value}` : "" } // Handle pqNumber if (column.id === "pqNumber") { return value ? (value as string) : - } // Handle file attachment status if (column.id === "hasAttachments") { return (
{value ? ( 📎 첨부 ) : ( - )}
) } if (column.id === "requesterName") { if (!value && !row.original.requesterEmail) { return 미배정 } return (
{value || "미배정"} {row.original.requesterEmail ? ( {row.original.requesterEmail} ) : null}
) } if (column.id === "qmManagerName") { if (!value && !row.original.qmManagerEmail) { return 미배정 } return (
{value || "미배정"} {row.original.qmManagerEmail ? ( {row.original.qmManagerEmail} ) : null}
) } return value ?? "" }, } groupMap[groupName].push(childCol) }) // Insert custom vendorNameColumn in the 협력업체 group if (groupMap["협력업체"]) { groupMap["협력업체"].unshift(vendorNameColumn) } // Convert the groupMap into nested columns const nestedColumns: ColumnDef[] = [] for (const [groupName, colDefs] of Object.entries(groupMap)) { if (groupName === "_noGroup") { nestedColumns.push(...colDefs) } else { nestedColumns.push({ id: groupName, header: groupName as any, columns: colDefs, }) } } // -------------------------------------------- // 5) Return final columns array (simplified) // -------------------------------------------- return [ selectColumn, ...nestedColumns, actionsColumn, ] } // Helper functions for formatting function formatStatus(status: string): string { switch (status) { case "PLANNED": return "계획됨" case "QM_REVIEW_CONFIRMED": return "QM 검토 확정" case "IN_PROGRESS": return "진행 중" case "COMPLETED": return "완료됨" case "CANCELED": return "취소됨" case "SUPPLEMENT_REQUIRED": return "보완 요구됨" case "RESULT_SENT": return "실사결과발송" default: return status } } function formatEnumValue(value: string): string { switch (value) { // Evaluation types case "PURCHASE_SELF_EVAL": return "구매자체평가" case "DOCUMENT_EVAL": return "서류평가" case "PRODUCT_INSPECTION": return "제품검사평가" case "SITE_VISIT_EVAL": return "방문실사평가" // Evaluation results case "APPROVED": return "승인" case "SUPPLEMENT": return "보완" case "SUPPLEMENT_REINSPECT": return "보완-재실사" case "SUPPLEMENT_DOCUMENT": return "보완-서류제출" case "REJECTED": return "불가" default: return value.replace(/_/g, " ").toLowerCase() } } function getStatusVariant(status: string): "default" | "secondary" | "outline" | "destructive" { switch (status) { case "PLANNED": return "secondary" case "IN_PROGRESS": return "default" case "COMPLETED": return "outline" case "CANCELED": return "destructive" case "SUPPLEMENT_REQUIRED": return "secondary" case "RESULT_SENT": return "default" default: return "default" } } function getResultVariant(result: string): "default" | "secondary" | "outline" | "destructive" { switch (result) { case "APPROVED": return "default" case "SUPPLEMENT": return "secondary" case "SUPPLEMENT_REINSPECT": return "secondary" case "SUPPLEMENT_DOCUMENT": return "secondary" case "REJECTED": return "destructive" default: return "outline" } }