|
- import { Box, Button, Modal, ModalProps, Paper, SxProps, Tooltip, Typography } from "@mui/material"
- import StyledDataGrid from "../StyledDataGrid"
- import { useTranslation } from "react-i18next";
- import { useFormContext } from "react-hook-form";
- import { useCallback, useEffect, useMemo, useState } from "react";
- import { GridRenderEditCellParams, FooterPropsOverrides, GridActionsCellItem, GridCellParams, GridEventListener, GridRowEditStopReasons, GridRowId, GridRowIdGetter, GridRowModel, GridRowModes, GridRowModesModel, GridToolbarContainer, useGridApiRef, GridEditDateCell, GridEditSingleSelectCell } from "@mui/x-data-grid";
- import AddIcon from '@mui/icons-material/Add';
- import SaveIcon from '@mui/icons-material/Save';
- import DeleteIcon from '@mui/icons-material/Delete';
- import CancelIcon from '@mui/icons-material/Cancel';
- import EditIcon from '@mui/icons-material/Edit';
- import waitForCondition from "../utils/waitFor";
- import { gradeHistory } from "@/app/api/staff/actions";
- import { Add } from "@mui/icons-material";
- import { comboItem } from "../CreateStaff/CreateStaff";
- import { StaffEntryError, validateRowAndRowBefore } from "./validateDates";
- import { ProcessRowUpdateError } from "./TeamHistoryModal";
-
- interface Props {
- open: boolean;
- onClose: () => void;
- combos: comboItem;
- // columns: any[]
- }
-
- const modalSx: SxProps = {
- position: "absolute",
- top: "50%",
- left: "50%",
- transform: "translate(-50%, -50%)",
- width: "90%",
- maxWidth: "auto",
- maxHeight: "auto",
- padding: 3,
- display: "flex",
- flexDirection: "column",
- gap: 2,
- };
-
- export type GradeModalRow = Partial<
- gradeHistory & {
- _isNew: boolean
- _error: StaffEntryError;
- }>
- const thisField = "gradeHistory"
- const GradeHistoryModal: React.FC<Props> = ({ open, onClose, combos }) => {
- const {
- t,
- // i18n: { language },
- } = useTranslation();
- const { setValue, getValues } = useFormContext();
- const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
- const apiRef = useGridApiRef()
- const originalRows = getValues(thisField)
- const [_rows, setRows] = useState(() => {
- const list: GradeModalRow[] = getValues(thisField)
- return list && list.length > 0 ? list : []
- });
- const [_delRows, setDelRows] = useState<number[]>([]);
- const getRowId = useCallback<GridRowIdGetter<GradeModalRow>>(
- (row) => row.id!!,
- [],
- );
- const handleSave = useCallback(
- (id: GridRowId) => () => {
- setRowModesModel((prevRowModesModel) => ({
- ...prevRowModesModel,
- [id]: { mode: GridRowModes.View }
- }));
- },
- [setRowModesModel]
- );
-
- const onCancel = useCallback(() => {
- setRows(originalRows)
- onClose();
- }, [onClose, originalRows]);
-
- const handleClose = useCallback<NonNullable<ModalProps["onClose"]>>(
- (_, reason) => {
- if (reason !== "backdropClick") {
- onClose();
- }
- }, [onClose]);
-
- const isSaved = useCallback(() => {
- const saved = Object.keys(rowModesModel).every(key => {
- rowModesModel[key].mode === GridRowModes.Edit
- })
- return saved
- }, [rowModesModel])
-
- const doSave = useCallback(async () => {
- try {
- if (isSaved()) {
- setValue(thisField, _rows)
- onClose()
- }
- } catch (error) {
- console.error(error);
- }
- }, [isSaved, onClose, _rows])
-
- const addRow = useCallback(() => {
- const id = Date.now()
- const newEntry = { id, _isNew: true } satisfies GradeModalRow;
- setRows((prev) => [...prev, newEntry])
- setRowModesModel((model) => ({
- ...model,
- [getRowId(newEntry)]: {
- mode: GridRowModes.Edit,
- fieldToFocus: "grade",
- }
- }))
- }, []);
- const onProcessRowUpdateError = useCallback(
- (updateError: ProcessRowUpdateError<GradeModalRow>) => {
- const errors = updateError.errors;
- // const prevRow = updateError.prevRow;
- const currRow = updateError.currRow;
- // if (updateError.prevRow) {
- // apiRef.current.updateRows([{ ...prevRow, _error: errors }]);
- // }
- apiRef.current.updateRows([{ ...currRow, _error: errors }]);
- },
- [apiRef, rowModesModel],
- );
-
- const processRowUpdate = useCallback((
- newRow: GridRowModel<GradeModalRow>,
- originalRow: GridRowModel<GradeModalRow>
- ) => {
- const rowIndex = _rows.findIndex((row: GradeModalRow) => row.id === newRow.id);
- const prevRow: GradeModalRow | null = rowIndex > 0 ? _rows[rowIndex - 1] : null;
- const errors = validateRowAndRowBefore(prevRow, newRow)
- console.log(errors)
- if (errors) {
- throw new ProcessRowUpdateError(
- prevRow,
- newRow,
- "validation error",
- errors
- )
- }
- const { _isNew, _error, ...updatedRow } = newRow;
-
- const rowToSave = {
- ...updatedRow,
- }
- console.log(_rows)
- if (_rows.length != 0) {
- setRows((prev: any[]) => prev?.map((row: any) => (row.id === newRow.id ? rowToSave : row)).sort((a, b) => {
- if (!a.from || !b.from) return 0;
- return new Date(a.from).getTime() - new Date(b.from).getTime();
- }));
- }
- return rowToSave;
- }
- , [_rows, validateRowAndRowBefore])
-
- const handleCancel = useCallback(
- (id: any) => () => {
- setRowModesModel((prevRowModesModel) => ({
- ...prevRowModesModel,
- [id]: { mode: GridRowModes.View, ignoreModifications: true }
- }));
- const editedRow = _rows.find((r) => getRowId(r) === id)
- if (editedRow?._isNew) {
- setRows((rw) => rw.filter((r) => r.id !== id))
- } else {
- setRows((rw) =>
- rw.map((r) =>
- getRowId(r) === id
- ? { ...r, _error: undefined }
- : r,
- ),
- );
- }
- },
- [setRowModesModel, _rows]
- );
-
- const handleDelete = useCallback(
- (id: GridRowId) => () => {
- setRows((prevRows) => prevRows.filter((row) => row.id !== id));
- setDelRows((prevRowsId: number[]) => [...prevRowsId, id as number])
- },
- []
- );
-
- useEffect(()=> {
- console.log(_rows)
- // setValue(thisField, _rows)
- setValue('delGradeHistory', _delRows)
- }, [_rows, _delRows])
-
- const footer = (
- <Box display="flex" gap={2} alignItems="center">
- <Button
- disableRipple
- variant="outlined"
- startIcon={<Add />}
- onClick={addRow}
- size="small"
- >
- {t("Add Record")}
- </Button>
- </Box>
- )
- const columns = useMemo(
- () => [
- {
- field: 'grade',
- headerName: 'grade',
- flex: 1,
- editable: true,
- type: 'singleSelect',
- valueOptions: combos.grade.map(item => item.label),
- renderEditCell(params: GridRenderEditCellParams<GradeModalRow>) {
- const errorMessage = params.row._error?.[params.field as keyof StaffEntryError]
- const content = (
- <GridEditSingleSelectCell variant="outlined" {...params} />
- );
- return errorMessage ? (
- <Tooltip title={errorMessage}>
- <Box width="100%">{content}</Box>
- </Tooltip>
- ) : (
- content
- );
- }
- },
- {
- field: 'from',
- headerName: 'from',
- flex: 1,
- editable: true,
- type: 'date',
- renderEditCell(params: GridRenderEditCellParams<GradeModalRow>) {
- const errorMessage = params.row._error?.[params.field as keyof StaffEntryError]
- const content = <GridEditDateCell {...params} />;
- return errorMessage ? (
- <Tooltip title={errorMessage}>
- <Box width="100%">{content}</Box>
- </Tooltip>
- ) : (
- content
- );
- }
- },
- {
- field: 'actions',
- type: 'actions',
- headerName: 'edit',
- width: 100,
- cellClassName: 'actions',
- getActions: ({ id }: { id: number }) => {
- const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
- if (isInEditMode) {
- return [
- <GridActionsCellItem
- icon={<SaveIcon />}
- label="Save"
- key="edit"
- sx={{
- color: 'primary.main'
- }}
- onClick={handleSave(id)}
- />,
- <GridActionsCellItem
- icon={<CancelIcon />}
- label="Cancel"
- key="edit"
- onClick={handleCancel(id)}
- />
- ];
- }
- return [
- <GridActionsCellItem
- icon={<DeleteIcon />}
- label="Delete"
- sx={{
- color: 'error.main'
- }}
- onClick={handleDelete(id)} color="inherit" key="edit" />
- ];
- }
- }
- ], [combos, rowModesModel, handleSave, handleCancel, handleDelete])
-
- return (
- <Modal open={open} onClose={handleClose}>
- <Paper sx={{ ...modalSx }}>
- <Typography variant="h6" component="h2">
- {t('GradeHistoryModal')}
- </Typography>
- <StyledDataGrid
- getRowId={getRowId}
- apiRef={apiRef}
- rows={_rows}
- columns={columns}
- editMode="row"
- autoHeight
- sx={{
- "--DataGrid-overlayHeight": "100px",
- ".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
- border: "1px solid",
- borderColor: "error.main",
- },
- ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": {
- border: "1px solid",
- borderColor: "warning.main",
- },
- }}
- disableColumnMenu
- rowModesModel={rowModesModel}
- onRowModesModelChange={setRowModesModel}
- processRowUpdate={processRowUpdate}
- onProcessRowUpdateError={onProcessRowUpdateError}
- getCellClassName={(params: GridCellParams<GradeModalRow>) => {
- let classname = "";
- if (params.row._error) {
- classname = "hasError"
- }
- return classname;
- }}
- slots={{
- footer: FooterToolbar,
- noRowsOverlay: NoRowsOverlay,
- }}
- slotProps={{
- footer: { child: footer },
- }}
- />
- <Box display="flex" justifyContent="flex-end" gap={2}>
- <Button variant="text" onClick={onCancel}>
- {t('Close')}
- </Button>
- <Button variant="contained" onClick={doSave}>
- {t("Save")}
- </Button>
- </Box>
- {/* </FormControl> */}
- </Paper>
- </Modal>
- )
- }
- const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
- return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
- };
- const NoRowsOverlay: React.FC = () => {
- const { t } = useTranslation("home");
- return (
- <Box
- display="flex"
- justifyContent="center"
- alignItems="center"
- height="100%"
- >
- <Typography variant="caption">{t("Add some entries!")}</Typography>
- </Box>
- );
- };
- export default GradeHistoryModal
|