"use client" import * as React from "react" import type { DataTableAdvancedFilterField, DataTableFilterField, DataTableRowAction, } from "@/types/table" import { toSentenceCase } from "@/lib/utils" 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 { getRFQStatusIcon } from "@/lib/tasks/utils" import { useFeatureFlags } from "./feature-flags-provider" import { getColumns } from "./rfqs-table-columns" import { fetchRfqAttachments, fetchRfqItems, getRfqs, getRfqStatusCounts } from "../service" import { RfqWithItemCount, rfqs } from "@/db/schema/rfq" import { UpdateRfqSheet } from "./update-rfq-sheet" import { DeleteRfqsDialog } from "./delete-rfqs-dialog" import { RfqsTableToolbarActions } from "./rfqs-table-toolbar-actions" import { RfqsItemsDialog } from "./ItemsDialog" import { getAllOffshoreItems } from "@/lib/items-tech/service" import { RfqAttachmentsSheet } from "./attachment-rfq-sheet" import { useRouter } from "next/navigation" interface RfqsTableProps { promises: Promise< [ Awaited>, Awaited>, Awaited>, ] >; } export interface ExistingAttachment { id: number; fileName: string; filePath: string; createdAt?: Date; vendorId?: number | null; size?: number; } export interface ExistingItem { id?: number; itemCode: string; description: string | null; quantity: number | null; uom: string | null; } export function RfqsTable({ promises }: RfqsTableProps) { const [{ data, pageCount }, statusCounts, items] = React.use(promises) const [attachmentsOpen, setAttachmentsOpen] = React.useState(false) const [selectedRfqIdForAttachments, setSelectedRfqIdForAttachments] = React.useState(null) const [attachDefault, setAttachDefault] = React.useState([]) const [itemsDefault, setItemsDefault] = React.useState([]) const router = useRouter() const itemsList = items?.map((v) => ({ code: v.itemCode ?? "", itemList: v.itemList ?? "", subItemList: v.subItemList ?? "", })); const [rowAction, setRowAction] = React.useState | null>(null) const [rowData, setRowData] = React.useState(() => data) const [itemsModalOpen, setItemsModalOpen] = React.useState(false); const [selectedRfqId, setSelectedRfqId] = React.useState(null); const selectedRfq = React.useMemo(() => { return rowData.find(row => row.rfqId === selectedRfqId) || null; }, [rowData, selectedRfqId]); async function openItemsModal(rfqId: number) { const itemList = await fetchRfqItems(rfqId) setItemsDefault(itemList) setSelectedRfqId(rfqId); setItemsModalOpen(true); } async function openAttachmentsSheet(rfqId: number) { // 4.1) Fetch current attachments from server (server action) const list = await fetchRfqAttachments(rfqId) // returns ExistingAttachment[] setAttachDefault(list) setSelectedRfqIdForAttachments(rfqId) setAttachmentsOpen(true) setSelectedRfqId(rfqId); } function handleAttachmentsUpdated(rfqId: number, newCount: number, newList?: ExistingAttachment[]) { // 5.1) update rowData itemCount setRowData(prev => prev.map(r => r.rfqId === rfqId ? { ...r, itemCount: newCount } : r ) ) // 5.2) if newList is provided, store it if (newList) { setAttachDefault(newList) } } const columns = React.useMemo(() => getColumns({ setRowAction, router, // we pass openItemsModal as a prop so the itemsColumn can call it openItemsModal, openAttachmentsSheet, }), [setRowAction, router]); /** * This component can render either a faceted filter or a search filter based on the `options` prop. */ const filterFields: DataTableFilterField[] = [ { id: "rfqCode", label: "RFQ Code", placeholder: "Filter RFQ Code...", }, { id: "status", label: "Status", options: rfqs.status.enumValues?.map((status) => { // 명시적으로 status를 허용된 리터럴 타입으로 변환 const s = status as "DRAFT" | "PUBLISHED" | "EVALUATION" | "AWARDED"; return { label: toSentenceCase(s), value: s, icon: getRFQStatusIcon(s), count: statusCounts[s], }; }), } ] /** * Advanced filter fields for the data table. */ const advancedFilterFields: DataTableAdvancedFilterField[] = [ { id: "rfqCode", label: "RFQ Code", type: "text", }, { id: "description", label: "Description", type: "text", }, { id: "projectCode", label: "Project Code", type: "text", }, { id: "dueDate", label: "Due Date", type: "date", }, { id: "status", label: "Status", type: "multi-select", options: rfqs.status.enumValues?.map((status) => { // 명시적으로 status를 허용된 리터럴 타입으로 변환 const s = status as "DRAFT" | "PUBLISHED" | "EVALUATION" | "AWARDED"; return { label: toSentenceCase(s), value: s, icon: getRFQStatusIcon(s), count: statusCounts[s], }; }), }, ] const { table } = useDataTable({ data, columns, pageCount, filterFields, enablePinning: true, enableAdvancedFilter: true, initialState: { sorting: [{ id: "createdAt", desc: true }], columnPinning: { right: ["actions"] }, }, getRowId: (originalRow) => String(originalRow.rfqId), shallow: false, clearOnDefault: true, }) return (
} > setRowAction(null)} rfq={rowAction?.row.original ?? null} /> setRowAction(null)} rfqs={rowAction?.row.original ? [rowAction?.row.original] : []} showTrigger={false} onSuccess={() => rowAction?.row.toggleSelected(false)} />
) }