From fe51f819ac38120f7bce59fd9aa36eed82afcc2b Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Thu, 6 Feb 2025 05:41:51 +0100 Subject: [PATCH 1/3] refactor(frontend): update nlpValue formDialog --- .../src/components/nlp/NlpValueDialog.tsx | 145 ------------------ .../{NlpValues.tsx => NlpValue.tsx} | 103 ++++++------- .../nlp/components/NlpValueForm.tsx | 120 +++++++++++++++ .../nlp/components/NlpValueFormDialog.tsx | 29 ++++ frontend/src/components/nlp/index.tsx | 5 +- 5 files changed, 198 insertions(+), 204 deletions(-) delete mode 100644 frontend/src/components/nlp/NlpValueDialog.tsx rename frontend/src/components/nlp/components/{NlpValues.tsx => NlpValue.tsx} (76%) create mode 100644 frontend/src/components/nlp/components/NlpValueForm.tsx create mode 100644 frontend/src/components/nlp/components/NlpValueFormDialog.tsx diff --git a/frontend/src/components/nlp/NlpValueDialog.tsx b/frontend/src/components/nlp/NlpValueDialog.tsx deleted file mode 100644 index 1b5e51c2..00000000 --- a/frontend/src/components/nlp/NlpValueDialog.tsx +++ /dev/null @@ -1,145 +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 } from "@mui/material"; -import { useRouter } from "next/router"; -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 MultipleInput from "@/app-components/inputs/MultipleInput"; -import { useCreate } from "@/hooks/crud/useCreate"; -import { useGet } from "@/hooks/crud/useGet"; -import { useUpdate } from "@/hooks/crud/useUpdate"; -import { DialogControlProps } from "@/hooks/useDialog"; -import { useToast } from "@/hooks/useToast"; -import { useTranslate } from "@/hooks/useTranslate"; -import { EntityType, Format } from "@/services/types"; -import { INlpValue, INlpValueAttributes } from "@/types/nlp-value.types"; - -export type TNlpValueAttributesWithRequiredExpressions = INlpValueAttributes & { - expressions: string[]; -}; - -export type NlpValueDialogProps = DialogControlProps & { - canHaveSynonyms: boolean; -}; - -export const NlpValueDialog: FC = ({ - open, - closeDialog, - data, - canHaveSynonyms, - callback, -}) => { - const { t } = useTranslate(); - const { toast } = useToast(); - const { query } = useRouter(); - const { refetch: refetchEntity } = useGet(data?.entity || String(query.id), { - entity: EntityType.NLP_ENTITY, - format: Format.FULL, - }); - const { mutateAsync: createNlpValue } = useCreate(EntityType.NLP_VALUE, { - onError: () => { - toast.error(t("message.internal_server_error")); - }, - onSuccess(data) { - refetchEntity(); - closeDialog(); - toast.success(t("message.success_save")); - callback?.(data); - }, - }); - const { mutateAsync: updateNlpValue } = useUpdate(EntityType.NLP_VALUE, { - onError: () => { - toast.error(t("message.internal_server_error")); - }, - onSuccess(data) { - closeDialog(); - toast.success(t("message.success_save")); - callback?.(data); - }, - }); - const { reset, register, handleSubmit, control } = - useForm({ - defaultValues: { - value: data?.value || "", - expressions: data?.expressions || [], - }, - }); - const validationRules = { - value: { - required: t("message.value_is_required"), - }, - name: {}, - description: {}, - }; - const onSubmitForm = async (params: INlpValueAttributes) => { - if (data) { - updateNlpValue({ id: data.id, params }); - } else { - createNlpValue({ ...params, entity: String(query.id) }); - } - }; - - useEffect(() => { - if (open) reset(); - }, [open, reset]); - - useEffect(() => { - if (data) { - reset({ - value: data.value, - expressions: data.expressions, - }); - } else { - reset(); - } - }, [data, reset]); - - return ( - -
- - {data ? t("title.edit_nlp_value") : t("title.new_nlp_entity_value")} - - - - - - - - {canHaveSynonyms ? ( - - ( - - )} - /> - - ) : null} - - - - - -
-
- ); -}; diff --git a/frontend/src/components/nlp/components/NlpValues.tsx b/frontend/src/components/nlp/components/NlpValue.tsx similarity index 76% rename from frontend/src/components/nlp/components/NlpValues.tsx rename to frontend/src/components/nlp/components/NlpValue.tsx index 74729578..ccdcfcf4 100644 --- a/frontend/src/components/nlp/components/NlpValues.tsx +++ b/frontend/src/components/nlp/components/NlpValue.tsx @@ -1,11 +1,12 @@ /* - * 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. * 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 { faGraduationCap } from "@fortawesome/free-solid-svg-icons"; import AddIcon from "@mui/icons-material/Add"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; @@ -15,7 +16,7 @@ import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { DeleteDialog } from "@/app-components/dialogs"; +import { ConfirmDialogBody } from "@/app-components/dialogs"; import { FilterTextfield } from "@/app-components/inputs/FilterTextfield"; import { ActionColumnLabel, @@ -27,7 +28,7 @@ import { useDelete } from "@/hooks/crud/useDelete"; import { useDeleteMany } from "@/hooks/crud/useDeleteMany"; import { useFind } from "@/hooks/crud/useFind"; import { useGet } from "@/hooks/crud/useGet"; -import { useDialog } from "@/hooks/useDialog"; +import { useDialogs } from "@/hooks/useDialogs"; import { useHasPermission } from "@/hooks/useHasPermission"; import { useSearch } from "@/hooks/useSearch"; import { useToast } from "@/hooks/useToast"; @@ -39,21 +40,20 @@ import { INlpValue } from "@/types/nlp-value.types"; import { PermissionAction } from "@/types/permission.types"; import { getDateTimeFormatter } from "@/utils/date"; -import { NlpValueDialog } from "../NlpValueDialog"; +import { NlpValueFormDialog } from "./NlpValueFormDialog"; export const NlpValues = ({ entityId }: { entityId: string }) => { - const [direction, setDirection] = useState<"up" | "down">("up"); - const deleteEntityDialogCtl = useDialog(false); - const editValueDialogCtl = useDialog(false); - const addNlpValueDialogCtl = useDialog(false); - const hasPermission = useHasPermission(); - const router = useRouter(); const { t } = useTranslate(); const { toast } = useToast(); + const dialogs = useDialogs(); + const router = useRouter(); + const [direction, setDirection] = useState<"up" | "down">("up"); + const hasPermission = useHasPermission(); const { data: nlpEntity, refetch: refetchEntity } = useGet(entityId, { entity: EntityType.NLP_ENTITY, format: Format.FULL, }); + const canHaveSynonyms = nlpEntity?.lookups?.[0] === NlpLookups.keywords; const { onSearch, searchPayload } = useSearch({ $eq: [{ entity: entityId }], $iLike: ["value"], @@ -64,37 +64,41 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { params: searchPayload, }, ); - const { mutateAsync: deleteNlpValue } = useDelete(EntityType.NLP_VALUE, { - onError: () => { - toast.error(t("message.internal_server_error")); + const options = { + onError: (error: Error) => { + toast.error(error.message || t("message.internal_server_error")); }, onSuccess() { - deleteEntityDialogCtl.closeDialog(); - toast.success(t("message.item_delete_success")); - refetchEntity(); - }, - }); - const { mutateAsync: deleteNlpValues } = useDeleteMany(EntityType.NLP_VALUE, { - onError: (error) => { - toast.error(error); - }, - onSuccess: () => { - deleteEntityDialogCtl.closeDialog(); - setSelectedNlpValues([]); toast.success(t("message.item_delete_success")); }, - }); + }; + const { mutateAsync: deleteNlpValue } = useDelete( + EntityType.NLP_VALUE, + options, + ); + const { mutate: deleteNlpValues } = useDeleteMany( + EntityType.NLP_VALUE, + options, + ); const [selectedNlpValues, setSelectedNlpValues] = useState([]); const actionColumns = useActionColumns( EntityType.NLP_VALUE, [ { label: ActionColumnLabel.Edit, - action: (row) => editValueDialogCtl.openDialog(row), + action: (row) => + dialogs.open(NlpValueFormDialog, { data: row, canHaveSynonyms }), }, { label: ActionColumnLabel.Delete, - action: (row) => deleteEntityDialogCtl.openDialog(row.id), + action: async ({ id }) => { + const isConfirmed = await dialogs.confirm(ConfirmDialogBody); + + if (isConfirmed) { + await deleteNlpValue(id); + refetchEntity(); + } + }, }, ], t("label.operations"), @@ -150,7 +154,6 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { return setDirection("down"); }, []); - const canHaveSynonyms = nlpEntity?.lookups?.[0] === NlpLookups.keywords; const handleSelectionChange = (selection: GridRowSelectionModel) => { setSelectedNlpValues(selection as string[]); }; @@ -198,8 +201,8 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { @@ -210,9 +213,19 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { startIcon={} variant="contained" color="error" - onClick={() => - deleteEntityDialogCtl.openDialog(undefined) - } + onClick={async () => { + const isConfirmed = await dialogs.confirm( + ConfirmDialogBody, + { + mode: "selection", + count: selectedNlpValues.length, + }, + ); + + if (isConfirmed) { + deleteNlpValues(selectedNlpValues); + } + }} > {t("button.delete")} @@ -221,30 +234,6 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { - { - refetchEntity(); - }} - /> - { - if (selectedNlpValues.length > 0) { - deleteNlpValues(selectedNlpValues); - setSelectedNlpValues([]); - deleteEntityDialogCtl.closeDialog(); - } else if (deleteEntityDialogCtl.data) { - deleteNlpValue(deleteEntityDialogCtl.data); - } - }} - /> - {}} - /> +> = ({ data: props, Wrapper = Fragment, WrapperProps, ...rest }) => { + const { data, canHaveSynonyms } = props || {}; + const { t } = useTranslate(); + const { toast } = useToast(); + const { query } = useRouter(); + const { refetch: refetchEntity } = useGet(data?.entity || String(query.id), { + entity: EntityType.NLP_ENTITY, + format: Format.FULL, + }); + const options = { + onError: () => { + rest.onError?.(); + toast.error(t("message.internal_server_error")); + }, + onSuccess() { + rest.onSuccess?.(); + toast.success(t("message.success_save")); + }, + }; + const { mutateAsync: createNlpValue } = useCreate( + EntityType.NLP_VALUE, + options, + ); + const { mutate: updateNlpValue } = useUpdate(EntityType.NLP_VALUE, options); + const { reset, register, handleSubmit, control } = useForm< + INlpValueAttributes & { + expressions: string[]; + } + >({ + defaultValues: { + value: data?.value || "", + expressions: data?.expressions || [], + }, + }); + const validationRules = { + value: { + required: t("message.value_is_required"), + }, + name: {}, + description: {}, + }; + const onSubmitForm = async (params: INlpValueAttributes) => { + if (data) { + updateNlpValue({ id: data.id, params }); + } else { + await createNlpValue({ ...params, entity: String(query.id) }); + refetchEntity(); + } + }; + + useEffect(() => { + if (data) { + reset({ + value: data.value, + expressions: data.expressions, + }); + } else { + reset(); + } + }, [data, reset]); + + return ( + +
+ + + + + + {canHaveSynonyms ? ( + + ( + + )} + /> + + ) : null} + +
+
+ ); +}; diff --git a/frontend/src/components/nlp/components/NlpValueFormDialog.tsx b/frontend/src/components/nlp/components/NlpValueFormDialog.tsx new file mode 100644 index 00000000..a8ef383a --- /dev/null +++ b/frontend/src/components/nlp/components/NlpValueFormDialog.tsx @@ -0,0 +1,29 @@ +/* + * 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 { GenericFormDialog } from "@/app-components/dialogs"; +import { ComponentFormDialogProps } from "@/types/common/dialogs.types"; +import { INlpValue } from "@/types/nlp-value.types"; + +import { NlpValueForm } from "./NlpValueForm"; + +export const NlpValueFormDialog = < + T extends { data: INlpValue; canHaveSynonyms: boolean } = { + data: INlpValue; + canHaveSynonyms: boolean; + }, +>( + props: ComponentFormDialogProps, +) => ( + + Form={NlpValueForm} + addText="title.new_nlp_entity_value" + editText="title.edit_nlp_value" + {...props} + /> +); diff --git a/frontend/src/components/nlp/index.tsx b/frontend/src/components/nlp/index.tsx index 96f0f08e..b8e7a755 100644 --- a/frontend/src/components/nlp/index.tsx +++ b/frontend/src/components/nlp/index.tsx @@ -1,11 +1,12 @@ /* - * 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. * 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 { faGraduationCap } from "@fortawesome/free-solid-svg-icons"; import { Grid, Paper, Tab, Tabs } from "@mui/material"; import dynamic from "next/dynamic"; @@ -29,7 +30,7 @@ import { import NlpDatasetCounter from "./components/NlpDatasetCounter"; import NlpSample from "./components/NlpSample"; import NlpDatasetSample from "./components/NlpTrainForm"; -import { NlpValues } from "./components/NlpValues"; +import { NlpValues } from "./components/NlpValue"; const NlpEntity = dynamic(() => import("./components/NlpEntity")); From cd31b873fde568bf97c523d32f29af6fe2702f9b Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Thu, 6 Feb 2025 17:06:10 +0100 Subject: [PATCH 2/3] fix(frontend): refactor NLP value logic to use synchronous methods --- .../components/nlp/components/NlpValue.tsx | 25 +++++++++---------- .../nlp/components/NlpValueForm.tsx | 23 ++++++++++------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/nlp/components/NlpValue.tsx b/frontend/src/components/nlp/components/NlpValue.tsx index ccdcfcf4..57dd0462 100644 --- a/frontend/src/components/nlp/components/NlpValue.tsx +++ b/frontend/src/components/nlp/components/NlpValue.tsx @@ -6,7 +6,6 @@ * 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 { faGraduationCap } from "@fortawesome/free-solid-svg-icons"; import AddIcon from "@mui/icons-material/Add"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; @@ -64,22 +63,23 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { params: searchPayload, }, ); - const options = { + const { mutate: deleteNlpValue } = useDelete(EntityType.NLP_VALUE, { + onError: (error: Error) => { + toast.error(error.message || t("message.internal_server_error")); + }, + onSuccess() { + refetchEntity(); + toast.success(t("message.item_delete_success")); + }, + }); + const { mutate: deleteNlpValues } = useDeleteMany(EntityType.NLP_VALUE, { onError: (error: Error) => { toast.error(error.message || t("message.internal_server_error")); }, onSuccess() { toast.success(t("message.item_delete_success")); }, - }; - const { mutateAsync: deleteNlpValue } = useDelete( - EntityType.NLP_VALUE, - options, - ); - const { mutate: deleteNlpValues } = useDeleteMany( - EntityType.NLP_VALUE, - options, - ); + }); const [selectedNlpValues, setSelectedNlpValues] = useState([]); const actionColumns = useActionColumns( EntityType.NLP_VALUE, @@ -95,8 +95,7 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { const isConfirmed = await dialogs.confirm(ConfirmDialogBody); if (isConfirmed) { - await deleteNlpValue(id); - refetchEntity(); + deleteNlpValue(id); } }, }, diff --git a/frontend/src/components/nlp/components/NlpValueForm.tsx b/frontend/src/components/nlp/components/NlpValueForm.tsx index eb16bc96..68bcbe98 100644 --- a/frontend/src/components/nlp/components/NlpValueForm.tsx +++ b/frontend/src/components/nlp/components/NlpValueForm.tsx @@ -33,7 +33,18 @@ export const NlpValueForm: FC< entity: EntityType.NLP_ENTITY, format: Format.FULL, }); - const options = { + const { mutate: createNlpValue } = useCreate(EntityType.NLP_VALUE, { + onError: () => { + rest.onError?.(); + toast.error(t("message.internal_server_error")); + }, + onSuccess() { + rest.onSuccess?.(); + refetchEntity(); + toast.success(t("message.success_save")); + }, + }); + const { mutate: updateNlpValue } = useUpdate(EntityType.NLP_VALUE, { onError: () => { rest.onError?.(); toast.error(t("message.internal_server_error")); @@ -42,12 +53,7 @@ export const NlpValueForm: FC< rest.onSuccess?.(); toast.success(t("message.success_save")); }, - }; - const { mutateAsync: createNlpValue } = useCreate( - EntityType.NLP_VALUE, - options, - ); - const { mutate: updateNlpValue } = useUpdate(EntityType.NLP_VALUE, options); + }); const { reset, register, handleSubmit, control } = useForm< INlpValueAttributes & { expressions: string[]; @@ -69,8 +75,7 @@ export const NlpValueForm: FC< if (data) { updateNlpValue({ id: data.id, params }); } else { - await createNlpValue({ ...params, entity: String(query.id) }); - refetchEntity(); + createNlpValue({ ...params, entity: String(query.id) }); } }; From 71b2c3cf3bea4a4bf157db603b8eb9cba65e5bf3 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 7 Feb 2025 10:09:49 +0100 Subject: [PATCH 3/3] fix(frontend): add handleDeleteNlpValues function --- .../components/nlp/components/NlpValue.tsx | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/nlp/components/NlpValue.tsx b/frontend/src/components/nlp/components/NlpValue.tsx index 57dd0462..1864eb43 100644 --- a/frontend/src/components/nlp/components/NlpValue.tsx +++ b/frontend/src/components/nlp/components/NlpValue.tsx @@ -156,6 +156,16 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { const handleSelectionChange = (selection: GridRowSelectionModel) => { setSelectedNlpValues(selection as string[]); }; + const handleDeleteNlpValues = async () => { + const isConfirmed = await dialogs.confirm(ConfirmDialogBody, { + mode: "selection", + count: selectedNlpValues.length, + }); + + if (isConfirmed) { + deleteNlpValues(selectedNlpValues); + } + }; return ( @@ -209,22 +219,10 @@ export const NlpValues = ({ entityId }: { entityId: string }) => { {selectedNlpValues.length > 0 && (