diff --git a/src/components/shared/TableFilterProfiles.tsx b/src/components/shared/TableFilterProfiles.tsx index 592cc2b04f..0c02a1a472 100644 --- a/src/components/shared/TableFilterProfiles.tsx +++ b/src/components/shared/TableFilterProfiles.tsx @@ -12,10 +12,9 @@ import { } from "../../thunks/tableThunks"; import { getFilters } from "../../selectors/tableFilterSelectors"; import { FilterData, loadFilterProfile } from "../../slices/tableFilterSlice"; -import { AppThunk, useAppDispatch, useAppSelector } from "../../store"; +import { useAppDispatch, useAppSelector } from "../../store"; import { useHotkeys } from "react-hotkeys-hook"; import { availableHotkeys } from "../../configs/hotkeysConfig"; -import { AsyncThunk } from "@reduxjs/toolkit"; import ButtonLikeAnchor from "./ButtonLikeAnchor"; import { ParseKeys } from "i18next"; import { Resource } from "../../slices/tableSlice"; @@ -29,13 +28,11 @@ const TableFiltersProfiles = ({ showFilterSettings, setFilterSettings, loadResource, - loadResourceIntoTable, resource, }: { showFilterSettings: boolean, setFilterSettings: (_: boolean) => void, - loadResource: AsyncThunk, - loadResourceIntoTable: () => AppThunk, + loadResource: () => Promise, resource: Resource, }) => { const dispatch = useAppDispatch(); @@ -132,14 +129,13 @@ const TableFiltersProfiles = ({ } }; - const chooseFilterProfile = (filterMap: FilterData[]) => { + const chooseFilterProfile = async (filterMap: FilterData[]) => { dispatch(loadFilterProfile(filterMap)); // No matter what, we go to page one. dispatch(goToPage(0)); // Reload resources when filters are removed - dispatch(loadResource()); - dispatch(loadResourceIntoTable()); + await loadResource(); }; return ( diff --git a/src/components/shared/TableFilters.tsx b/src/components/shared/TableFilters.tsx index e938bd8e66..f9ce18bded 100644 --- a/src/components/shared/TableFilters.tsx +++ b/src/components/shared/TableFilters.tsx @@ -13,17 +13,18 @@ import { resetFilterValues, } from "../../slices/tableFilterSlice"; import { - goToPage, -} from "../../thunks/tableThunks"; + deselectAll, + setOffset, + setPageActive, +} from "../../slices/tableSlice"; import TableFilterProfiles from "./TableFilterProfiles"; import { availableHotkeys } from "../../configs/hotkeysConfig"; import { useHotkeys } from "react-hotkeys-hook"; import moment from "moment"; -import { AppThunk, useAppDispatch, useAppSelector } from "../../store"; +import { useAppDispatch, useAppSelector } from "../../store"; import { renderValidDate } from "../../utils/dateUtils"; import { getCurrentLanguageInformation } from "../../utils/utils"; import DropDown from "./DropDown"; -import { AsyncThunk } from "@reduxjs/toolkit"; import ButtonLikeAnchor from "./ButtonLikeAnchor"; import { ParseKeys } from "i18next"; import SearchContainer from "./SearchContainer"; @@ -36,11 +37,9 @@ import { LuSettings, LuX } from "react-icons/lu"; */ const TableFilters = ({ loadResource, - loadResourceIntoTable, resource, }: { - loadResource: AsyncThunk, - loadResourceIntoTable: () => AppThunk, + loadResource: () => Promise, resource: Resource, }) => { const { t } = useTranslation(); @@ -77,8 +76,7 @@ const TableFilters = ({ dispatch(resetFilterValues()); // Reload resources when filters are removed - await dispatch(loadResource()); - dispatch(loadResourceIntoTable()); + await loadResource(); }; // Remove a certain filter @@ -92,8 +90,7 @@ const TableFilters = ({ dispatch(editFilterValue({ filterName: filter.name, value: "", resource })); // Reload resources when filter is removed - await dispatch(loadResource()); - dispatch(loadResourceIntoTable()); + await loadResource(); }; const handleSearchChange = (value: string) => { @@ -141,10 +138,11 @@ const TableFilters = ({ // This helps increase performance by reducing the number of calls to load resources. const applyFilterChangesDebounced = async () => { // No matter what, we go to page one. - dispatch(goToPage(0)); + dispatch(deselectAll()); + dispatch(setOffset(0)); + dispatch(setPageActive(1)); // Reload of resource - await dispatch(loadResource()); - dispatch(loadResourceIntoTable()); + await loadResource(); }; useEffect(() => { @@ -207,9 +205,10 @@ const TableFilters = ({ setFilterSelector(false); setSelectedFilter(""); // Reload of resource after going to very first page. - dispatch(goToPage(0)); - await dispatch(loadResource()); - dispatch(loadResourceIntoTable()); + dispatch(deselectAll()); + dispatch(setOffset(0)); + dispatch(setPageActive(1)); + await loadResource(); } } }; @@ -374,7 +373,6 @@ const TableFilters = ({ setFilterSettings={setFilterSettings} resource={resource} loadResource={loadResource} - loadResourceIntoTable={loadResourceIntoTable} /> )} diff --git a/src/components/shared/TablePage.tsx b/src/components/shared/TablePage.tsx index e3a15ab5d8..e1f6cc579a 100644 --- a/src/components/shared/TablePage.tsx +++ b/src/components/shared/TablePage.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useEffect } from "react"; +import { ReactNode, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import TableFilters from "../shared/TableFilters"; import Table, { TemplateMap } from "../shared/Table"; @@ -7,7 +7,7 @@ import { fetchFilters } from "../../slices/tableFilterSlice"; import { CreateType, NavBarLink } from "../NavBar"; import { AppThunk, RootState, useAppDispatch, useAppSelector } from "../../store"; import { resetTableProperties, Resource } from "../../slices/tableSlice"; -import { AsyncThunk } from "@reduxjs/toolkit"; +import { AsyncThunk, PayloadAction } from "@reduxjs/toolkit"; import { ParseKeys } from "i18next"; import { useLocation } from "react-router"; import MainPage from "./MainPage"; @@ -40,14 +40,70 @@ const TablePage = ({ }) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const currentLoadRequest = useRef<{ abort:() => void } | null>(null); + const latestLoadRequestId = useRef(0); + const allowLoadIntoTable = useRef(true); + const currentLoadSource = useRef<"auto" | "filters">(null); + const autoRefreshPaused = useRef(false); + const autoRefreshPauseTimeout = useRef | null>(null); const location = useLocation(); const numberOfRows = useAppSelector(state => getTotalResources(state)); + const pauseAutoRefresh = () => { + autoRefreshPaused.current = true; + + if (autoRefreshPauseTimeout.current) { + clearTimeout(autoRefreshPauseTimeout.current); + } + + autoRefreshPauseTimeout.current = setTimeout(() => { + autoRefreshPaused.current = false; + autoRefreshPauseTimeout.current = null; + }, 2000); + }; + + const loadResource = async (source: "auto" | "filters" = "auto") => { + if (source === "filters") { + pauseAutoRefresh(); + } + + if (source === "auto" && autoRefreshPaused.current) { + return; + } + + if (source === "auto" && currentLoadSource.current === "filters") { + return; + } + + const requestId = ++latestLoadRequestId.current; + currentLoadSource.current = source; + + currentLoadRequest.current?.abort?.(); + + const fetchRequest = dispatch(fetchResource()) as Promise> & { abort:() => void }; + currentLoadRequest.current = fetchRequest; + + const fetchResult = await fetchRequest as { meta?: { requestStatus?: string } }; + + if (requestId === latestLoadRequestId.current) { + currentLoadSource.current = null; + } + + if ( + allowLoadIntoTable.current + && requestId === latestLoadRequestId.current + && fetchResult?.meta?.requestStatus === "fulfilled" + ) { + dispatch(loadResourceIntoTable()); + } + }; + + const loadResourceFromFilters = () => loadResource("filters"); + useEffect(() => { - // State variable for interrupting the load function - let allowLoadIntoTable = true; + allowLoadIntoTable.current = true; // Clear table of previous data dispatch(resetTableProperties()); @@ -55,22 +111,18 @@ const TablePage = ({ dispatch(fetchFilters(resource)); // Load resource on mount - const loadResource = async () => { - // Fetching resources from server - await dispatch(fetchResource()); - - // Load resources into table - if (allowLoadIntoTable) { - dispatch(loadResourceIntoTable()); - } - }; - loadResource(); + loadResource("auto"); // Fetch resources every minute - const fetchResourceInterval = setInterval(loadResource, 5000); + const fetchResourceInterval = setInterval(() => loadResource("auto"), 5000); return () => { - allowLoadIntoTable = false; + allowLoadIntoTable.current = false; + currentLoadRequest.current?.abort?.(); + if (autoRefreshPauseTimeout.current) { + clearTimeout(autoRefreshPauseTimeout.current); + autoRefreshPauseTimeout.current = null; + } clearInterval(fetchResourceInterval); }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -91,8 +143,7 @@ const TablePage = ({ {/* Include filters component */}