diff options
Diffstat (limited to 'lib/bidding/detail/table/bidding-detail-vendor-columns.tsx')
| -rw-r--r-- | lib/bidding/detail/table/bidding-detail-vendor-columns.tsx | 223 |
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> + ) + }, + }, + ] +} |
