|
- "use client";
-
- import React, { ChangeEvent, Dispatch, MouseEvent, SetStateAction, useCallback, useState } from "react";
- import Paper from "@mui/material/Paper";
- import Table from "@mui/material/Table";
- import TableBody from "@mui/material/TableBody";
- import TableCell, { TableCellProps } from "@mui/material/TableCell";
- import TableContainer from "@mui/material/TableContainer";
- import TableHead from "@mui/material/TableHead";
- import TablePagination, {
- TablePaginationProps,
- } from "@mui/material/TablePagination";
- import TableRow from "@mui/material/TableRow";
- import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton";
- import {
- ButtonOwnProps,
- Checkbox,
- Icon,
- IconOwnProps,
- SxProps,
- Theme,
- } from "@mui/material";
- import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
- import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
-
- export interface ResultWithId {
- id: string | number;
- }
-
- type ColumnType = "icon" | "decimal" | "integer" | "checkbox";
-
- interface BaseColumn<T extends ResultWithId> {
- name: keyof T;
- label: string;
- align?: TableCellProps["align"];
- headerAlign?: TableCellProps["align"];
- sx?: SxProps<Theme> | undefined;
- style?: Partial<HTMLElement["style"]> & { [propName: string]: string };
- type?: ColumnType;
- renderCell?: (params: T) => React.ReactNode;
- }
-
- interface IconColumn<T extends ResultWithId> extends BaseColumn<T> {
- name: keyof T;
- type: "icon";
- icon?: React.ReactNode;
- icons?: { [columnValue in keyof T]: React.ReactNode };
- color?: IconOwnProps["color"];
- colors?: { [columnValue in keyof T]: IconOwnProps["color"] };
- }
-
- interface DecimalColumn<T extends ResultWithId> extends BaseColumn<T> {
- type: "decimal";
- }
-
- interface IntegerColumn<T extends ResultWithId> extends BaseColumn<T> {
- type: "integer";
- }
-
- interface CheckboxColumn<T extends ResultWithId> extends BaseColumn<T> {
- type: "checkbox";
- disabled?: (params: T) => boolean;
- // checkboxIds: readonly (string | number)[],
- // setCheckboxIds: (ids: readonly (string | number)[]) => void
- }
-
- interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> {
- onClick: (item: T) => void;
- buttonIcon: React.ReactNode;
- buttonIcons: { [columnValue in keyof T]: React.ReactNode };
- buttonColor?: IconButtonOwnProps["color"];
- }
-
- export type Column<T extends ResultWithId> =
- | BaseColumn<T>
- | IconColumn<T>
- | DecimalColumn<T>
- | CheckboxColumn<T>
- | ColumnWithAction<T>;
-
- interface Props<T extends ResultWithId> {
- totalCount?: number;
- items: T[];
- columns: Column<T>[];
- noWrapper?: boolean;
- setPagingController?: Dispatch<SetStateAction<{
- pageNum: number;
- pageSize: number;
- }>>
- pagingController: { pageNum: number; pageSize: number; };
- isAutoPaging?: boolean;
- checkboxIds?: (string | number)[];
- setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>;
- }
-
- function isActionColumn<T extends ResultWithId>(
- column: Column<T>
- ): column is ColumnWithAction<T> {
- return Boolean((column as ColumnWithAction<T>).onClick);
- }
-
- function isIconColumn<T extends ResultWithId>(
- column: Column<T>
- ): column is IconColumn<T> {
- return column.type === "icon";
- }
-
- function isDecimalColumn<T extends ResultWithId>(
- column: Column<T>
- ): column is DecimalColumn<T> {
- return column.type === "decimal";
- }
-
- function isIntegerColumn<T extends ResultWithId>(
- column: Column<T>
- ): column is IntegerColumn<T> {
- return column.type === "integer";
- }
-
- function isCheckboxColumn<T extends ResultWithId>(
- column: Column<T>
- ): column is CheckboxColumn<T> {
- return column.type === "checkbox";
- }
-
- // Icon Component Functions
- function convertObjectKeysToLowercase<T extends object>(
- obj: T
- ): object | undefined {
- return obj
- ? Object.fromEntries(
- Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value])
- )
- : undefined;
- }
-
- function handleIconColors<T extends ResultWithId>(
- column: IconColumn<T>,
- value: T[keyof T]
- ): IconOwnProps["color"] {
- const colors = convertObjectKeysToLowercase(column.colors ?? {});
- const valueKey = String(value).toLowerCase() as keyof typeof colors;
-
- if (colors && valueKey in colors) {
- return colors[valueKey];
- }
-
- return column.color ?? "primary";
- }
-
- function handleIconIcons<T extends ResultWithId>(
- column: IconColumn<T>,
- value: T[keyof T]
- ): React.ReactNode {
- const icons = convertObjectKeysToLowercase(column.icons ?? {});
- const valueKey = String(value).toLowerCase() as keyof typeof icons;
-
- if (icons && valueKey in icons) {
- return icons[valueKey];
- }
-
- return column.icon ?? <CheckCircleOutlineIcon fontSize="small" />;
- }
- export const defaultPagingController: { pageNum: number; pageSize: number } = {
- "pageNum": 1,
- "pageSize": 10,
- }
- function SearchResults<T extends ResultWithId>({
- items,
- columns,
- noWrapper,
- pagingController,
- setPagingController,
- isAutoPaging = true,
- totalCount,
- checkboxIds = [],
- setCheckboxIds = undefined,
- }: Props<T>) {
- const [page, setPage] = React.useState(0);
- const [rowsPerPage, setRowsPerPage] = React.useState(10);
-
- /// this
- const handleChangePage: TablePaginationProps["onPageChange"] = (
- _event,
- newPage
- ) => {
- console.log(_event);
- setPage(newPage);
- if (setPagingController) {
- setPagingController({
- ...pagingController,
- pageNum: newPage + 1,
- });
- }
- };
-
- const handleChangeRowsPerPage: TablePaginationProps["onRowsPerPageChange"] = (
- event
- ) => {
- console.log(event);
- setRowsPerPage(+event.target.value);
- setPage(0);
- if (setPagingController) {
- setPagingController({
- ...pagingController,
- pageNum: 1,
- });
- }
- };
-
- // checkbox
- const handleRowClick = useCallback((event: MouseEvent<unknown>, item: T, columns: Column<T>[]) => {
- // check is disabled or not
- var disabled = false
- columns.forEach((col) => {
- if (isCheckboxColumn(col) && col.disabled) {
- disabled = col.disabled(item)
- if (disabled) {
- return;
- }
- }
- })
-
- if (disabled) {
- return;
- }
-
- // set id
- const id = item.id
- if (setCheckboxIds) {
- const selectedIndex = checkboxIds.indexOf(id);
- let newSelected: (string | number)[] = [];
-
- if (selectedIndex === -1) {
- newSelected = newSelected.concat(checkboxIds, id);
- } else if (selectedIndex === 0) {
- newSelected = newSelected.concat(checkboxIds.slice(1));
- } else if (selectedIndex === checkboxIds.length - 1) {
- newSelected = newSelected.concat(checkboxIds.slice(0, -1));
- } else if (selectedIndex > 0) {
- newSelected = newSelected.concat(
- checkboxIds.slice(0, selectedIndex),
- checkboxIds.slice(selectedIndex + 1),
- );
- }
- setCheckboxIds(newSelected);
- }
- }, [checkboxIds])
-
- const table = (
- <>
- <TableContainer sx={{ maxHeight: 440 }}>
- <Table stickyHeader>
- <TableHead>
- <TableRow>
- {columns.map((column, idx) => (
- <TableCell
- align={column.headerAlign}
- sx={column.sx}
- key={`${column.name.toString()}${idx}`}
- >
- {column.label}
- </TableCell>
- ))}
- </TableRow>
- </TableHead>
- <TableBody>
- {isAutoPaging
- ? items
- .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
- .map((item) => {
- return (
- <TableRow
- hover
- tabIndex={-1}
- key={item.id}
- onClick={setCheckboxIds? (event) => handleRowClick(event, item, columns) : undefined}
- role={setCheckboxIds ? "checkbox" : undefined}
- >
- {columns.map((column, idx) => {
- const columnName = column.name;
-
- return (
- <TabelCells
- key={`${columnName.toString()}-${idx}`}
- column={column}
- columnName={columnName}
- idx={idx}
- item={item}
- checkboxIds={checkboxIds}
- />
- );
- })}
- </TableRow>
- );
- })
- : items.map((item) => {
- return (
- <TableRow hover tabIndex={-1} key={item.id}>
- {columns.map((column, idx) => {
- const columnName = column.name;
-
- return (
- <TabelCells
- key={`${columnName.toString()}-${idx}`}
- column={column}
- columnName={columnName}
- idx={idx}
- item={item}
- checkboxIds={checkboxIds}
- />
- );
- })}
- </TableRow>
- );
- })}
- </TableBody>
- </Table>
- </TableContainer>
- <TablePagination
- rowsPerPageOptions={[10, 25, 100]}
- component="div"
- count={!totalCount || totalCount == 0
- ? items.length
- : totalCount
- }
- // count={
- // !pagingController || pagingController.totalCount == 0
- // ? items.length
- // : pagingController.totalCount
- // }
- rowsPerPage={rowsPerPage}
- page={page}
- onPageChange={handleChangePage}
- onRowsPerPageChange={handleChangeRowsPerPage}
- />
- </>
- );
-
- return noWrapper ? table : <Paper sx={{ overflow: "hidden" }}>{table}</Paper>;
- }
-
- // Table cells
- interface TableCellsProps<T extends ResultWithId> {
- column: Column<T>;
- columnName: keyof T;
- idx: number;
- item: T;
- checkboxIds: (string | number)[];
- }
-
- function TabelCells<T extends ResultWithId>({
- column,
- columnName,
- idx,
- item,
- checkboxIds = [],
- }: TableCellsProps<T>) {
- const isItemSelected = checkboxIds.includes(item.id);
-
- return (
- <TableCell
- align={column.align}
- sx={column.sx}
- key={`${columnName.toString()}-${idx}`}
- >
- {isActionColumn(column) ? (
- <IconButton
- color={column.buttonColor ?? "primary"}
- onClick={() => column.onClick(item)}
- >
- {column.buttonIcon}
- </IconButton>
- ) : isIconColumn(column) ? (
- <Icon color={handleIconColors(column, item[columnName])}>
- {handleIconIcons(column, item[columnName])}
- </Icon>
- ) : isDecimalColumn(column) ? (
- <>{decimalFormatter.format(Number(item[columnName]))}</>
- ) : isIntegerColumn(column) ? (
- <>{integerFormatter.format(Number(item[columnName]))}</>
- ) : isCheckboxColumn(column) ? (
- <Checkbox disabled={column.disabled ? column.disabled(item) : undefined} checked={isItemSelected} />
- ) : column.renderCell ? (
- column.renderCell(item)
- ) : (
- <>{item[columnName] as string}</>
- )}
- </TableCell>
- );
- }
-
- export default SearchResults;
|