(sideBarFolderButtons): add success alert for folder upload

♻️ (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
This commit is contained in:
cristhianzl 2024-06-07 10:38:32 -03:00
commit fe371d650b
10 changed files with 374 additions and 167 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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}
>
<></>
</DeleteConfirmationModal>