summaryrefslogtreecommitdiff
path: root/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/detail/table/bidding-detail-vendor-columns.tsx')
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-columns.tsx223
1 files changed, 223 insertions, 0 deletions
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>
+ )
+ },
+ },
+ ]
+}