FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

334 lines
15 KiB

  1. "use client";
  2. import React, { CSSProperties, DetailedHTMLProps, HTMLAttributes, useEffect, useState } from "react";
  3. import Paper from "@mui/material/Paper";
  4. import Table from "@mui/material/Table";
  5. import TableBody from "@mui/material/TableBody";
  6. import TableCell from "@mui/material/TableCell";
  7. import TableContainer from "@mui/material/TableContainer";
  8. import TableHead from "@mui/material/TableHead";
  9. import TablePagination from "@mui/material/TablePagination";
  10. import TableRow from "@mui/material/TableRow";
  11. import IconButton from "@mui/material/IconButton";
  12. import EditIcon from "@mui/icons-material/Edit";
  13. import SaveIcon from "@mui/icons-material/Save";
  14. import CancelIcon from "@mui/icons-material/Close";
  15. import DeleteIcon from "@mui/icons-material/Delete";
  16. import TextField from "@mui/material/TextField";
  17. import MultiSelect from "@/components/SearchBox/MultiSelect";
  18. import { Collapse, Typography } from "@mui/material";
  19. import BomMaterialTable from "./BomMaterialTable";
  20. import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
  21. import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
  22. import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
  23. import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
  24. import { useTranslation } from "react-i18next";
  25. import { ProdScheduleLineBomMaterialResult, ProdScheduleLineResultByFg, ProdScheduleResult, ScheduleType } from "@/app/api/scheduling";
  26. export interface ResultWithId {
  27. id: string | number;
  28. // id: number;
  29. }
  30. interface BaseColumn<T extends ResultWithId> {
  31. field: keyof T;
  32. label: string;
  33. type: string;
  34. options?: T[];
  35. renderCell?: (params: T) => React.ReactNode;
  36. style?: Partial<HTMLElement["style"]> & { [propName: string]: string } & CSSProperties;
  37. }
  38. interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> {
  39. onClick: (item: T) => void;
  40. buttonIcon: React.ReactNode;
  41. buttonColor?: "inherit" | "default" | "primary" | "secondary";
  42. }
  43. export type Column<T extends ResultWithId> =
  44. | BaseColumn<T>
  45. | ColumnWithAction<T>;
  46. interface Props<T extends ResultWithId> {
  47. index?: number,
  48. items: T[],
  49. columns: Column<T>[],
  50. noWrapper?: boolean,
  51. setPagingController: (value: { pageNum: number; pageSize: number; totalCount: number, index?: number }) => void,
  52. pagingController: { pageNum: number; pageSize: number; totalCount: number },
  53. isAutoPaging: boolean,
  54. isEdit: boolean,
  55. isEditable: boolean,
  56. hasCollapse: boolean,
  57. type: ScheduleType
  58. }
  59. function ScheduleTable<T extends ResultWithId>({
  60. type,
  61. index = 7,
  62. items,
  63. columns,
  64. noWrapper,
  65. pagingController,
  66. setPagingController,
  67. isAutoPaging = true,
  68. isEdit = false,
  69. isEditable = true,
  70. hasCollapse = false,
  71. }: Props<T>) {
  72. const [page, setPage] = useState(0);
  73. const [rowsPerPage, setRowsPerPage] = useState(10);
  74. const [editingRowId, setEditingRowId] = useState<number | null>(null);
  75. const [editedItems, setEditedItems] = useState<T[]>(items);
  76. const { t } = useTranslation("schedule");
  77. useEffect(() => {
  78. setEditedItems(items)
  79. }, [items])
  80. const handleChangePage = (_event: unknown, newPage: number) => {
  81. setPage(newPage);
  82. if (setPagingController) {
  83. setPagingController({ ...pagingController, pageNum: newPage + 1, index: (index ?? -1)});
  84. }
  85. };
  86. const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
  87. setRowsPerPage(+event.target.value);
  88. setPage(0);
  89. if (setPagingController) {
  90. setPagingController({ ...pagingController, pageSize: +event.target.value, pageNum: 1, index: index });
  91. }
  92. };
  93. const handleEditClick = (id: number) => {
  94. setEditingRowId(id);
  95. };
  96. const handleSaveClick = (item: T) => {
  97. setEditingRowId(null);
  98. // Call API or any save logic here
  99. setEditedItems((prev) =>
  100. prev.map((row) => (row.id === item.id ? { ...row } : row))
  101. );
  102. };
  103. const handleInputChange = (id: number, field: keyof T, value: string | number[]) => {
  104. setEditedItems((prev) =>
  105. prev.map((item) =>
  106. item.id === id ? { ...item, [field]: value } : item
  107. )
  108. );
  109. };
  110. const handleDeleteClick = (id: number) => {
  111. // Implement delete logic here
  112. setEditedItems((prev) => prev.filter(item => item.id !== id));
  113. };
  114. useEffect(() => {
  115. console.log("[debug] isEdit in table", isEdit)
  116. //TODO: switch all record to not in edit mode and save the changes
  117. if (!isEdit) {
  118. editedItems?.forEach(item => {
  119. // Call save logic here
  120. // console.log("Saving item:", item);
  121. // Reset editing state if needed
  122. });
  123. setEditingRowId(null);
  124. }
  125. }, [isEdit])
  126. function isRoughType(
  127. type: ScheduleType
  128. ): type is "rough" {
  129. return type === "rough";
  130. }
  131. function isDetailType(
  132. type: ScheduleType
  133. ): type is "detail" {
  134. return type === "detail";
  135. }
  136. function Row(props: { row: T }) {
  137. const { row } = props;
  138. const [open, setOpen] = useState(false);
  139. // console.log(row)
  140. return (
  141. <>
  142. <TableRow hover tabIndex={-1} key={row.id}>
  143. {isDetailType(type) && <TableCell>
  144. <IconButton disabled={!isEdit}>
  145. <PlayCircleOutlineIcon />
  146. </IconButton>
  147. </TableCell>}
  148. {
  149. (isEditable || hasCollapse) && <TableCell>
  150. {(editingRowId === row.id) ? (
  151. <>
  152. {
  153. isDetailType(type) && isEditable && <IconButton disabled={!isEdit} onClick={() => handleSaveClick(row)}>
  154. <SaveIcon />
  155. </IconButton>
  156. }
  157. {
  158. isDetailType(type) && isEditable && <IconButton disabled={!isEdit} onClick={() => setEditingRowId(null)}>
  159. <CancelIcon />
  160. </IconButton>
  161. }
  162. {
  163. hasCollapse && <IconButton
  164. aria-label="expand row"
  165. size="small"
  166. onClick={() => setOpen(!open)}
  167. >
  168. {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
  169. <Typography>{t("View BoM")}</Typography>
  170. </IconButton>
  171. }
  172. </>
  173. ) : (
  174. <>
  175. {
  176. isDetailType(type) && isEditable && <IconButton disabled={!isEdit}
  177. onClick={() => handleEditClick(row.id as number)}>
  178. <EditIcon />
  179. </IconButton>
  180. }
  181. {
  182. isDetailType(type) && isEditable && <IconButton disabled={!isEdit}
  183. onClick={() => handleDeleteClick(row.id as number)}>
  184. <DeleteIcon />
  185. </IconButton>
  186. }
  187. {
  188. hasCollapse && <IconButton
  189. aria-label="expand row"
  190. size="small"
  191. onClick={() => setOpen(!open)}
  192. >
  193. {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
  194. <Typography>{t("View BoM")}</Typography>
  195. </IconButton>
  196. }
  197. </>
  198. )}
  199. </TableCell>
  200. }
  201. {columns.map((column, idx) => {
  202. const columnName = column.field;
  203. return (
  204. <TableCell key={`${columnName.toString()}-${idx}`}>
  205. {editingRowId === row.id ? (
  206. (() => {
  207. switch (column.type) {
  208. case 'input':
  209. return (
  210. <TextField
  211. hiddenLabel={true}
  212. fullWidth
  213. defaultValue={row[columnName] as string}
  214. onChange={(e) => handleInputChange(row.id as number, columnName, e.target.value)}
  215. />
  216. );
  217. // case 'multi-select':
  218. // //TODO: May need update if use
  219. // return (
  220. // <MultiSelect
  221. // //label={column.label}
  222. // options={column.options ?? []}
  223. // selectedValues={[]}
  224. // onChange={(selectedValues) => handleInputChange(row.id as number, columnName, selectedValues)}
  225. // />
  226. // );
  227. case 'read-only':
  228. return (
  229. <span>
  230. {row[columnName] as string}
  231. </span>
  232. );
  233. default:
  234. return null; // Handle any default case if needed
  235. }
  236. })()
  237. ) : (
  238. column.renderCell ?
  239. <div style={column.style}>
  240. {column.renderCell(row)}
  241. </div>
  242. :
  243. <div style={column.style}>
  244. <span onDoubleClick={() => isEdit && handleEditClick(row.id as number)}>
  245. {row[columnName] as String}
  246. </span>
  247. </div>
  248. )}
  249. </TableCell>
  250. );
  251. })}
  252. </TableRow>
  253. <TableRow>
  254. {
  255. hasCollapse &&
  256. <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
  257. <Collapse in={open} timeout="auto" unmountOnExit>
  258. <Table>
  259. <TableBody>
  260. <TableRow>
  261. <TableCell>
  262. <BomMaterialTable
  263. type={type}
  264. bomMaterial={(row as unknown as ProdScheduleLineResultByFg).bomMaterials}
  265. />
  266. </TableCell>
  267. </TableRow>
  268. </TableBody>
  269. </Table>
  270. </Collapse>
  271. </TableCell>
  272. }
  273. </TableRow>
  274. </>
  275. )
  276. }
  277. const table = (
  278. <>
  279. <TableContainer sx={{ maxHeight: 440 }}>
  280. <Table stickyHeader>
  281. <TableHead>
  282. <TableRow>
  283. {isDetailType(type) && <TableCell>{t("Release")}</TableCell>}
  284. {(isEditable || hasCollapse) && <TableCell>{t("Actions")}</TableCell>} {/* Action Column Header */}
  285. {columns.map((column, idx) => (
  286. <TableCell style={column.style} key={`${column.field.toString()}${idx}`}>
  287. {column.label}
  288. </TableCell>
  289. ))}
  290. </TableRow>
  291. </TableHead>
  292. <TableBody>
  293. {/* {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( */}
  294. {(editedItems?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage))?.map((item) => (
  295. <Row key={item.id} row={item} />
  296. ))}
  297. </TableBody>
  298. </Table>
  299. </TableContainer>
  300. <TablePagination
  301. rowsPerPageOptions={[10, 25, 100]}
  302. component="div"
  303. // count={pagingController.totalCount === 0 ? editedItems.length : pagingController.totalCount}
  304. count={editedItems?.length ?? 0}
  305. rowsPerPage={rowsPerPage}
  306. page={page}
  307. onPageChange={handleChangePage}
  308. onRowsPerPageChange={handleChangeRowsPerPage}
  309. />
  310. </>
  311. );
  312. return noWrapper ? table : <Paper sx={{ overflow: "hidden" }}>{table}</Paper>;
  313. }
  314. export default ScheduleTable;