Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 

301 řádky
11 KiB

  1. "use client";
  2. import Stack from "@mui/material/Stack";
  3. import Card from "@mui/material/Card";
  4. import CardContent from "@mui/material/CardContent";
  5. import Typography from "@mui/material/Typography";
  6. import { useTranslation } from "react-i18next";
  7. import CardActions from "@mui/material/CardActions";
  8. import RestartAlt from "@mui/icons-material/RestartAlt";
  9. import Button from "@mui/material/Button";
  10. import AddIcon from '@mui/icons-material/Add';
  11. import EditIcon from '@mui/icons-material/Edit';
  12. import DeleteIcon from '@mui/icons-material/DeleteOutlined';
  13. import SaveIcon from '@mui/icons-material/Save';
  14. import CancelIcon from '@mui/icons-material/Close';
  15. import {
  16. GridRowsProp,
  17. GridRowModesModel,
  18. GridRowModes,
  19. GridColDef,
  20. GridToolbarContainer,
  21. GridActionsCellItem,
  22. GridEventListener,
  23. GridRowId,
  24. GridRowModel,
  25. GridRowEditStopReasons,
  26. } from '@mui/x-data-grid';
  27. import CustomDatagrid from "../CustomDatagrid/CustomDatagrid";
  28. import { useFieldArray, useFormContext } from "react-hook-form";
  29. import { useCallback, useEffect, useMemo, useState } from "react";
  30. interface Props {
  31. }
  32. interface EditToolbarProps {
  33. setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
  34. setRowModesModel: (
  35. newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
  36. ) => void;
  37. }
  38. var rowId = -1
  39. function EditToolbar(props: EditToolbarProps) {
  40. const { setRows, setRowModesModel } = props;
  41. const { t } = useTranslation();
  42. const handleClick = () => {
  43. const id = rowId;
  44. rowId = rowId - 1;
  45. setRows((oldRows) => [{ id, name: '', phone: '', email: '', isNew: true }, ...oldRows]);
  46. setRowModesModel((oldModel) => ({
  47. ...oldModel,
  48. [id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' },
  49. }));
  50. };
  51. return (
  52. <GridToolbarContainer>
  53. <Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
  54. {t("Add Contact Person")}
  55. </Button>
  56. </GridToolbarContainer>
  57. );
  58. }
  59. const ContactInfo: React.FC<Props> = ({
  60. }) => {
  61. const { t } = useTranslation();
  62. const { control, setValue, getValues, formState: { errors, defaultValues }, setError, clearErrors, reset, watch, resetField } = useFormContext();
  63. const { fields } = useFieldArray({
  64. control,
  65. name: "addContacts"
  66. })
  67. const initialRows: GridRowsProp = fields.map((item, index) => {
  68. return ({
  69. id: Number(getValues(`addContacts[${index}].id`)),
  70. name: getValues(`addContacts[${index}].name`),
  71. phone: getValues(`addContacts[${index}].phone`),
  72. email: getValues(`addContacts[${index}].email`),
  73. isNew: false,
  74. })
  75. })
  76. const [rows, setRows] = useState<GridRowsProp>([]);
  77. const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
  78. useEffect(() => {
  79. if (initialRows.length > 0 && rows.length === 0) {
  80. setRows(initialRows)
  81. }
  82. }, [initialRows.length > 0])
  83. const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => {
  84. if (params.reason === GridRowEditStopReasons.rowFocusOut) {
  85. event.defaultMuiPrevented = true;
  86. }
  87. };
  88. const handleEditClick = (id: GridRowId) => () => {
  89. setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
  90. };
  91. const handleSaveClick = (id: GridRowId) => () => {
  92. setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
  93. };
  94. const handleDeleteClick = (id: GridRowId) => () => {
  95. const updatedRows = rows.filter((row) => row.id !== id)
  96. setRows(updatedRows);
  97. setValue("addContacts", updatedRows)
  98. };
  99. const handleCancelClick = (id: GridRowId) => () => {
  100. setRowModesModel({
  101. ...rowModesModel,
  102. [id]: { mode: GridRowModes.View, ignoreModifications: true },
  103. });
  104. const editedRow = rows.find((row) => row.id === id);
  105. if (editedRow!.isNew) {
  106. setRows(rows.filter((row) => row.id !== id));
  107. }
  108. };
  109. const processRowUpdate = useCallback((newRow: GridRowModel) => {
  110. const updatedRow = { ...newRow };
  111. const updatedRows = rows.map((row) => (row.id === newRow.id ? updatedRow : row))
  112. setRows(updatedRows);
  113. setValue("addContacts", updatedRows)
  114. return updatedRow;
  115. }, [rows]);
  116. const handleRowModesModelChange = useCallback((newRowModesModel: GridRowModesModel) => {
  117. setRowModesModel(newRowModesModel);
  118. }, [rows]);
  119. const resetContact = useCallback(() => {
  120. if (defaultValues !== undefined) {
  121. resetField("addContacts")
  122. // reset({addContacts: defaultValues.addContacts})
  123. setRows((prev) => defaultValues.addContacts)
  124. setRowModesModel(rows.reduce((acc, row) => ({...acc, [row.id]: { mode: GridRowModes.View } }), {}))
  125. }
  126. }, [defaultValues])
  127. const columns = useMemo<GridColDef[]>(
  128. () => [
  129. {
  130. field: 'name',
  131. headerName: t('Contact Name'),
  132. editable: true,
  133. flex: 1,
  134. },
  135. {
  136. field: 'phone',
  137. headerName: t('Contact Phone'),
  138. editable: true,
  139. flex: 1,
  140. },
  141. {
  142. field: 'email',
  143. headerName: t('Contact Email'),
  144. editable: true,
  145. flex: 1,
  146. },
  147. {
  148. field: 'actions',
  149. type: 'actions',
  150. headerName: '',
  151. flex: 0.6,
  152. // width: 100,
  153. cellClassName: 'actions',
  154. getActions: ({ id, ...params }) => {
  155. const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
  156. if (isInEditMode) {
  157. return [
  158. <GridActionsCellItem
  159. icon={<SaveIcon />}
  160. label="Save"
  161. sx={{
  162. color: 'primary.main',
  163. }}
  164. onClick={handleSaveClick(id)}
  165. />,
  166. <GridActionsCellItem
  167. icon={<CancelIcon />}
  168. label="Cancel"
  169. className="textPrimary"
  170. onClick={handleCancelClick(id)}
  171. color="inherit"
  172. />,
  173. ];
  174. }
  175. return [
  176. <GridActionsCellItem
  177. icon={<EditIcon />}
  178. label="Edit"
  179. className="textPrimary"
  180. onClick={handleEditClick(id)}
  181. color="inherit"
  182. />,
  183. <GridActionsCellItem
  184. icon={<DeleteIcon />}
  185. label="Delete"
  186. onClick={handleDeleteClick(id)}
  187. color="inherit"
  188. />,
  189. ];
  190. },
  191. },
  192. ],
  193. [rows, rowModesModel, t],
  194. );
  195. // check error
  196. useEffect(() => {
  197. if (getValues("addContacts") === undefined || getValues("addContacts") === null) {
  198. return;
  199. }
  200. if (getValues("addContacts").length === 0) {
  201. clearErrors("addContacts")
  202. } else {
  203. const errorRows = rows.filter(row => String(row.name).trim().length === 0 || String(row.phone).trim().length === 0 || String(row.email).trim().length === 0)
  204. if (errorRows.length > 0) {
  205. setError("addContacts", { message: "Contact details include empty fields", type: "required" })
  206. } else {
  207. const errorRows_EmailFormat = rows.filter(row => !/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/.test(String(row.email)))
  208. if (errorRows_EmailFormat.length > 0) {
  209. setError("addContacts", { message: "Contact details include empty fields", type: "email_format" })
  210. } else {
  211. clearErrors("addContacts")
  212. }
  213. }
  214. }
  215. }, [rows, rowModesModel])
  216. // check editing
  217. useEffect(() => {
  218. const filteredByKey = Object.fromEntries(
  219. Object.entries(rowModesModel).filter(([key, value]) => rowModesModel[key].mode === 'edit'))
  220. if (Object.keys(filteredByKey).length > 0) {
  221. setValue("isGridEditing", true)
  222. } else {
  223. setValue("isGridEditing", false)
  224. }
  225. }, [rowModesModel])
  226. return (
  227. <Card sx={{ display: "block" }}>
  228. <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
  229. <Stack gap={2}>
  230. {/* <div> */}
  231. <Typography variant="overline" display='inline-block' noWrap>
  232. {t("Contact Info")}
  233. </Typography>
  234. {Boolean(errors.addContacts?.type === "required") && <Typography sx={(theme) => ({ color: theme.palette.error.main })} variant="overline" display='inline-block' noWrap>
  235. {t("Please ensure at least one row is created, and all the fields are inputted and saved")}
  236. </Typography>}
  237. {Boolean(errors.addContacts?.type === "email_format") && <Typography sx={(theme) => ({ color: theme.palette.error.main })} variant="overline" display='inline-block' noWrap>
  238. {t("Please ensure all the email formats are correct")}
  239. </Typography>}
  240. {/* </div> */}
  241. <CustomDatagrid
  242. rows={[...rows]}
  243. columns={columns}
  244. editMode="row"
  245. rowModesModel={rowModesModel}
  246. onRowEditStop={handleRowEditStop}
  247. processRowUpdate={processRowUpdate}
  248. // onProcessRowUpdateError={handleProcessRowUpdateError}
  249. onRowModesModelChange={handleRowModesModelChange}
  250. slots={{
  251. toolbar: EditToolbar,
  252. }}
  253. slotProps={{
  254. toolbar: { setRows, setRowModesModel },
  255. }}
  256. sx={{
  257. height: '100%'
  258. }}
  259. />
  260. <CardActions sx={{ justifyContent: "flex-end" }}>
  261. <Button variant="text" startIcon={<RestartAlt />} onClick={resetContact} disabled={Boolean(watch("isGridEditing"))}>
  262. {t("Reset")}
  263. </Button>
  264. </CardActions>
  265. </Stack>
  266. </CardContent>
  267. </Card>
  268. );
  269. };
  270. export default ContactInfo;