From fccb00d15466cd0b2d861163663a5070c768ff77 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Tue, 2 Sep 2025 09:52:21 +0000
Subject: (대표님) OCR 박진석프로 요청 대응, rfq 변경된 요구사항 구현
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/rfq-last/table/rfq-table-columns.tsx | 445 +++++++++++++++++++++++++++++++
 1 file changed, 445 insertions(+)
 create mode 100644 lib/rfq-last/table/rfq-table-columns.tsx

(limited to 'lib/rfq-last/table/rfq-table-columns.tsx')

diff --git a/lib/rfq-last/table/rfq-table-columns.tsx b/lib/rfq-last/table/rfq-table-columns.tsx
new file mode 100644
index 00000000..3fac8881
--- /dev/null
+++ b/lib/rfq-last/table/rfq-table-columns.tsx
@@ -0,0 +1,445 @@
+"use client";
+
+import * as React from "react";
+import { type ColumnDef } from "@tanstack/react-table";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Eye, FileText, Send, Package, Users, ChevronRight } from "lucide-react";
+import {
+  Tooltip,
+  TooltipContent,
+  TooltipProvider,
+  TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header";
+import { RfqsLastView } from "@/db/schema";
+import { DataTableRowAction } from "@/types/table";
+import { format } from "date-fns";
+import { ko } from "date-fns/locale";
+
+interface GetColumnsProps {
+  setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<RfqsLastView> | null>>;
+  rfqCategory?: "all" | "general" | "itb" | "rfq";
+}
+
+// RFQ 상태별 색상
+const getStatusBadgeVariant = (status: string) => {
+  switch (status) {
+    case "RFQ 생성": return "outline";
+    case "구매담당지정": return "secondary";
+    case "견적요청문서 확정": return "default";
+    case "Short List 확정": return "default";
+    case "TBE 완료": return "default";
+    case "RFQ 발송": return "default";
+    case "견적접수": return "default";
+    case "최종업체선정": return "default";
+    default: return "outline";
+  }
+};
+
+// 시리즈 배지
+const getSeriesBadge = (series: string | null) => {
+  if (!series) return null;
+  
+  const label = series === "SS" ? "시리즈 통합" : series === "II" ? "품목 통합" : series;
+  const variant = series === "SS" ? "default" : series === "II" ? "secondary" : "outline";
+  
+  return <Badge variant={variant} className="text-xs">{label}</Badge>;
+};
+
+export function getRfqColumns({
+  setRowAction,
+  rfqCategory = "all"
+}: GetColumnsProps): ColumnDef<RfqsLastView>[] {
+  
+  const baseColumns: ColumnDef<RfqsLastView>[] = [
+    // ═══════════════════════════════════════════════════════════════
+    // 선택 및 기본 정보
+    // ═══════════════════════════════════════════════════════════════
+
+    // Checkbox
+    {
+      id: "select",
+      header: ({ table }) => (
+        <Checkbox
+          checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")}
+          onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)}
+          aria-label="select all"
+          className="translate-y-0.5"
+        />
+      ),
+      cell: ({ row }) => (
+        <Checkbox
+          checked={row.getIsSelected()}
+          onCheckedChange={(v) => row.toggleSelected(!!v)}
+          aria-label="select row"
+          className="translate-y-0.5"
+        />
+      ),
+      size: 40,
+      enableSorting: false,
+      enableHiding: false,
+    },
+
+    // RFQ 코드
+    {
+      accessorKey: "rfqCode",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="RFQ 코드" />,
+      cell: ({ row }) => {
+        const rfqSealed = row.original.rfqSealedYn;
+        return (
+          <div className="flex items-center gap-1">
+            <span className="font-mono font-medium">{row.original.rfqCode}</span>
+            {rfqSealed && (
+              <Badge variant="destructive" className="text-xs">봉인</Badge>
+            )}
+          </div>
+        );
+      },
+      size: 140,
+    },
+
+    // 상태
+    {
+      accessorKey: "status",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="상태" />,
+      cell: ({ row }) => (
+        <Badge variant={getStatusBadgeVariant(row.original.status)}>
+          {row.original.status}
+        </Badge>
+      ),
+      size: 120,
+    },
+
+    // ═══════════════════════════════════════════════════════════════
+    // 일반견적 필드 (rfqCategory가 'general' 또는 'all'일 때만)
+    // ═══════════════════════════════════════════════════════════════
+    ...(rfqCategory === "general" || rfqCategory === "all" ? [
+      {
+        id: "rfqType",
+        accessorKey: "rfqType",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적 유형" />,
+        cell: ({ row }) => row.original.rfqType || "-",
+        size: 100,
+      },
+      {
+        id: "rfqTitle",
+        accessorKey: "rfqTitle",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적 제목" />,
+        cell: ({ row }) => (
+          <div className="max-w-[200px] truncate" title={row.original.rfqTitle || ""}>
+            {row.original.rfqTitle || "-"}
+          </div>
+        ),
+        size: 200,
+      },
+    ] as ColumnDef<RfqsLastView>[] : []),
+
+    // ═══════════════════════════════════════════════════════════════
+    // ITB 필드 (rfqCategory가 'itb' 또는 'all'일 때만)
+    // ═══════════════════════════════════════════════════════════════
+    ...(rfqCategory === "itb" || rfqCategory === "all" ? [
+      {
+        id: "projectCompany",
+        accessorKey: "projectCompany",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 회사" />,
+        cell: ({ row }) => row.original.projectCompany || "-",
+        size: 120,
+      },
+      {
+        id: "projectFlag",
+        accessorKey: "projectFlag",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 플래그" />,
+        cell: ({ row }) => row.original.projectFlag || "-",
+        size: 100,
+      },
+      {
+        id: "projectSite",
+        accessorKey: "projectSite",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 사이트" />,
+        cell: ({ row }) => row.original.projectSite || "-",
+        size: 120,
+      },
+      {
+        id: "smCode",
+        accessorKey: "smCode",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="SM 코드" />,
+        cell: ({ row }) => row.original.smCode || "-",
+        size: 80,
+      },
+    ] as ColumnDef<RfqsLastView>[] : []),
+
+    // ═══════════════════════════════════════════════════════════════
+    // RFQ(PR) 필드 (rfqCategory가 'rfq' 또는 'all'일 때만)
+    // ═══════════════════════════════════════════════════════════════
+    ...(rfqCategory === "rfq" || rfqCategory === "all" ? [
+      {
+        id: "prNumber",
+        accessorKey: "prNumber",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="PR 번호" />,
+        cell: ({ row }) => (
+          <span className="font-mono text-sm">{row.original.prNumber || "-"}</span>
+        ),
+        size: 120,
+      },
+      {
+        id: "prIssueDate",
+        accessorKey: "prIssueDate",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="PR 발행일" />,
+        cell: ({ row }) => {
+          const date = row.original.prIssueDate;
+          return date ? format(new Date(date), "yyyy-MM-dd") : "-";
+        },
+        size: 100,
+      },
+      {
+        id: "series",
+        accessorKey: "series",
+        header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="시리즈" />,
+        cell: ({ row }) => getSeriesBadge(row.original.series),
+        size: 100,
+      },
+    ] as ColumnDef<RfqsLastView>[] : []),
+
+    // ═══════════════════════════════════════════════════════════════
+    // 공통 프로젝트 정보
+    // ═══════════════════════════════════════════════════════════════
+    {
+      header: "프로젝트 정보",
+      columns: [
+        {
+          accessorKey: "projectCode",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />,
+          cell: ({ row }) => (
+            <span className="font-mono text-sm">{row.original.projectCode || "-"}</span>
+          ),
+          size: 120,
+        },
+        {
+          accessorKey: "projectName",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트명" />,
+          cell: ({ row }) => (
+            <div className="max-w-[200px] truncate" title={row.original.projectName || ""}>
+              {row.original.projectName || "-"}
+            </div>
+          ),
+          size: 200,
+        },
+      ]
+    },
+
+    // ═══════════════════════════════════════════════════════════════
+    // 품목 정보
+    // ═══════════════════════════════════════════════════════════════
+    {
+      header: "품목 정보",
+      columns: [
+        {
+          accessorKey: "itemCode",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재코드" />,
+          cell: ({ row }) => (
+            <span className="font-mono text-sm">{row.original.itemCode || "-"}</span>
+          ),
+          size: 100,
+        },
+        {
+          accessorKey: "itemName",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재명" />,
+          cell: ({ row }) => (
+            <div className="max-w-[200px] truncate" title={row.original.itemName || ""}>
+              {row.original.itemName || "-"}
+            </div>
+          ),
+          size: 200,
+        },
+        {
+          accessorKey: "packageNo",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="패키지 번호" />,
+          cell: ({ row }) => row.original.packageNo || "-",
+          size: 100,
+        },
+        {
+          accessorKey: "packageName",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="패키지명" />,
+          cell: ({ row }) => (
+            <div className="max-w-[200px] truncate" title={row.original.packageName || ""}>
+              {row.original.packageName || "-"}
+            </div>
+          ),
+          size: 200,
+        },
+      ]
+    },
+
+    // ═══════════════════════════════════════════════════════════════
+    // 담당자 정보
+    // ═══════════════════════════════════════════════════════════════
+    {
+      header: "담당자",
+      columns: [
+        {
+          accessorKey: "picUserName",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="구매담당자" />,
+          cell: ({ row }) => row.original.picUserName || row.original.picName || "-",
+          size: 100,
+        },
+        {
+          accessorKey: "engPicName",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="엔지니어링 담당" />,
+          cell: ({ row }) => row.original.engPicName || "-",
+          size: 120,
+        },
+      ]
+    },
+
+    // ═══════════════════════════════════════════════════════════════
+    // 일정 정보
+    // ═══════════════════════════════════════════════════════════════
+    {
+      header: "일정",
+      columns: [
+        {
+          accessorKey: "rfqSendDate",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="발송일" />,
+          cell: ({ row }) => {
+            const date = row.original.rfqSendDate;
+            return date ? (
+              <div className="flex items-center gap-1">
+                <Send className="h-3 w-3 text-muted-foreground" />
+                <span className="text-sm">
+                  {format(new Date(date), "MM-dd", { locale: ko })}
+                </span>
+              </div>
+            ) : "-";
+          },
+          size: 90,
+        },
+        {
+          accessorKey: "dueDate",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="마감일" />,
+          cell: ({ row }) => {
+            const date = row.original.dueDate;
+            if (!date) return "-";
+            
+            const now = new Date();
+            const dueDate = new Date(date);
+            const isOverdue = now > dueDate;
+            
+            return (
+              <span className={`text-sm ${isOverdue ? "text-red-600 font-medium" : ""}`}>
+                {format(dueDate, "MM-dd", { locale: ko })}
+              </span>
+            );
+          },
+          size: 90,
+        },
+      ]
+    },
+
+    // ═══════════════════════════════════════════════════════════════
+    // 벤더 및 견적 현황
+    // ═══════════════════════════════════════════════════════════════
+    {
+      header: "견적 현황",
+      columns: [
+        {
+          accessorKey: "vendorCount",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="업체수" />,
+          cell: ({ row }) => (
+            <div className="flex items-center gap-1">
+              <Users className="h-3 w-3 text-muted-foreground" />
+              <span className="font-medium">{row.original.vendorCount || 0}</span>
+            </div>
+          ),
+          size: 80,
+        },
+        {
+          accessorKey: "shortListedVendorCount",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="Short List" />,
+          cell: ({ row }) => {
+            const count = row.original.shortListedVendorCount || 0;
+            return count > 0 ? (
+              <Badge variant="default" className="font-mono">
+                {count}
+              </Badge>
+            ) : "-";
+          },
+          size: 90,
+        },
+        {
+          accessorKey: "quotationReceivedCount",
+          header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="견적접수" />,
+          cell: ({ row }) => {
+            const received = row.original.quotationReceivedCount || 0;
+            const total = row.original.vendorCount || 0;
+            
+            return (
+              <div className="flex items-center gap-1">
+                <FileText className="h-3 w-3 text-muted-foreground" />
+                <span className={`text-sm ${received === total && total > 0 ? "text-green-600 font-medium" : ""}`}>
+                  {received}/{total}
+                </span>
+              </div>
+            );
+          },
+          size: 90,
+        },
+      ]
+    },
+
+    // PR Items 정보
+    {
+      accessorKey: "prItemsCount",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="PR Items" />,
+      cell: ({ row }) => {
+        const prItems = row.original.prItemsCount || 0;
+        const majorItems = row.original.majorItemsCount || 0;
+        
+        return (
+          <div className="flex flex-col gap-0.5">
+            <span className="text-sm font-medium">{prItems}개</span>
+            {majorItems > 0 && (
+              <Badge variant="secondary" className="text-xs">
+                주요 {majorItems}
+              </Badge>
+            )}
+          </div>
+        );
+      },
+      size: 90,
+    },
+
+    // 액션
+    {
+      id: "actions",
+      header: "액션",
+      enableHiding: false,
+      size: 80,
+      minSize: 80,
+      cell: ({ row }) => {
+        return (
+          <div className="flex items-center gap-1">
+            <TooltipProvider>
+              <Tooltip>
+                <TooltipTrigger asChild>
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="h-8 w-8"
+                    onClick={() => setRowAction({ row, type: "view" })}
+                  >
+                    <Eye className="h-4 w-4" />
+                  </Button>
+                </TooltipTrigger>
+                <TooltipContent>상세보기</TooltipContent>
+              </Tooltip>
+            </TooltipProvider>
+          </div>
+        );
+      },
+    },
+  ];
+
+  return baseColumns;
+}
\ No newline at end of file
-- 
cgit v1.2.3

