From 37f55540833c2d5894513eca9fc8f7c6233fc2d2 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 29 May 2025 05:17:13 +0000
Subject: (대표님) 0529 14시 16분 변경사항 저장 (Vendor Data, Docu)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../vendor-tbe-table/tbe-table-columns.tsx         | 350 +++++++++++++++++++++
 1 file changed, 350 insertions(+)
 create mode 100644 lib/tech-vendor-rfq-response/vendor-tbe-table/tbe-table-columns.tsx

(limited to 'lib/tech-vendor-rfq-response/vendor-tbe-table/tbe-table-columns.tsx')

diff --git a/lib/tech-vendor-rfq-response/vendor-tbe-table/tbe-table-columns.tsx b/lib/tech-vendor-rfq-response/vendor-tbe-table/tbe-table-columns.tsx
new file mode 100644
index 00000000..b880506a
--- /dev/null
+++ b/lib/tech-vendor-rfq-response/vendor-tbe-table/tbe-table-columns.tsx
@@ -0,0 +1,350 @@
+"use client"
+
+import * as React from "react"
+import { type DataTableRowAction } from "@/types/table"
+import { type ColumnDef } from "@tanstack/react-table"
+import { Download, MessageSquare, Upload } from "lucide-react"
+import { toast } from "sonner"
+
+import { getErrorMessage } from "@/lib/handle-error"
+import { formatDate } from "@/lib/utils"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { useRouter } from "next/navigation"
+
+import {
+  tbeVendorColumnsConfig,
+  VendorTbeColumnConfig,
+  vendorTbeColumnsConfig,
+  TbeVendorFields,
+} from "@/config/vendorTbeColumnsConfig"
+
+type NextRouter = ReturnType<typeof useRouter>
+
+interface GetColumnsProps {
+  setRowAction: React.Dispatch<
+    React.SetStateAction<DataTableRowAction<TbeVendorFields> | null>
+  >
+  router: NextRouter
+  openCommentSheet: (vendorId: number) => void
+  handleDownloadTbeTemplate: (tbeId: number, vendorId: number, rfqId: number) => void
+  handleUploadTbeResponse: (tbeId: number, vendorId: number, rfqId: number, vendorResponseId:number) => void
+  openVendorContactsDialog: (rfqId: number, rfq: TbeVendorFields) => void // 수정된 시그니처
+
+}
+
+/**
+ * tanstack table 컬럼 정의 (중첩 헤더 버전)
+ */
+export function getColumns({
+  setRowAction,
+  router,
+  openCommentSheet,
+  handleDownloadTbeTemplate,
+  handleUploadTbeResponse,
+  openVendorContactsDialog
+}: GetColumnsProps): ColumnDef<TbeVendorFields>[] {
+  // ----------------------------------------------------------------
+  // 1) Select 컬럼 (체크박스)
+  // ----------------------------------------------------------------
+  const selectColumn: ColumnDef<TbeVendorFields> = {
+    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) 그룹화(Nested) 컬럼 구성
+  // ----------------------------------------------------------------
+  const groupMap: Record<string, ColumnDef<TbeVendorFields>[]> = {}
+
+  tbeVendorColumnsConfig.forEach((cfg) => {
+    const groupName = cfg.group || "_noGroup"
+    if (!groupMap[groupName]) {
+      groupMap[groupName] = []
+    }
+
+    // childCol: ColumnDef<TbeVendorFields>
+    const childCol: ColumnDef<TbeVendorFields> = {
+      accessorKey: cfg.id,
+      enableResizing: true,
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title={cfg.label} />
+      ),
+      meta: {
+        excelHeader: cfg.excelHeader,
+        group: cfg.group,
+        type: cfg.type,
+      },
+      maxSize: 120,
+      // 셀 렌더링
+      cell: ({ row, getValue }) => {
+        // 1) 필드값 가져오기
+        const val = getValue()
+
+        if (cfg.id === "vendorStatus") {
+          const statusVal = row.original.vendorStatus
+          if (!statusVal) return null
+          // const Icon = getStatusIcon(statusVal)
+          return (
+            <Badge variant="outline">
+              {statusVal}
+            </Badge>
+          )
+        }
+
+        
+        if (cfg.id === "rfqCode") {
+          const rfq = row.original;
+          const rfqId = rfq.rfqId;
+          
+          // 협력업체 이름을 클릭할 수 있는 버튼으로 렌더링
+          const handleVendorNameClick = () => {
+            if (rfqId) {
+              openVendorContactsDialog(rfqId, rfq); // vendor 전체 객체 전달
+            } else {
+              toast.error("협력업체 ID를 찾을 수 없습니다.");
+            }
+          };
+          
+          return (
+            <Button
+              variant="link"
+              className="p-0 h-auto text-left font-normal justify-start hover:underline"
+              onClick={handleVendorNameClick}
+            >
+              {val as string}
+            </Button>
+          );
+        }
+        if (cfg.id === "rfqVendorStatus") {
+          const statusVal = row.original.rfqVendorStatus
+          if (!statusVal) return null
+          // const Icon = getStatusIcon(statusVal)
+          const variant = statusVal === "INVITED" ? "default" : statusVal === "REJECTED" ? "destructive" : statusVal === "ACCEPTED" ? "secondary" : "outline"
+          return (
+            <Badge variant={variant}>
+              {statusVal}
+            </Badge>
+          )
+        }
+
+        // 예) TBE Updated (날짜)
+        if (cfg.id === "tbeUpdated") {
+          const dateVal = val as Date | undefined
+          if (!dateVal) return null
+          return formatDate(dateVal)
+        }
+
+        // 그 외 필드는 기본 값 표시
+        return val ?? ""
+      },
+    }
+
+    groupMap[groupName].push(childCol)
+  })
+
+  // groupMap → nestedColumns
+  const nestedColumns: ColumnDef<TbeVendorFields>[] = []
+  Object.entries(groupMap).forEach(([groupName, colDefs]) => {
+    if (groupName === "_noGroup") {
+      nestedColumns.push(...colDefs)
+    } else {
+      nestedColumns.push({
+        id: groupName,
+        header: groupName,
+        columns: colDefs,
+      })
+    }
+  })
+
+  // ----------------------------------------------------------------
+  // 3) Comments 컬럼
+  // ----------------------------------------------------------------
+  const commentsColumn: ColumnDef<TbeVendorFields> = {
+    id: "comments",
+    header: ({ column }) => (
+      <DataTableColumnHeaderSimple column={column} title="Comments" />
+    ),
+    cell: ({ row }) => {
+      const vendor = row.original
+      const commCount = vendor.comments?.filter(c => c.evaluationId === vendor.tbeId)?.length ?? 0
+
+      function handleClick() {
+        // rowAction + openCommentSheet
+        setRowAction({ row, type: "comments" })
+        openCommentSheet(vendor.tbeId ?? 0)
+      }
+
+      return (
+        <Button
+          variant="ghost"
+          size="sm"
+          className="relative h-8 w-8 p-0 group"
+          onClick={handleClick}
+          aria-label={
+            commCount > 0 ? `View ${commCount} comments` : "No comments"
+          }
+        >
+          <MessageSquare className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
+          {commCount > 0 && (
+            <Badge
+              variant="secondary"
+              className="pointer-events-none absolute -top-1 -right-1 h-4 min-w-[1rem] p-0 text-[0.625rem] leading-none flex items-center justify-center"
+            >
+              {commCount}
+            </Badge>
+          )}
+          <span className="sr-only">
+            {commCount > 0 ? `${commCount} Comments` : "No Comments"}
+          </span>
+        </Button>
+      )
+    },
+    enableSorting: false,
+    maxSize: 80
+  }
+
+  // ----------------------------------------------------------------
+  // 4) TBE 다운로드 컬럼 - 템플릿 다운로드 기능
+  // ----------------------------------------------------------------
+  const tbeDownloadColumn: ColumnDef<TbeVendorFields> = {
+    id: "tbeDownload",
+    header: ({ column }) => (
+      <DataTableColumnHeaderSimple column={column} title="TBE Sheets" />
+    ),
+    cell: ({ row }) => {
+      const vendor = row.original
+      const tbeId = vendor.tbeId
+      const vendorId = vendor.vendorId
+      const rfqId = vendor.rfqId
+      const templateFileCount = vendor.templateFileCount || 0
+
+      if (!tbeId || !vendorId || !rfqId) {
+        return <div className="text-center text-muted-foreground">-</div>
+      }
+
+      // 템플릿 파일이 없으면 다운로드 버튼 비활성화
+      const isDisabled = templateFileCount <= 0
+
+      return (
+        <Button
+          variant="ghost"
+          size="sm"
+          className="relative h-8 w-8 p-0 group"
+          onClick={
+            isDisabled
+              ? undefined
+              : () => handleDownloadTbeTemplate(tbeId, vendorId, rfqId)
+          }
+          aria-label={
+            templateFileCount > 0
+              ? `TBE 템플릿 다운로드 (${templateFileCount}개)`
+              : "다운로드할 파일 없음"
+          }
+          disabled={isDisabled}
+        >
+          <Download className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
+
+          {/* 파일이 1개 이상인 경우 뱃지로 개수 표시 */}
+          {templateFileCount > 0 && (
+            <Badge
+              variant="secondary"
+              className="pointer-events-none absolute -top-1 -right-1 h-4 min-w-[1rem] p-0 text-[0.625rem] leading-none flex items-center justify-center"
+            >
+              {templateFileCount}
+            </Badge>
+          )}
+
+          <span className="sr-only">
+            {templateFileCount > 0
+              ? `TBE 템플릿 다운로드 (${templateFileCount}개)`
+              : "다운로드할 파일 없음"}
+          </span>
+        </Button>
+      )
+    },
+    enableSorting: false,
+    maxSize: 80,
+  }
+  // ----------------------------------------------------------------
+  // 5) TBE 업로드 컬럼 - 응답 업로드 기능
+  // ----------------------------------------------------------------
+  const tbeUploadColumn: ColumnDef<TbeVendorFields> = {
+    id: "tbeUpload",
+    header: ({ column }) => (
+      <DataTableColumnHeaderSimple column={column} title="Upload Response" />
+    ),
+    cell: ({ row }) => {
+      const vendor = row.original
+      const tbeId = vendor.tbeId
+      const vendorId = vendor.vendorId
+      const rfqId = vendor.rfqId
+      const vendorResponseId = vendor.vendorResponseId || 0
+      const status = vendor.rfqVendorStatus
+      const hasResponse = vendor.hasResponse || false
+
+
+      if (!tbeId || !vendorId || !rfqId || status === "REJECTED") {
+        return <div className="text-center text-muted-foreground">-</div>
+      }
+
+      return (
+        <div >
+          <Button
+            variant="ghost"
+            size="sm"
+            className="h-8 w-8 p-0 group relative"
+            onClick={() => handleUploadTbeResponse(tbeId, vendorId, rfqId, vendorResponseId)}
+            aria-label={hasResponse ? "TBE 응답 확인" : "TBE 응답 업로드"}
+          >
+            <div className="flex items-center justify-center relative">
+              <Upload className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
+            </div>
+            {hasResponse && (
+              <span className="absolute -top-1 -right-1 inline-flex h-2 w-2 rounded-full" style={{ backgroundColor: '#10B981' }}></span>
+            )}
+            <span className="sr-only">
+              {"TBE 응답 업로드"}
+            </span>
+          </Button>
+        </div>
+      )
+    },
+    enableSorting: false,
+    maxSize: 80
+  }
+
+  // ----------------------------------------------------------------
+  // 6) 최종 컬럼 배열
+  // ----------------------------------------------------------------
+  return [
+    selectColumn,
+    ...nestedColumns,
+    commentsColumn,
+    tbeDownloadColumn,
+    tbeUploadColumn,
+  ]
+}
\ No newline at end of file
-- 
cgit v1.2.3

