From 7548e2ad6948f1c6aa102fcac408bc6c9c0f9796 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 27 Aug 2025 12:06:26 +0000
Subject: (대표님, 최겸) 기본계약, 입찰, 파일라우트, 계약서명라우트,
 인포메이션, 메뉴설정, PQ(메일템플릿 관련)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../detail/table/bidding-detail-vendor-columns.tsx | 223 +++++++++++++++++++++
 1 file changed, 223 insertions(+)
 create mode 100644 lib/bidding/detail/table/bidding-detail-vendor-columns.tsx

(limited to 'lib/bidding/detail/table/bidding-detail-vendor-columns.tsx')

diff --git a/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx b/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
new file mode 100644
index 00000000..ef075459
--- /dev/null
+++ b/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
@@ -0,0 +1,223 @@
+"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 {
+  MoreHorizontal, Edit, Trash2, Trophy
+} from "lucide-react"
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuLabel,
+  DropdownMenuSeparator,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { QuotationVendor } from "@/lib/bidding/detail/service"
+
+interface GetVendorColumnsProps {
+  onEdit: (vendor: QuotationVendor) => void
+  onDelete: (vendor: QuotationVendor) => void
+  onSelectWinner: (vendor: QuotationVendor) => void
+}
+
+export function getBiddingDetailVendorColumns({
+  onEdit,
+  onDelete,
+  onSelectWinner
+}: GetVendorColumnsProps): ColumnDef<QuotationVendor>[] {
+  return [
+    {
+      id: 'select',
+      header: ({ table }) => (
+        <Checkbox
+          checked={table.getIsAllPageRowsSelected()}
+          onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+          aria-label="모두 선택"
+        />
+      ),
+      cell: ({ row }) => (
+        <Checkbox
+          checked={row.getIsSelected()}
+          onCheckedChange={(value) => row.toggleSelected(!!value)}
+          aria-label="행 선택"
+        />
+      ),
+      enableSorting: false,
+      enableHiding: false,
+    },
+    {
+      accessorKey: 'vendorName',
+      header: '업체명',
+      cell: ({ row }) => (
+        <div className="font-medium">{row.original.vendorName}</div>
+      ),
+    },
+    {
+      accessorKey: 'vendorCode',
+      header: '업체코드',
+      cell: ({ row }) => (
+        <div className="font-mono text-sm">{row.original.vendorCode}</div>
+      ),
+    },
+    {
+      accessorKey: 'contactPerson',
+      header: '담당자',
+      cell: ({ row }) => (
+        <div className="text-sm">{row.original.contactPerson || '-'}</div>
+      ),
+    },
+    {
+      accessorKey: 'quotationAmount',
+      header: '견적금액',
+      cell: ({ row }) => (
+        <div className="text-right font-mono">
+          {row.original.quotationAmount ? Number(row.original.quotationAmount).toLocaleString() : '-'} {row.original.currency}
+        </div>
+      ),
+    },
+    {
+      accessorKey: 'awardRatio',
+      header: '발주비율',
+      cell: ({ row }) => (
+        <div className="text-right">
+          {row.original.awardRatio ? `${row.original.awardRatio}%` : '-'}
+        </div>
+      ),
+    },
+    {
+      accessorKey: 'status',
+      header: '상태',
+      cell: ({ row }) => {
+        const status = row.original.status
+        const variant = status === 'selected' ? 'default' :
+                       status === 'submitted' ? 'secondary' :
+                       status === 'rejected' ? 'destructive' : 'outline'
+
+        const label = status === 'selected' ? '선정' :
+                     status === 'submitted' ? '제출' :
+                     status === 'rejected' ? '거절' : '대기'
+
+        return <Badge variant={variant}>{label}</Badge>
+      },
+    },
+    {
+      accessorKey: 'submissionDate',
+      header: '제출일',
+      cell: ({ row }) => (
+        <div className="text-sm">
+          {row.original.submissionDate ? new Date(row.original.submissionDate).toLocaleDateString('ko-KR') : '-'}
+        </div>
+      ),
+    },
+    {
+      accessorKey: 'offeredPaymentTerms',
+      header: '지급조건',
+      cell: ({ row }) => {
+        const terms = row.original.offeredPaymentTerms
+        if (!terms) return <div className="text-muted-foreground">-</div>
+
+        try {
+          const parsed = JSON.parse(terms)
+          return (
+            <div className="text-sm max-w-32 truncate" title={parsed.join(', ')}>
+              {parsed.join(', ')}
+            </div>
+          )
+        } catch {
+          return <div className="text-sm max-w-32 truncate">{terms}</div>
+        }
+      },
+    },
+    {
+      accessorKey: 'offeredTaxConditions',
+      header: '세금조건',
+      cell: ({ row }) => {
+        const conditions = row.original.offeredTaxConditions
+        if (!conditions) return <div className="text-muted-foreground">-</div>
+
+        try {
+          const parsed = JSON.parse(conditions)
+          return (
+            <div className="text-sm max-w-32 truncate" title={parsed.join(', ')}>
+              {parsed.join(', ')}
+            </div>
+          )
+        } catch {
+          return <div className="text-sm max-w-32 truncate">{conditions}</div>
+        }
+      },
+    },
+    {
+      accessorKey: 'offeredIncoterms',
+      header: '운송조건',
+      cell: ({ row }) => {
+        const terms = row.original.offeredIncoterms
+        if (!terms) return <div className="text-muted-foreground">-</div>
+
+        try {
+          const parsed = JSON.parse(terms)
+          return (
+            <div className="text-sm max-w-24 truncate" title={parsed.join(', ')}>
+              {parsed.join(', ')}
+            </div>
+          )
+        } catch {
+          return <div className="text-sm max-w-24 truncate">{terms}</div>
+        }
+      },
+    },
+    {
+      accessorKey: 'offeredContractDeliveryDate',
+      header: '납품요청일',
+      cell: ({ row }) => (
+        <div className="text-sm">
+          {row.original.offeredContractDeliveryDate ?
+            new Date(row.original.offeredContractDeliveryDate).toLocaleDateString('ko-KR') : '-'}
+        </div>
+      ),
+    },
+    {
+      id: 'actions',
+      header: '작업',
+      cell: ({ row }) => {
+        const vendor = row.original
+
+        return (
+          <DropdownMenu>
+            <DropdownMenuTrigger asChild>
+              <Button variant="ghost" className="h-8 w-8 p-0">
+                <span className="sr-only">메뉴 열기</span>
+                <MoreHorizontal className="h-4 w-4" />
+              </Button>
+            </DropdownMenuTrigger>
+            <DropdownMenuContent align="end">
+              <DropdownMenuLabel>작업</DropdownMenuLabel>
+              <DropdownMenuItem onClick={() => onEdit(vendor)}>
+                <Edit className="mr-2 h-4 w-4" />
+                수정
+              </DropdownMenuItem>
+              {vendor.status !== 'selected' && (
+                <DropdownMenuItem onClick={() => onSelectWinner(vendor)}>
+                  <Trophy className="mr-2 h-4 w-4" />
+                  낙찰 선정
+                </DropdownMenuItem>
+              )}
+              <DropdownMenuSeparator />
+              <DropdownMenuItem
+                onClick={() => onDelete(vendor)}
+                className="text-destructive"
+              >
+                <Trash2 className="mr-2 h-4 w-4" />
+                삭제
+              </DropdownMenuItem>
+            </DropdownMenuContent>
+          </DropdownMenu>
+        )
+      },
+    },
+  ]
+}
-- 
cgit v1.2.3

