summaryrefslogtreecommitdiff
path: root/lib/vendors
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors')
-rw-r--r--lib/vendors/bid-history-table/bid-history-table-columns.tsx327
-rw-r--r--lib/vendors/bid-history-table/bid-history-table.tsx208
-rw-r--r--lib/vendors/service.ts128
-rw-r--r--lib/vendors/validations.ts27
4 files changed, 688 insertions, 2 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,
+ ];
+}
diff --git a/lib/vendors/bid-history-table/bid-history-table.tsx b/lib/vendors/bid-history-table/bid-history-table.tsx
new file mode 100644
index 00000000..ec810429
--- /dev/null
+++ b/lib/vendors/bid-history-table/bid-history-table.tsx
@@ -0,0 +1,208 @@
+"use client";
+
+import * as React from "react";
+import { useRouter } from "next/navigation";
+import type {
+ DataTableAdvancedFilterField,
+ DataTableFilterField,
+ DataTableRowAction,
+} from "@/types/table";
+
+import { useDataTable } from "@/hooks/use-data-table";
+import { DataTable } from "@/components/data-table/data-table";
+import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar";
+import { getColumns } from "./bid-history-table-columns";
+import { getBidHistory } from "../service";
+import { TooltipProvider } from "@/components/ui/tooltip";
+
+export interface BidHistoryRow {
+ id: number;
+ biddingId: number;
+ biddingNumber: string | null;
+ revision: number | null;
+ contractType: "unit_price" | "general" | "sale";
+ biddingType: "equipment" | "construction" | "service" | "lease" | "steel_stock" | "piping" | "transport" | "waste" | "sale";
+ biddingStatus: "bidding_generated" | "request_for_quotation" | "received_quotation" | "set_target_price" | "bidding_opened" | "bidding_closed" | "evaluation_of_bidding" | "bidding_disposal" | "vendor_selected";
+ projectCode: string | null;
+ projectName: string | null;
+ itemName: string | null;
+ materialGroup: string | null;
+ materialGroupName: string | null;
+ biddingTitle: string | null;
+ poNumber: string | null;
+ contractNumber: string | null;
+ biddingRequestDate: Date | null;
+ biddingDeadline: Date | null;
+ biddingManager: string | null;
+ currency: string | null;
+ finalBidPrice: string | null;
+ expectedAmount: string | null;
+ awardRatio: string | null;
+ preQuotePrice: string | null;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+interface BidHistoryTableProps {
+ promises: Promise<
+ [
+ Awaited<ReturnType<typeof getBidHistory>>,
+ ]
+ >;
+ lng: string;
+}
+
+export function VendorBidHistoryTable({ promises, lng }: BidHistoryTableProps) {
+ const router = useRouter();
+
+ // SSR을 위해 React.use() 사용 - 서버에서 렌더링된 데이터를 클라이언트에서 사용
+ const [{ data = [], pageCount = 0 }] = React.use(promises);
+
+ const [, setRowAction] = React.useState<DataTableRowAction<BidHistoryRow> | null>(null);
+
+ const handleViewDetails = React.useCallback((biddingId: number) => {
+ router.push(`/${lng}/evcp/bid/${biddingId}/detail`);
+ }, [router, lng]);
+
+ const columns = React.useMemo(() => getColumns({
+ setRowAction,
+ onViewDetails: handleViewDetails,
+ }), [setRowAction, handleViewDetails]);
+
+ const filterFields: DataTableFilterField<BidHistoryRow>[] = [
+ {
+ id: "biddingNumber",
+ label: "입찰번호",
+ placeholder: "입찰번호로 검색...",
+ },
+ {
+ id: "contractType",
+ label: "계약구분",
+ options: [
+ { label: "단가", value: "unit_price" },
+ { label: "일반", value: "general" },
+ { label: "매각", value: "sale" }
+ ],
+ },
+ {
+ id: "biddingType",
+ label: "입찰유형",
+ options: [
+ { label: "기자재", value: "equipment" },
+ { label: "공사", value: "construction" },
+ { label: "용역", value: "service" },
+ { label: "임차", value: "lease" },
+ { label: "형강스톡", value: "steel_stock" },
+ { label: "배관", value: "piping" },
+ { label: "운송", value: "transport" },
+ { label: "폐기물", value: "waste" },
+ { label: "매각", value: "sale" }
+ ],
+ },
+ {
+ id: "biddingStatus",
+ label: "입찰상태",
+ options: [
+ { label: "입찰생성", value: "bidding_generated" },
+ { label: "사전견적요청", value: "request_for_quotation" },
+ { label: "사전견적접수", value: "received_quotation" },
+ { label: "내정가산정", value: "set_target_price" },
+ { label: "입찰공고", value: "bidding_opened" },
+ { label: "입찰마감", value: "bidding_closed" },
+ { label: "입찰평가중", value: "evaluation_of_bidding" },
+ { label: "유찰", value: "bidding_disposal" },
+ { label: "업체선정", value: "vendor_selected" }
+ ],
+ },
+ {
+ id: "projectName",
+ label: "프로젝트명",
+ placeholder: "프로젝트명으로 검색...",
+ }
+ ];
+
+ const advancedFilterFields: DataTableAdvancedFilterField<BidHistoryRow>[] = [
+ { id: "biddingNumber", label: "입찰번호", type: "text" },
+ { id: "projectCode", label: "프로젝트코드", type: "text" },
+ { id: "projectName", label: "프로젝트명", type: "text" },
+ {
+ id: "contractType",
+ label: "계약구분",
+ type: "multi-select",
+ options: [
+ { label: "단가", value: "unit_price" },
+ { label: "일반", value: "general" },
+ { label: "매각", value: "sale" }
+ ],
+ },
+ {
+ id: "biddingType",
+ label: "입찰유형",
+ type: "multi-select",
+ options: [
+ { label: "기자재", value: "equipment" },
+ { label: "공사", value: "construction" },
+ { label: "용역", value: "service" },
+ { label: "임차", value: "lease" },
+ { label: "형강스톡", value: "steel_stock" },
+ { label: "배관", value: "piping" },
+ { label: "운송", value: "transport" },
+ { label: "폐기물", value: "waste" },
+ { label: "매각", value: "sale" }
+ ],
+ },
+ {
+ id: "biddingStatus",
+ label: "입찰상태",
+ type: "multi-select",
+ options: [
+ { label: "입찰생성", value: "bidding_generated" },
+ { label: "사전견적요청", value: "request_for_quotation" },
+ { label: "사전견적접수", value: "received_quotation" },
+ { label: "내정가산정", value: "set_target_price" },
+ { label: "입찰공고", value: "bidding_opened" },
+ { label: "입찰마감", value: "bidding_closed" },
+ { label: "입찰평가중", value: "evaluation_of_bidding" },
+ { label: "유찰", value: "bidding_disposal" },
+ { label: "업체선정", value: "vendor_selected" }
+ ],
+ },
+ { id: "biddingManager", label: "입찰담당자", type: "text" },
+ { id: "biddingRequestDate", label: "입찰요청일", type: "date" },
+ { id: "biddingDeadline", label: "입찰마감일", type: "date" },
+ { id: "createdAt", label: "생성일", type: "date" },
+ ];
+
+ const { table } = useDataTable({
+ data,
+ columns,
+ pageCount,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState: {
+ sorting: [{ id: "createdAt", desc: true }],
+ columnPinning: { right: ["actions"] },
+ },
+ getRowId: (originalRow, index) => originalRow?.id ? String(originalRow.id) : String(index),
+ shallow: false,
+ clearOnDefault: true,
+ });
+
+ return (
+ <>
+ <TooltipProvider>
+ <DataTable
+ table={table}
+ >
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ >
+ </DataTableAdvancedToolbar>
+ </DataTable>
+ </TooltipProvider>
+ </>
+ );
+}
diff --git a/lib/vendors/service.ts b/lib/vendors/service.ts
index 9a37f5d7..9362a88c 100644
--- a/lib/vendors/service.ts
+++ b/lib/vendors/service.ts
@@ -45,6 +45,7 @@ import type {
CreateVendorItemSchema,
GetRfqHistorySchema,
GetVendorMaterialsSchema,
+ GetBidHistorySchema,
} from "./validations";
import { asc, desc, ilike, inArray, and, or, eq, isNull, sql } from "drizzle-orm";
@@ -55,7 +56,7 @@ import { items, materials } from "@/db/schema/items";
import { mfaTokens, roles, userRoles, users } from "@/db/schema/users";
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
-import { contractsDetailView, projects, vendorPQSubmissions, vendorsLogs } from "@/db/schema";
+import { contractsDetailView, projects, vendorPQSubmissions, vendorsLogs, biddingCompanies, biddings } from "@/db/schema";
import { deleteFile, saveFile, saveBuffer } from "../file-stroage";
import { basicContractTemplates } from "@/db/schema/basicContractDocumnet";
import { basicContract } from "@/db/schema/basicContractDocumnet";
@@ -3213,3 +3214,128 @@ export async function getVendorByTaxId(taxId: string) {
};
}
}
+export async function getBidHistory(input: GetBidHistorySchema, vendorId: number) {
+ try {
+ const offset = (input.page - 1) * input.perPage;
+
+ // 기본 where 조건 (vendorId)
+ const vendorWhere = eq(biddingCompanies.companyId, vendorId);
+
+ // 고급 필터링
+ const advancedWhere = filterColumns({
+ table: biddings,
+ filters: input.filters,
+ joinOperator: input.joinOperator,
+ joinedTables: {
+ biddingCompanies,
+ projects,
+ },
+ customColumnMapping: {
+ biddingManager: biddingCompanies.contactPerson,
+ projectCode: projects.code,
+ },
+ });
+
+ // 글로벌 검색
+ let globalWhere;
+ if (input.search) {
+ const s = `%${input.search}%`;
+ globalWhere = or(
+ ilike(biddings.biddingNumber, s),
+ ilike(biddings.projectName, s),
+ ilike(biddings.itemName, s),
+ ilike(biddings.title, s)
+ );
+ }
+
+ const finalWhere = and(
+ advancedWhere,
+ globalWhere,
+ vendorWhere
+ );
+
+ // 정렬 조건 - 동적 매핑 (하드코딩 최소화)
+ // biddings, biddingCompanies, projects 등에서 정렬 가능한 컬럼을 동적으로 매핑
+ const sortFieldMap: Record<string, any> = {
+ biddingNumber: biddings.biddingNumber,
+ revision: biddings.revision,
+ contractType: biddings.contractType,
+ biddingType: biddings.biddingType,
+ biddingStatus: biddings.status,
+ projectName: biddings.projectName,
+ itemName: biddings.itemName,
+ biddingTitle: biddings.title,
+ biddingRequestDate: biddings.submissionStartDate,
+ biddingDeadline: biddings.submissionEndDate,
+ biddingManager: biddingCompanies.contactPerson,
+ projectCode: projects.code,
+ createdAt: biddings.createdAt,
+ };
+
+ const orderBy =
+ input.sort.length > 0
+ ? input.sort.map((item) => {
+ const field = sortFieldMap[item.id] ?? biddings.createdAt;
+ return item.desc ? desc(field) : asc(field);
+ })
+ : [desc(biddings.createdAt)];
+
+ // 트랜잭션으로 데이터 조회
+ const { data, total } = await db.transaction(async (tx) => {
+
+ // 데이터 조회 (biddingCompanies와 biddings 조인)
+ const bidHistoryData = await tx
+ .select({
+ id: biddingCompanies.id,
+ biddingId: biddings.id,
+ biddingNumber: biddings.biddingNumber,
+ revision: biddings.revision,
+ contractType: biddings.contractType,
+ biddingType: biddings.biddingType,
+ biddingStatus: biddings.status,
+ projectCode: projects.code,
+ projectName: biddings.projectName,
+ itemName: biddings.itemName,
+ // materialGroup: sql<string>`null`,
+ // materialGroupName: sql<string>`null`,
+ biddingTitle: biddings.title,
+ poNumber: sql<string>`null`,
+ contractNumber: sql<string>`null`,
+ biddingRequestDate: biddings.submissionStartDate,
+ biddingDeadline: biddings.submissionEndDate,
+ biddingManager: biddingCompanies.contactPerson,
+ currency: biddings.currency,
+ finalBidPrice: biddingCompanies.finalQuoteAmount,
+ expectedAmount: biddings.targetPrice,
+ awardRatio: biddingCompanies.awardRatio,
+ preQuotePrice: biddingCompanies.preQuoteAmount,
+ createdAt: biddings.createdAt,
+ updatedAt: biddings.updatedAt,
+ })
+ .from(biddingCompanies)
+ .innerJoin(biddings, eq(biddingCompanies.biddingId, biddings.id))
+ .leftJoin(projects, eq(biddings.projectId, projects.id))
+ .where(finalWhere)
+ .orderBy(...orderBy)
+ .limit(input.perPage)
+ .offset(offset);
+
+
+ const total = await tx
+ .select({ count: sql<number>`count(*)` })
+ .from(biddingCompanies)
+ .innerJoin(biddings, eq(biddingCompanies.biddingId, biddings.id))
+ .leftJoin(projects, eq(biddings.projectId, projects.id))
+ .where(finalWhere);
+
+ const totalCount = total[0]?.count ?? 0;
+ return { data: bidHistoryData, total: totalCount };
+ });
+
+ const pageCount = Math.ceil(total / input.perPage);
+ return { data, pageCount };
+ } catch (err) {
+ console.error("Error fetching bid history:", err);
+ return { data: [], pageCount: 0 };
+ }
+} \ No newline at end of file
diff --git a/lib/vendors/validations.ts b/lib/vendors/validations.ts
index d1902866..44237963 100644
--- a/lib/vendors/validations.ts
+++ b/lib/vendors/validations.ts
@@ -11,7 +11,7 @@ import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"
import { VendorContact, VendorItemsView, VendorMaterialsView, vendors, VendorWithType } from "@/db/schema/vendors";
import { rfqs } from "@/db/schema/rfq"
import { countryDialCodes } from '@/components/common/country-dial-codes';
-
+import { biddings } from "@/db/schema/bidding";
export const searchParamsCache = createSearchParamsCache({
@@ -537,3 +537,28 @@ export const searchParamsMaterialCache = createSearchParamsCache({
});
export type GetVendorMaterialsSchema = Awaited<ReturnType<typeof searchParamsMaterialCache.parse>>
+
+export const searchParamsBidHistoryCache = createSearchParamsCache({
+ // 공통 플래그
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault(
+ []
+ ),
+
+ // 페이징
+ page: parseAsInteger.withDefault(1),
+ perPage: parseAsInteger.withDefault(10),
+
+ // 정렬
+ sort: getSortingStateParser<typeof biddings.$inferSelect>().withDefault([
+ { id: "createdAt", desc: true },
+ ]),
+
+ // 고급 필터
+ filters: getFiltersStateParser().withDefault([]),
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+
+ // 검색 키워드
+ search: parseAsString.withDefault(""),
+});
+
+export type GetBidHistorySchema = Awaited<ReturnType<typeof searchParamsBidHistoryCache.parse>>