From cbb4c7fe0b94459162ad5e998bc05cd293e0ff96 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 11 Aug 2025 09:02:00 +0000 Subject: (대표님) 입찰, EDP 변경사항 대응, spreadJS 오류 수정, 벤더실사 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bidding/list/biddings-table-columns.tsx | 578 ++++++++++++++++++++++++++++ 1 file changed, 578 insertions(+) create mode 100644 lib/bidding/list/biddings-table-columns.tsx (limited to 'lib/bidding/list/biddings-table-columns.tsx') diff --git a/lib/bidding/list/biddings-table-columns.tsx b/lib/bidding/list/biddings-table-columns.tsx new file mode 100644 index 00000000..34fc574e --- /dev/null +++ b/lib/bidding/list/biddings-table-columns.tsx @@ -0,0 +1,578 @@ +"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, Edit, MoreHorizontal, FileText, Users, Calendar, + Building, Package, DollarSign, Clock, CheckCircle, XCircle +} from "lucide-react" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import { BiddingListItem } from "@/db/schema" +import { DataTableRowAction } from "@/types/table" +import { + biddingStatusLabels, + contractTypeLabels, + biddingTypeLabels, + awardCountLabels +} from "@/db/schema" +import { formatDate } from "@/lib/utils" + +interface GetColumnsProps { + setRowAction: React.Dispatch | null>> +} + +// 상태별 배지 색상 +const getStatusBadgeVariant = (status: string) => { + switch (status) { + case 'bidding_generated': + return 'outline' + case 'request_for_quotation': + case 'received_quotation': + return 'secondary' + case 'set_target_price': + case 'bidding_opened': + return 'default' + case 'bidding_closed': + case 'evaluation_of_bidding': + return 'default' + case 'vendor_selected': + return 'default' + case 'bidding_disposal': + return 'destructive' + default: + return 'outline' + } +} + +// 금액 포맷팅 +const formatCurrency = (amount: string | number | null, currency = 'KRW') => { + if (!amount) return '-' + + const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount + if (isNaN(numAmount)) return '-' + + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: currency, + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(numAmount) +} + + + +export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef[] { + return [ + // ═══════════════════════════════════════════════════════════════ + // 선택 및 기본 정보 + // ═══════════════════════════════════════════════════════════════ + { + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!v)} + aria-label="select all" + className="translate-y-0.5" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!v)} + aria-label="select row" + className="translate-y-0.5" + /> + ), + size: 40, + enableSorting: false, + enableHiding: false, + }, + + // ░░░ 입찰 No. ░░░ + { + accessorKey: "biddingNumber", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.biddingNumber} + {row.original.revision > 0 && ( + + Rev.{row.original.revision} + + )} +
+ ), + size: 120, + meta: { excelHeader: "입찰 No." }, + }, + + // ░░░ 입찰상태 ░░░ + { + accessorKey: "status", + header: ({ column }) => , + cell: ({ row }) => ( + + {biddingStatusLabels[row.original.status]} + + ), + size: 120, + meta: { excelHeader: "입찰상태" }, + }, + + // ░░░ 사전견적 ░░░ + { + id: "preQuote", + header: ({ column }) => , + cell: ({ row }) => { + const hasPreQuote = ['request_for_quotation', 'received_quotation'].includes(row.original.status) + const preQuoteDate = row.original.preQuoteDate + + return hasPreQuote ? ( +
+ + {preQuoteDate && ( + + {formatDate(preQuoteDate, "KR")} + + )} +
+ ) : ( + + ) + }, + size: 90, + meta: { excelHeader: "사전견적" }, + }, + + // ░░░ 입찰담당자 ░░░ + { + accessorKey: "managerName", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.managerName || '-'} +
+ ), + size: 100, + meta: { excelHeader: "입찰담당자" }, + }, + + // ═══════════════════════════════════════════════════════════════ + // 프로젝트 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "프로젝트 정보", + columns: [ + { + accessorKey: "projectName", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.projectName || '-'} +
+ ), + size: 150, + meta: { excelHeader: "프로젝트명" }, + }, + + { + accessorKey: "itemName", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.itemName || '-'} +
+ ), + size: 150, + meta: { excelHeader: "품목명" }, + }, + + { + accessorKey: "title", + header: ({ column }) => , + cell: ({ row }) => ( +
+ +
+ ), + size: 200, + meta: { excelHeader: "입찰명" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 계약 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "계약 정보", + columns: [ + { + accessorKey: "contractType", + header: ({ column }) => , + cell: ({ row }) => ( + + {contractTypeLabels[row.original.contractType]} + + ), + size: 100, + meta: { excelHeader: "계약구분" }, + }, + + { + accessorKey: "biddingType", + header: ({ column }) => , + cell: ({ row }) => ( + + {biddingTypeLabels[row.original.biddingType]} + + ), + size: 100, + meta: { excelHeader: "입찰유형" }, + }, + + { + accessorKey: "awardCount", + header: ({ column }) => , + cell: ({ row }) => ( + + {awardCountLabels[row.original.awardCount]} + + ), + size: 80, + meta: { excelHeader: "낙찰수" }, + }, + + { + accessorKey: "contractPeriod", + header: ({ column }) => , + cell: ({ row }) => ( + {row.original.contractPeriod || '-'} + ), + size: 100, + meta: { excelHeader: "계약기간" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 일정 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "일정 정보", + columns: [ + { + id: "submissionPeriod", + header: ({ column }) => , + cell: ({ row }) => { + const startDate = row.original.submissionStartDate + const endDate = row.original.submissionEndDate + + if (!startDate || !endDate) return - + + const now = new Date() + const isActive = now >= new Date(startDate) && now <= new Date(endDate) + const isPast = now > new Date(endDate) + + return ( +
+
+ {formatDate(startDate, "KR")} ~ {formatDate(endDate, "KR")} +
+ {isActive && ( + 진행중 + )} +
+ ) + }, + size: 140, + meta: { excelHeader: "입찰서제출기간" }, + }, + + { + accessorKey: "hasSpecificationMeeting", + header: ({ column }) => , + cell: ({ row }) => { + const hasMeeting = row.original.hasSpecificationMeeting + + return ( + + ) + }, + size: 100, + meta: { excelHeader: "사양설명회" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 가격 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "가격 정보", + columns: [ + { + accessorKey: "currency", + header: ({ column }) => , + cell: ({ row }) => ( + {row.original.currency} + ), + size: 60, + meta: { excelHeader: "통화" }, + }, + + { + accessorKey: "budget", + header: ({ column }) => , + cell: ({ row }) => ( + + {formatCurrency(row.original.budget, row.original.currency)} + + ), + size: 120, + meta: { excelHeader: "예산" }, + }, + + { + accessorKey: "targetPrice", + header: ({ column }) => , + cell: ({ row }) => ( + + {formatCurrency(row.original.targetPrice, row.original.currency)} + + ), + size: 120, + meta: { excelHeader: "내정가" }, + }, + + { + accessorKey: "finalBidPrice", + header: ({ column }) => , + cell: ({ row }) => ( + + {formatCurrency(row.original.finalBidPrice, row.original.currency)} + + ), + size: 120, + meta: { excelHeader: "최종입찰가" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 참여 현황 + // ═══════════════════════════════════════════════════════════════ + { + header: "참여 현황", + columns: [ + { + id: "participantExpected", + header: ({ column }) => , + cell: ({ row }) => ( + + {row.original.participantStats.expected} + + ), + size: 80, + meta: { excelHeader: "참여예정" }, + }, + + { + id: "participantParticipated", + header: ({ column }) => , + cell: ({ row }) => ( + + {row.original.participantStats.participated} + + ), + size: 60, + meta: { excelHeader: "참여" }, + }, + + { + id: "participantDeclined", + header: ({ column }) => , + cell: ({ row }) => ( + + {row.original.participantStats.declined} + + ), + size: 60, + meta: { excelHeader: "포기" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // PR 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "PR 정보", + columns: [ + { + accessorKey: "prNumber", + header: ({ column }) => , + cell: ({ row }) => ( + {row.original.prNumber || '-'} + ), + size: 100, + meta: { excelHeader: "PR No." }, + }, + + { + accessorKey: "hasPrDocument", + header: ({ column }) => , + cell: ({ row }) => { + const hasPrDoc = row.original.hasPrDocument + + return ( + + ) + }, + size: 80, + meta: { excelHeader: "PR 문서" }, + }, + ] + }, + + // ═══════════════════════════════════════════════════════════════ + // 메타 정보 + // ═══════════════════════════════════════════════════════════════ + { + header: "메타 정보", + columns: [ + { + accessorKey: "preQuoteDate", + header: ({ column }) => , + cell: ({ row }) => ( + {formatDate(row.original.preQuoteDate, "KR")} + ), + size: 90, + meta: { excelHeader: "사전견적일" }, + }, + + { + accessorKey: "biddingRegistrationDate", + header: ({ column }) => , + cell: ({ row }) => ( + {formatDate(row.original.biddingRegistrationDate , "KR")} + ), + size: 100, + meta: { excelHeader: "입찰등록일" }, + }, + + { + accessorKey: "updatedAt", + header: ({ column }) => , + cell: ({ row }) => ( + {formatDate(row.original.updatedAt, "KR")} + ), + size: 100, + meta: { excelHeader: "최종수정일" }, + }, + + { + accessorKey: "updatedBy", + header: ({ column }) => , + cell: ({ row }) => ( + {row.original.updatedBy || '-'} + ), + size: 100, + meta: { excelHeader: "최종수정자" }, + }, + ] + }, + + // ░░░ 비고 ░░░ + { + accessorKey: "remarks", + header: ({ column }) => , + cell: ({ row }) => ( +
+ {row.original.remarks || '-'} +
+ ), + size: 150, + meta: { excelHeader: "비고" }, + }, + + // ═══════════════════════════════════════════════════════════════ + // 액션 + // ═══════════════════════════════════════════════════════════════ + { + id: "actions", + header: "작업", + cell: ({ row }) => ( + + + + + + setRowAction({ row, type: "view" })}> + + 상세보기 + + setRowAction({ row, type: "edit" })}> + + 수정 + + + setRowAction({ row, type: "copy" })}> + + 복사 생성 + + setRowAction({ row, type: "manage_companies" })}> + + 참여업체 관리 + + + + ), + size: 50, + enableSorting: false, + enableHiding: false, + }, + ] +} \ No newline at end of file -- cgit v1.2.3