|
|
@@ -1,6 +1,6 @@ |
|
|
|
"use client"; |
|
|
|
|
|
|
|
import React from "react"; |
|
|
|
import React, { Dispatch, SetStateAction } from "react"; |
|
|
|
import Paper from "@mui/material/Paper"; |
|
|
|
import Table from "@mui/material/Table"; |
|
|
|
import TableBody from "@mui/material/TableBody"; |
|
|
@@ -8,279 +8,314 @@ import TableCell, { TableCellProps } from "@mui/material/TableCell"; |
|
|
|
import TableContainer from "@mui/material/TableContainer"; |
|
|
|
import TableHead from "@mui/material/TableHead"; |
|
|
|
import TablePagination, { |
|
|
|
TablePaginationProps, |
|
|
|
TablePaginationProps, |
|
|
|
} from "@mui/material/TablePagination"; |
|
|
|
import TableRow from "@mui/material/TableRow"; |
|
|
|
import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton"; |
|
|
|
import { ButtonOwnProps, Icon, IconOwnProps, SxProps, Theme } from "@mui/material"; |
|
|
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; |
|
|
|
import { |
|
|
|
ButtonOwnProps, |
|
|
|
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; |
|
|
|
id: string | number; |
|
|
|
} |
|
|
|
|
|
|
|
type ColumnType = "icon" | "decimal" | "integer"; |
|
|
|
|
|
|
|
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; |
|
|
|
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"] }; |
|
|
|
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"; |
|
|
|
type: "decimal"; |
|
|
|
} |
|
|
|
|
|
|
|
interface IntegerColumn<T extends ResultWithId> extends BaseColumn<T> { |
|
|
|
type: "integer"; |
|
|
|
type: "integer"; |
|
|
|
} |
|
|
|
|
|
|
|
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"]; |
|
|
|
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> |
|
|
|
| ColumnWithAction<T>; |
|
|
|
| BaseColumn<T> |
|
|
|
| IconColumn<T> |
|
|
|
| DecimalColumn<T> |
|
|
|
| ColumnWithAction<T>; |
|
|
|
|
|
|
|
interface Props<T extends ResultWithId> { |
|
|
|
items: T[], |
|
|
|
columns: Column<T>[], |
|
|
|
noWrapper?: boolean, |
|
|
|
setPagingController?: (value: (((prevState: { pageNum: number; pageSize: number; totalCount: number }) => { |
|
|
|
pageNum: number; |
|
|
|
pageSize: number; |
|
|
|
totalCount: number |
|
|
|
}) | { pageNum: number; pageSize: number; totalCount: number })) => void, |
|
|
|
pagingController: { pageNum: number; pageSize: number; totalCount: number }, |
|
|
|
isAutoPaging?: boolean |
|
|
|
totalCount?: number; |
|
|
|
items: T[]; |
|
|
|
columns: Column<T>[]; |
|
|
|
noWrapper?: boolean; |
|
|
|
setPagingController?: Dispatch<SetStateAction<{ |
|
|
|
pageNum: number; |
|
|
|
pageSize: number; |
|
|
|
}>> |
|
|
|
pagingController: { pageNum: number; pageSize: number;}; |
|
|
|
isAutoPaging?: boolean; |
|
|
|
} |
|
|
|
|
|
|
|
function isActionColumn<T extends ResultWithId>( |
|
|
|
column: Column<T>, |
|
|
|
column: Column<T> |
|
|
|
): column is ColumnWithAction<T> { |
|
|
|
return Boolean((column as ColumnWithAction<T>).onClick); |
|
|
|
return Boolean((column as ColumnWithAction<T>).onClick); |
|
|
|
} |
|
|
|
|
|
|
|
function isIconColumn<T extends ResultWithId>( |
|
|
|
column: Column<T>, |
|
|
|
column: Column<T> |
|
|
|
): column is IconColumn<T> { |
|
|
|
return column.type === "icon"; |
|
|
|
return column.type === "icon"; |
|
|
|
} |
|
|
|
|
|
|
|
function isDecimalColumn<T extends ResultWithId>( |
|
|
|
column: Column<T>, |
|
|
|
column: Column<T> |
|
|
|
): column is DecimalColumn<T> { |
|
|
|
return column.type === "decimal"; |
|
|
|
return column.type === "decimal"; |
|
|
|
} |
|
|
|
|
|
|
|
function isIntegerColumn<T extends ResultWithId>( |
|
|
|
column: Column<T>, |
|
|
|
column: Column<T> |
|
|
|
): column is IntegerColumn<T> { |
|
|
|
return column.type === "integer"; |
|
|
|
return column.type === "integer"; |
|
|
|
} |
|
|
|
|
|
|
|
// Icon Component Functions |
|
|
|
function convertObjectKeysToLowercase<T extends object>(obj: T): object | undefined { |
|
|
|
return obj ? Object.fromEntries( |
|
|
|
function convertObjectKeysToLowercase<T extends object>( |
|
|
|
obj: T |
|
|
|
): object | undefined { |
|
|
|
return obj |
|
|
|
? Object.fromEntries( |
|
|
|
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]) |
|
|
|
) : undefined; |
|
|
|
) |
|
|
|
: undefined; |
|
|
|
} |
|
|
|
|
|
|
|
function handleIconColors<T extends ResultWithId>( |
|
|
|
column: IconColumn<T>, |
|
|
|
value: T[keyof T], |
|
|
|
column: IconColumn<T>, |
|
|
|
value: T[keyof T] |
|
|
|
): IconOwnProps["color"] { |
|
|
|
const colors = convertObjectKeysToLowercase(column.colors ?? {}); |
|
|
|
const valueKey = String(value).toLowerCase() as keyof typeof colors; |
|
|
|
const colors = convertObjectKeysToLowercase(column.colors ?? {}); |
|
|
|
const valueKey = String(value).toLowerCase() as keyof typeof colors; |
|
|
|
|
|
|
|
if (colors && valueKey in colors) { |
|
|
|
return colors[valueKey]; |
|
|
|
} |
|
|
|
if (colors && valueKey in colors) { |
|
|
|
return colors[valueKey]; |
|
|
|
} |
|
|
|
|
|
|
|
return column.color ?? "primary"; |
|
|
|
}; |
|
|
|
return column.color ?? "primary"; |
|
|
|
} |
|
|
|
|
|
|
|
function handleIconIcons<T extends ResultWithId>( |
|
|
|
column: IconColumn<T>, |
|
|
|
value: T[keyof T], |
|
|
|
column: IconColumn<T>, |
|
|
|
value: T[keyof T] |
|
|
|
): React.ReactNode { |
|
|
|
const icons = convertObjectKeysToLowercase(column.icons ?? {}); |
|
|
|
const valueKey = String(value).toLowerCase() as keyof typeof icons; |
|
|
|
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" />; |
|
|
|
}; |
|
|
|
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, |
|
|
|
items, |
|
|
|
columns, |
|
|
|
noWrapper, |
|
|
|
pagingController, |
|
|
|
setPagingController, |
|
|
|
isAutoPaging = true, |
|
|
|
totalCount |
|
|
|
}: Props<T>) { |
|
|
|
const [page, setPage] = React.useState(0); |
|
|
|
const [rowsPerPage, setRowsPerPage] = React.useState(10); |
|
|
|
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, |
|
|
|
}) |
|
|
|
} |
|
|
|
/// 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: +event.target.value, |
|
|
|
}) |
|
|
|
} |
|
|
|
}; |
|
|
|
const handleChangeRowsPerPage: TablePaginationProps["onRowsPerPageChange"] = ( |
|
|
|
event |
|
|
|
) => { |
|
|
|
console.log(event); |
|
|
|
setRowsPerPage(+event.target.value); |
|
|
|
setPage(0); |
|
|
|
if (setPagingController) { |
|
|
|
setPagingController({ |
|
|
|
...pagingController, |
|
|
|
pageNum: +event.target.value, |
|
|
|
}); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
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}> |
|
|
|
{columns.map((column, idx) => { |
|
|
|
const columnName = column.name; |
|
|
|
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}> |
|
|
|
{columns.map((column, idx) => { |
|
|
|
const columnName = column.name; |
|
|
|
|
|
|
|
return ( |
|
|
|
<TabelCells key={`${columnName.toString()}-${idx}`} column={column} columnName={columnName} idx={idx} item={item}/> |
|
|
|
); |
|
|
|
})} |
|
|
|
</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} |
|
|
|
/> |
|
|
|
); |
|
|
|
})} |
|
|
|
</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}/> |
|
|
|
return ( |
|
|
|
<TabelCells |
|
|
|
key={`${columnName.toString()}-${idx}`} |
|
|
|
column={column} |
|
|
|
columnName={columnName} |
|
|
|
idx={idx} |
|
|
|
item={item} |
|
|
|
/> |
|
|
|
); |
|
|
|
})} |
|
|
|
</TableRow> |
|
|
|
); |
|
|
|
}) |
|
|
|
} |
|
|
|
</TableBody> |
|
|
|
</Table> |
|
|
|
</TableContainer> |
|
|
|
<TablePagination |
|
|
|
rowsPerPageOptions={[10, 25, 100]} |
|
|
|
component="div" |
|
|
|
count={!pagingController || pagingController.totalCount == 0 ? items.length : pagingController.totalCount} |
|
|
|
rowsPerPage={rowsPerPage} |
|
|
|
page={page} |
|
|
|
onPageChange={handleChangePage} |
|
|
|
onRowsPerPageChange={handleChangeRowsPerPage} |
|
|
|
/> |
|
|
|
</> |
|
|
|
); |
|
|
|
})} |
|
|
|
</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>; |
|
|
|
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, |
|
|
|
column: Column<T>; |
|
|
|
columnName: keyof T; |
|
|
|
idx: number; |
|
|
|
item: T; |
|
|
|
} |
|
|
|
|
|
|
|
function TabelCells<T extends ResultWithId>({ |
|
|
|
column, |
|
|
|
columnName, |
|
|
|
idx, |
|
|
|
item |
|
|
|
column, |
|
|
|
columnName, |
|
|
|
idx, |
|
|
|
item, |
|
|
|
}: TableCellsProps<T>) { |
|
|
|
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]))}</> |
|
|
|
) : |
|
|
|
( |
|
|
|
column.renderCell ? column.renderCell(item) : <>{item[columnName] as string}</> |
|
|
|
)} |
|
|
|
</TableCell>) |
|
|
|
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]))}</> |
|
|
|
) : column.renderCell ? ( |
|
|
|
column.renderCell(item) |
|
|
|
) : ( |
|
|
|
<>{item[columnName] as string}</> |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
export default SearchResults; |