FPSMS-frontend
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 

483 satır
14 KiB

  1. "use client";
  2. import React, {
  3. ChangeEvent,
  4. Dispatch,
  5. MouseEvent,
  6. SetStateAction,
  7. useCallback,
  8. useMemo,
  9. useState,
  10. } from "react";
  11. import { useTranslation } from "react-i18next";
  12. import Paper from "@mui/material/Paper";
  13. import Table from "@mui/material/Table";
  14. import TableBody from "@mui/material/TableBody";
  15. import TableCell, { TableCellProps } from "@mui/material/TableCell";
  16. import TableContainer from "@mui/material/TableContainer";
  17. import TableHead from "@mui/material/TableHead";
  18. import TablePagination, {
  19. TablePaginationProps,
  20. } from "@mui/material/TablePagination";
  21. import TableRow from "@mui/material/TableRow";
  22. import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton";
  23. import {
  24. ButtonOwnProps,
  25. Checkbox,
  26. Icon,
  27. IconOwnProps,
  28. SxProps,
  29. Theme,
  30. } from "@mui/material";
  31. import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
  32. import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
  33. import { filter, remove, uniq } from "lodash";
  34. export interface ResultWithId {
  35. id: string | number;
  36. }
  37. type ColumnType = "icon" | "decimal" | "integer" | "checkbox";
  38. interface BaseColumn<T extends ResultWithId> {
  39. name: keyof T;
  40. label: string;
  41. align?: TableCellProps["align"];
  42. headerAlign?: TableCellProps["align"];
  43. sx?: SxProps<Theme> | undefined;
  44. style?: Partial<HTMLElement["style"]> & { [propName: string]: string };
  45. type?: ColumnType;
  46. renderCell?: (params: T) => React.ReactNode;
  47. }
  48. interface IconColumn<T extends ResultWithId> extends BaseColumn<T> {
  49. name: keyof T;
  50. type: "icon";
  51. icon?: React.ReactNode;
  52. icons?: { [columnValue in keyof T]: React.ReactNode };
  53. color?: IconOwnProps["color"];
  54. colors?: { [columnValue in keyof T]: IconOwnProps["color"] };
  55. }
  56. interface DecimalColumn<T extends ResultWithId> extends BaseColumn<T> {
  57. type: "decimal";
  58. }
  59. interface IntegerColumn<T extends ResultWithId> extends BaseColumn<T> {
  60. type: "integer";
  61. }
  62. interface CheckboxColumn<T extends ResultWithId> extends BaseColumn<T> {
  63. type: "checkbox";
  64. disabled?: (params: T) => boolean;
  65. // checkboxIds: readonly (string | number)[],
  66. // setCheckboxIds: (ids: readonly (string | number)[]) => void
  67. }
  68. interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> {
  69. onClick: (item: T) => void;
  70. buttonIcon: React.ReactNode;
  71. buttonIcons: { [columnValue in keyof T]: React.ReactNode };
  72. buttonColor?: IconButtonOwnProps["color"];
  73. }
  74. export type Column<T extends ResultWithId> =
  75. | BaseColumn<T>
  76. | IconColumn<T>
  77. | DecimalColumn<T>
  78. | CheckboxColumn<T>
  79. | ColumnWithAction<T>;
  80. interface Props<T extends ResultWithId> {
  81. totalCount?: number;
  82. items: T[];
  83. columns: Column<T>[];
  84. noWrapper?: boolean;
  85. setPagingController?: Dispatch<
  86. SetStateAction<{
  87. pageNum: number;
  88. pageSize: number;
  89. }>
  90. >;
  91. pagingController?: { pageNum: number; pageSize: number };
  92. isAutoPaging?: boolean;
  93. checkboxIds?: (string | number)[];
  94. setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>;
  95. onRowClick?: (item: T) => void;
  96. }
  97. function isActionColumn<T extends ResultWithId>(
  98. column: Column<T>,
  99. ): column is ColumnWithAction<T> {
  100. return Boolean((column as ColumnWithAction<T>).onClick);
  101. }
  102. function isIconColumn<T extends ResultWithId>(
  103. column: Column<T>,
  104. ): column is IconColumn<T> {
  105. return column.type === "icon";
  106. }
  107. function isDecimalColumn<T extends ResultWithId>(
  108. column: Column<T>,
  109. ): column is DecimalColumn<T> {
  110. return column.type === "decimal";
  111. }
  112. function isIntegerColumn<T extends ResultWithId>(
  113. column: Column<T>,
  114. ): column is IntegerColumn<T> {
  115. return column.type === "integer";
  116. }
  117. function isCheckboxColumn<T extends ResultWithId>(
  118. column: Column<T>,
  119. ): column is CheckboxColumn<T> {
  120. return column.type === "checkbox";
  121. }
  122. // Icon Component Functions
  123. function convertObjectKeysToLowercase<T extends object>(
  124. obj: T,
  125. ): object | undefined {
  126. return obj
  127. ? Object.fromEntries(
  128. Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]),
  129. )
  130. : undefined;
  131. }
  132. function handleIconColors<T extends ResultWithId>(
  133. column: IconColumn<T>,
  134. value: T[keyof T],
  135. ): IconOwnProps["color"] {
  136. const colors = convertObjectKeysToLowercase(column.colors ?? {});
  137. const valueKey = String(value).toLowerCase() as keyof typeof colors;
  138. if (colors && valueKey in colors) {
  139. return colors[valueKey];
  140. }
  141. return column.color ?? "primary";
  142. }
  143. function handleIconIcons<T extends ResultWithId>(
  144. column: IconColumn<T>,
  145. value: T[keyof T],
  146. ): React.ReactNode {
  147. const icons = convertObjectKeysToLowercase(column.icons ?? {});
  148. const valueKey = String(value).toLowerCase() as keyof typeof icons;
  149. if (icons && valueKey in icons) {
  150. return icons[valueKey];
  151. }
  152. return column.icon ?? <CheckCircleOutlineIcon fontSize="small" />;
  153. }
  154. export const defaultPagingController: { pageNum: number; pageSize: number } = {
  155. pageNum: 1,
  156. pageSize: 10,
  157. };
  158. export type defaultSetPagingController = Dispatch<
  159. SetStateAction<{
  160. pageNum: number;
  161. pageSize: number;
  162. }>
  163. >
  164. function SearchResults<T extends ResultWithId>({
  165. items,
  166. columns,
  167. noWrapper,
  168. pagingController,
  169. setPagingController,
  170. isAutoPaging = true,
  171. totalCount,
  172. checkboxIds = [],
  173. setCheckboxIds = undefined,
  174. onRowClick = undefined,
  175. }: Props<T>) {
  176. const { t } = useTranslation("dashboard");
  177. const [page, setPage] = React.useState(0);
  178. const [rowsPerPage, setRowsPerPage] = React.useState(10);
  179. /// this
  180. const handleChangePage: TablePaginationProps["onPageChange"] = (
  181. _event,
  182. newPage,
  183. ) => {
  184. console.log(_event);
  185. setPage(newPage);
  186. if (setPagingController) {
  187. setPagingController({
  188. ...(pagingController ?? defaultPagingController),
  189. pageNum: newPage + 1,
  190. });
  191. }
  192. };
  193. const handleChangeRowsPerPage: TablePaginationProps["onRowsPerPageChange"] = (
  194. event,
  195. ) => {
  196. console.log(event);
  197. const newSize = +event.target.value;
  198. setRowsPerPage(newSize);
  199. setPage(0);
  200. if (setPagingController) {
  201. setPagingController({
  202. ...(pagingController ?? defaultPagingController),
  203. pageNum: 1,
  204. pageSize: newSize,
  205. });
  206. }
  207. };
  208. // checkbox
  209. const currItems = useMemo(() => {
  210. return items.length > 10 ? items
  211. .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
  212. .map((i) => i.id)
  213. : items.map((i) => i.id)
  214. }, [items, page, rowsPerPage])
  215. const currItemsWithChecked = useMemo(() => {
  216. return filter(checkboxIds, function (c) {
  217. return currItems.includes(c);
  218. })
  219. }, [checkboxIds, items, page, rowsPerPage])
  220. const handleRowClick = useCallback(
  221. (event: MouseEvent<unknown>, item: T, columns: Column<T>[]) => {
  222. // check is disabled or not
  223. let disabled = false;
  224. columns.forEach((col) => {
  225. if (isCheckboxColumn(col) && col.disabled) {
  226. disabled = col.disabled(item);
  227. if (disabled) {
  228. return;
  229. }
  230. }
  231. });
  232. if (disabled) {
  233. return;
  234. }
  235. // set id
  236. const id = item.id;
  237. if (setCheckboxIds) {
  238. const selectedIndex = checkboxIds.indexOf(id);
  239. let newSelected: (string | number)[] = [];
  240. if (selectedIndex === -1) {
  241. newSelected = newSelected.concat(checkboxIds, id);
  242. } else if (selectedIndex === 0) {
  243. newSelected = newSelected.concat(checkboxIds.slice(1));
  244. } else if (selectedIndex === checkboxIds.length - 1) {
  245. newSelected = newSelected.concat(checkboxIds.slice(0, -1));
  246. } else if (selectedIndex > 0) {
  247. newSelected = newSelected.concat(
  248. checkboxIds.slice(0, selectedIndex),
  249. checkboxIds.slice(selectedIndex + 1),
  250. );
  251. }
  252. setCheckboxIds(newSelected);
  253. }
  254. },
  255. [checkboxIds, setCheckboxIds],
  256. );
  257. const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
  258. if (setCheckboxIds) {
  259. const pageItemId = currItems
  260. if (event.target.checked) {
  261. setCheckboxIds((prev) => uniq([...prev, ...pageItemId]))
  262. } else {
  263. setCheckboxIds((prev) => filter(prev, function (p) { return !pageItemId.includes(p); }))
  264. }
  265. }
  266. }
  267. const table = (
  268. <>
  269. <TableContainer sx={{ maxHeight: 440 }}>
  270. <Table stickyHeader>
  271. <TableHead>
  272. <TableRow>
  273. {columns.map((column, idx) => (
  274. isCheckboxColumn(column) ?
  275. <TableCell
  276. align={column.headerAlign}
  277. sx={column.sx}
  278. key={`${column.name.toString()}${idx}`}
  279. >
  280. <Checkbox
  281. color="primary"
  282. indeterminate={currItemsWithChecked.length > 0 && currItemsWithChecked.length < currItems.length}
  283. checked={currItems.length > 0 && currItemsWithChecked.length >= currItems.length}
  284. onChange={handleSelectAllClick}
  285. />
  286. </TableCell>
  287. : <TableCell
  288. align={column.headerAlign}
  289. sx={column.sx}
  290. key={`${column.name.toString()}${idx}`}
  291. >
  292. {column.label.split('\n').map((line, index) => (
  293. <div key={index}>{line}</div> // Render each line in a div
  294. ))}
  295. </TableCell>
  296. ))}
  297. </TableRow>
  298. </TableHead>
  299. <TableBody>
  300. {isAutoPaging
  301. ? items
  302. .slice((pagingController?.pageNum ? pagingController?.pageNum - 1 :page) * (pagingController?.pageSize ?? rowsPerPage),
  303. (pagingController?.pageNum ? pagingController?.pageNum - 1 :page) * (pagingController?.pageSize ?? rowsPerPage) + (pagingController?.pageSize ?? rowsPerPage))
  304. .map((item) => {
  305. return (
  306. <TableRow
  307. hover
  308. tabIndex={-1}
  309. key={item.id}
  310. onClick={(event) => {
  311. setCheckboxIds
  312. ? handleRowClick(event, item, columns)
  313. : undefined
  314. if (onRowClick) {
  315. onRowClick(item)
  316. }
  317. }
  318. }
  319. role={setCheckboxIds ? "checkbox" : undefined}
  320. >
  321. {columns.map((column, idx) => {
  322. const columnName = column.name;
  323. return (
  324. <TabelCells
  325. key={`${columnName.toString()}-${idx}`}
  326. column={column}
  327. columnName={columnName}
  328. idx={idx}
  329. item={item}
  330. checkboxIds={checkboxIds}
  331. />
  332. );
  333. })}
  334. </TableRow>
  335. );
  336. })
  337. : items.map((item) => {
  338. return (
  339. <TableRow hover tabIndex={-1} key={item.id}
  340. onClick={(event) => {
  341. setCheckboxIds
  342. ? handleRowClick(event, item, columns)
  343. : undefined
  344. if (onRowClick) {
  345. onRowClick(item)
  346. }
  347. }
  348. }
  349. role={setCheckboxIds ? "checkbox" : undefined}
  350. >
  351. {columns.map((column, idx) => {
  352. const columnName = column.name;
  353. return (
  354. <TabelCells
  355. key={`${columnName.toString()}-${idx}`}
  356. column={column}
  357. columnName={columnName}
  358. idx={idx}
  359. item={item}
  360. checkboxIds={checkboxIds}
  361. />
  362. );
  363. })}
  364. </TableRow>
  365. );
  366. })}
  367. </TableBody>
  368. </Table>
  369. </TableContainer>
  370. <TablePagination
  371. rowsPerPageOptions={[10, 25, 100]}
  372. component="div"
  373. count={!totalCount || totalCount == 0 ? items.length : totalCount}
  374. rowsPerPage={pagingController?.pageSize ? pagingController?.pageSize : rowsPerPage}
  375. page={pagingController?.pageNum ? pagingController?.pageNum - 1 : page}
  376. onPageChange={handleChangePage}
  377. onRowsPerPageChange={handleChangeRowsPerPage}
  378. labelRowsPerPage={t("Rows per page")}
  379. labelDisplayedRows={({ from, to, count }) =>
  380. `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
  381. }
  382. />
  383. </>
  384. );
  385. return noWrapper ? table : <Paper variant="outlined" sx={{ overflow: "hidden" }}>{table}</Paper>;
  386. }
  387. // Table cells
  388. interface TableCellsProps<T extends ResultWithId> {
  389. column: Column<T>;
  390. columnName: keyof T;
  391. idx: number;
  392. item: T;
  393. checkboxIds: (string | number)[];
  394. }
  395. function TabelCells<T extends ResultWithId>({
  396. column,
  397. columnName,
  398. idx,
  399. item,
  400. checkboxIds = [],
  401. }: TableCellsProps<T>) {
  402. const isItemSelected = checkboxIds.includes(item.id);
  403. return (
  404. <TableCell
  405. align={column.align}
  406. sx={column.sx}
  407. key={`${columnName.toString()}-${idx}`}
  408. >
  409. {isActionColumn(column) ? (
  410. <IconButton
  411. color={column.buttonColor ?? "primary"}
  412. onClick={() => column.onClick(item)}
  413. >
  414. {column.buttonIcon}
  415. </IconButton>
  416. ) : isIconColumn(column) ? (
  417. <Icon color={handleIconColors(column, item[columnName])}>
  418. {handleIconIcons(column, item[columnName])}
  419. </Icon>
  420. ) : isDecimalColumn(column) ? (
  421. <>{decimalFormatter.format(Number(item[columnName]))}</>
  422. ) : isIntegerColumn(column) ? (
  423. <>{integerFormatter.format(Number(item[columnName]))}</>
  424. ) : isCheckboxColumn(column) ? (
  425. <Checkbox
  426. disabled={column.disabled ? column.disabled(item) : undefined}
  427. checked={isItemSelected}
  428. />
  429. ) : column.renderCell ? (
  430. column.renderCell(item)
  431. ) : (
  432. <>{item[columnName] as string}</>
  433. )}
  434. </TableCell>
  435. );
  436. }
  437. export default SearchResults;