diff --git a/src/app/hooks/useInfinityScroll.ts b/src/app/hooks/useInfinityScroll.ts new file mode 100644 index 000000000..0857f1592 --- /dev/null +++ b/src/app/hooks/useInfinityScroll.ts @@ -0,0 +1,32 @@ +import { useState, useEffect } from "react"; + +type LoadDataFunction = (searchStr: string, sortBy: string) => void; + +export const useInfinityScroll = ( + callback: LoadDataFunction, + observerTarget: React.MutableRefObject, + searchStr: string, + sortByStr: string +) => { + useEffect(() => { + //handle infinity scroll with IntersectionObserver api + + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting) { + callback(searchStr, sortByStr); + } + }, + { threshold: 1 } + ); + + if (observerTarget.current) { + observer.observe(observerTarget.current); + } + return () => { + if (observerTarget.current) { + observer.unobserve(observerTarget.current); + } + }; + }, []); +}; diff --git a/src/app/modules/home-module/components/Charts/chartsGrid.tsx b/src/app/modules/home-module/components/Charts/chartsGrid.tsx index 04d259b4b..939350db4 100644 --- a/src/app/modules/home-module/components/Charts/chartsGrid.tsx +++ b/src/app/modules/home-module/components/Charts/chartsGrid.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useRef } from "react"; import axios from "axios"; import find from "lodash/find"; import Box from "@material-ui/core/Box"; @@ -10,6 +10,7 @@ import { HomepageTable } from "app/modules/home-module/components/Table"; import { coloredEchartTypes } from "app/modules/chart-module/routes/chart-type/data"; import ReformedGridItem from "app/modules/home-module/components/Charts/reformedGridItem"; import ChartAddnewCard from "./chartAddNewCard"; +import { useInfinityScroll } from "app/hooks/useInfinityScroll"; interface Props { sortBy: string; @@ -19,10 +20,15 @@ interface Props { } export default function ChartsGrid(props: Props) { + const observerTarget = useRef(null); const [cardId, setCardId] = React.useState(0); const [modalDisplay, setModalDisplay] = React.useState(false); const [enableButton, setEnableButton] = React.useState(false); + const [loadedCharts, setLoadedCharts] = React.useState([]); + const limit = 15; + //used over usestate to get current offset value in the IntersectionObserver api, as it is not updated in usestate. + const offset = useRef(0); const charts = useStoreState( (state) => (state.charts.ChartGetList.crudData ?? []) as any[] ); @@ -30,6 +36,9 @@ export default function ChartsGrid(props: Props) { const loadCharts = useStoreActions( (actions) => actions.charts.ChartGetList.fetch ); + const chartsLoadSuccess = useStoreState( + (state) => state.charts.ChartGetList.success + ); const handleDelete = (index?: number) => { setModalDisplay(false); @@ -94,15 +103,27 @@ export default function ChartsGrid(props: Props) { : ""; loadCharts({ storeInCrudData: true, - filterString: `filter={${value}"order":"${sortByStr} desc"}`, + filterString: `filter={${value}"order":"${sortByStr} desc","limit":${limit},"offset":${offset.current}}`, }); + offset.current = offset.current + limit; } + useInfinityScroll(loadData, observerTarget, props.searchStr, props.sortBy); React.useEffect(() => { if (props.searchStr.length === 0) { loadData(props.searchStr, props.sortBy); } }, [props.sortBy]); + React.useEffect(() => { + if (!chartsLoadSuccess) { + return; + } + //update the loaded reports + setLoadedCharts((prevCharts) => { + const f = charts.filter((report, i) => prevCharts[i]?.id !== report.id); + return [...prevCharts, ...f]; + }); + }, [chartsLoadSuccess]); const [,] = useDebounce( () => { @@ -119,7 +140,7 @@ export default function ChartsGrid(props: Props) { {!props.tableView && ( {props.addCard && } - {charts.map((c, index) => ( + {loadedCharts.map((c, index) => ( ({ + data={loadedCharts.map((data) => ({ id: data.id, name: data.name, description: data.title, @@ -145,6 +166,9 @@ export default function ChartsGrid(props: Props) { }))} /> )} + + +
(""); const [enableButton, setEnableButton] = React.useState(false); const [modalDisplay, setModalDisplay] = React.useState(false); + const limit = 15; + //used over usestate to get current offset value in the IntersectionObserver api, as it is not updated in usestate. + const offset = useRef(0); + const [loadedDatasets, setLoadedDatasets] = React.useState< + DatasetListItemAPIModel[] + >([]); + + const loadDatasets = useStoreActions( + (actions) => actions.dataThemes.DatasetGetList.fetch + ); + const clearDatasets = useStoreActions( + (actions) => actions.dataThemes.DatasetGetList.clear + ); + const loadDatasetCount = useStoreActions( + (actions) => actions.dataThemes.DatasetCount.fetch + ); + const datasetLoadSuccess = useStoreState( + (state) => state.dataThemes.DatasetGetList.success + ); + const datasetCount = useStoreState( + (state) => get(state, "dataThemes.DatasetCount.data.count", 0) as number + ); const handleDelete = (id: string) => { deleteDataset(id); @@ -42,10 +66,6 @@ export default function DatasetsGrid(props: Props) { setModalDisplay(true); }; - const loadDatasets = useStoreActions( - (actions) => actions.dataThemes.DatasetGetList.fetch - ); - const datasets = useStoreState( (state) => get( @@ -67,23 +87,46 @@ export default function DatasetsGrid(props: Props) { .catch((error) => console.log(error)); } - function loadData(searchStr: string, sortByStr: string) { + const loadData = (searchStr: string, sortByStr: string) => { const value = searchStr.length > 0 ? `"where":{"name":{"like":"${searchStr}.*","options":"i"}},` : ""; loadDatasets({ storeInCrudData: true, - filterString: `filter={${value}"order":"${sortByStr} desc"}`, + filterString: `filter={${value}"order":"${sortByStr} desc","limit":${limit},"offset":${offset.current}}`, }); - } + offset.current = offset.current + limit; + }; + useInfinityScroll(loadData, observerTarget, props.searchStr, props.sortBy); React.useEffect(() => { + loadDatasetCount({}); if (props.searchStr.length === 0) { loadData(props.searchStr, props.sortBy); } }, [props.searchStr, props.sortBy]); + React.useEffect(() => { + clearDatasets(); + setLoadedDatasets([]); + }, []); + React.useEffect(() => { + if (datasets === null) { + setLoadedDatasets([]); + } + }, [datasets]); + + React.useEffect(() => { + if (!datasetLoadSuccess) { + return; + } + //update the loaded datasets + setLoadedDatasets((prevDatasets) => { + return [...prevDatasets, ...datasets]; + }); + }, [datasetLoadSuccess]); + const [,] = useDebounce( () => { if (props.searchStr.length > 0) { @@ -99,7 +142,7 @@ export default function DatasetsGrid(props: Props) { {!props.tableView && ( {props.addCard && } - {(datasets || []).map((data, index) => ( + {(loadedDatasets || []).map((data, index) => ( {}} handleDelete={() => {}} /> + ))} )} + {props.tableView && ( ({ + data={loadedDatasets.map((data) => ({ id: data.id, name: data.name, description: data.description, @@ -125,6 +170,14 @@ export default function DatasetsGrid(props: Props) { }))} /> )} + + +
(0); const [modalDisplay, setModalDisplay] = React.useState(false); const [enableButton, setEnableButton] = React.useState(false); - + const [loadedReports, setLoadedReports] = React.useState([]); + const limit = 15; + //used over usestate to get current offset value in the IntersectionObserver api, as it is not updated in usestate. + const offset = useRef(0); const reports = useStoreState( (state) => (state.reports.ReportGetList.crudData ?? []) as ReportModel[] ); - const loadReports = useStoreActions( (actions) => actions.reports.ReportGetList.fetch ); + const reportsLoadSuccess = useStoreState( + (state) => state.reports.ReportGetList.success + ); const handleDelete = (index?: number) => { setModalDisplay(false); @@ -87,9 +94,11 @@ export default function ReportsGrid(props: Props) { : ""; loadReports({ storeInCrudData: true, - filterString: `filter={${value}"order":"${sortByStr} desc"}`, + filterString: `filter={${value}"order":"${sortByStr} desc","limit":${limit},"offset":${offset.current}}`, }); + offset.current = offset.current + limit; } + useInfinityScroll(loadData, observerTarget, props.searchStr, props.sortBy); React.useEffect(() => { if (props.searchStr.length === 0) { @@ -97,6 +106,17 @@ export default function ReportsGrid(props: Props) { } }, [props.searchStr, props.sortBy]); + React.useEffect(() => { + if (!reportsLoadSuccess) { + return; + } + //update the loaded reports + setLoadedReports((prevReports) => { + const f = reports.filter((report, i) => prevReports[i]?.id !== report.id); + return [...prevReports, ...f]; + }); + }, [reportsLoadSuccess]); + const [,] = useDebounce( () => { if (props.searchStr.length > 0) { @@ -112,7 +132,7 @@ export default function ReportsGrid(props: Props) { {!props.tableView && ( {props.addCard && } - {reports.map((data, index) => ( + {loadedReports.map((data, index) => ( ({ + data={loadedReports.map((data) => ({ id: data.id, name: data.name, description: data.title, @@ -143,6 +163,9 @@ export default function ReportsGrid(props: Props) { }))} /> )} + + +
(null); @@ -389,7 +381,17 @@ export default function HomeModule() { -
{displayGrid(searchValue, sortValue)}
+
+ {displayGrid(searchValue, sortValue)} +
diff --git a/src/app/state/api/action-reducers/data-themes/index.ts b/src/app/state/api/action-reducers/data-themes/index.ts index 74a69fb6d..367f72c2a 100644 --- a/src/app/state/api/action-reducers/data-themes/index.ts +++ b/src/app/state/api/action-reducers/data-themes/index.ts @@ -32,3 +32,7 @@ export const DatasetGetList: ApiCallModel = { export const DatasetCreate: ApiCallModel = { ...APIModel(`${process.env.REACT_APP_API}/datasets`), }; + +export const DatasetCount: ApiCallModel = { + ...APIModel(`${process.env.REACT_APP_API}/datasets/count`), +}; diff --git a/src/app/state/api/interfaces/index.ts b/src/app/state/api/interfaces/index.ts index c69056062..c472725f3 100644 --- a/src/app/state/api/interfaces/index.ts +++ b/src/app/state/api/interfaces/index.ts @@ -358,6 +358,7 @@ export interface StoreModel { DataThemeDuplicate: ApiCallModel; DataThemeGetList: ApiCallModel; DatasetGetList: ApiCallModel; + DatasetCount: ApiCallModel; DatasetCreate: ApiCallModel; }; charts: { diff --git a/src/app/state/store/index.ts b/src/app/state/store/index.ts index 4227faf8b..0ab522ce0 100644 --- a/src/app/state/store/index.ts +++ b/src/app/state/store/index.ts @@ -191,6 +191,7 @@ import { DataThemeUpdate, DatasetGetList, DatasetCreate, + DatasetCount, } from "app/state/api/action-reducers/data-themes"; import countrySummary from "../api/action-reducers/cms/countrySummary"; import notesAndDisclaimers from "../api/action-reducers/cms/notesAndDisclaimers"; @@ -452,6 +453,7 @@ const storeContent: StoreModel = { DataThemeDuplicate: persist(DataThemeDuplicate), DataThemeGetList: persist(DataThemeGetList), DatasetGetList: persist(DatasetGetList), + DatasetCount: persist(DatasetCount), DatasetCreate: persist(DatasetCreate), }, charts: { diff --git a/src/index.css b/src/index.css index 4c8244028..fe1f29fb4 100644 --- a/src/index.css +++ b/src/index.css @@ -79,8 +79,10 @@ body { transition: background 0.2s ease-in-out; font-family: "GothamNarrow-Book", "Helvetica Neue", sans-serif; background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #f2f7fd 100%); + } + #root { width: 100vw; height: 100vh;