Files
frontend/apps/sn/src/features/exmn/ui/ExmnStandardDataTable.tsx
T

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 };