From 7a1524ba54f43d0f2a19e4bca2c6a2e0b01c5ef1 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 17 Jun 2025 09:02:32 +0000 Subject: (대표님) 20250617 18시 작업사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vendor-responses-table-columns.tsx | 351 +++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx (limited to 'lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx') diff --git a/lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx b/lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx new file mode 100644 index 00000000..47b7570b --- /dev/null +++ b/lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx @@ -0,0 +1,351 @@ +// lib/vendor-responses/table/vendor-responses-table-columns.tsx +"use client" + +import * as React from "react" +import { type DataTableRowAction } from "@/types/table" +import { type ColumnDef } from "@tanstack/react-table" +import { + Ellipsis, FileText, Pencil, Edit, Trash2, + Eye, MessageSquare, Clock, CheckCircle, AlertTriangle, FileX +} from "lucide-react" +import { formatDate, formatDateTime } from "@/lib/utils" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { VendorResponseDetail } from "../service" +import { VendorRfqResponseSummary } from "../validations" + +// 응답 상태에 따른 배지 컴포넌트 +function ResponseStatusBadge({ status }: { status: string }) { + switch (status) { + case "NOT_RESPONDED": + return ( + + + 미응답 + + ) + case "RESPONDED": + return ( + + + 응답완료 + + ) + case "REVISION_REQUESTED": + return ( + + + 수정요청 + + ) + case "WAIVED": + return ( + + + 포기 + + ) + default: + return {status} + } +} + + +type NextRouter = ReturnType; + +interface GetColumnsProps { + router: NextRouter +} + +/** + * tanstack table 컬럼 정의 + */ +export function getColumns({ + 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 컬럼 (작성하기 버튼만) + // ---------------------------------------------------------------- + const actionsColumn: ColumnDef = { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + const vendorId = row.original.vendorId + const rfqRecordId = row.original.rfqRecordId + const rfqType = row.original.rfqType + const rfqCode = row.original.rfq?.rfqCode || "RFQ" + + return ( + + + + + + +

{rfqCode} 응답 작성하기

+
+
+
+ ) + }, + size: 100, + minSize: 100, + maxSize: 150, + } + + // ---------------------------------------------------------------- + // 3) 컬럼 정의 배열 + // ---------------------------------------------------------------- + const columnDefinitions = [ + { + id: "rfqCode", + label: "RFQ 번호", + group: "RFQ 정보", + size: 120, + minSize: 100, + maxSize: 150, + }, + + { + id: "rfqDueDate", + label: "RFQ 마감일", + group: "RFQ 정보", + size: 120, + minSize: 100, + maxSize: 150, + }, + + { + id: "overallStatus", + label: "전체 상태", + group: null, + size: 80, + minSize: 60, + maxSize: 100, + }, + { + id: "totalAttachments", + label: "총 첨부파일", + group: "응답 통계", + size: 100, + minSize: 80, + maxSize: 120, + }, + { + id: "respondedCount", + label: "응답완료", + group: "응답 통계", + size: 100, + minSize: 80, + maxSize: 120, + }, + { + id: "pendingCount", + label: "미응답", + group: "응답 통계", + size: 100, + minSize: 80, + maxSize: 120, + }, + { + id: "responseRate", + label: "응답률", + group: "진행률", + size: 100, + minSize: 80, + maxSize: 120, + }, + { + id: "completionRate", + label: "완료율", + group: "진행률", + size: 100, + minSize: 80, + maxSize: 120, + }, + { + id: "requestedAt", + label: "요청일", + group: "날짜 정보", + size: 120, + minSize: 100, + maxSize: 150, + }, + { + id: "lastRespondedAt", + label: "최종 응답일", + group: "날짜 정보", + size: 120, + minSize: 100, + maxSize: 150, + }, + ]; + + // ---------------------------------------------------------------- + // 4) 그룹별로 컬럼 정리 (중첩 헤더 생성) + // ---------------------------------------------------------------- + const groupMap: Record[]> = {} + + columnDefinitions.forEach((cfg) => { + const groupName = cfg.group || "_noGroup" + + if (!groupMap[groupName]) { + groupMap[groupName] = [] + } + + // 개별 컬럼 정의 + const columnDef: ColumnDef = { + accessorKey: cfg.id, + enableResizing: true, + header: ({ column }) => ( + + ), + cell: ({ row, cell }) => { + // 각 컬럼별 특별한 렌더링 처리 + switch (cfg.id) { + case "rfqCode": + return row.original.rfq?.rfqCode || "-" + + + case "rfqDueDate": + const dueDate = row.original.rfq?.dueDate; + return dueDate ? formatDate(new Date(dueDate)) : "-"; + + case "overallStatus": + return + + case "totalAttachments": + return ( +
+ {row.original.totalAttachments} +
+ ) + + case "respondedCount": + return ( +
+ {row.original.respondedCount} +
+ ) + + case "pendingCount": + return ( +
+ {row.original.pendingCount} +
+ ) + + case "responseRate": + const responseRate = row.original.responseRate; + return ( +
+ = 80 ? 'text-green-600' : responseRate >= 50 ? 'text-yellow-600' : 'text-red-600'}`}> + {responseRate}% + +
+ ) + + case "completionRate": + const completionRate = row.original.completionRate; + return ( +
+ = 80 ? 'text-green-600' : completionRate >= 50 ? 'text-yellow-600' : 'text-red-600'}`}> + {completionRate}% + +
+ ) + + case "requestedAt": + return formatDateTime(new Date(row.original.requestedAt)) + + case "lastRespondedAt": + const lastRespondedAt = row.original.lastRespondedAt; + return lastRespondedAt ? formatDateTime(new Date(lastRespondedAt)) : "-"; + + default: + return row.getValue(cfg.id) ?? "" + } + }, + size: cfg.size, + minSize: cfg.minSize, + maxSize: cfg.maxSize, + } + + groupMap[groupName].push(columnDef) + }) + + // ---------------------------------------------------------------- + // 5) 그룹별 중첩 컬럼 생성 + // ---------------------------------------------------------------- + const nestedColumns: ColumnDef[] = [] + Object.entries(groupMap).forEach(([groupName, colDefs]) => { + if (groupName === "_noGroup") { + // 그룹이 없는 컬럼들은 직접 추가 + nestedColumns.push(...colDefs) + } else { + // 그룹이 있는 컬럼들은 중첩 구조로 추가 + nestedColumns.push({ + id: groupName, + header: groupName, + columns: colDefs, + }) + } + }) + + // ---------------------------------------------------------------- + // 6) 최종 컬럼 배열 + // ---------------------------------------------------------------- + return [ + selectColumn, + ...nestedColumns, + actionsColumn, + ] +} \ No newline at end of file -- cgit v1.2.3