summaryrefslogtreecommitdiff
path: root/lib/vendors/rfq-history-table
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-19 07:51:27 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-19 07:51:27 +0000
commit9ecdfb23fe3df6a5df86782385002c562dfc1198 (patch)
tree4188cb7e6bf2c862d9c86a59d79946bd41217227 /lib/vendors/rfq-history-table
parentb67861fbb424c7ad47ad1538f75e2945bd8890c5 (diff)
(대표님) rfq 히스토리, swp 등
Diffstat (limited to 'lib/vendors/rfq-history-table')
-rw-r--r--lib/vendors/rfq-history-table/rfq-history-table-columns.tsx129
-rw-r--r--lib/vendors/rfq-history-table/rfq-history-table.tsx172
2 files changed, 193 insertions, 108 deletions
diff --git a/lib/vendors/rfq-history-table/rfq-history-table-columns.tsx b/lib/vendors/rfq-history-table/rfq-history-table-columns.tsx
index 66ddee47..8054b128 100644
--- a/lib/vendors/rfq-history-table/rfq-history-table-columns.tsx
+++ b/lib/vendors/rfq-history-table/rfq-history-table-columns.tsx
@@ -3,7 +3,7 @@
import * as React from "react"
import { type DataTableRowAction } from "@/types/table"
import { type ColumnDef } from "@tanstack/react-table"
-import { Ellipsis } from "lucide-react"
+import { Ellipsis, MoreHorizontal } from "lucide-react"
import { toast } from "sonner"
import { getErrorMessage } from "@/lib/handle-error"
@@ -15,13 +15,6 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
- DropdownMenuRadioGroup,
- DropdownMenuRadioItem,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header"
@@ -30,7 +23,6 @@ import { VendorItem, vendors } from "@/db/schema/vendors"
import { modifyVendor } from "../service"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
-import { getRFQStatusIcon } from "@/lib/tasks/utils"
import { rfqHistoryColumnsConfig } from "@/config/rfqHistoryColumnsConfig"
export interface RfqHistoryRow {
@@ -61,13 +53,13 @@ export interface RfqHistoryRow {
interface GetColumnsProps {
setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<RfqHistoryRow> | null>>;
- openItemsModal: (rfqId: number) => void;
+ onViewDetails: (rfqId: number) => void;
}
/**
* tanstack table 컬럼 정의 (중첩 헤더 버전)
*/
-export function getColumns({ setRowAction, openItemsModal }: GetColumnsProps): ColumnDef<RfqHistoryRow>[] {
+export function getColumns({ setRowAction, onViewDetails }: GetColumnsProps): ColumnDef<RfqHistoryRow>[] {
// ----------------------------------------------------------------
// 1) select 컬럼 (체크박스)
// ----------------------------------------------------------------
@@ -112,14 +104,14 @@ export function getColumns({ setRowAction, openItemsModal }: GetColumnsProps): C
variant="ghost"
className="flex size-8 p-0 data-[state=open]:bg-muted"
>
- <Ellipsis className="size-4" aria-hidden="true" />
+ <span className="text-lg">⋯</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "update" })}
+ onSelect={() => onViewDetails(row.original.id)}
>
- View Details
+ 견적상세
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@@ -129,30 +121,60 @@ export function getColumns({ setRowAction, openItemsModal }: GetColumnsProps): C
}
// ----------------------------------------------------------------
- // 3) 일반 컬럼들
+ // 3) 개별 컬럼들 (그룹 없음)
// ----------------------------------------------------------------
- const basicColumns: ColumnDef<RfqHistoryRow>[] = rfqHistoryColumnsConfig.map((cfg) => {
+ const basicColumns1: ColumnDef<RfqHistoryRow>[] = []
+ const quotationGroupColumns: ColumnDef<RfqHistoryRow>[] = []
+ const basicColumns2: ColumnDef<RfqHistoryRow>[] = []
+
+ const sortableIds = new Set([
+ "rfqType",
+ "status",
+ "rfqCode",
+ "projectInfo",
+ "packageInfo",
+ "materialInfo",
+ "currency",
+ "totalAmount",
+ "leadTime",
+ "paymentTerms",
+ "incoterms",
+ "shippingLocation",
+ "contractInfo",
+ "rfqSendDate",
+ "submittedAt",
+ "picName",
+ ])
+
+ rfqHistoryColumnsConfig.forEach((cfg) => {
+ const isSortable = sortableIds.has(cfg.id)
const column: ColumnDef<RfqHistoryRow> = {
accessorKey: cfg.id,
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title={cfg.label} />
),
+ meta: {
+ excelHeader: cfg.excelHeader,
+ group: cfg.group,
+ type: cfg.type,
+ },
size: cfg.size,
+ enableSorting: isSortable,
}
- if (cfg.id === "description") {
+ if (cfg.id === "materialInfo") {
column.cell = ({ row }) => {
- const description = row.original.description
- if (!description) return null
+ const materialInfo = row.original.materialInfo
+ if (!materialInfo) return null
return (
<Tooltip>
<TooltipTrigger asChild>
<div className="break-words whitespace-normal line-clamp-2">
- {description}
+ {materialInfo}
</div>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-[400px] whitespace-pre-wrap break-words">
- {description}
+ {materialInfo}
</TooltipContent>
</Tooltip>
)
@@ -163,10 +185,8 @@ export function getColumns({ setRowAction, openItemsModal }: GetColumnsProps): C
column.cell = ({ row }) => {
const statusVal = row.original.status
if (!statusVal) return null
- const Icon = getRFQStatusIcon(statusVal)
return (
- <div className="flex items-center">
- <Icon className="mr-2 size-4 text-muted-foreground" aria-hidden="true" />
+ <div className="whitespace-nowrap">
<span className="capitalize">{statusVal}</span>
</div>
)
@@ -176,48 +196,57 @@ export function getColumns({ setRowAction, openItemsModal }: GetColumnsProps): C
if (cfg.id === "totalAmount") {
column.cell = ({ row }) => {
const amount = row.original.totalAmount
- const currency = row.original.currency
- if (!amount || !currency) return null
+ if (!amount) return null
return (
<div className="whitespace-nowrap">
- {`${currency} ${amount.toLocaleString()}`}
+ {amount.toLocaleString()}
</div>
)
}
}
- if (cfg.id === "dueDate" || cfg.id === "createdAt") {
- column.cell = ({ row }) => (
- <div className="whitespace-nowrap">
- {formatDate(row.getValue(cfg.id), "KR")}
- </div>
- )
+ if (cfg.id === "rfqSendDate" || cfg.id === "submittedAt") {
+ column.cell = ({ row }) => {
+ const v = row.getValue(cfg.id) as Date | null
+ if (!v) return <div className="whitespace-nowrap">-</div>
+ return (
+ <div className="whitespace-nowrap">{formatDate(v, "KR")}</div>
+ )
+ }
+ }
+
+ // 컬럼을 적절한 배열에 분류
+ if (cfg.group === "견적정보") {
+ quotationGroupColumns.push(column)
+ } else if (["contractInfo", "rfqSendDate", "submittedAt", "picName"].includes(cfg.id)) {
+ basicColumns2.push(column)
+ } else {
+ basicColumns1.push(column)
}
return column
})
- const itemsColumn: ColumnDef<RfqHistoryRow> = {
- id: "items",
- header: "Items",
- cell: ({ row }) => {
- const rfq = row.original;
- const count = rfq.itemCount || 0;
- return (
- <Button variant="ghost" onClick={() => openItemsModal(rfq.id)}>
- {count === 0 ? "No Items" : `${count} Items`}
- </Button>
- )
- },
- }
-
// ----------------------------------------------------------------
- // 4) 최종 컬럼 배열
+ // 4) 최종 컬럼 배열 (bid-history-table-columns.tsx 방식)
// ----------------------------------------------------------------
+ const createGroupColumn = (groupName: string, columns: ColumnDef<RfqHistoryRow>[]): ColumnDef<RfqHistoryRow> => {
+ return {
+ id: `group-${groupName.replace(/\s+/g, '-')}`,
+ header: groupName,
+ columns: columns,
+ meta: {
+ isGroupColumn: true,
+ groupBorders: true,
+ } as any
+ }
+ }
+
return [
selectColumn,
- ...basicColumns,
- itemsColumn,
+ ...basicColumns1,
+ ...(quotationGroupColumns.length > 0 ? [createGroupColumn("견적정보", quotationGroupColumns)] : []),
+ ...basicColumns2,
actionsColumn,
]
} \ No newline at end of file
diff --git a/lib/vendors/rfq-history-table/rfq-history-table.tsx b/lib/vendors/rfq-history-table/rfq-history-table.tsx
index 71830303..11a4bf9d 100644
--- a/lib/vendors/rfq-history-table/rfq-history-table.tsx
+++ b/lib/vendors/rfq-history-table/rfq-history-table.tsx
@@ -14,26 +14,38 @@ import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-adv
import { getColumns } from "./rfq-history-table-columns"
import { getRfqHistory } from "../service"
import { RfqHistoryTableToolbarActions } from "./rfq-history-table-toolbar-actions"
-import { RfqItemsTableDialog } from "./rfq-items-table-dialog"
import { getRFQStatusIcon } from "@/lib/tasks/utils"
import { TooltipProvider } from "@/components/ui/tooltip"
+import { useRouter } from "next/navigation"
export interface RfqHistoryRow {
id: number;
+ rfqType: string | null;
+ status: string;
rfqCode: string | null;
+ projectInfo: string | null;
+ packageInfo: string | null;
+ materialInfo: string | null;
+ // 견적정보 세부 필드들
+ currency: string | null;
+ totalAmount: number | null;
+ leadTime: string | null;
+ paymentTerms: string | null;
+ incoterms: string | null;
+ shippingLocation: string | null;
+ contractInfo: string | null;
+ rfqSendDate: Date | null;
+ submittedAt: Date | null;
+ picName: string | null;
+ // 기존 필드들 (호환성을 위해 유지)
projectCode: string | null;
projectName: string | null;
description: string | null;
dueDate: Date;
- status: "DRAFT" | "PUBLISHED" | "EVALUATION" | "AWARDED";
vendorStatus: string;
- totalAmount: number | null;
- currency: string | null;
- leadTime: string | null;
itemCount: number;
tbeResult: string | null;
cbeResult: string | null;
- createdAt: Date;
items: {
rfqId: number;
id: number;
@@ -42,6 +54,7 @@ export interface RfqHistoryRow {
quantity: number | null;
uom: string | null;
}[];
+ actions?: any; // actions 컬럼용
}
interface RfqHistoryTableProps {
@@ -50,68 +63,117 @@ interface RfqHistoryTableProps {
Awaited<ReturnType<typeof getRfqHistory>>,
]
>
+ lng: string
+ vendorId: number
}
-export function VendorRfqHistoryTable({ promises }: RfqHistoryTableProps) {
- const [{ data, pageCount }] = React.use(promises)
+export function VendorRfqHistoryTable({ promises, lng, vendorId }: RfqHistoryTableProps) {
+ const [{ data = [], pageCount = 0 }] = React.use(promises)
+ const router = useRouter()
- const [rowAction, setRowAction] = React.useState<DataTableRowAction<RfqHistoryRow> | null>(null)
+ const [, setRowAction] = React.useState<DataTableRowAction<RfqHistoryRow> | null>(null)
- const [itemsModalOpen, setItemsModalOpen] = React.useState(false);
- const [selectedRfq, setSelectedRfq] = React.useState<RfqHistoryRow | null>(null);
-
- const openItemsModal = React.useCallback((rfqId: number) => {
- const rfq = data.find(r => r.id === rfqId);
- if (rfq) {
- setSelectedRfq(rfq);
- setItemsModalOpen(true);
- }
- }, [data]);
+ const onViewDetails = React.useCallback((rfqId: number) => {
+ router.push(`/${lng}/evcp/rfq-last/${rfqId}`);
+ }, [router, lng]);
const columns = React.useMemo(() => getColumns({
setRowAction,
- openItemsModal,
- }), [setRowAction, openItemsModal]);
+ onViewDetails,
+ }), [setRowAction, onViewDetails]);
const filterFields: DataTableFilterField<RfqHistoryRow>[] = [
{
- id: "rfqCode",
- label: "RFQ Code",
- placeholder: "Filter RFQ Code...",
+ id: "rfqType",
+ label: "견적종류",
+ options: [
+ { label: "ITB", value: "ITB" },
+ { label: "RFQ", value: "RFQ" },
+ { label: "일반", value: "일반" },
+ { label: "정기견적", value: "정기견적" }
+ ],
},
{
id: "status",
- label: "Status",
- options: ["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"].map((status) => ({
- label: toSentenceCase(status),
- value: status,
- icon: getRFQStatusIcon(status),
- })),
+ label: "견적상태",
+ options: [
+ { label: "ITB 발송", value: "ITB 발송" },
+ { label: "Short List 확정", value: "Short List 확정" },
+ { label: "최종업체선정", value: "최종업체선정" },
+ { label: "견적접수", value: "견적접수" },
+ { label: "견적평가중", value: "견적평가중" },
+ { label: "견적완료", value: "견적완료" }
+ ],
},
+ { id: "rfqCode", label: "견적번호", placeholder: "견적번호로 검색..." },
+ { id: "projectInfo", label: "프로젝트", placeholder: "프로젝트로 검색..." },
+ { id: "packageInfo", label: "PKG No.", placeholder: "PKG로 검색..." },
+ { id: "materialInfo", label: "자재그룹", placeholder: "자재그룹으로 검색..." },
{
- id: "vendorStatus",
- label: "Vendor Status",
- placeholder: "Filter Vendor Status...",
- }
+ id: "currency",
+ label: "통화",
+ options: [
+ { label: "USD", value: "USD" },
+ { label: "KRW", value: "KRW" },
+ { label: "EUR", value: "EUR" },
+ { label: "JPY", value: "JPY" }
+ ],
+ },
+ { id: "paymentTerms", label: "지급조건", placeholder: "지급조건으로 검색..." },
+ { id: "incoterms", label: "Incoterms", placeholder: "Incoterms로 검색..." },
+ { id: "shippingLocation", label: "선적지", placeholder: "선적지로 검색..." },
+ { id: "picName", label: "견적담당자", placeholder: "담당자로 검색..." },
]
const advancedFilterFields: DataTableAdvancedFilterField<RfqHistoryRow>[] = [
- { id: "rfqCode", label: "RFQ Code", type: "text" },
- { id: "projectCode", label: "Project Code", type: "text" },
- { id: "projectName", label: "Project Name", type: "text" },
- {
- id: "status",
- label: "RFQ Status",
+ {
+ id: "rfqType",
+ label: "견적종류",
+ type: "multi-select",
+ options: [
+ { label: "ITB", value: "ITB" },
+ { label: "RFQ", value: "RFQ" },
+ { label: "일반", value: "일반" },
+ { label: "정기견적", value: "정기견적" }
+ ],
+ },
+ {
+ id: "status",
+ label: "견적상태",
+ type: "multi-select",
+ options: [
+ { label: "ITB 발송", value: "ITB 발송" },
+ { label: "Short List 확정", value: "Short List 확정" },
+ { label: "최종업체선정", value: "최종업체선정" },
+ { label: "견적접수", value: "견적접수" },
+ { label: "견적평가중", value: "견적평가중" },
+ { label: "견적완료", value: "견적완료" }
+ ],
+ },
+ { id: "rfqCode", label: "견적번호", type: "text" },
+ { id: "projectInfo", label: "프로젝트", type: "text" },
+ { id: "packageInfo", label: "PKG No.", type: "text" },
+ { id: "materialInfo", label: "자재그룹", type: "text" },
+ {
+ id: "currency",
+ label: "통화",
type: "multi-select",
- options: ["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"].map((status) => ({
- label: toSentenceCase(status),
- value: status,
- icon: getRFQStatusIcon(status),
- })),
+ options: [
+ { label: "USD", value: "USD" },
+ { label: "KRW", value: "KRW" },
+ { label: "EUR", value: "EUR" },
+ { label: "JPY", value: "JPY" }
+ ],
},
- { id: "vendorStatus", label: "Vendor Status", type: "text" },
- { id: "dueDate", label: "Due Date", type: "date" },
- { id: "createdAt", label: "Created At", type: "date" },
+ { id: "totalAmount", label: "총 견적금액", type: "number" },
+ { id: "leadTime", label: "업체 L/T(W)", type: "text" },
+ { id: "paymentTerms", label: "지급조건", type: "text" },
+ { id: "incoterms", label: "Incoterms", type: "text" },
+ { id: "shippingLocation", label: "선적지", type: "text" },
+ { id: "contractInfo", label: "PO/계약정보", type: "text" },
+ { id: "rfqSendDate", label: "견적요청일", type: "date" },
+ { id: "submittedAt", label: "견적회신일", type: "date" },
+ { id: "picName", label: "견적담당자", type: "text" },
]
const { table } = useDataTable({
@@ -122,13 +184,13 @@ export function VendorRfqHistoryTable({ promises }: RfqHistoryTableProps) {
enablePinning: true,
enableAdvancedFilter: true,
initialState: {
- sorting: [{ id: "createdAt", desc: true }],
+ sorting: [{ id: "rfqSendDate", desc: true }],
columnPinning: { right: ["actions"] },
},
- getRowId: (originalRow) => String(originalRow.id),
- shallow: true,
+ getRowId: (originalRow, index) => originalRow?.id ? String(originalRow.id) : String(index),
+ shallow: false,
clearOnDefault: true,
- })
+ });
return (
<>
@@ -141,15 +203,9 @@ export function VendorRfqHistoryTable({ promises }: RfqHistoryTableProps) {
filterFields={advancedFilterFields}
shallow={false}
>
- <RfqHistoryTableToolbarActions table={table} />
</DataTableAdvancedToolbar>
</DataTable>
- <RfqItemsTableDialog
- open={itemsModalOpen}
- onOpenChange={setItemsModalOpen}
- items={selectedRfq?.items ?? []}
- />
</TooltipProvider>
</>
)