From 5036cf2908792cef45f06256e71f10920f647f49 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 28 May 2025 19:03:21 +0000
Subject: (김준회) 기술영업 조선 RFQ (SHI/벤더)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../table/detail-table/rfq-detail-column.tsx       | 291 +++++++++++++++++++++
 1 file changed, 291 insertions(+)
 create mode 100644 lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx

(limited to 'lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx')

diff --git a/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx b/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx
new file mode 100644
index 00000000..c4a7edde
--- /dev/null
+++ b/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx
@@ -0,0 +1,291 @@
+"use client"
+
+import * as React from "react"
+import type { ColumnDef, Row } from "@tanstack/react-table";
+import { formatDate } from "@/lib/utils"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Ellipsis, MessageCircle } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+
+export interface DataTableRowAction<TData> {
+  row: Row<TData>;
+  type: "delete" | "update" | "communicate"; 
+}
+
+// 벤더 견적 데이터 타입 정의
+export interface RfqDetailView {
+  id: number
+  rfqId: number
+  vendorId?: number | null
+  vendorName: string | null
+  vendorCode: string | null
+  totalPrice: string | number | null
+  currency: string | null
+  validUntil: Date | null
+  status: string | null
+  remark: string | null
+  submittedAt: Date | null
+  acceptedAt: Date | null
+  rejectionReason: string | null
+  createdAt: Date | null
+  updatedAt: Date | null
+  createdByName: string | null
+}
+
+interface GetColumnsProps<TData> {
+  setRowAction: React.Dispatch<
+    React.SetStateAction<DataTableRowAction<TData> | null>
+  >;
+  unreadMessages?: Record<number, number>; // 읽지 않은 메시지 개수
+}
+
+export function getRfqDetailColumns({
+  setRowAction,
+  unreadMessages = {}
+}: GetColumnsProps<RfqDetailView>): ColumnDef<RfqDetailView>[] {
+  return [
+    {
+      id: "select",
+      header: ({ table }) => (
+        <Checkbox
+          checked={
+            table.getIsAllPageRowsSelected() ||
+            (table.getIsSomePageRowsSelected() && "indeterminate")
+          }
+          onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+          aria-label="모두 선택"
+        />
+      ),
+      cell: ({ row }) => {
+        const status = row.original.status;
+        const isDraft = status === "Draft";
+        
+        return (
+          <Checkbox
+            checked={row.getIsSelected()}
+            onCheckedChange={(value) => row.toggleSelected(!!value)}
+            disabled={!isDraft}
+            aria-label="행 선택"
+            className={!isDraft ? "opacity-50 cursor-not-allowed" : ""}
+          />
+        );
+      },
+      enableSorting: false,
+      enableHiding: false,
+      size: 40,
+    },
+    {
+      accessorKey: "status",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="견적 상태" />
+      ),
+      cell: ({ row }) => {
+        const status = row.getValue("status") as string;
+        // 상태에 따른 배지 색상 설정
+        let variant: "default" | "secondary" | "outline" | "destructive" = "outline";
+        
+        if (status === "Submitted") {
+          variant = "default"; // 제출됨 - 기본 색상
+        } else if (status === "Accepted") {
+          variant = "secondary"; // 승인됨 - 보조 색상
+        } else if (status === "Rejected") {
+          variant = "destructive"; // 거부됨 - 위험 색상
+        }
+        
+        return (
+          <Badge variant={variant}>{status || "Draft"}</Badge>
+        );
+      },
+      meta: {
+        excelHeader: "견적 상태"
+      },
+      enableResizing: true,
+      size: 120,
+    },
+    {
+      accessorKey: "vendorCode",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="벤더 코드" />
+      ),
+      cell: ({ row }) => <div>{row.getValue("vendorCode")}</div>,
+      meta: {
+        excelHeader: "벤더 코드"
+      },
+      enableResizing: true,
+      size: 120,
+    },
+    {
+      accessorKey: "vendorName",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="벤더명" />
+      ),
+      cell: ({ row }) => <div>{row.getValue("vendorName")}</div>,
+      meta: {
+        excelHeader: "벤더명"
+      },
+      enableResizing: true,
+      size: 160,
+    },
+    {
+      accessorKey: "totalPrice",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="견적 금액" />
+      ),
+      cell: ({ row }) => {
+        const value = row.getValue("totalPrice") as string | number | null;
+        const currency = row.getValue("currency") as string | null;
+        
+        if (value === null || value === undefined) return "-";
+        
+        // 숫자로 변환 시도
+        const numValue = typeof value === 'string' ? parseFloat(value) : value;
+        
+        return (
+          <div className="font-medium">
+            {isNaN(numValue) ? value : numValue.toLocaleString()} {currency}
+          </div>
+        );
+      },
+      meta: {
+        excelHeader: "견적 금액"
+      },
+      enableResizing: true,
+      size: 140,
+    },
+    {
+      accessorKey: "currency",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="통화" />
+      ),
+      cell: ({ row }) => <div>{row.getValue("currency")}</div>,
+      meta: {
+        excelHeader: "통화"
+      },
+      enableResizing: true,
+      size: 80,
+    },
+    {
+      accessorKey: "validUntil",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="유효기간" />
+      ),
+      cell: ({ cell }) => {
+        const value = cell.getValue() as Date | null;
+        return value ? formatDate(value, "KR") : "-";
+      },
+      meta: {
+        excelHeader: "유효기간"
+      },
+      enableResizing: true,
+      size: 120,
+    },
+    {
+      accessorKey: "submittedAt",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="제출일" />
+      ),
+      cell: ({ cell }) => {
+        const value = cell.getValue() as Date | null;
+        return value ? formatDate(value, "KR") : "-";
+      },
+      meta: {
+        excelHeader: "제출일"
+      },
+      enableResizing: true,
+      size: 120,
+    },
+    {
+      accessorKey: "createdByName",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="등록자" />
+      ),
+      cell: ({ row }) => <div>{row.getValue("createdByName")}</div>,
+      meta: {
+        excelHeader: "등록자"
+      },
+      enableResizing: true,
+      size: 120,
+    },
+    {
+      accessorKey: "remark",
+      header: ({ column }) => (
+        <DataTableColumnHeaderSimple column={column} title="비고" />
+      ),
+      cell: ({ row }) => <div>{row.getValue("remark") || "-"}</div>,
+      meta: {
+        excelHeader: "비고"
+      },
+      enableResizing: true,
+      size: 200,
+    },
+    {
+      id: "actions",
+      header: () => <div className="text-right">동작</div>,
+      cell: function Cell({ row }) {
+        const vendorId = row.original.vendorId;
+        const unreadCount = vendorId ? unreadMessages[vendorId] || 0 : 0;
+        
+        return (
+          <div className="text-right flex items-center justify-end gap-1">
+            {/* 커뮤니케이션 버튼 */}
+            <div className="relative">
+              <Button
+                variant="ghost"
+                size="sm"
+                className="h-8 w-8 p-0"
+                onClick={() => setRowAction({ row, type: "communicate" })}
+                title="벤더와 커뮤니케이션"
+              >
+                <MessageCircle className="h-4 w-4" />
+              </Button>
+              {unreadCount > 0 && (
+                <Badge 
+                  variant="destructive" 
+                  className="absolute -top-1 -right-1 h-4 w-4 p-0 text-xs flex items-center justify-center"
+                >
+                  {unreadCount > 9 ? '9+' : unreadCount}
+                </Badge>
+              )}
+            </div>
+            
+            {/* 기존 드롭다운 메뉴 */}
+            <DropdownMenu>
+              <DropdownMenuTrigger asChild>
+                <Button
+                  variant="ghost"
+                  className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
+                >
+                  <Ellipsis className="h-4 w-4" />
+                  <span className="sr-only">메뉴 열기</span>
+                </Button>
+              </DropdownMenuTrigger>
+              <DropdownMenuContent align="end" className="w-[160px]">
+                <DropdownMenuItem
+                  onClick={() => setRowAction({ row, type: "update" })}
+                >
+                  벤더 수정
+                </DropdownMenuItem>
+                <DropdownMenuItem
+                  onClick={() => setRowAction({ row, type: "delete" })}
+                  className="text-destructive focus:text-destructive"
+                >
+                  벤더 제거
+                </DropdownMenuItem>
+              </DropdownMenuContent>
+            </DropdownMenu>
+          </div>
+        );
+      },
+      enableResizing: false,
+      size: 80,
+    },
+  ];
+}
\ No newline at end of file
-- 
cgit v1.2.3

