summaryrefslogtreecommitdiff
path: root/lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-17 09:02:32 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-17 09:02:32 +0000
commit7a1524ba54f43d0f2a19e4bca2c6a2e0b01c5ef1 (patch)
treedaa214d404c7fc78b32419a028724e5671a6c7a4 /lib/b-rfq/vendor-response/vendor-responses-table-columns.tsx
parentfa6a6093014c5d60188edfc9c4552e81c4b97bd1 (diff)
(대표님) 20250617 18시 작업사항
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.tsx351
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