"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 { getUserCodeByEmail } from "@/lib/bidding/service" import { Eye, Edit, MoreHorizontal, FileText, Users, Calendar, Building, Package, DollarSign, Clock, CheckCircle, XCircle, AlertTriangle } 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" // BiddingListItem에 manager 정보 추가 type BiddingListItemWithManagerCode = BiddingListItem & { managerName?: string | null managerCode?: string | null } 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: "입찰상태" }, }, // ░░░ 긴급여부 ░░░ { accessorKey: "isUrgent", header: ({ column }) => , cell: ({ row }) => { const isUrgent = row.original.isUrgent return isUrgent ? (
긴급
) : (
일반
) }, size: 90, 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 }) => { const name = row.original.managerName || "-"; const managerCode = row.original.managerCode || ""; return name === "-" ? "-" : `${name}(${managerCode})`; }, 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: "낙찰수" }, }, { id: "contractPeriod", header: ({ column }) => , cell: ({ row }) => { const startDate = row.original.contractStartDate const endDate = row.original.contractEndDate if (!startDate || !endDate) { return - } return (
{formatDate(startDate, "KR")} ~ {formatDate(endDate, "KR")}
) }, size: 120, 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 }) => ( {row.original.budget} ), size: 120, meta: { excelHeader: "예산" }, }, { accessorKey: "targetPrice", header: ({ column }) => , cell: ({ row }) => ( {row.original.targetPrice} ), size: 120, meta: { excelHeader: "내정가" }, }, { accessorKey: "finalBidPrice", header: ({ column }) => , cell: ({ row }) => ( {row.original.finalBidPrice} ), size: 120, meta: { excelHeader: "최종입찰가" }, }, ] }, // ═══════════════════════════════════════════════════════════════ // 참여 현황 // ═══════════════════════════════════════════════════════════════ { header: "참여 현황", columns: [ { id: "participantExpected", header: ({ column }) => , cell: ({ row }) => ( {row.original.participantExpected} ), size: 80, meta: { excelHeader: "참여예정" }, }, { id: "participantParticipated", header: ({ column }) => , cell: ({ row }) => ( {row.original.participantParticipated} ), size: 60, meta: { excelHeader: "참여" }, }, { id: "participantDeclined", header: ({ column }) => , cell: ({ row }) => ( {row.original.participantDeclined} ), 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: "update" })} disabled={['bidding_opened', 'bidding_closed', 'vendor_selected'].includes(row.original.status)} > 수정 {['bidding_opened', 'bidding_closed', 'vendor_selected'].includes(row.original.status) && ( (수정 불가) )} {/* setRowAction({ row, type: "copy" })}> 복사 생성 setRowAction({ row, type: "manage_companies" })}> 참여업체 관리 */} ), size: 50, enableSorting: false, enableHiding: false, }, ] }