summaryrefslogtreecommitdiff
path: root/lib/vendors/bid-history-table/bid-history-table-columns.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors/bid-history-table/bid-history-table-columns.tsx')
-rw-r--r--lib/vendors/bid-history-table/bid-history-table-columns.tsx327
1 files changed, 327 insertions, 0 deletions
diff --git a/lib/vendors/bid-history-table/bid-history-table-columns.tsx b/lib/vendors/bid-history-table/bid-history-table-columns.tsx
new file mode 100644
index 00000000..b235917f
--- /dev/null
+++ b/lib/vendors/bid-history-table/bid-history-table-columns.tsx
@@ -0,0 +1,327 @@
+"use client";
+
+import * as React from "react";
+import { type DataTableRowAction } from "@/types/table";
+import { type ColumnDef } from "@tanstack/react-table";
+import { Ellipsis } from "lucide-react";
+import { formatDate } from "@/lib/utils";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+import { BidHistoryRow } from "./bid-history-table";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header";
+import { bidHistoryColumnsConfig } from "@/config/bidHistoryColumnsConfig";
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<BidHistoryRow> | null>>;
+ onViewDetails: (biddingId: number) => void;
+}
+
+/**
+ * tanstack table 컬럼 정의 (중첩 헤더 버전)
+ */
+export function getColumns({ setRowAction, onViewDetails }: GetColumnsProps): ColumnDef<BidHistoryRow>[] {
+ // ----------------------------------------------------------------
+ // 1) select 컬럼 (체크박스)
+ // ----------------------------------------------------------------
+ const selectColumn: ColumnDef<BidHistoryRow> = {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={
+ table.getIsAllPageRowsSelected() ||
+ (table.getIsSomePageRowsSelected() && "indeterminate")
+ }
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ className="translate-y-0.5"
+ />
+ ),
+ cell: ({ row }) => (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ aria-label="Select row"
+ className="translate-y-0.5"
+ />
+ ),
+ size: 40,
+ enableSorting: false,
+ enableHiding: false,
+ };
+
+ // ----------------------------------------------------------------
+ // 2) actions 컬럼 (Dropdown 메뉴)
+ // ----------------------------------------------------------------
+ const actionsColumn: ColumnDef<BidHistoryRow> = {
+ id: "actions",
+ enableHiding: false,
+ cell: function Cell({ row }) {
+ return (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ aria-label="Open menu"
+ variant="ghost"
+ className="flex size-8 p-0 data-[state=open]:bg-muted"
+ >
+ <Ellipsis className="size-4" aria-hidden="true" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end" className="w-40">
+ <DropdownMenuItem
+ onSelect={() => onViewDetails(row.original.biddingId)}
+ >
+ 입찰상세
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ );
+ },
+ size: 40,
+ };
+
+ // ----------------------------------------------------------------
+ // 3) 일반 컬럼들
+ // ----------------------------------------------------------------
+ const basicColumns: ColumnDef<BidHistoryRow>[] = bidHistoryColumnsConfig.map((cfg) => {
+ const column: ColumnDef<BidHistoryRow> = {
+ accessorKey: cfg.id,
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title={cfg.label} />
+ ),
+ size: cfg.size,
+ };
+
+ // 계약구분 표시
+ if (cfg.id === "contractType") {
+ column.cell = ({ row }) => {
+ const contractType = row.original.contractType;
+ if (!contractType) return null;
+
+ const contractTypeLabels = {
+ unit_price: "단가",
+ general: "일반",
+ sale: "매각"
+ };
+
+ return (
+ <span className="capitalize">
+ {contractTypeLabels[contractType] || contractType}
+ </span>
+ );
+ };
+ }
+
+ // 입찰유형 표시
+ if (cfg.id === "biddingType") {
+ column.cell = ({ row }) => {
+ const biddingType = row.original.biddingType;
+ if (!biddingType) return null;
+
+ const biddingTypeLabels = {
+ equipment: "기자재",
+ construction: "공사",
+ service: "용역",
+ lease: "임차",
+ steel_stock: "형강스톡",
+ piping: "배관",
+ transport: "운송",
+ waste: "폐기물",
+ sale: "매각"
+ };
+
+ return (
+ <span className="capitalize">
+ {biddingTypeLabels[biddingType] || biddingType}
+ </span>
+ );
+ };
+ }
+
+ // 입찰상태 표시
+ if (cfg.id === "biddingStatus") {
+ column.cell = ({ row }) => {
+ const biddingStatus = row.original.biddingStatus;
+ if (!biddingStatus) return null;
+
+ const statusLabels = {
+ bidding_generated: "입찰생성",
+ request_for_quotation: "사전견적요청",
+ received_quotation: "사전견적접수",
+ set_target_price: "내정가산정",
+ bidding_opened: "입찰공고",
+ bidding_closed: "입찰마감",
+ evaluation_of_bidding: "입찰평가중",
+ bidding_disposal: "유찰",
+ vendor_selected: "업체선정"
+ };
+
+ const statusColors = {
+ bidding_generated: "secondary",
+ request_for_quotation: "outline",
+ received_quotation: "outline",
+ set_target_price: "outline",
+ bidding_opened: "default",
+ bidding_closed: "destructive",
+ evaluation_of_bidding: "secondary",
+ bidding_disposal: "destructive",
+ vendor_selected: "default"
+ };
+
+ return (
+ <Badge variant={statusColors[biddingStatus] || "secondary"}>
+ {statusLabels[biddingStatus] || biddingStatus}
+ </Badge>
+ );
+ };
+ }
+
+ // 입찰번호 표시 (Rev. 포함)
+ if (cfg.id === "biddingNumber") {
+ column.cell = ({ row }) => {
+ const biddingNumber = row.original.biddingNumber;
+ const revision = row.original.revision;
+ if (!biddingNumber) return null;
+
+ return (
+ <div className="whitespace-nowrap">
+ {biddingNumber}
+ </div>
+ );
+ };
+ }
+
+ // 품목명 표시 (자재그룹 포함)
+ if (cfg.id === "itemName") {
+ column.cell = ({ row }) => {
+ const itemName = row.original.itemName;
+ const materialGroup = row.original.materialGroup;
+ const materialGroupName = row.original.materialGroupName;
+
+ if (!itemName) return null;
+
+ const displayText = materialGroup && materialGroupName
+ ? `${itemName} (${materialGroup}/${materialGroupName})`
+ : itemName;
+
+ return (
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <div className="break-words whitespace-normal line-clamp-2 max-w-[300px]">
+ {displayText}
+ </div>
+ </TooltipTrigger>
+ <TooltipContent side="bottom" className="max-w-[400px] whitespace-pre-wrap break-words">
+ {displayText}
+ </TooltipContent>
+ </Tooltip>
+ );
+ };
+ }
+
+ // 입찰명 표시
+ if (cfg.id === "biddingTitle") {
+ column.cell = ({ row }) => {
+ const title = row.original.biddingTitle;
+ if (!title) return null;
+
+ return (
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <div className="break-words whitespace-normal line-clamp-2 max-w-[300px]">
+ {title}
+ </div>
+ </TooltipTrigger>
+ <TooltipContent side="bottom" className="max-w-[400px] whitespace-pre-wrap break-words">
+ {title}
+ </TooltipContent>
+ </Tooltip>
+ );
+ };
+ }
+
+ // 계약정보 표시 (PO/계약정보)
+ if (cfg.id === "contractNumber") {
+ column.cell = ({ row }) => {
+ const poNumber = row.original.poNumber;
+ const contractNumber = row.original.contractNumber;
+
+ if (!poNumber && !contractNumber) return null;
+
+ const displayText = poNumber && contractNumber
+ ? `${poNumber}/${contractNumber}`
+ : poNumber || contractNumber;
+
+ return (
+ <div className="whitespace-nowrap">
+ {displayText}
+ </div>
+ );
+ };
+ }
+
+ // 날짜 필드들 표시
+ if (cfg.id === "biddingRequestDate" || cfg.id === "biddingDeadline") {
+ column.cell = ({ row }) => (
+ <div className="whitespace-nowrap">
+ {formatDate(row.getValue(cfg.id), "KR")}
+ </div>
+ );
+ }
+
+ // 금액 필드들 표시
+ if (cfg.id === "finalBidPrice" || cfg.id === "expectedAmount" || cfg.id === "preQuotePrice") {
+ column.cell = ({ row }) => {
+ const amount = row.getValue(cfg.id) as string;
+ const currency = row.original.currency;
+ if (!amount || !currency) return null;
+
+ const numericAmount = parseFloat(amount);
+ if (isNaN(numericAmount)) return null;
+
+ return (
+ <div className="whitespace-nowrap">
+ {`${currency} ${numericAmount.toLocaleString()}`}
+ </div>
+ );
+ };
+ }
+
+ // 발주비율 표시
+ if (cfg.id === "awardRatio") {
+ column.cell = ({ row }) => {
+ const ratio = row.original.awardRatio;
+ if (!ratio) return null;
+
+ const numericRatio = parseFloat(ratio);
+ if (isNaN(numericRatio)) return null;
+
+ return (
+ <div className="whitespace-nowrap">
+ {`${numericRatio}%`}
+ </div>
+ );
+ };
+ }
+
+ return column;
+ });
+
+ // ----------------------------------------------------------------
+ // 4) 최종 컬럼 배열
+ // ----------------------------------------------------------------
+ return [
+ selectColumn,
+ ...basicColumns,
+ actionsColumn,
+ ];
+}