From ff902243a658067fae858a615c0629aa2e0a4837 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 11 Jun 2025 12:18:38 +0000 Subject: (대표님) 20250611 21시 15분 OCR 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/b-rfq/summary-table/summary-rfq-columns.tsx | 499 ++++++++++++++++++++++++ 1 file changed, 499 insertions(+) create mode 100644 lib/b-rfq/summary-table/summary-rfq-columns.tsx (limited to 'lib/b-rfq/summary-table/summary-rfq-columns.tsx') diff --git a/lib/b-rfq/summary-table/summary-rfq-columns.tsx b/lib/b-rfq/summary-table/summary-rfq-columns.tsx new file mode 100644 index 00000000..f620858a --- /dev/null +++ b/lib/b-rfq/summary-table/summary-rfq-columns.tsx @@ -0,0 +1,499 @@ +"use client" + +import * as React from "react" +import { type DataTableRowAction } from "@/types/table" +import { type ColumnDef } from "@tanstack/react-table" +import { Ellipsis, Eye, Calendar, AlertTriangle, CheckCircle2, Clock, FileText } from "lucide-react" + +import { formatDate, cn } from "@/lib/utils" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { Progress } from "@/components/ui/progress" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { useRouter } from "next/navigation" +import { RfqDashboardView } from "@/db/schema" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" + +type NextRouter = ReturnType; + +interface GetRFQColumnsProps { + setRowAction: React.Dispatch | null>>; + router: NextRouter; +} + +// 상태에 따른 Badge 변형 결정 함수 +function getStatusBadge(status: string) { + switch (status) { + case "DRAFT": + return { variant: "outline" as const, label: "초안" }; + case "Doc. Received": + return { variant: "secondary" as const, label: "문서접수" }; + case "PIC Assigned": + return { variant: "secondary" as const, label: "담당자배정" }; + case "Doc. Confirmed": + return { variant: "default" as const, label: "문서확인" }; + case "Init. RFQ Sent": + return { variant: "default" as const, label: "초기RFQ발송" }; + case "Init. RFQ Answered": + return { variant: "default" as const, label: "초기RFQ회신" }; + case "TBE started": + return { variant: "secondary" as const, label: "TBE시작" }; + case "TBE finished": + return { variant: "secondary" as const, label: "TBE완료" }; + case "Final RFQ Sent": + return { variant: "default" as const, label: "최종RFQ발송" }; + case "Quotation Received": + return { variant: "default" as const, label: "견적접수" }; + case "Vendor Selected": + return { variant: "success" as const, label: "업체선정" }; + default: + return { variant: "outline" as const, label: status }; + } +} + +function getProgressBadge(progress: number) { + if (progress >= 100) { + return { variant: "success" as const, label: "완료" }; + } else if (progress >= 70) { + return { variant: "default" as const, label: "진행중" }; + } else if (progress >= 30) { + return { variant: "secondary" as const, label: "초기진행" }; + } else { + return { variant: "outline" as const, label: "시작" }; + } +} + +function getUrgencyLevel(daysToDeadline: number): "high" | "medium" | "low" { + if (daysToDeadline <= 3) return "high"; + if (daysToDeadline <= 7) return "medium"; + return "low"; +} + +export function getRFQColumns({ setRowAction, router }: GetRFQColumnsProps): ColumnDef[] { + + // 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, + }; + + // RFQ 코드 컬럼 + const rfqCodeColumn: ColumnDef = { + accessorKey: "rfqCode", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.getValue("rfqCode")} + {row.original.description && ( + + {row.original.description} + + )} +
+ ), + }; + + // 프로젝트 정보 컬럼 + const projectColumn: ColumnDef = { + accessorKey: "projectName", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const projectName = row.original.projectName; + const projectCode = row.original.projectCode; + + if (!projectName) { + return -; + } + + return ( +
+ {projectName} +
+ {projectCode && {projectCode}} +
+
+ ); + }, + }; + + // 패키지 정보 컬럼 + const packageColumn: ColumnDef = { + accessorKey: "packageNo", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const packageNo = row.original.packageNo; + const packageName = row.original.packageName; + + if (!packageNo) { + return -; + } + + return ( +
+ {packageNo} + {packageName && ( + + {packageName} + + )} +
+ ); + }, + }; + + const updatedColumn: ColumnDef = { + accessorKey: "updatedBy", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const updatedByName = row.original.updatedByName; + const updatedByEmail = row.original.updatedByEmail; + + if (!updatedByName) { + return -; + } + + return ( +
+ {updatedByName} + {updatedByEmail && ( + + {updatedByEmail} + + )} +
+ ); + }, + }; + + + // 상태 컬럼 + const statusColumn: ColumnDef = { + accessorKey: "status", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const statusBadge = getStatusBadge(row.original.status); + return {statusBadge.label}; + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }; + + // 진행률 컬럼 + const progressColumn: ColumnDef = { + accessorKey: "overallProgress", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const progress = row.original.overallProgress; + const progressBadge = getProgressBadge(progress); + + return ( +
+
+ {progress}% + + {progressBadge.label} + +
+ +
+ ); + }, + }; + + // 마감일 컬럼 + const dueDateColumn: ColumnDef = { + accessorKey: "dueDate", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const dueDate = row.original.dueDate; + const daysToDeadline = row.original.daysToDeadline; + const urgencyLevel = getUrgencyLevel(daysToDeadline); + + if (!dueDate) { + return -; + } + + return ( +
+
+ + {formatDate(dueDate, 'KR')} +
+
+ {urgencyLevel === "high" && ( + + )} + {urgencyLevel === "medium" && ( + + )} + {urgencyLevel === "low" && ( + + )} + + {daysToDeadline > 0 ? `${daysToDeadline}일 남음` : + daysToDeadline === 0 ? "오늘 마감" : + `${Math.abs(daysToDeadline)}일 지남`} + +
+
+ ); + }, + }; + + // 담당자 컬럼 + const picColumn: ColumnDef = { + accessorKey: "picName", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const picName = row.original.picName; + return picName ? ( + {picName} + ) : ( + 미배정 + ); + }, + }; + + const engPicColumn: ColumnDef = { + accessorKey: "engPicName", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const picName = row.original.engPicName; + return picName ? ( + {picName} + ) : ( + 미배정 + ); + }, + }; + + + const pjtCompanyColumn: ColumnDef = { + accessorKey: "projectCompany", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const projectCompany = row.original.projectCompany; + return projectCompany ? ( + {projectCompany} + ) : ( + - + ); + }, + }; + + const pjtFlagColumn: ColumnDef = { + accessorKey: "projectFlag", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const projectFlag = row.original.projectFlag; + return projectFlag ? ( + {projectFlag} + ) : ( + - + ); + }, + }; + + + const pjtSiteColumn: ColumnDef = { + accessorKey: "projectSite", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const projectSite = row.original.projectSite; + return projectSite ? ( + {projectSite} + ) : ( + - + ); + }, + }; + const remarkColumn: ColumnDef = { + accessorKey: "remark", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const remark = row.original.remark; + return remark ? ( + {remark} + ) : ( + - + ); + }, + }; + + // 첨부파일 수 컬럼 + const attachmentColumn: ColumnDef = { + accessorKey: "totalAttachments", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const count = row.original.totalAttachments; + return ( +
+ + {count} +
+ ); + }, + }; + + // 벤더 현황 컬럼 + const vendorStatusColumn: ColumnDef = { + accessorKey: "initialVendorCount", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const initial = row.original.initialVendorCount; + const final = row.original.finalVendorCount; + const initialRate = row.original.initialResponseRate; + const finalRate = row.original.finalResponseRate; + + return ( +
+
+ 초기: + {initial}개사 ({initialRate}%) +
+
+ 최종: + {final}개사 ({finalRate}%) +
+
+ ); + }, + }; + + // 생성일 컬럼 + const createdAtColumn: ColumnDef = { + accessorKey: "createdAt", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const dateVal = row.original.createdAt as Date; + return formatDate(dateVal, 'KR'); + }, + }; + + const updatedAtColumn: ColumnDef = { + accessorKey: "updatedAt", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const dateVal = row.original.updatedAt as Date; + return formatDate(dateVal, 'KR'); + }, + }; + + // Actions 컬럼 + const actionsColumn: ColumnDef = { + id: "detail", + header: ({ column }) => ( + + ), + // enableHiding: false, + cell: function Cell({ row }) { + const rfq = row.original; + const detailUrl = `/b-rfq/${rfq.rfqId}/initial`; + + return ( + + + ); + }, + size: 40, + }; + + return [ + selectColumn, + rfqCodeColumn, + projectColumn, + packageColumn, + statusColumn, + picColumn, + progressColumn, + dueDateColumn, + actionsColumn, + + engPicColumn, + + pjtCompanyColumn, + pjtFlagColumn, + pjtSiteColumn, + + attachmentColumn, + vendorStatusColumn, + createdAtColumn, + + updatedAtColumn, + updatedColumn, + remarkColumn + ]; +} \ No newline at end of file -- cgit v1.2.3