From fe371d650b00654315b0fc0fa47eebfb4ac684b8 Mon Sep 17 00:00:00 2001 From: cristhianzl Date: Fri, 7 Jun 2024 10:38:32 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20(sideBarFolderButtons):=20add=20suc?= =?UTF-8?q?cess=20alert=20for=20folder=20upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ♻️ (sideBarFolderButtons): remove unnecessary commas and improve code readability ✨ (hooks): add useDeleteMultipleFlows hook for deleting multiple flows ✨ (hooks): add useDescriptionModal hook for dynamic modal descriptions ✨ (hooks): add useFilteredFlows hook for filtering flows ✨ (hooks): add useDuplicateFlows hook for duplicating flows ✨ (hooks): add custom hooks for handling export, select all, and option changes - Add `useExportFlows` for exporting selected flows - Add `useSelectAll` for handling select all functionality - Add `useSelectOptionsChange` for handling option changes - Add `useSelectedFlows` for managing selected flows state ♻️ (index.tsx): refactor to use custom hooks for better code modularity and readability 💡 (index.tsx): add cardTypes memoization to determine the type of cards displayed --- .../components/sideBarFolderButtons/index.tsx | 4 + .../hooks/use-delete-multiple.tsx | 47 ++++ .../hooks/use-description-modal.tsx | 32 +++ .../hooks/use-filtered-flows.tsx | 27 ++ .../hooks/use-handle-duplicate.tsx | 53 ++++ .../hooks/use-handle-export.tsx | 50 ++++ .../hooks/use-handle-select-all.tsx | 25 ++ .../hooks/use-select-options-change.tsx | 40 +++ .../hooks/use-selected-flows.tsx | 18 ++ .../components/componentsComponent/index.tsx | 245 ++++++------------ 10 files changed, 374 insertions(+), 167 deletions(-) create mode 100644 src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-delete-multiple.tsx create mode 100644 src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-description-modal.tsx create mode 100644 src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-filtered-flows.tsx create mode 100644 src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-duplicate.tsx create mode 100644 src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-export.tsx create mode 100644 src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-select-all.tsx create mode 100644 src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-select-options-change.tsx create mode 100644 src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-selected-flows.tsx diff --git a/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx b/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx index de394d2e1..4289c7cbe 100644 --- a/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx +++ b/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx @@ -51,6 +51,7 @@ const SideBarFoldersButtonsComponent = ({ const folderId = location?.state?.folderId ?? myCollectionId; const getFolderById = useFolderStore((state) => state.getFolderById); const setErrorData = useAlertStore((state) => state.setErrorData); + const setSuccessData = useAlertStore((state) => state.setSuccessData); const handleFolderChange = (folderId: string) => { getFolderById(folderId); @@ -65,6 +66,9 @@ const SideBarFoldersButtonsComponent = ({ uploadFolder(folderId) .then(() => { getFolderById(folderId); + setSuccessData({ + title: "Uploaded successfully", + }); }) .catch((err) => { console.log(err); diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-delete-multiple.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-delete-multiple.tsx new file mode 100644 index 000000000..395088193 --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-delete-multiple.tsx @@ -0,0 +1,47 @@ +import { useCallback } from "react"; + +const useDeleteMultipleFlows = ( + selectedFlowsComponentsCards, + removeFlow, + resetFilter, + getFoldersApi, + folderId, + myCollectionId, + getFolderById, + setSuccessData, + setErrorData, +) => { + const handleDeleteMultiple = useCallback(() => { + removeFlow(selectedFlowsComponentsCards) + .then(() => { + resetFilter(); + getFoldersApi(true); + if (!folderId || folderId === myCollectionId) { + getFolderById(folderId ? folderId : myCollectionId); + } + setSuccessData({ + title: "Selected items deleted successfully", + }); + }) + .catch(() => { + setErrorData({ + title: "Error deleting items", + list: ["Please try again"], + }); + }); + }, [ + selectedFlowsComponentsCards, + removeFlow, + resetFilter, + getFoldersApi, + folderId, + myCollectionId, + getFolderById, + setSuccessData, + setErrorData, + ]); + + return { handleDeleteMultiple }; +}; + +export default useDeleteMultipleFlows; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-description-modal.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-description-modal.tsx new file mode 100644 index 000000000..6de2ebb6d --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-description-modal.tsx @@ -0,0 +1,32 @@ +import { useMemo } from "react"; + +const useDescriptionModal = (selectedFlowsComponentsCards, type) => { + const getDescriptionModal = useMemo(() => { + const getTypeLabel = (type) => { + const labels = { + all: "item", + component: "component", + flow: "flow", + }; + return labels[type] || ""; + }; + + const getPluralizedLabel = (type) => { + const labels = { + all: "items", + component: "components", + flow: "flows", + }; + return labels[type] || ""; + }; + + if (selectedFlowsComponentsCards?.length === 1) { + return getTypeLabel(type); + } + return getPluralizedLabel(type); + }, [selectedFlowsComponentsCards, type]); + + return getDescriptionModal; +}; + +export default useDescriptionModal; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-filtered-flows.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-filtered-flows.tsx new file mode 100644 index 000000000..96b1757ff --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-filtered-flows.tsx @@ -0,0 +1,27 @@ +import cloneDeep from "lodash/cloneDeep"; +import { useEffect } from "react"; + +const useFilteredFlows = ( + flowsFromFolder, + searchFlowsComponents, + setAllFlows, +) => { + useEffect(() => { + const newFlows = cloneDeep(flowsFromFolder || []); + const filteredFlows = newFlows.filter( + (f) => + f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) || + f.description + .toLowerCase() + .includes(searchFlowsComponents.toLowerCase()), + ); + + if (searchFlowsComponents === "") { + setAllFlows(flowsFromFolder); + } else { + setAllFlows(filteredFlows); + } + }, [flowsFromFolder, searchFlowsComponents, setAllFlows]); +}; + +export default useFilteredFlows; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-duplicate.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-duplicate.tsx new file mode 100644 index 000000000..fc49fc0d1 --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-duplicate.tsx @@ -0,0 +1,53 @@ +import { useCallback } from "react"; + +const useDuplicateFlows = ( + selectedFlowsComponentsCards, + addFlow, + allFlows, + resetFilter, + getFoldersApi, + folderId, + myCollectionId, + getFolderById, + setSuccessData, + setSelectedFlowsComponentsCards, + handleSelectAll, + cardTypes, +) => { + const handleDuplicate = useCallback(() => { + Promise.all( + selectedFlowsComponentsCards.map((selectedFlow) => + addFlow( + true, + allFlows.find((flow) => flow.id === selectedFlow), + ), + ), + ).then(() => { + resetFilter(); + getFoldersApi(true); + if (!folderId || folderId === myCollectionId) { + getFolderById(folderId ? folderId : myCollectionId); + } + setSuccessData({ title: `${cardTypes} duplicated successfully` }); + setSelectedFlowsComponentsCards([]); + handleSelectAll(false); + }); + }, [ + selectedFlowsComponentsCards, + addFlow, + allFlows, + resetFilter, + getFoldersApi, + folderId, + myCollectionId, + getFolderById, + setSuccessData, + setSelectedFlowsComponentsCards, + handleSelectAll, + cardTypes, + ]); + + return { handleDuplicate }; +}; + +export default useDuplicateFlows; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-export.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-export.tsx new file mode 100644 index 000000000..bd742045b --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-export.tsx @@ -0,0 +1,50 @@ +import { useCallback } from "react"; + +const useExportFlows = ( + selectedFlowsComponentsCards, + allFlows, + downloadFlow, + removeApiKeys, + version, + setSuccessData, + setSelectedFlowsComponentsCards, + handleSelectAll, + cardTypes, +) => { + const handleExport = useCallback(() => { + selectedFlowsComponentsCards.forEach((selectedFlowId) => { + const selectedFlow = allFlows.find((flow) => flow.id === selectedFlowId); + if (selectedFlow) { + downloadFlow( + removeApiKeys({ + id: selectedFlow.id, + data: selectedFlow.data, + description: selectedFlow.description, + name: selectedFlow.name, + last_tested_version: version, + is_component: false, + }), + selectedFlow.name, + selectedFlow.description, + ); + } + }); + setSuccessData({ title: `${cardTypes} exported successfully` }); + setSelectedFlowsComponentsCards([]); + handleSelectAll(false); + }, [ + selectedFlowsComponentsCards, + allFlows, + downloadFlow, + removeApiKeys, + version, + setSuccessData, + setSelectedFlowsComponentsCards, + handleSelectAll, + cardTypes, + ]); + + return { handleExport }; +}; + +export default useExportFlows; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-select-all.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-select-all.tsx new file mode 100644 index 000000000..a81515e0a --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-handle-select-all.tsx @@ -0,0 +1,25 @@ +import { useCallback } from "react"; + +const useSelectAll = (flowsFromFolder, getValues, setValue) => { + const handleSelectAll = useCallback( + (select) => { + const flowsFromFolderIds = flowsFromFolder?.map((f) => f.id); + if (select) { + Object.keys(getValues()).forEach((key) => { + if (!flowsFromFolderIds?.includes(key)) return; + setValue(key, true); + }); + return; + } + + Object.keys(getValues()).forEach((key) => { + setValue(key, false); + }); + }, + [flowsFromFolder, getValues, setValue], + ); + + return { handleSelectAll }; +}; + +export default useSelectAll; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-select-options-change.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-select-options-change.tsx new file mode 100644 index 000000000..56dc204c7 --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-select-options-change.tsx @@ -0,0 +1,40 @@ +import { useCallback } from "react"; + +const useSelectOptionsChange = ( + selectedFlowsComponentsCards, + setErrorData, + setOpenDelete, + handleDuplicate, + handleExport, +) => { + const handleSelectOptionsChange = useCallback( + (action) => { + const hasSelected = selectedFlowsComponentsCards?.length > 0; + if (!hasSelected) { + setErrorData({ + title: "No items selected", + list: ["Please select items to delete"], + }); + return; + } + if (action === "delete") { + setOpenDelete(true); + } else if (action === "duplicate") { + handleDuplicate(); + } else if (action === "export") { + handleExport(); + } + }, + [ + selectedFlowsComponentsCards, + setErrorData, + setOpenDelete, + handleDuplicate, + handleExport, + ], + ); + + return { handleSelectOptionsChange }; +}; + +export default useSelectOptionsChange; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-selected-flows.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-selected-flows.tsx new file mode 100644 index 000000000..b6f00934e --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/hooks/use-selected-flows.tsx @@ -0,0 +1,18 @@ +import { useEffect } from "react"; + +const useSelectedFlows = ( + entireFormValues, + setSelectedFlowsComponentsCards, +) => { + useEffect(() => { + if (!entireFormValues || Object.keys(entireFormValues).length === 0) return; + + const selectedFlows = Object.keys(entireFormValues).filter((key) => { + return entireFormValues[key] === true; + }); + + setSelectedFlowsComponentsCards(selectedFlows); + }, [entireFormValues, setSelectedFlowsComponentsCards]); +}; + +export default useSelectedFlows; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx index 754848256..af13e2bb4 100644 --- a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx @@ -1,4 +1,3 @@ -import { cloneDeep } from "lodash"; import { useEffect, useMemo, useState } from "react"; import { FormProvider, useForm, useWatch } from "react-hook-form"; import { Link, useLocation, useNavigate } from "react-router-dom"; @@ -8,7 +7,6 @@ import IconComponent from "../../../../components/genericIconComponent"; import PaginatorComponent from "../../../../components/paginatorComponent"; import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent"; import { Button } from "../../../../components/ui/button"; -import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants"; import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal"; import useAlertStore from "../../../../stores/alertStore"; import { useDarkStore } from "../../../../stores/darkStore"; @@ -21,6 +19,14 @@ import { getNameByType } from "../../utils/get-name-by-type"; import { sortFlows } from "../../utils/sort-flows"; import EmptyComponent from "../emptyComponent"; import HeaderComponent from "../headerComponent"; +import useDeleteMultipleFlows from "./hooks/use-delete-multiple"; +import useDescriptionModal from "./hooks/use-description-modal"; +import useFilteredFlows from "./hooks/use-filtered-flows"; +import useDuplicateFlows from "./hooks/use-handle-duplicate"; +import useExportFlows from "./hooks/use-handle-export"; +import useSelectAll from "./hooks/use-handle-select-all"; +import useSelectOptionsChange from "./hooks/use-select-options-change"; +import useSelectedFlows from "./hooks/use-selected-flows"; export default function ComponentsComponent({ type = "all", @@ -34,22 +40,22 @@ export default function ComponentsComponent({ const allFlows = useFlowsManagerStore((state) => state.allFlows); const flowsFromFolder = useFolderStore( - (state) => state.selectedFolder?.flows + (state) => state.selectedFolder?.flows, ); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const [openDelete, setOpenDelete] = useState(false); const searchFlowsComponents = useFlowsManagerStore( - (state) => state.searchFlowsComponents + (state) => state.searchFlowsComponents, ); const setSelectedFlowsComponentsCards = useFlowsManagerStore( - (state) => state.setSelectedFlowsComponentsCards + (state) => state.setSelectedFlowsComponentsCards, ); const selectedFlowsComponentsCards = useFlowsManagerStore( - (state) => state.selectedFlowsComponentsCards + (state) => state.selectedFlowsComponentsCards, ); const [handleFileDrop] = useFileDrop(uploadFlow, type)!; @@ -71,6 +77,16 @@ export default function ComponentsComponent({ const setFolderUrl = useFolderStore((state) => state.setFolderUrl); const addFlow = useFlowsManagerStore((state) => state.addFlow); + const cardTypes = useMemo(() => { + if (window.location.pathname.includes("components")) { + return "Components"; + } + if (window.location.pathname.includes("flows")) { + return "Flows"; + } + return "Items"; + }, [window.location]); + useEffect(() => { setFolderUrl(folderId ?? ""); setSelectedFlowsComponentsCards([]); @@ -78,22 +94,7 @@ export default function ComponentsComponent({ getFolderById(folderId ? folderId : myCollectionId); }, [location]); - useEffect(() => { - const newFlows = cloneDeep(flowsFromFolder!); - const filteredFlows = newFlows?.filter( - (f) => - f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) || - f.description - .toLowerCase() - .includes(searchFlowsComponents.toLowerCase()) - ); - - if (searchFlowsComponents === "") { - setAllFlows(flowsFromFolder!); - } - - setAllFlows(filteredFlows); - }, [searchFlowsComponents]); + useFilteredFlows(flowsFromFolder, searchFlowsComponents, setAllFlows); const resetFilter = () => { setPageIndex(1); @@ -104,164 +105,74 @@ export default function ComponentsComponent({ const entireFormValues = useWatch({ control }); const methods = useForm(); - const handleSelectAll = (select) => { - const flowsFromFolderIds = flowsFromFolder?.map((f) => f.id); - if (select) { - Object.keys(getValues()).forEach((key) => { - if (!flowsFromFolderIds?.includes(key)) return; - setValue(key, true); - }); - return; - } - Object.keys(getValues()).forEach((key) => { - setValue(key, false); - }); - }; + const { handleSelectAll } = useSelectAll( + flowsFromFolder, + getValues, + setValue, + ); - const handleSelectOptionsChange = (action: string) => { - const hasSelected = selectedFlowsComponentsCards?.length > 0; - if (!hasSelected) { - setErrorData({ - title: "No items selected", - list: ["Please select items to delete"], - }); - return; - } - if (action === "delete") { - setOpenDelete(true); - } else if (action === "duplicate") { - handleDuplicate(); - } else if (action === "export") { - handleExport(); - } - }; - - const handleDuplicate = () => { - Promise.all( - selectedFlowsComponentsCards.map((selectedFlow) => - addFlow( - true, - allFlows.find((flow) => flow.id === selectedFlow) - ) - ) - ).then(() => { - resetFilter(); - getFoldersApi(true); - if (!folderId || folderId === myCollectionId) { - getFolderById(folderId ? folderId : myCollectionId); - } - setSelectedFlowsComponentsCards([]); - - setSuccessData({ title: "Flows duplicated successfully" }); - }); - }; - - const handleImport = () => { - uploadFlow({ newProject: true, isComponent: false }) - .then(() => { - resetFilter(); - getFoldersApi(true); - if (!folderId || folderId === myCollectionId) { - getFolderById(folderId ? folderId : myCollectionId); - } - setSelectedFlowsComponentsCards([]); - - setSuccessData({ title: "Flows imported successfully" }); - }) - .catch((error) => { - setErrorData({ - title: UPLOAD_ERROR_ALERT, - list: [error], - }); - }); - }; + const { handleDuplicate } = useDuplicateFlows( + selectedFlowsComponentsCards, + addFlow, + allFlows, + resetFilter, + getFoldersApi, + folderId, + myCollectionId, + getFolderById, + setSuccessData, + setSelectedFlowsComponentsCards, + handleSelectAll, + cardTypes, + ); const version = useDarkStore((state) => state.version); - const handleExport = () => { - selectedFlowsComponentsCards.map((selectedFlowId) => { - const selectedFlow = allFlows.find((flow) => flow.id === selectedFlowId); - downloadFlow( - removeApiKeys({ - id: selectedFlow!.id, - data: selectedFlow!.data!, - description: selectedFlow!.description, - name: selectedFlow!.name, - last_tested_version: version, - is_component: false, - }), - selectedFlow!.name, - selectedFlow!.description - ); - }); - setSuccessData({ title: "Flows exported successfully" }); - }; + const { handleExport } = useExportFlows( + selectedFlowsComponentsCards, + allFlows, + downloadFlow, + removeApiKeys, + version, + setSuccessData, + setSelectedFlowsComponentsCards, + handleSelectAll, + cardTypes, + ); - const handleDeleteMultiple = () => { - removeFlow(selectedFlowsComponentsCards) - .then(() => { - resetFilter(); - getFoldersApi(true); - if (!folderId || folderId === myCollectionId) { - getFolderById(folderId ? folderId : myCollectionId); - } - setSuccessData({ - title: "Selected items deleted successfully", - }); - }) - .catch(() => { - setErrorData({ - title: "Error deleting items", - list: ["Please try again"], - }); - }); - }; + const { handleSelectOptionsChange } = useSelectOptionsChange( + selectedFlowsComponentsCards, + setErrorData, + setOpenDelete, + handleDuplicate, + handleExport, + ); - useEffect(() => { - if (!entireFormValues || Object.keys(entireFormValues).length === 0) return; - const selectedFlows: string[] = Object.keys(entireFormValues).filter( - (key) => { - if (entireFormValues[key] === true) { - return true; - } - return false; - } - ); + const { handleDeleteMultiple } = useDeleteMultipleFlows( + selectedFlowsComponentsCards, + removeFlow, + resetFilter, + getFoldersApi, + folderId, + myCollectionId, + getFolderById, + setSuccessData, + setErrorData, + ); - setSelectedFlowsComponentsCards(selectedFlows); - }, [entireFormValues]); + useSelectedFlows(entireFormValues, setSelectedFlowsComponentsCards); - const getDescriptionModal = useMemo(() => { - const getTypeLabel = (type) => { - const labels = { - all: "item", - component: "component", - flow: "flow", - }; - return labels[type] || ""; - }; - - const getPluralizedLabel = (type) => { - const labels = { - all: "items", - component: "components", - flow: "flows", - }; - return labels[type] || ""; - }; - - if (selectedFlowsComponentsCards?.length === 1) { - return getTypeLabel(type); - } - return getPluralizedLabel(type); - }, [selectedFlowsComponentsCards, type]); + const descriptionModal = useDescriptionModal( + selectedFlowsComponentsCards, + type, + ); const getTotalRowsCount = () => { if (type === "all") return allFlows?.length; return allFlows?.filter( - (f) => (f.is_component ?? false) === (type === "component") + (f) => (f.is_component ?? false) === (type === "component"), )?.length; }; @@ -368,7 +279,7 @@ export default function ComponentsComponent({ open={openDelete} setOpen={setOpenDelete} onConfirm={handleDeleteMultiple} - description={getDescriptionModal} + description={descriptionModal} > <>