From bcbc48681f12aa7ea47aaacb73d2dd670e4ea0da Mon Sep 17 00:00:00 2001 From: Arnei Date: Fri, 21 Feb 2025 15:07:35 +0100 Subject: [PATCH 1/2] Reduce duplicate code for table action dropdown Creates a new component for table action dropdowns. This should result in more readable code and make it easier to introduce the component into other tables, should we ever need to. --- src/components/events/Events.tsx | 92 +++++------------- src/components/events/Series.tsx | 57 +++-------- src/components/shared/TableActionDropdown.tsx | 95 +++++++++++++++++++ 3 files changed, 132 insertions(+), 112 deletions(-) create mode 100644 src/components/shared/TableActionDropdown.tsx diff --git a/src/components/events/Events.tsx b/src/components/events/Events.tsx index aaa9f8dd83..734a657989 100644 --- a/src/components/events/Events.tsx +++ b/src/components/events/Events.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import cn from "classnames"; import { useLocation } from "react-router"; import TableFilters from "../shared/TableFilters"; import Stats from "../shared/Stats"; @@ -39,9 +38,7 @@ import EventDetailsModal from "./partials/modals/EventDetailsModal"; import { showModal } from "../../selectors/eventDetailsSelectors"; import { eventsLinks, loadEvents } from "./partials/EventsNavigation"; import { Modal, ModalHandle } from "../shared/modals/Modal"; - -// References for detecting a click outside of the container of the dropdown menu -const containerAction = React.createRef(); +import TableActionDropdown from "../shared/TableActionDropdown"; /** * This component renders the table view of events @@ -53,7 +50,6 @@ const Events = () => { const currentFilterType = useAppSelector(state => getCurrentFilterResource(state)); const displayEventDetailsModal = useAppSelector(state => showModal(state)); - const [displayActionMenu, setActionMenu] = useState(false); const [displayNavigation, setNavigation] = useState(false); const newEventModalRef = useRef(null); const startTaskModalRef = useRef(null); @@ -82,34 +78,15 @@ const Events = () => { // Load events on mount loadEvents(dispatch); - // Function for handling clicks outside of an open dropdown menu - const handleClickOutside = (e: MouseEvent) => { - if ( - containerAction.current && - !containerAction.current.contains(e.target as Node) - ) { - setActionMenu(false); - } - }; - // Fetch events every five seconds let fetchEventsInterval = setInterval(() => loadEvents(dispatch), 5000); - // Event listener for handle a click outside of dropdown menu - window.addEventListener("mousedown", handleClickOutside); - return () => { - window.removeEventListener("mousedown", handleClickOutside); clearInterval(fetchEventsInterval); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.hash]); - const handleActionMenu = (e: React.MouseEvent) => { - e.preventDefault(); - setActionMenu(!displayActionMenu); - }; - const onNewEventModal = async () => { await dispatch(fetchEventMetadata()); await dispatch(fetchAssetUploadOptions()); @@ -201,48 +178,31 @@ const Events = () => {
-
handleActionMenu(e)} - ref={containerAction} - > - {t("BULK_ACTIONS.CAPTION")} - {/* show dropdown if actions is clicked*/} - {displayActionMenu && ( -
    - {hasAccess("ROLE_UI_EVENTS_DELETE", user) && ( -
  • - -
  • - )} - {hasAccess("ROLE_UI_TASKS_CREATE", user) && ( -
  • - -
  • - )} - {hasAccess("ROLE_UI_EVENTS_DETAILS_SCHEDULING_EDIT", user) && - hasAccess("ROLE_UI_EVENTS_DETAILS_METADATA_EDIT", user) && ( -
  • - -
  • - )} - {hasAccess("ROLE_UI_EVENTS_DETAILS_METADATA_EDIT", user) && ( -
  • - -
  • - )} -
- )} -
- + deleteModalRef.current?.open(), + text: "BULK_ACTIONS.DELETE.EVENTS.CAPTION", + }, + { + accessRole: ["ROLE_UI_TASKS_CREATE"], + handleOnClick: () => startTaskModalRef.current?.open(), + text: "BULK_ACTIONS.SCHEDULE_TASK.CAPTION", + }, + { + accessRole: ["ROLE_UI_EVENTS_DETAILS_SCHEDULING_EDIT", "ROLE_UI_EVENTS_DETAILS_METADATA_EDIT"], + handleOnClick: () => editScheduledEventsModalRef.current?.open(), + text: "BULK_ACTIONS.EDIT_EVENTS.CAPTION", + }, + { + accessRole: ["ROLE_UI_EVENTS_DETAILS_METADATA_EDIT"], + handleOnClick: () => editMetadataEventsModalRef.current?.open(), + text: "BULK_ACTIONS.EDIT_EVENTS_METADATA.CAPTION", + } + ]} + disabled={!showActions} + /> {/* Include filters component*/} (); +import TableActionDropdown from "../shared/TableActionDropdown"; /** * This component renders the table view of series @@ -40,12 +35,10 @@ const containerAction = React.createRef(); const Series = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const [displayActionMenu, setActionMenu] = useState(false); const [displayNavigation, setNavigation] = useState(false); const newSeriesModalRef = useRef(null); const deleteModalRef = useRef(null); - const user = useAppSelector(state => getUserInformation(state)); const currentFilterType = useAppSelector(state => getCurrentFilterResource(state)); let location = useLocation(); @@ -67,34 +60,15 @@ const Series = () => { // Load events on mount loadSeries(dispatch); - // Function for handling clicks outside of an dropdown menu - const handleClickOutside = (e: MouseEvent) => { - if ( - containerAction.current && - !containerAction.current.contains(e.target as Node) - ) { - setActionMenu(false); - } - }; - // Fetch series every minute let fetchSeriesInterval = setInterval(() => loadSeries(dispatch), 5000); - // Event listener for handle a click outside of dropdown menu - window.addEventListener("mousedown", handleClickOutside); - return () => { - window.removeEventListener("mousedown", handleClickOutside); clearInterval(fetchSeriesInterval); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.hash]); - const handleActionMenu = (e: React.MouseEvent) => { - e.preventDefault(); - setActionMenu(!displayActionMenu); - }; - const onNewSeriesModal = async () => { await dispatch(fetchSeriesMetadata()); await dispatch(fetchSeriesThemes()); @@ -142,25 +116,16 @@ const Series = () => {
-
handleActionMenu(e)} - ref={containerAction} - > - {t("BULK_ACTIONS.CAPTION")} - {/* show dropdown if actions is clicked*/} - {displayActionMenu && ( -
    - {hasAccess("ROLE_UI_SERIES_DELETE", user) && ( -
  • - -
  • - )} -
- )} -
+ deleteModalRef.current?.open(), + text: "BULK_ACTIONS.DELETE.SERIES.CAPTION", + }, + ]} + disabled={!showActions} + /> {/* Include filters component */} (); + +/** + * A dropdown menu displayed above a table. The menu contains actions that can + * affect one or multiple selected entries in the table. + */ +const TableActionDropdown = ({ + actions, + disabled=true, +}: { + actions: React.ComponentProps[] + disabled: boolean +}) => { + const { t } = useTranslation(); + + const [displayActionMenu, setActionMenu] = useState(false); + + useEffect(() => { + // Function for handling clicks outside of an open dropdown menu + const handleClickOutside = (e: MouseEvent) => { + if ( + containerAction.current && + !containerAction.current.contains(e.target as Node) + ) { + setActionMenu(false); + } + }; + + // Event listener for handle a click outside of dropdown menu + window.addEventListener("mousedown", handleClickOutside); + + return () => { + window.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + const handleActionMenu = (e: React.MouseEvent) => { + e.preventDefault(); + setActionMenu(!displayActionMenu); + }; + + return ( +
handleActionMenu(e)} + ref={containerAction} + > + {t("BULK_ACTIONS.CAPTION")} + {/* show dropdown if actions is clicked*/} + {displayActionMenu && ( +
    + {actions.map((action => + + ))} +
+ )} +
+ ) +}; + +const Action = ({ + accessRole, + handleOnClick, + text, +}: { + accessRole: string[] + handleOnClick: (() => unknown) | void | undefined + text: string +}) => { + const { t } = useTranslation(); + const user = useAppSelector(state => getUserInformation(state)); + + return ( + !!handleOnClick && + accessRole.every((accessRole) => hasAccess(accessRole, user)) && ( +
  • + +
  • + ) + ) +} + +export default TableActionDropdown; \ No newline at end of file From 8c68128e5029aea4f1d948ffede913565a1fc332 Mon Sep 17 00:00:00 2001 From: Arnei Date: Fri, 14 Mar 2025 09:27:07 +0100 Subject: [PATCH 2/2] Add missing eof newline --- src/components/shared/TableActionDropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shared/TableActionDropdown.tsx b/src/components/shared/TableActionDropdown.tsx index b9cb7cd080..59b9a63c77 100644 --- a/src/components/shared/TableActionDropdown.tsx +++ b/src/components/shared/TableActionDropdown.tsx @@ -92,4 +92,4 @@ const Action = ({ ) } -export default TableActionDropdown; \ No newline at end of file +export default TableActionDropdown;