diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 07:51:27 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 07:51:27 +0000 |
| commit | 9ecdfb23fe3df6a5df86782385002c562dfc1198 (patch) | |
| tree | 4188cb7e6bf2c862d9c86a59d79946bd41217227 /lib/vendors/rfq-history-table | |
| parent | b67861fbb424c7ad47ad1538f75e2945bd8890c5 (diff) | |
(대표님) rfq 히스토리, swp 등
Diffstat (limited to 'lib/vendors/rfq-history-table')
| -rw-r--r-- | lib/vendors/rfq-history-table/rfq-history-table-columns.tsx | 129 | ||||
| -rw-r--r-- | lib/vendors/rfq-history-table/rfq-history-table.tsx | 172 |
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> </> ) |
