diff options
Diffstat (limited to 'lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx')
| -rw-r--r-- | lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx | 351 |
1 files changed, 351 insertions, 0 deletions
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 ( + <Badge variant="outline" className="text-orange-600 border-orange-600"> + <Clock className="mr-1 h-3 w-3" /> + 미응답 + </Badge> + ) + case "RESPONDED": + return ( + <Badge variant="default" className="bg-green-600 text-white"> + <CheckCircle className="mr-1 h-3 w-3" /> + 응답완료 + </Badge> + ) + case "REVISION_REQUESTED": + return ( + <Badge variant="secondary" className="text-yellow-600 border-yellow-600"> + <AlertTriangle className="mr-1 h-3 w-3" /> + 수정요청 + </Badge> + ) + case "WAIVED": + return ( + <Badge variant="outline" className="text-gray-600 border-gray-600"> + <FileX className="mr-1 h-3 w-3" /> + 포기 + </Badge> + ) + default: + return <Badge>{status}</Badge> + } +} + + +type NextRouter = ReturnType<typeof useRouter>; + +interface GetColumnsProps { + router: NextRouter +} + +/** + * tanstack table 컬럼 정의 + */ +export function getColumns({ + router, +}: GetColumnsProps): ColumnDef<VendorResponseDetail>[] { + + // ---------------------------------------------------------------- + // 1) select 컬럼 (체크박스) + // ---------------------------------------------------------------- + const selectColumn: ColumnDef<VendorRfqResponseSummary> = { + id: "select", + header: ({ table }) => ( + <Checkbox + checked={ + table.getIsAllPageRowsSelected() || + (table.getIsSomePageRowsSelected() && "indeterminate") + } + onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + className="translate-y-0.5" + /> + ), + cell: ({ row }) => ( + <Checkbox + checked={row.getIsSelected()} + onCheckedChange={(value) => row.toggleSelected(!!value)} + aria-label="Select row" + className="translate-y-0.5" + /> + ), + size: 40, + enableSorting: false, + enableHiding: false, + } + + // ---------------------------------------------------------------- + // 2) actions 컬럼 (작성하기 버튼만) + // ---------------------------------------------------------------- + const actionsColumn: ColumnDef<VendorRfqResponseSummary> = { + 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 ( + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="ghost" + size="sm" + onClick={() => router.push(`/partners/rfq-answer/${vendorId}/${rfqRecordId}`)} + className="h-8 px-3" + > + <Edit className="h-4 w-4 mr-1" /> + 작성하기 + </Button> + </TooltipTrigger> + <TooltipContent> + <p>{rfqCode} 응답 작성하기</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> + ) + }, + 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<string, ColumnDef<VendorRfqResponseSummary>[]> = {} + + columnDefinitions.forEach((cfg) => { + const groupName = cfg.group || "_noGroup" + + if (!groupMap[groupName]) { + groupMap[groupName] = [] + } + + // 개별 컬럼 정의 + const columnDef: ColumnDef<VendorRfqResponseSummary> = { + accessorKey: cfg.id, + enableResizing: true, + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={cfg.label} /> + ), + 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 <ResponseStatusBadge status={row.original.overallStatus} /> + + case "totalAttachments": + return ( + <div className="text-center font-medium"> + {row.original.totalAttachments} + </div> + ) + + case "respondedCount": + return ( + <div className="text-center text-green-600 font-medium"> + {row.original.respondedCount} + </div> + ) + + case "pendingCount": + return ( + <div className="text-center text-orange-600 font-medium"> + {row.original.pendingCount} + </div> + ) + + case "responseRate": + const responseRate = row.original.responseRate; + return ( + <div className="text-center"> + <span className={`font-medium ${responseRate >= 80 ? 'text-green-600' : responseRate >= 50 ? 'text-yellow-600' : 'text-red-600'}`}> + {responseRate}% + </span> + </div> + ) + + case "completionRate": + const completionRate = row.original.completionRate; + return ( + <div className="text-center"> + <span className={`font-medium ${completionRate >= 80 ? 'text-green-600' : completionRate >= 50 ? 'text-yellow-600' : 'text-red-600'}`}> + {completionRate}% + </span> + </div> + ) + + 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<VendorRfqResponseSummary>[] = [] + 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 |
