summaryrefslogtreecommitdiff
path: root/lib/poa/table
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-03-27 18:50:59 +0900
committerjoonhoekim <26rote@gmail.com>2025-03-27 18:50:59 +0900
commit281a4060cff0396253192f4e852be6770ad97cbd (patch)
treedc7f7d6e01469982638be02889fa1bb95fe60d84 /lib/poa/table
parent34bbeb86c1a8d24b5f526710889b5e54d699cfd0 (diff)
feat: POA 관련 파일 추가 및 contract 스키마 수정
Diffstat (limited to 'lib/poa/table')
-rw-r--r--lib/poa/table/poa-table-columns.tsx165
-rw-r--r--lib/poa/table/poa-table-toolbar-actions.tsx45
-rw-r--r--lib/poa/table/poa-table.tsx189
3 files changed, 399 insertions, 0 deletions
diff --git a/lib/poa/table/poa-table-columns.tsx b/lib/poa/table/poa-table-columns.tsx
new file mode 100644
index 00000000..b362e54c
--- /dev/null
+++ b/lib/poa/table/poa-table-columns.tsx
@@ -0,0 +1,165 @@
+"use client"
+
+import * as React from "react"
+import { type DataTableRowAction } from "@/types/table"
+import { type ColumnDef } from "@tanstack/react-table"
+import { InfoIcon, PenIcon } from "lucide-react"
+
+import { formatDate } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { POADetail } from "@/db/schema/contract"
+import { poaColumnsConfig } from "@/config/poaColumnsConfig"
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<POADetail> | null>>
+}
+
+/**
+ * tanstack table column definitions with nested headers
+ */
+export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<POADetail>[] {
+ // ----------------------------------------------------------------
+ // 1) actions column (buttons for item info)
+ // ----------------------------------------------------------------
+ const actionsColumn: ColumnDef<POADetail> = {
+ id: "actions",
+ enableHiding: false,
+ cell: function Cell({ row }) {
+ const hasSignature = row.original.hasSignature;
+
+ return (
+ <div className="flex items-center space-x-1">
+ {/* Item Info Button */}
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ size="icon"
+ onClick={() => setRowAction({ row, type: "items" })}
+ >
+ <InfoIcon className="h-4 w-4" aria-hidden="true" />
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ View Item Info
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+
+ {/* Signature Request Button - only show if no signature exists */}
+ {!hasSignature && (
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ size="icon"
+ onClick={() => setRowAction({ row, type: "signature" })}
+ >
+ <PenIcon className="h-4 w-4" aria-hidden="true" />
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ Request Electronic Signature
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ )}
+ </div>
+ );
+ },
+ size: 80,
+ };
+
+ // ----------------------------------------------------------------
+ // 2) Regular columns grouped by group name
+ // ----------------------------------------------------------------
+ // 2-1) groupMap: { [groupName]: ColumnDef<POADetail>[] }
+ const groupMap: Record<string, ColumnDef<POADetail>[]> = {};
+
+ poaColumnsConfig.forEach((cfg) => {
+ // Use "_noGroup" if no group is specified
+ const groupName = cfg.group || "_noGroup";
+
+ if (!groupMap[groupName]) {
+ groupMap[groupName] = [];
+ }
+
+ // Child column definition
+ const childCol: ColumnDef<POADetail> = {
+ accessorKey: cfg.id,
+ enableResizing: true,
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title={cfg.label} />
+ ),
+ meta: {
+ excelHeader: cfg.excelHeader,
+ group: cfg.group,
+ type: cfg.type,
+ },
+ cell: ({ cell }) => {
+ const value = cell.getValue();
+
+ if (cfg.type === "date") {
+ const dateVal = value as Date;
+ return (
+ <div className="text-sm">
+ {formatDate(dateVal)}
+ </div>
+ );
+ }
+ if (cfg.type === "number") {
+ const numVal = value as number;
+ return (
+ <div className="text-sm">
+ {numVal ? numVal.toLocaleString() : "-"}
+ </div>
+ );
+ }
+ return (
+ <div className="text-sm">
+ {value ?? "-"}
+ </div>
+ );
+ },
+ };
+
+ groupMap[groupName].push(childCol);
+ });
+
+ // ----------------------------------------------------------------
+ // 2-2) Create actual parent columns (groups) from the groupMap
+ // ----------------------------------------------------------------
+ const nestedColumns: ColumnDef<POADetail>[] = [];
+
+ // Order can be fixed by pre-defining group order or sorting
+ Object.entries(groupMap).forEach(([groupName, colDefs]) => {
+ if (groupName === "_noGroup") {
+ // No group → Add as top-level columns
+ nestedColumns.push(...colDefs);
+ } else {
+ // Parent column
+ nestedColumns.push({
+ id: groupName,
+ header: groupName,
+ columns: colDefs,
+ });
+ }
+ });
+
+ // ----------------------------------------------------------------
+ // 3) Final column array: nestedColumns + actionsColumn
+ // ----------------------------------------------------------------
+ return [
+ ...nestedColumns,
+ actionsColumn,
+ ];
+} \ No newline at end of file
diff --git a/lib/poa/table/poa-table-toolbar-actions.tsx b/lib/poa/table/poa-table-toolbar-actions.tsx
new file mode 100644
index 00000000..97a9cc55
--- /dev/null
+++ b/lib/poa/table/poa-table-toolbar-actions.tsx
@@ -0,0 +1,45 @@
+"use client"
+
+import * as React from "react"
+import { type Table } from "@tanstack/react-table"
+import { Download, RefreshCcw } from "lucide-react"
+
+import { exportTableToExcel } from "@/lib/export"
+import { Button } from "@/components/ui/button"
+import { POADetail } from "@/db/schema/contract"
+
+interface ItemsTableToolbarActionsProps {
+ table: Table<POADetail>
+}
+
+export function PoaTableToolbarActions({ table }: ItemsTableToolbarActionsProps) {
+ return (
+ <div className="flex items-center gap-2">
+ {/** Refresh 버튼 */}
+ <Button
+ variant="samsung"
+ size="sm"
+ className="gap-2"
+ >
+ <RefreshCcw className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">Get POAs</span>
+ </Button>
+
+ {/** Export 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() =>
+ exportTableToExcel(table, {
+ filename: "poa-list",
+ excludeColumns: ["select", "actions"],
+ })
+ }
+ className="gap-2"
+ >
+ <Download className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">Export</span>
+ </Button>
+ </div>
+ )
+} \ No newline at end of file
diff --git a/lib/poa/table/poa-table.tsx b/lib/poa/table/poa-table.tsx
new file mode 100644
index 00000000..a5cad02a
--- /dev/null
+++ b/lib/poa/table/poa-table.tsx
@@ -0,0 +1,189 @@
+"use client"
+
+import * as React from "react"
+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 { getChangeOrders } from "../service"
+import { POADetail } from "@/db/schema/contract"
+import { getColumns } from "./poa-table-columns"
+import { PoaTableToolbarActions } from "./poa-table-toolbar-actions"
+
+interface ItemsTableProps {
+ promises: Promise<
+ [
+ Awaited<ReturnType<typeof getChangeOrders>>,
+ ]
+ >
+}
+
+export function ChangeOrderListsTable({ promises }: ItemsTableProps) {
+ const [result] = React.use(promises)
+ const { data, pageCount } = result
+
+ const [rowAction, setRowAction] =
+ React.useState<DataTableRowAction<POADetail> | null>(null)
+
+ // Handle row actions
+ React.useEffect(() => {
+ if (!rowAction) return
+
+ if (rowAction.type === "items") {
+ // Handle items view action
+ setRowAction(null)
+ }
+ }, [rowAction])
+
+ const columns = React.useMemo(
+ () => getColumns({ setRowAction }),
+ [setRowAction]
+ )
+
+ const filterFields: DataTableFilterField<POADetail>[] = [
+ {
+ id: "contractNo",
+ label: "계약번호",
+ },
+ {
+ id: "originalContractName",
+ label: "계약명",
+ },
+ {
+ id: "approvalStatus",
+ label: "승인 상태",
+ },
+ ]
+
+ const advancedFilterFields: DataTableAdvancedFilterField<POADetail>[] = [
+ {
+ id: "contractNo",
+ label: "계약번호",
+ type: "text",
+ },
+ {
+ id: "originalContractName",
+ label: "계약명",
+ type: "text",
+ },
+ {
+ id: "projectId",
+ label: "프로젝트 ID",
+ type: "number",
+ },
+ {
+ id: "vendorId",
+ label: "벤더 ID",
+ type: "number",
+ },
+ {
+ id: "originalStatus",
+ label: "상태",
+ type: "text",
+ },
+ {
+ id: "deliveryTerms",
+ label: "납품조건",
+ type: "text",
+ },
+ {
+ id: "deliveryDate",
+ label: "납품기한",
+ type: "date",
+ },
+ {
+ id: "deliveryLocation",
+ label: "납품장소",
+ type: "text",
+ },
+ {
+ id: "currency",
+ label: "통화",
+ type: "text",
+ },
+ {
+ id: "totalAmount",
+ label: "총 금액",
+ type: "number",
+ },
+ {
+ id: "discount",
+ label: "할인",
+ type: "number",
+ },
+ {
+ id: "tax",
+ label: "세금",
+ type: "number",
+ },
+ {
+ id: "shippingFee",
+ label: "배송비",
+ type: "number",
+ },
+ {
+ id: "netTotal",
+ label: "최종 금액",
+ type: "number",
+ },
+ {
+ id: "changeReason",
+ label: "변경 사유",
+ type: "text",
+ },
+ {
+ id: "approvalStatus",
+ label: "승인 상태",
+ type: "text",
+ },
+ {
+ id: "createdAt",
+ label: "생성일",
+ type: "date",
+ },
+ {
+ id: "updatedAt",
+ 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) => String(originalRow.id),
+ shallow: false,
+ clearOnDefault: true,
+ })
+
+ return (
+ <>
+ <DataTable
+ table={table}
+ className="h-[calc(100vh-12rem)]"
+ >
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ >
+ <PoaTableToolbarActions table={table} />
+ </DataTableAdvancedToolbar>
+ </DataTable>
+ </>
+ )
+} \ No newline at end of file