Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 

364 lignes
12 KiB

  1. import { Box, Button, Modal, ModalProps, Paper, SxProps, Tooltip, Typography } from "@mui/material"
  2. import StyledDataGrid from "../StyledDataGrid"
  3. import { useTranslation } from "react-i18next";
  4. import { useFormContext } from "react-hook-form";
  5. import { useCallback, useEffect, useMemo, useState } from "react";
  6. import { GridRenderEditCellParams, FooterPropsOverrides, GridActionsCellItem, GridCellParams, GridEventListener, GridRowEditStopReasons, GridRowId, GridRowIdGetter, GridRowModel, GridRowModes, GridRowModesModel, GridToolbarContainer, useGridApiRef, GridEditDateCell, GridEditSingleSelectCell } from "@mui/x-data-grid";
  7. import AddIcon from '@mui/icons-material/Add';
  8. import SaveIcon from '@mui/icons-material/Save';
  9. import DeleteIcon from '@mui/icons-material/Delete';
  10. import CancelIcon from '@mui/icons-material/Cancel';
  11. import EditIcon from '@mui/icons-material/Edit';
  12. import waitForCondition from "../utils/waitFor";
  13. import { gradeHistory } from "@/app/api/staff/actions";
  14. import { Add } from "@mui/icons-material";
  15. import { comboItem } from "../CreateStaff/CreateStaff";
  16. import { StaffEntryError, validateRowAndRowBefore } from "./validateDates";
  17. import { ProcessRowUpdateError } from "./TeamHistoryModal";
  18. interface Props {
  19. open: boolean;
  20. onClose: () => void;
  21. combos: comboItem;
  22. // columns: any[]
  23. }
  24. const modalSx: SxProps = {
  25. position: "absolute",
  26. top: "50%",
  27. left: "50%",
  28. transform: "translate(-50%, -50%)",
  29. width: "90%",
  30. maxWidth: "auto",
  31. maxHeight: "auto",
  32. padding: 3,
  33. display: "flex",
  34. flexDirection: "column",
  35. gap: 2,
  36. };
  37. export type GradeModalRow = Partial<
  38. gradeHistory & {
  39. _isNew: boolean
  40. _error: StaffEntryError;
  41. }>
  42. const thisField = "gradeHistory"
  43. const GradeHistoryModal: React.FC<Props> = ({ open, onClose, combos }) => {
  44. const {
  45. t,
  46. // i18n: { language },
  47. } = useTranslation();
  48. const { setValue, getValues } = useFormContext();
  49. const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
  50. const apiRef = useGridApiRef()
  51. const originalRows = getValues(thisField)
  52. const [_rows, setRows] = useState(() => {
  53. const list: GradeModalRow[] = getValues(thisField)
  54. return list && list.length > 0 ? list : []
  55. });
  56. const [_delRows, setDelRows] = useState<number[]>([]);
  57. const getRowId = useCallback<GridRowIdGetter<GradeModalRow>>(
  58. (row) => row.id!!,
  59. [],
  60. );
  61. const handleSave = useCallback(
  62. (id: GridRowId) => () => {
  63. setRowModesModel((prevRowModesModel) => ({
  64. ...prevRowModesModel,
  65. [id]: { mode: GridRowModes.View }
  66. }));
  67. },
  68. [setRowModesModel]
  69. );
  70. const onCancel = useCallback(() => {
  71. setRows(originalRows)
  72. onClose();
  73. }, [onClose, originalRows]);
  74. const handleClose = useCallback<NonNullable<ModalProps["onClose"]>>(
  75. (_, reason) => {
  76. if (reason !== "backdropClick") {
  77. onClose();
  78. }
  79. }, [onClose]);
  80. const isSaved = useCallback(() => {
  81. const saved = Object.keys(rowModesModel).every(key => {
  82. rowModesModel[key].mode === GridRowModes.Edit
  83. })
  84. return saved
  85. }, [rowModesModel])
  86. const doSave = useCallback(async () => {
  87. try {
  88. if (isSaved()) {
  89. setValue(thisField, _rows)
  90. onClose()
  91. }
  92. } catch (error) {
  93. console.error(error);
  94. }
  95. }, [isSaved, onClose, _rows])
  96. const addRow = useCallback(() => {
  97. const id = Date.now()
  98. const newEntry = { id, _isNew: true } satisfies GradeModalRow;
  99. setRows((prev) => [...prev, newEntry])
  100. setRowModesModel((model) => ({
  101. ...model,
  102. [getRowId(newEntry)]: {
  103. mode: GridRowModes.Edit,
  104. fieldToFocus: "grade",
  105. }
  106. }))
  107. }, []);
  108. const onProcessRowUpdateError = useCallback(
  109. (updateError: ProcessRowUpdateError<GradeModalRow>) => {
  110. const errors = updateError.errors;
  111. // const prevRow = updateError.prevRow;
  112. const currRow = updateError.currRow;
  113. // if (updateError.prevRow) {
  114. // apiRef.current.updateRows([{ ...prevRow, _error: errors }]);
  115. // }
  116. apiRef.current.updateRows([{ ...currRow, _error: errors }]);
  117. },
  118. [apiRef, rowModesModel],
  119. );
  120. const processRowUpdate = useCallback((
  121. newRow: GridRowModel<GradeModalRow>,
  122. originalRow: GridRowModel<GradeModalRow>
  123. ) => {
  124. const rowIndex = _rows.findIndex((row: GradeModalRow) => row.id === newRow.id);
  125. const prevRow: GradeModalRow | null = rowIndex > 0 ? _rows[rowIndex - 1] : null;
  126. const errors = validateRowAndRowBefore(prevRow, newRow)
  127. console.log(errors)
  128. if (errors) {
  129. throw new ProcessRowUpdateError(
  130. prevRow,
  131. newRow,
  132. "validation error",
  133. errors
  134. )
  135. }
  136. const { _isNew, _error, ...updatedRow } = newRow;
  137. const rowToSave = {
  138. ...updatedRow,
  139. }
  140. console.log(_rows)
  141. if (_rows.length != 0) {
  142. setRows((prev: any[]) => prev?.map((row: any) => (row.id === newRow.id ? rowToSave : row)).sort((a, b) => {
  143. if (!a.from || !b.from) return 0;
  144. return new Date(a.from).getTime() - new Date(b.from).getTime();
  145. }));
  146. }
  147. return rowToSave;
  148. }
  149. , [_rows, validateRowAndRowBefore])
  150. const handleCancel = useCallback(
  151. (id: any) => () => {
  152. setRowModesModel((prevRowModesModel) => ({
  153. ...prevRowModesModel,
  154. [id]: { mode: GridRowModes.View, ignoreModifications: true }
  155. }));
  156. const editedRow = _rows.find((r) => getRowId(r) === id)
  157. if (editedRow?._isNew) {
  158. setRows((rw) => rw.filter((r) => r.id !== id))
  159. } else {
  160. setRows((rw) =>
  161. rw.map((r) =>
  162. getRowId(r) === id
  163. ? { ...r, _error: undefined }
  164. : r,
  165. ),
  166. );
  167. }
  168. },
  169. [setRowModesModel, _rows]
  170. );
  171. const handleDelete = useCallback(
  172. (id: GridRowId) => () => {
  173. setRows((prevRows) => prevRows.filter((row) => row.id !== id));
  174. setDelRows((prevRowsId: number[]) => [...prevRowsId, id as number])
  175. },
  176. []
  177. );
  178. useEffect(()=> {
  179. console.log(_rows)
  180. // setValue(thisField, _rows)
  181. setValue('delGradeHistory', _delRows)
  182. }, [_rows, _delRows])
  183. const footer = (
  184. <Box display="flex" gap={2} alignItems="center">
  185. <Button
  186. disableRipple
  187. variant="outlined"
  188. startIcon={<Add />}
  189. onClick={addRow}
  190. size="small"
  191. >
  192. {t("Add Record")}
  193. </Button>
  194. </Box>
  195. )
  196. const columns = useMemo(
  197. () => [
  198. {
  199. field: 'grade',
  200. headerName: 'grade',
  201. flex: 1,
  202. editable: true,
  203. type: 'singleSelect',
  204. valueOptions: combos.grade.map(item => item.label),
  205. renderEditCell(params: GridRenderEditCellParams<GradeModalRow>) {
  206. const errorMessage = params.row._error?.[params.field as keyof StaffEntryError]
  207. const content = (
  208. <GridEditSingleSelectCell variant="outlined" {...params} />
  209. );
  210. return errorMessage ? (
  211. <Tooltip title={errorMessage}>
  212. <Box width="100%">{content}</Box>
  213. </Tooltip>
  214. ) : (
  215. content
  216. );
  217. }
  218. },
  219. {
  220. field: 'from',
  221. headerName: 'from',
  222. flex: 1,
  223. editable: true,
  224. type: 'date',
  225. renderEditCell(params: GridRenderEditCellParams<GradeModalRow>) {
  226. const errorMessage = params.row._error?.[params.field as keyof StaffEntryError]
  227. const content = <GridEditDateCell {...params} />;
  228. return errorMessage ? (
  229. <Tooltip title={errorMessage}>
  230. <Box width="100%">{content}</Box>
  231. </Tooltip>
  232. ) : (
  233. content
  234. );
  235. }
  236. },
  237. {
  238. field: 'actions',
  239. type: 'actions',
  240. headerName: 'edit',
  241. width: 100,
  242. cellClassName: 'actions',
  243. getActions: ({ id }: { id: number }) => {
  244. const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
  245. if (isInEditMode) {
  246. return [
  247. <GridActionsCellItem
  248. icon={<SaveIcon />}
  249. label="Save"
  250. key="edit"
  251. sx={{
  252. color: 'primary.main'
  253. }}
  254. onClick={handleSave(id)}
  255. />,
  256. <GridActionsCellItem
  257. icon={<CancelIcon />}
  258. label="Cancel"
  259. key="edit"
  260. onClick={handleCancel(id)}
  261. />
  262. ];
  263. }
  264. return [
  265. <GridActionsCellItem
  266. icon={<DeleteIcon />}
  267. label="Delete"
  268. sx={{
  269. color: 'error.main'
  270. }}
  271. onClick={handleDelete(id)} color="inherit" key="edit" />
  272. ];
  273. }
  274. }
  275. ], [combos, rowModesModel, handleSave, handleCancel, handleDelete])
  276. return (
  277. <Modal open={open} onClose={handleClose}>
  278. <Paper sx={{ ...modalSx }}>
  279. <Typography variant="h6" component="h2">
  280. {t('GradeHistoryModal')}
  281. </Typography>
  282. <StyledDataGrid
  283. getRowId={getRowId}
  284. apiRef={apiRef}
  285. rows={_rows}
  286. columns={columns}
  287. editMode="row"
  288. autoHeight
  289. sx={{
  290. "--DataGrid-overlayHeight": "100px",
  291. ".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
  292. border: "1px solid",
  293. borderColor: "error.main",
  294. },
  295. ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": {
  296. border: "1px solid",
  297. borderColor: "warning.main",
  298. },
  299. }}
  300. disableColumnMenu
  301. rowModesModel={rowModesModel}
  302. onRowModesModelChange={setRowModesModel}
  303. processRowUpdate={processRowUpdate}
  304. onProcessRowUpdateError={onProcessRowUpdateError}
  305. getCellClassName={(params: GridCellParams<GradeModalRow>) => {
  306. let classname = "";
  307. if (params.row._error) {
  308. classname = "hasError"
  309. }
  310. return classname;
  311. }}
  312. slots={{
  313. footer: FooterToolbar,
  314. noRowsOverlay: NoRowsOverlay,
  315. }}
  316. slotProps={{
  317. footer: { child: footer },
  318. }}
  319. />
  320. <Box display="flex" justifyContent="flex-end" gap={2}>
  321. <Button variant="text" onClick={onCancel}>
  322. {t('Close')}
  323. </Button>
  324. <Button variant="contained" onClick={doSave}>
  325. {t("Save")}
  326. </Button>
  327. </Box>
  328. {/* </FormControl> */}
  329. </Paper>
  330. </Modal>
  331. )
  332. }
  333. const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
  334. return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
  335. };
  336. const NoRowsOverlay: React.FC = () => {
  337. const { t } = useTranslation("home");
  338. return (
  339. <Box
  340. display="flex"
  341. justifyContent="center"
  342. alignItems="center"
  343. height="100%"
  344. >
  345. <Typography variant="caption">{t("Add some entries!")}</Typography>
  346. </Box>
  347. );
  348. };
  349. export default GradeHistoryModal