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.
 
 
 

395 regels
17 KiB

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