From 4ed0d5976c3eeae61daeb704788d08f03624b742 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Wed, 5 Feb 2025 11:31:41 +0100 Subject: [PATCH 1/3] refactor(frontend): update contextVar dialogs --- .../context-vars/ContextVarDialog.tsx | 165 ------------------ .../context-vars/ContextVarForm.tsx | 146 ++++++++++++++++ .../context-vars/ContextVarFormDialog.tsx | 38 ++++ .../src/components/context-vars/index.tsx | 106 ++++++----- 4 files changed, 233 insertions(+), 222 deletions(-) delete mode 100644 frontend/src/components/context-vars/ContextVarDialog.tsx create mode 100644 frontend/src/components/context-vars/ContextVarForm.tsx create mode 100644 frontend/src/components/context-vars/ContextVarFormDialog.tsx diff --git a/frontend/src/components/context-vars/ContextVarDialog.tsx b/frontend/src/components/context-vars/ContextVarDialog.tsx deleted file mode 100644 index b894eae1..00000000 --- a/frontend/src/components/context-vars/ContextVarDialog.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright © 2024 Hexastack. All rights reserved. - * - * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: - * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. - * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). - */ - -import { - Dialog, - DialogActions, - DialogContent, - FormControlLabel, - FormHelperText, - Switch, -} from "@mui/material"; -import { FC, useEffect } from "react"; -import { Controller, useForm } from "react-hook-form"; - -import DialogButtons from "@/app-components/buttons/DialogButtons"; -import { DialogTitle } from "@/app-components/dialogs/DialogTitle"; -import { ContentContainer } from "@/app-components/dialogs/layouts/ContentContainer"; -import { ContentItem } from "@/app-components/dialogs/layouts/ContentItem"; -import { Input } from "@/app-components/inputs/Input"; -import { useCreate } from "@/hooks/crud/useCreate"; -import { useUpdate } from "@/hooks/crud/useUpdate"; -import { DialogControlProps } from "@/hooks/useDialog"; -import { useToast } from "@/hooks/useToast"; -import { useTranslate } from "@/hooks/useTranslate"; -import { EntityType } from "@/services/types"; -import { IContextVar, IContextVarAttributes } from "@/types/context-var.types"; -import { slugify } from "@/utils/string"; - -export type ContextVarDialogProps = DialogControlProps; -export const ContextVarDialog: FC = ({ - open, - data, - closeDialog, - ...rest -}) => { - const { t } = useTranslate(); - const { toast } = useToast(); - const { mutateAsync: createContextVar } = useCreate(EntityType.CONTEXT_VAR, { - onError: (error) => { - toast.error(error); - }, - onSuccess() { - closeDialog(); - toast.success(t("message.success_save")); - }, - }); - const { mutateAsync: updateContextVar } = useUpdate(EntityType.CONTEXT_VAR, { - onError: () => { - toast.error(t("message.internal_server_error")); - }, - onSuccess() { - closeDialog(); - toast.success(t("message.success_save")); - }, - }); - const { - reset, - register, - setValue, - handleSubmit, - formState: { errors }, - control, - } = useForm({ - defaultValues: { - name: data?.name || "", - label: data?.label || "", - permanent: data?.permanent || false, - }, - }); - const validationRules = { - label: { - required: t("message.label_is_required"), - }, - name: { - pattern: { - value: /^[a-z_0-9]+$/, - message: t("message.context_vars_name_is_invalid"), - }, - }, - }; - const onSubmitForm = async (params: IContextVarAttributes) => { - if (data) { - updateContextVar({ id: data.id, params }); - } else { - createContextVar(params); - } - }; - - useEffect(() => { - if (open) reset(); - }, [open, reset]); - - useEffect(() => { - if (data) { - reset({ - label: data.label, - name: data.name, - permanent: data.permanent, - }); - } else { - reset(); - } - }, [data, reset]); - - return ( - -
- - {data ? t("title.edit_context_var") : t("title.new_context_var")} - - - - - { - setValue("label", value); - setValue("name", slugify(value)); - }, - }} - helperText={errors.label ? errors.label.message : null} - /> - - - - - - ( - } - label={t("label.permanent")} - /> - )} - /> - {t("help.permanent")} - - - - - - -
-
- ); -}; diff --git a/frontend/src/components/context-vars/ContextVarForm.tsx b/frontend/src/components/context-vars/ContextVarForm.tsx new file mode 100644 index 00000000..ba8d9ae2 --- /dev/null +++ b/frontend/src/components/context-vars/ContextVarForm.tsx @@ -0,0 +1,146 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { FormControlLabel, FormHelperText, Switch } from "@mui/material"; +import { FC, Fragment, useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; + +import { ContentContainer, ContentItem } from "@/app-components/dialogs/"; +import { Input } from "@/app-components/inputs/Input"; +import { useCreate } from "@/hooks/crud/useCreate"; +import { useUpdate } from "@/hooks/crud/useUpdate"; +import { useToast } from "@/hooks/useToast"; +import { useTranslate } from "@/hooks/useTranslate"; +import { EntityType } from "@/services/types"; +import { ComponentFormProps } from "@/types/common/dialogs.types"; +import { IContextVar, IContextVarAttributes } from "@/types/context-var.types"; +import { slugify } from "@/utils/string"; + +export const ContextVarForm: FC> = ({ + data, + Wrapper = Fragment, + WrapperProps, + ...rest +}) => { + const { t } = useTranslate(); + const { toast } = useToast(); + const options = { + onError: (error: Error) => { + rest.onError?.(); + toast.error(error || t("message.internal_server_error")); + }, + onSuccess: () => { + rest.onSuccess?.(); + toast.success(t("message.success_save")); + }, + }; + const { mutateAsync: createContextVar } = useCreate( + EntityType.CONTEXT_VAR, + options, + ); + const { mutateAsync: updateContextVar } = useUpdate( + EntityType.CONTEXT_VAR, + options, + ); + const { + reset, + control, + register, + setValue, + formState: { errors }, + handleSubmit, + } = useForm({ + defaultValues: { + name: data?.name || "", + label: data?.label || "", + permanent: data?.permanent || false, + }, + }); + const validationRules = { + name: { + pattern: { + value: /^[a-z_0-9]+$/, + message: t("message.context_vars_name_is_invalid"), + }, + }, + label: { + required: t("message.label_is_required"), + }, + }; + const onSubmitForm = async (params: IContextVarAttributes) => { + if (data) { + updateContextVar({ id: data.id, params }); + } else { + createContextVar(params); + } + }; + + useEffect(() => { + if (data) { + reset({ + name: data.name, + label: data.label, + permanent: data.permanent, + }); + } else { + reset(); + } + }, [data, reset]); + + return ( + +
+ + + { + setValue("label", value); + setValue("name", slugify(value)); + }, + }} + helperText={errors.label ? errors.label.message : null} + /> + + + + + + ( + } + label={t("label.permanent")} + /> + )} + /> + {t("help.permanent")} + + +
+
+ ); +}; diff --git a/frontend/src/components/context-vars/ContextVarFormDialog.tsx b/frontend/src/components/context-vars/ContextVarFormDialog.tsx new file mode 100644 index 00000000..d33d71e1 --- /dev/null +++ b/frontend/src/components/context-vars/ContextVarFormDialog.tsx @@ -0,0 +1,38 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { FC } from "react"; + +import { FormDialog } from "@/app-components/dialogs"; +import { useTranslate } from "@/hooks/useTranslate"; +import { ComponentFormDialogProps } from "@/types/common/dialogs.types"; +import { IContextVar } from "@/types/context-var.types"; + +import { ContextVarForm } from "./ContextVarForm"; + +export const ContextVarFormDialog: FC< + ComponentFormDialogProps +> = ({ payload, ...rest }) => { + const { t } = useTranslate(); + + return ( + { + rest.onClose(true); + }} + Wrapper={FormDialog} + WrapperProps={{ + title: payload + ? t("title.edit_context_var") + : t("title.new_context_var"), + ...rest, + }} + /> + ); +}; diff --git a/frontend/src/components/context-vars/index.tsx b/frontend/src/components/context-vars/index.tsx index 502dc580..a16a38b1 100644 --- a/frontend/src/components/context-vars/index.tsx +++ b/frontend/src/components/context-vars/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -11,9 +11,9 @@ import AddIcon from "@mui/icons-material/Add"; import DeleteIcon from "@mui/icons-material/Delete"; import { Button, Grid, Paper, Switch } from "@mui/material"; import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; -import React, { useState } from "react"; +import { useState } from "react"; -import { DeleteDialog } from "@/app-components/dialogs/DeleteDialog"; +import { ConfirmDialogBody } from "@/app-components/dialogs"; import { FilterTextfield } from "@/app-components/inputs/FilterTextfield"; import { ActionColumnLabel, @@ -25,7 +25,7 @@ import { useDelete } from "@/hooks/crud/useDelete"; import { useDeleteMany } from "@/hooks/crud/useDeleteMany"; import { useFind } from "@/hooks/crud/useFind"; import { useUpdate } from "@/hooks/crud/useUpdate"; -import { getDisplayDialogs, useDialog } from "@/hooks/useDialog"; +import { useDialogs } from "@/hooks/useDialogs"; import { useHasPermission } from "@/hooks/useHasPermission"; import { useSearch } from "@/hooks/useSearch"; import { useToast } from "@/hooks/useToast"; @@ -36,14 +36,12 @@ import { IContextVar } from "@/types/context-var.types"; import { PermissionAction } from "@/types/permission.types"; import { getDateTimeFormatter } from "@/utils/date"; -import { ContextVarDialog } from "./ContextVarDialog"; +import { ContextVarFormDialog } from "./ContextVarFormDialog"; export const ContextVars = () => { const { t } = useTranslate(); const { toast } = useToast(); - const addDialogCtl = useDialog(false); - const editDialogCtl = useDialog(false); - const deleteDialogCtl = useDialog(false); + const dialogs = useDialogs(); const hasPermission = useHasPermission(); const { onSearch, searchPayload } = useSearch({ $iLike: ["label"], @@ -54,7 +52,7 @@ export const ContextVars = () => { params: searchPayload, }, ); - const { mutateAsync: updateContextVar } = useUpdate(EntityType.CONTEXT_VAR, { + const { mutate: updateContextVar } = useUpdate(EntityType.CONTEXT_VAR, { onError: () => { toast.error(t("message.internal_server_error")); }, @@ -62,41 +60,42 @@ export const ContextVars = () => { toast.success(t("message.success_save")); }, }); - const { mutateAsync: deleteContextVar } = useDelete(EntityType.CONTEXT_VAR, { + const { mutate: deleteContextVar } = useDelete(EntityType.CONTEXT_VAR, { onError: (error) => { toast.error(error); }, onSuccess() { - deleteDialogCtl.closeDialog(); setSelectedContextVars([]); toast.success(t("message.item_delete_success")); }, }); - const { mutateAsync: deleteContextVars } = useDeleteMany( - EntityType.CONTEXT_VAR, - { - onError: (error) => { - toast.error(error); - }, - onSuccess: () => { - deleteDialogCtl.closeDialog(); - setSelectedContextVars([]); - toast.success(t("message.item_delete_success")); - }, + const { mutate: deleteContextVars } = useDeleteMany(EntityType.CONTEXT_VAR, { + onError: (error) => { + toast.error(error); }, - ); + onSuccess: () => { + setSelectedContextVars([]); + toast.success(t("message.item_delete_success")); + }, + }); const [selectedContextVars, setSelectedContextVars] = useState([]); const actionColumns = useActionColumns( EntityType.CONTEXT_VAR, [ { label: ActionColumnLabel.Edit, - action: (row) => editDialogCtl.openDialog(row), + action: (row) => dialogs.open(ContextVarFormDialog, row), requires: [PermissionAction.UPDATE], }, { label: ActionColumnLabel.Delete, - action: (row) => deleteDialogCtl.openDialog(row.id), + action: async ({ id }) => { + const isConfirmed = await dialogs.confirm(ConfirmDialogBody); + + if (isConfirmed) { + deleteContextVar(id); + } + }, requires: [PermissionAction.DELETE], }, ], @@ -119,9 +118,9 @@ export const ContextVars = () => { disableColumnMenu: true, renderHeader, headerAlign: "left", - renderCell: (params) => ( + renderCell: ({ row, value }) => ( { } onChange={() => { updateContextVar({ - id: params.row.id, - params: { permanent: !params.value }, + id: row.id, + params: { permanent: !value }, }); }} /> @@ -166,21 +165,6 @@ export const ContextVars = () => { return ( - - - - { - if (selectedContextVars.length > 0) { - deleteContextVars(selectedContextVars); - setSelectedContextVars([]); - deleteDialogCtl.closeDialog(); - } else if (deleteDialogCtl?.data) { - deleteContextVar(deleteDialogCtl.data); - } - }} - /> { startIcon={} variant="contained" sx={{ float: "right" }} - onClick={() => addDialogCtl.openDialog()} + onClick={() => dialogs.open(ContextVarFormDialog, null)} > {t("button.add")} ) : null} - {selectedContextVars.length > 0 && ( - - - - )} + + + From e630d1e2ac660e016425a074e195dcf95ae4c821 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Wed, 5 Feb 2025 11:35:59 +0100 Subject: [PATCH 2/3] fix(frontend): centrelize delete options in one variable --- .../src/components/context-vars/index.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/context-vars/index.tsx b/frontend/src/components/context-vars/index.tsx index a16a38b1..fdee1011 100644 --- a/frontend/src/components/context-vars/index.tsx +++ b/frontend/src/components/context-vars/index.tsx @@ -60,24 +60,23 @@ export const ContextVars = () => { toast.success(t("message.success_save")); }, }); - const { mutate: deleteContextVar } = useDelete(EntityType.CONTEXT_VAR, { - onError: (error) => { + const options = { + onError: (error: Error) => { toast.error(error); }, onSuccess() { setSelectedContextVars([]); toast.success(t("message.item_delete_success")); }, - }); - const { mutate: deleteContextVars } = useDeleteMany(EntityType.CONTEXT_VAR, { - onError: (error) => { - toast.error(error); - }, - onSuccess: () => { - setSelectedContextVars([]); - toast.success(t("message.item_delete_success")); - }, - }); + }; + const { mutate: deleteContextVar } = useDelete( + EntityType.CONTEXT_VAR, + options, + ); + const { mutate: deleteContextVars } = useDeleteMany( + EntityType.CONTEXT_VAR, + options, + ); const [selectedContextVars, setSelectedContextVars] = useState([]); const actionColumns = useActionColumns( EntityType.CONTEXT_VAR, From ea64e2960ec3c5b89ea0be7cbea310e94e33d65d Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Wed, 5 Feb 2025 15:07:02 +0100 Subject: [PATCH 3/3] fix(frontend): update onError toast error message --- frontend/src/components/context-vars/ContextVarForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/context-vars/ContextVarForm.tsx b/frontend/src/components/context-vars/ContextVarForm.tsx index ba8d9ae2..6250daea 100644 --- a/frontend/src/components/context-vars/ContextVarForm.tsx +++ b/frontend/src/components/context-vars/ContextVarForm.tsx @@ -32,7 +32,7 @@ export const ContextVarForm: FC> = ({ const options = { onError: (error: Error) => { rest.onError?.(); - toast.error(error || t("message.internal_server_error")); + toast.error(error.message || t("message.internal_server_error")); }, onSuccess: () => { rest.onSuccess?.();