This is an automated email from the ASF dual-hosted git repository.

pawarprasad123 pushed a commit to branch ATLAS-5246-v1
in repository https://gitbox.apache.org/repos/asf/atlas.git

commit 2ca44bb2c95832d20982ff2962f038572208d78c
Author: Prasad Pawar <[email protected]>
AuthorDate: Fri May 8 12:54:08 2026 +0530

    ATLAS-5267: ATLAS UI: Create Glossary: Add Import glossary terms tab 
(template + upload) (#617)
---
 dashboard/src/components/ImportDialog.tsx          |  44 +-
 dashboard/src/utils/apiErrorToastMessage.ts        |  43 ++
 dashboard/src/utils/glossaryImportFlow.ts          |  57 +++
 .../DetailPage/GlossaryDetails/TermRelation.tsx    |   1 +
 .../src/views/Glossary/AddUpdateCategoryForm.tsx   |   1 +
 .../src/views/Glossary/AddUpdateGlossaryForm.tsx   | 450 ++++++++++++++++-----
 dashboard/src/views/Glossary/AddUpdateTermForm.tsx |   1 +
 dashboard/src/views/Glossary/AssignCategory.tsx    |   1 +
 dashboard/src/views/Glossary/AssignTerm.tsx        |   1 +
 dashboard/src/views/Glossary/DeleteGlossary.tsx    |  52 +--
 10 files changed, 517 insertions(+), 134 deletions(-)

diff --git a/dashboard/src/components/ImportDialog.tsx 
b/dashboard/src/components/ImportDialog.tsx
index a3adfbb54..10d82aa1d 100644
--- a/dashboard/src/components/ImportDialog.tsx
+++ b/dashboard/src/components/ImportDialog.tsx
@@ -37,7 +37,8 @@ import List from "@mui/material/List";
 import ListItem from "@mui/material/ListItem";
 import ListItemText from "@mui/material/ListItemText";
 import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
-import { getGlossaryImport } from "../api/apiMethods/glossaryApiMethod";
+import { postGlossaryImportFormData } from "@utils/glossaryImportFlow";
+import { getApiErrorToastMessage } from "@utils/apiErrorToastMessage";
 
 const BootstrapDialog = styled(Dialog)(({ theme }) => ({
   "& .MuiDialogContent-root": {
@@ -77,22 +78,27 @@ export const ImportDialog: React.FC<CustomModalProps> = ({
   const onUpload = async () => {
     if (fileData) {
       try {
-        let apiMethod =
+        const onProgress = (progressValue: number) => {
+          setProgress(progressValue);
+        };
+        const importResp =
           title == "Import Business Metadata"
-            ? getBusinessMetadataImport
-            : getGlossaryImport;
-        let formData = new FormData();
-        formData.append("file", fileData);
-        const importResp = await apiMethod(formData, {
-          onUploadProgress: (progressEvent: {
-            loaded: number;
-            total: number;
-          }) => {
-            let progressValue =
-              (progressEvent.loaded / progressEvent.total) * 100;
-            setProgress(progressValue);
-          }
-        });
+            ? await (async () => {
+                const formData = new FormData();
+                formData.append("file", fileData);
+                return getBusinessMetadataImport(formData, {
+                  onUploadProgress: (progressEvent: {
+                    loaded: number;
+                    total: number;
+                  }) => {
+                    if (!progressEvent.total) return;
+                    onProgress(
+                      (progressEvent.loaded / progressEvent.total) * 100
+                    );
+                  }
+                });
+              })()
+            : await postGlossaryImportFormData(fileData, onProgress);
 
         if (importResp.data.failedImportInfoList == undefined) {
           toast.dismiss(toastId.current);
@@ -114,8 +120,12 @@ export const ImportDialog: React.FC<CustomModalProps> = ({
         }
         setImportData(importResp.data);
       } catch (error) {
+        const message = getApiErrorToastMessage(error);
+        if (message === null) {
+          return;
+        }
         toast.dismiss(toastId.current);
-        toastId.current = toast.error(`Invalid JSON response from server`);
+        toastId.current = toast.error(message);
       }
     }
   };
diff --git a/dashboard/src/utils/apiErrorToastMessage.ts 
b/dashboard/src/utils/apiErrorToastMessage.ts
new file mode 100644
index 000000000..7afa98d0b
--- /dev/null
+++ b/dashboard/src/utils/apiErrorToastMessage.ts
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Resolves user-visible toast text from an axios/fetchApi error.
+ * @returns null when the caller must not toast (403 is handled in fetchApi).
+ */
+export const getApiErrorToastMessage = (error: unknown): string | null => {
+       const er = error as {
+               response?: { status?: number; data?: unknown };
+       };
+       if (er?.response?.status === 403) {
+               return null;
+       }
+       const data = er?.response?.data;
+       if (data !== null && data !== undefined && typeof data === "object") {
+               const o = data as Record<string, unknown>;
+               if (o.errorMessage != null && String(o.errorMessage).trim() !== 
"") {
+                       return String(o.errorMessage);
+               }
+               if (o.msgDesc != null && String(o.msgDesc).trim() !== "") {
+                       return String(o.msgDesc);
+               }
+       }
+       if (typeof data === "string" && data.trim() !== "") {
+               return data;
+       }
+       return "Invalid JSON response from server";
+};
diff --git a/dashboard/src/utils/glossaryImportFlow.ts 
b/dashboard/src/utils/glossaryImportFlow.ts
new file mode 100644
index 000000000..a3f564050
--- /dev/null
+++ b/dashboard/src/utils/glossaryImportFlow.ts
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+       getGlossaryImport,
+       getGlossaryImportTmpl
+} from '@api/apiMethods/glossaryApiMethod';
+
+/**
+ * Same behavior as glossary branch of SideBarTree.downloadFile.
+ */
+export const downloadGlossaryImportTemplate = async (): Promise<void> => {
+       const apiResp = await getGlossaryImportTmpl({});
+       const text =
+               apiResp && typeof apiResp.data !== 'undefined'
+                       ? String(apiResp.data)
+                       : '';
+       const blob = new Blob([text], { type: 'text/plain' });
+       const url = window.URL.createObjectURL(blob);
+       const link = document.createElement('a');
+       link.href = url;
+       link.setAttribute('download', 'template');
+       document.body.appendChild(link);
+       link.click();
+       document.body.removeChild(link);
+       window.URL.revokeObjectURL(url);
+};
+
+export const postGlossaryImportFormData = (
+       file: File,
+       onUploadProgress?: (progressPercent: number) => void
+) => {
+       const formData = new FormData();
+       formData.append('file', file);
+       return getGlossaryImport(formData, {
+               onUploadProgress: (progressEvent: { loaded: number; total: 
number }) => {
+                       if (!onUploadProgress || !progressEvent.total) return;
+                       const progressValue =
+                               (progressEvent.loaded / progressEvent.total) * 
100;
+                       onUploadProgress(progressValue);
+               }
+       });
+};
diff --git a/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx 
b/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx
index ba1381528..68d069ea7 100644
--- a/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx
+++ b/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx
@@ -219,6 +219,7 @@ const TermRelation = ({ glossaryTypeData }: any) => {
           button2Label={editModal ? "Update" : "Close"}
           button2Handler={editModal ? handleSubmit(onSubmit) : 
handleCloseModal}
           disableButton2={isSubmitting}
+          button2Loading={isSubmitting}
           maxWidth="md"
         >
           <form onSubmit={handleSubmit(onSubmit)}>
diff --git a/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx 
b/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx
index 12a80b6de..bd9084861 100644
--- a/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx
+++ b/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx
@@ -146,6 +146,7 @@ const AddUpdateCategoryForm = (props: {
         button1Handler={onClose}
         button2Label={isAdd ? "Create" : "Update"}
         disableButton2={isSubmitting}
+        button2Loading={isSubmitting}
         maxWidth="sm"
         button2Handler={handleSubmit(onSubmit)}
       >
diff --git a/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx 
b/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx
index 4aa2248d1..5f4a22854 100644
--- a/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx
+++ b/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx
@@ -19,108 +19,370 @@ import CustomModal from "@components/Modal";
 import GlossaryForm from "./GlossaryForm";
 import { useForm } from "react-hook-form";
 import {
-  createGlossary,
-  editGlossary
+       createGlossary,
+       editGlossary
 } from "@api/apiMethods/glossaryApiMethod";
 import { isEmpty, serverError } from "@utils/Utils";
+import {
+       downloadGlossaryImportTemplate,
+       postGlossaryImportFormData
+} from "@utils/glossaryImportFlow";
+import { getApiErrorToastMessage } from "@utils/apiErrorToastMessage";
 import { toast } from "react-toastify";
-import { useRef } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
+import type { MouseEvent } from "react";
 import { useAppDispatch, useAppSelector } from "@hooks/reducerHook";
 import { fetchGlossaryData } from "@redux/slice/glossarySlice";
+import {
+       Button,
+       IconButton,
+       List,
+       ListItem,
+       ListItemText,
+       Stack,
+       ToggleButton,
+       ToggleButtonGroup
+} from "@mui/material";
+import FileDownloadIcon from "@mui/icons-material/FileDownload";
+import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
+import ImportLayout from "@views/SideBar/Import/ImportLayout";
+import { LightTooltip } from "@components/muiComponents";
+
+type GlossaryCreateTab = "create" | "import";
+
+const hasSelectedImportFile = (fileData: unknown): boolean => {
+       if (fileData == null) return false;
+       if (fileData instanceof File) return true;
+       if (typeof fileData === "object" && Object.keys(fileData as 
object).length > 0)
+               return true;
+       return false;
+};
 
 const AddUpdateGlossaryForm = (props: {
-  open: any;
-  onClose: any;
-  isAdd: any;
-  node: Record<string, any> | undefined;
+       open: any;
+       onClose: any;
+       isAdd: any;
+       node: Record<string, any> | undefined;
 }) => {
-  const { open, onClose, isAdd, node } = props;
-  const dispatch = useAppDispatch();
-  const toastId: any = useRef(null);
-  const { glossaryData }: any = useAppSelector((state: any) => state.glossary);
-  const { id } = node || {};
-  let defaultValue: Record<string, string> = {};
-  let glossaryObj: Record<string, string> = {};
-  if (!isAdd) {
-    glossaryObj = glossaryData.find((obj: { name: string }) => {
-      return obj.name == id;
-    });
-    const { name, shortDescription, longDescription } = glossaryObj || {};
-
-    defaultValue["name"] = name;
-    defaultValue["shortDescription"] = shortDescription;
-    defaultValue["longDescription"] = longDescription;
-  }
-  const {
-    control,
-    handleSubmit,
-    setValue,
-    formState: { isSubmitting }
-  } = useForm({
-    defaultValues: isAdd ? {} : defaultValue,
-    mode: "onChange",
-    shouldUnregister: true
-  });
-
-  const onSubmit = async (formValues: any) => {
-    let formData = { ...formValues };
-    const { guid, qualifiedName } = glossaryObj;
-    const {
-      name,
-      shortDescription,
-      longDescription
-    }: { name: string; shortDescription: string; longDescription: string } =
-      formData;
-    let data: Record<string, string> = {};
-    if (!isAdd) {
-      data["guid"] = guid;
-      data["qualifiedName"] = qualifiedName;
-    }
-    data["name"] = name;
-    data["shortDescription"] = !isEmpty(shortDescription)
-      ? shortDescription
-      : "";
-    data["longDescription"] = !isEmpty(longDescription) ? longDescription : "";
-
-    try {
-      if (isAdd) {
-        await createGlossary(data);
-      } else {
-        await editGlossary(guid, data);
-      }
-
-      await dispatch(fetchGlossaryData());
-      toast.dismiss(toastId.current);
-      toastId.current = toast.success(
-        `Glossary ${name} was ${isAdd ? "created" : "updated"} successfully`
-      );
-      onClose();
-    } catch (error) {
-      serverError(error, toastId);
-    }
-  };
-
-  return (
-    <>
-      <CustomModal
-        open={open}
-        onClose={onClose}
-        title={isAdd ? "Create Glossary" : "Edit Glossary"}
-        button1Label="Cancel"
-        button1Handler={onClose}
-        button2Label={isAdd ? "Create" : "Update"}
-        maxWidth="sm"
-        button2Handler={handleSubmit(onSubmit)}
-        disableButton2={isSubmitting}
-      >
-        <GlossaryForm
-          control={control}
-          handleSubmit={handleSubmit(onSubmit)}
-          setValue={setValue}
-        />
-      </CustomModal>
-    </>
-  );
+       const { open, onClose, isAdd, node } = props;
+       const dispatch = useAppDispatch();
+       const toastId: any = useRef(null);
+       const { glossaryData }: any = useAppSelector((state: any) => 
state.glossary);
+       const { id } = node || {};
+       let defaultValue: Record<string, string> = {};
+       let glossaryObj: Record<string, string> = {};
+       if (!isAdd) {
+               glossaryObj = glossaryData.find((obj: { name: string }) => {
+                       return obj.name == id;
+               });
+               const { name, shortDescription, longDescription } = glossaryObj 
|| {};
+
+               defaultValue["name"] = name;
+               defaultValue["shortDescription"] = shortDescription;
+               defaultValue["longDescription"] = longDescription;
+       }
+       const {
+               control,
+               handleSubmit,
+               setValue,
+               formState: { isSubmitting }
+       } = useForm({
+               defaultValues: isAdd ? {} : defaultValue,
+               mode: "onChange",
+               shouldUnregister: true
+       });
+
+       const [glossaryCreateTab, setGlossaryCreateTab] =
+               useState<GlossaryCreateTab>("create");
+       const [importFileData, setImportFileData] = useState<any>([]);
+       const [importProgress, setImportProgress] = useState(0);
+       const [importErrorDetails, setImportErrorDetails] = useState(false);
+       const [importData, setImportData] = useState<any>(null);
+       const [importUploading, setImportUploading] = useState(false);
+
+       const resetImportUi = useCallback(() => {
+               setGlossaryCreateTab("create");
+               setImportFileData([]);
+               setImportProgress(0);
+               setImportErrorDetails(false);
+               setImportData(null);
+               setImportUploading(false);
+       }, []);
+
+       const handleModalClose = useCallback(() => {
+               resetImportUi();
+               onClose();
+       }, [onClose, resetImportUi]);
+
+       useEffect(() => {
+               if (!open) {
+                       resetImportUi();
+               }
+       }, [open, resetImportUi]);
+
+       const handleGlossaryCreateTabChange = (
+               _event: MouseEvent<HTMLElement>,
+               next: GlossaryCreateTab | null
+       ) => {
+               if (next == null) return;
+               setGlossaryCreateTab(next);
+               if (next === "create") {
+                       setImportFileData([]);
+                       setImportProgress(0);
+                       setImportErrorDetails(false);
+                       setImportData(null);
+               }
+       };
+
+       const handleDownloadTemplate = async () => {
+               try {
+                       await downloadGlossaryImportTemplate();
+               } catch {
+                       toast.dismiss(toastId.current);
+                       toastId.current = toast.error("Could not download 
template");
+               }
+       };
+
+       const handleImportUpload = async () => {
+               if (!hasSelectedImportFile(importFileData)) return;
+               setImportUploading(true);
+               try {
+                       const importResp = await postGlossaryImportFormData(
+                               importFileData,
+                               (pct) => setImportProgress(pct)
+                       );
+                       if (importResp.data.failedImportInfoList == undefined) {
+                               toast.dismiss(toastId.current);
+                               toastId.current = toast.success(
+                                       `File: ${importFileData.name} imported 
successfully`
+                               );
+                               await dispatch(fetchGlossaryData());
+                               handleModalClose();
+                               return;
+                       }
+                       if (importResp.data.failedImportInfoList != undefined) {
+                               toast.dismiss(toastId.current);
+                               toastId.current = toast.error(
+                                       
importResp.data.failedImportInfoList[0].remarks
+                               );
+                               setImportErrorDetails(true);
+                       }
+                       setImportData(importResp.data);
+               } catch (error) {
+                       const message = getApiErrorToastMessage(error);
+                       if (message === null) {
+                               return;
+                       }
+                       toast.dismiss(toastId.current);
+                       toastId.current = toast.error(message);
+               } finally {
+                       setImportUploading(false);
+               }
+       };
+
+       const onSubmit = async (formValues: any) => {
+               let formData = { ...formValues };
+               const { guid, qualifiedName } = glossaryObj;
+               const {
+                       name,
+                       shortDescription,
+                       longDescription
+               }: { name: string; shortDescription: string; longDescription: 
string } =
+                       formData;
+               let data: Record<string, string> = {};
+               if (!isAdd) {
+                       data["guid"] = guid;
+                       data["qualifiedName"] = qualifiedName;
+               }
+               data["name"] = name;
+               data["shortDescription"] = !isEmpty(shortDescription)
+                       ? shortDescription
+                       : "";
+               data["longDescription"] = !isEmpty(longDescription) ? 
longDescription : "";
+
+               try {
+                       if (isAdd) {
+                               await createGlossary(data);
+                       } else {
+                               await editGlossary(guid, data);
+                       }
+
+                       await dispatch(fetchGlossaryData());
+                       toast.dismiss(toastId.current);
+                       toastId.current = toast.success(
+                               `Glossary ${name} was ${isAdd ? "created" : 
"updated"} successfully`
+                       );
+                       handleModalClose();
+               } catch (error) {
+                       serverError(error, toastId);
+               }
+       };
+
+       const showImportError = isAdd && glossaryCreateTab === "import" && 
importErrorDetails;
+
+       const modalTitle = showImportError
+               ? "Error Details"
+               : isAdd
+                       ? "Create Glossary"
+                       : "Edit Glossary";
+
+       const titleIconNode =
+               showImportError ? (
+                       <IconButton
+                               aria-label="Back to import file"
+                               onClick={(e) => {
+                                       e.stopPropagation();
+                                       setImportErrorDetails(false);
+                               }}
+                               size="small"
+                               sx={{ color: (theme) => theme.palette.grey[500] 
}}
+                       >
+                               <LightTooltip title="Back to import file">
+                                       <ArrowBackIosNewIcon sx={{ fontSize: 
"1.25rem" }} />
+                               </LightTooltip>
+                       </IconButton>
+               ) : undefined;
+
+       const isImportMode = isAdd && glossaryCreateTab === "import" && 
!importErrorDetails;
+
+       const button2Label = (() => {
+               if (!isAdd) return "Update";
+               if (glossaryCreateTab === "create") return "Create";
+               if (importErrorDetails) return "";
+               return "Upload";
+       })();
+
+       const button2Handler = (() => {
+               if (!isAdd || glossaryCreateTab === "create") return 
handleSubmit(onSubmit);
+               if (importErrorDetails) return () => {};
+               return handleImportUpload;
+       })();
+
+       const disableButton2 = (() => {
+               if (!isAdd) return isSubmitting;
+               if (glossaryCreateTab === "create") return isSubmitting;
+               if (importErrorDetails) return true;
+               return !hasSelectedImportFile(importFileData);
+       })();
+
+       const button2Loading =
+               isAdd && glossaryCreateTab === "import" && !importErrorDetails
+                       ? importUploading
+                       : isSubmitting;
+
+       const hideButton2 = Boolean(
+               isAdd && glossaryCreateTab === "import" && importErrorDetails
+       );
+
+       return (
+               <>
+                       <CustomModal
+                               open={open}
+                               onClose={handleModalClose}
+                               title={modalTitle}
+                               titleIcon={titleIconNode}
+                               button1Label="Cancel"
+                               button1Handler={handleModalClose}
+                               button2Label={button2Label}
+                               maxWidth="sm"
+                               button2Handler={button2Handler}
+                               disableButton2={disableButton2}
+                               button2Loading={button2Loading}
+                               hideButton2={hideButton2}
+                       >
+                               <Stack>
+                                       {isAdd && (
+                                               <Stack marginBottom="1rem">
+                                                       <ToggleButtonGroup
+                                                               exclusive
+                                                               
value={glossaryCreateTab}
+                                                               
onChange={handleGlossaryCreateTabChange}
+                                                               size="small"
+                                                               color="primary"
+                                                               
aria-label="Choose create glossary or import glossary terms"
+                                                       >
+                                                               <ToggleButton
+                                                                       
value="create"
+                                                                       
className="entity-form-toggle-btn"
+                                                                       
data-cy="create-glossary-tab"
+                                                               >
+                                                                       Create 
glossary
+                                                               </ToggleButton>
+                                                               <ToggleButton
+                                                                       
value="import"
+                                                                       
className="entity-form-toggle-btn"
+                                                                       
data-cy="import-glossary-terms-tab"
+                                                               >
+                                                                       Import 
glossary terms
+                                                               </ToggleButton>
+                                                       </ToggleButtonGroup>
+                                               </Stack>
+                                       )}
+                                       {(!isAdd || glossaryCreateTab === 
"create") && (
+                                               <GlossaryForm
+                                                       control={control}
+                                                       
handleSubmit={handleSubmit(onSubmit)}
+                                                       setValue={setValue}
+                                               />
+                                       )}
+                                       {isImportMode && (
+                                               <Stack gap={2}>
+                                                       <Button
+                                                               
variant="outlined"
+                                                               color="primary"
+                                                               
startIcon={<FileDownloadIcon />}
+                                                               
onClick={handleDownloadTemplate}
+                                                               
aria-label="Download import template"
+                                                       >
+                                                               Download import 
template
+                                                       </Button>
+                                                       <ImportLayout
+                                                               
setFileData={setImportFileData}
+                                                               
progressVal={importProgress}
+                                                               
setProgress={setImportProgress}
+                                                               selectedFile={
+                                                                       
importFileData !== undefined &&
+                                                                       
hasSelectedImportFile(importFileData)
+                                                                               
? [importFileData]
+                                                                               
: []
+                                                               }
+                                                               
errorDetails={importErrorDetails}
+                                                       />
+                                               </Stack>
+                                       )}
+                                       {showImportError && 
importData?.failedImportInfoList && (
+                                               <Stack
+                                                       sx={{
+                                                               width: "100%",
+                                                               minHeight: 200,
+                                                               maxHeight: 400,
+                                                               bgcolor: 
"background.paper"
+                                                       }}
+                                               >
+                                                       <List>
+                                                               
{importData.failedImportInfoList.map(
+                                                                       (
+                                                                               
value: {
+                                                                               
        index: number;
+                                                                               
        remarks: string;
+                                                                               
},
+                                                                               
index: number
+                                                                       ) => (
+                                                                               
<ListItem key={value.index} disableGutters disablePadding>
+                                                                               
        <ListItemText
+                                                                               
                className="dropzone-listitem"
+                                                                               
                primary={`${index + 1}. ${value.remarks}`}
+                                                                               
        />
+                                                                               
</ListItem>
+                                                                       )
+                                                               )}
+                                                       </List>
+                                               </Stack>
+                                       )}
+                               </Stack>
+                       </CustomModal>
+               </>
+       );
 };
 
 export default AddUpdateGlossaryForm;
diff --git a/dashboard/src/views/Glossary/AddUpdateTermForm.tsx 
b/dashboard/src/views/Glossary/AddUpdateTermForm.tsx
index feea62032..f5ece0289 100644
--- a/dashboard/src/views/Glossary/AddUpdateTermForm.tsx
+++ b/dashboard/src/views/Glossary/AddUpdateTermForm.tsx
@@ -142,6 +142,7 @@ const AddUpdateTermForm = (props: {
         button1Handler={onClose}
         button2Label={isAdd ? "Create" : "Update"}
         disableButton2={isSubmitting}
+        button2Loading={isSubmitting}
         maxWidth="sm"
         button2Handler={handleSubmit(onSubmit)}
       >
diff --git a/dashboard/src/views/Glossary/AssignCategory.tsx 
b/dashboard/src/views/Glossary/AssignCategory.tsx
index 9e588ae3c..5a6f8f8b9 100644
--- a/dashboard/src/views/Glossary/AssignCategory.tsx
+++ b/dashboard/src/views/Glossary/AssignCategory.tsx
@@ -288,6 +288,7 @@ const AssignCategory = ({
         maxWidth="sm"
         button2Handler={assignCatgeory}
         disableButton2={loading}
+        button2Loading={loading}
       >
         <Stack>
           <TextField
diff --git a/dashboard/src/views/Glossary/AssignTerm.tsx 
b/dashboard/src/views/Glossary/AssignTerm.tsx
index 14eceb720..e4c2fc0ed 100644
--- a/dashboard/src/views/Glossary/AssignTerm.tsx
+++ b/dashboard/src/views/Glossary/AssignTerm.tsx
@@ -452,6 +452,7 @@ const AssignTerm = ({
         maxWidth="sm"
         button2Handler={relatedTerm ? handleSubmit(onSubmit) : assignTerm}
         disableButton2={isSubmitting}
+        button2Loading={isSubmitting}
         isDirty={!isEmpty(selectedNode)}
       >
         {relatedTerm ? (
diff --git a/dashboard/src/views/Glossary/DeleteGlossary.tsx 
b/dashboard/src/views/Glossary/DeleteGlossary.tsx
index 744447494..c4b5dcf60 100644
--- a/dashboard/src/views/Glossary/DeleteGlossary.tsx
+++ b/dashboard/src/views/Glossary/DeleteGlossary.tsx
@@ -26,6 +26,7 @@ import { Typography } from "@mui/material";
 import { fetchGlossaryData } from "@redux/slice/glossarySlice";
 import { isEmpty, serverError } from "@utils/Utils";
 import { useRef } from "react";
+import { useAsyncPending } from "@hooks/useAsyncPending";
 import { useLocation, useNavigate, useParams } from "react-router-dom";
 import { toast } from "react-toastify";
 
@@ -48,36 +49,39 @@ const DeleteGlossary = (props: {
   const navigate = useNavigate();
   const dispatchApi = useAppDispatch();
   const toastId: any = useRef(null);
+  const { pending: deleteInProgress, run: runDelete } = useAsyncPending();
 
   const fetchCurrentData = async () => {
     await dispatchApi(fetchGlossaryData());
   };
 
-  const handleRemove = async () => {
-    try {
-      gtype == "term"
-        ? await deleteGlossaryorType(cGuid)
-        : await deleteGlossaryorTerm(guid);
-      updatedData();
-      fetchCurrentData();
-      toast.success(
-        `${
-          gtype == "term" ? "Term" : "Glossary"
-        } ${id} was deleted successfully`
-      );
-      if (!isEmpty(glossaryGuid) || !isEmpty(glossaryType)) {
-        navigate(
-          {
-            pathname: "/"
-          },
-          { replace: true }
+  const handleRemove = () => {
+    void runDelete(async () => {
+      try {
+        gtype == "term"
+          ? await deleteGlossaryorType(cGuid)
+          : await deleteGlossaryorTerm(guid);
+        updatedData();
+        await fetchCurrentData();
+        toast.success(
+          `${
+            gtype == "term" ? "Term" : "Glossary"
+          } ${id} was deleted successfully`
         );
+        if (!isEmpty(glossaryGuid) || !isEmpty(glossaryType)) {
+          navigate(
+            {
+              pathname: "/"
+            },
+            { replace: true }
+          );
+        }
+        onClose();
+        setExpandNode(null);
+      } catch (error) {
+        serverError(error, toastId);
       }
-      onClose();
-      setExpandNode(null);
-    } catch (error) {
-      serverError(error, toastId);
-    }
+    });
   };
 
   return (
@@ -92,6 +96,8 @@ const DeleteGlossary = (props: {
         button2Label="Ok"
         maxWidth="sm"
         button2Handler={handleRemove}
+        disableButton2={deleteInProgress}
+        button2Loading={deleteInProgress}
       >
         <Typography fontSize={15}>
           Are you sure you want to delete{" "}

Reply via email to