144 lines
4.0 KiB
TypeScript
144 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { getSnRowNumber } from "@/shared/lib/row-number";
|
|
import { useMemo, type ReactNode } from "react";
|
|
import {
|
|
getCoreRowModel,
|
|
useReactTable,
|
|
type ColumnDef,
|
|
type PaginationState,
|
|
} from "@tanstack/react-table";
|
|
import {
|
|
StandardDataTable,
|
|
type ScrollMode,
|
|
type UiHeadProps,
|
|
} from "@common/business_components";
|
|
|
|
type ExmnStandardDataTableProps<T extends Record<string, unknown>> = {
|
|
rows: T[];
|
|
headlist: UiHeadProps[];
|
|
title: ReactNode;
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
onPageChange: (page: number) => void;
|
|
actions?: ReactNode;
|
|
loading?: boolean;
|
|
scrollMode?: ScrollMode;
|
|
minTableWidth?: string;
|
|
clickableHeadIds?: string[];
|
|
onCellClick?: (row: T, rowIndex: number, headId: string) => void;
|
|
onRowClick?: (row: T, rowIndex: number) => void;
|
|
rowId?: (row: T, rowIndex: number) => string;
|
|
};
|
|
|
|
const ROW_NUMBER_IDS = new Set(["no", "rowNo", "rowNumber"]);
|
|
const EXMN_TABLE_HEADER_HEIGHT_REM = 5.6;
|
|
const EXMN_TABLE_HEADER_CLASS = "[&_thead_th]:!h-[5.6rem]";
|
|
|
|
const formatValue = (value: unknown) => {
|
|
if (value === null || value === undefined || value === "") return "-";
|
|
return typeof value === "number"
|
|
? value.toLocaleString("ko-KR")
|
|
: String(value);
|
|
};
|
|
|
|
/** 조사관리 표를 CS 목록 규격과 페이지 연속 오름차순 순번으로 연결한다. */
|
|
export function ExmnStandardDataTable<T extends Record<string, unknown>>({
|
|
rows,
|
|
headlist,
|
|
title,
|
|
total,
|
|
page,
|
|
pageSize,
|
|
onPageChange,
|
|
actions,
|
|
loading = false,
|
|
scrollMode = "none",
|
|
minTableWidth = "100%",
|
|
clickableHeadIds = [],
|
|
onCellClick,
|
|
onRowClick,
|
|
rowId,
|
|
}: ExmnStandardDataTableProps<T>) {
|
|
const columns = useMemo<ColumnDef<T>[]>(
|
|
() =>
|
|
headlist.map((head) => ({
|
|
id: head.id,
|
|
accessorFn: (row) => row[head.id],
|
|
header: ROW_NUMBER_IDS.has(head.id) ? "순번" : head.title,
|
|
cell: ({ row, getValue }) => {
|
|
const value = ROW_NUMBER_IDS.has(head.id)
|
|
? getSnRowNumber({
|
|
total,
|
|
rowIndex: row.index,
|
|
pageIndex: page - 1,
|
|
pageSize,
|
|
})
|
|
: formatValue(getValue());
|
|
const clickable =
|
|
Boolean(onCellClick) && clickableHeadIds.includes(head.id);
|
|
return clickable ? (
|
|
<button
|
|
type="button"
|
|
className="hover:text-primary/80 w-full cursor-pointer text-primary underline underline-offset-4"
|
|
onClick={(event) => {
|
|
event.stopPropagation();
|
|
onCellClick?.(row.original, row.index, head.id);
|
|
}}
|
|
>
|
|
{value}
|
|
</button>
|
|
) : (
|
|
value
|
|
);
|
|
},
|
|
meta: {
|
|
align:
|
|
head.align === "left" || head.align === "right"
|
|
? head.align
|
|
: "center",
|
|
},
|
|
})),
|
|
[clickableHeadIds, headlist, onCellClick, page, pageSize, total],
|
|
);
|
|
const pagination: PaginationState = {
|
|
pageIndex: Math.max(page - 1, 0),
|
|
pageSize,
|
|
};
|
|
const table = useReactTable({
|
|
data: rows,
|
|
columns,
|
|
state: { pagination },
|
|
rowCount: total,
|
|
manualPagination: true,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getRowId: (row, index) => rowId?.(row, index) ?? String(index),
|
|
onPaginationChange: (updater) => {
|
|
const next =
|
|
typeof updater === "function" ? updater(pagination) : updater;
|
|
if (next.pageIndex !== pagination.pageIndex) {
|
|
onPageChange(next.pageIndex + 1);
|
|
}
|
|
},
|
|
});
|
|
|
|
return (
|
|
<StandardDataTable
|
|
table={table}
|
|
title={title}
|
|
actions={actions}
|
|
loading={loading}
|
|
className={EXMN_TABLE_HEADER_CLASS}
|
|
scrollMode={scrollMode}
|
|
minTableWidth={minTableWidth}
|
|
onRowClick={
|
|
onRowClick ? (row) => onRowClick(row, rows.indexOf(row)) : undefined
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export { EXMN_TABLE_HEADER_CLASS, EXMN_TABLE_HEADER_HEIGHT_REM };
|
|
export type { ExmnStandardDataTableProps };
|