Skip to content
Snippets Groups Projects
Commit 663fd02d authored by Remco Tukker's avatar Remco Tukker
Browse files

custom hook to cleanly load data with an optional preview flag

parent 410cf85a
No related branches found
No related tags found
1 merge request!71Create a preview mode for publishing survey data
Showing
with 132 additions and 151 deletions
......@@ -4,6 +4,7 @@ import SidebarProvider from "./helpers/SidebarProvider";
import UserProvider from "./shared/UserProvider";
import FilterSelectionProvider from "./helpers/FilterSelectionProvider";
import ChartContainerProvider from "./helpers/ChartContainerProvider";
import PreviewProvider from "./helpers/PreviewProvider";
function Providers({ children }): ReactElement {
......@@ -12,7 +13,9 @@ function Providers({ children }): ReactElement {
<UserProvider>
<FilterSelectionProvider>
<ChartContainerProvider>
<PreviewProvider>
{children}
</PreviewProvider>
</ChartContainerProvider>
</FilterSelectionProvider>
</UserProvider>
......
import React, { Dispatch, SetStateAction, createContext, useState } from 'react';
interface Props {
children: React.ReactNode;
}
const PreviewContext = createContext<{
preview: boolean;
setPreview: Dispatch<SetStateAction<boolean>>;
}>({
preview: false,
setPreview: () => {}
});
const PreviewProvider: React.FC<Props> = ({ children }) => {
const [preview, setPreview] = useState<boolean>(false);
return (
<PreviewContext.Provider value={{ preview, setPreview }}>
{children}
</PreviewContext.Provider>
);
};
export { PreviewContext };
export default PreviewProvider;
\ No newline at end of file
import { cartesianProduct } from 'cartesian-product-multiple-arrays';
import {
FundingSource, FundingSourceDataset, ChargingStructure, NrenAndYearDatapoint, Nren,
Budget, BasicDataset, NrenStaff, NrenStaffDataset, Organisation, ECProject, Policy, FilterSelection
FundingSource, FundingSourceDataset, ChargingStructure,
Budget, BasicDataset, NrenStaff, NrenStaffDataset, Organisation, ECProject, Policy
} from "../Schema";
// create a color from a string, credits https://stackoverflow.com/a/16348977
......@@ -19,54 +19,6 @@ const stringToColour = function (str) {
return colour;
}
export function getYearsAndNrens(sourceData: NrenAndYearDatapoint[]) {
const years = new Set<number>();
const nrenMap = new Map<string, Nren>();
sourceData.forEach(datapoint => {
years.add(datapoint.year);
nrenMap.set(datapoint.nren, { name: datapoint.nren, country: datapoint.nren_country });
});
return { years: years, nrens: nrenMap };
}
export async function loadDataWithFilterSelectionFallback<Datatype extends NrenAndYearDatapoint>(
url: string,
setData: React.Dispatch<React.SetStateAction<Datatype[]>>,
setFilterSelection: React.Dispatch<React.SetStateAction<FilterSelection>>
) {
const response = await fetch(url);
const data: Datatype[] = await response.json();
setData(data);
// filter fallback for when nothing is selected (only last year for all nrens)
const { years, nrens } = getYearsAndNrens(data);
setFilterSelection(previous => {
const visibleYears = previous.selectedYears.filter(year => years.has(year));
const newSelectedYears = visibleYears.length ? previous.selectedYears : [Math.max(...years)];
const visibleNrens = previous.selectedNrens.filter(nren => nrens.has(nren));
const newSelectedNrens = visibleNrens.length ? previous.selectedNrens : [...nrens.keys()];
return { selectedYears: newSelectedYears, selectedNrens: newSelectedNrens };
});
}
export async function loadDataWithFilterNrenSelectionFallback<Datatype extends NrenAndYearDatapoint>(
url: string,
setData: React.Dispatch<React.SetStateAction<Datatype[]>>,
setFilterSelection: React.Dispatch<React.SetStateAction<FilterSelection>>
) {
const response = await fetch(url);
const data: Datatype[] = await response.json();
setData(data);
// filter fallback for when nothing is selected (all nrens)
const { nrens } = getYearsAndNrens(data);
setFilterSelection(previous => {
const visibleNrens = previous.selectedNrens.filter(nren => nrens.has(nren));
const newSelectedNrens = visibleNrens.length ? previous.selectedNrens : [...nrens.keys()];
return { selectedYears: previous.selectedYears, selectedNrens: newSelectedNrens };
});
}
function getColorMap() {
const rgbToHex = (r: number, g: number, b: number) => '#' + [r, g, b].map(x => {
const hex = x.toString(16)
......
import { useContext, useEffect, useMemo, useState } from "react";
import { PreviewContext } from "./PreviewProvider";
import { Nren, NrenAndYearDatapoint } from "../Schema";
function getYearsAndNrens(sourceData: NrenAndYearDatapoint[]) {
const years = new Set<number>();
const nrenMap = new Map<string, Nren>();
sourceData.forEach(datapoint => {
years.add(datapoint.year);
nrenMap.set(datapoint.nren, { name: datapoint.nren, country: datapoint.nren_country });
});
return { years: years, nrens: nrenMap };
}
export function useData<Datatype extends NrenAndYearDatapoint>(url, setFilterSelection) {
const [data, setData] = useState<Datatype[]>([]);
const {preview} = useContext(PreviewContext);
const loadUrl = url + (preview ? '?preview' : '');
useEffect(() => {
fetch(loadUrl)
.then(response => response.json())
.then(json => {
setData(json);
// filter fallback for when nothing is selected (only last year for all nrens)
const { years, nrens } = getYearsAndNrens(json);
setFilterSelection(previous => {
const visibleYears = previous.selectedYears.filter(year => years.has(year));
const newSelectedYears = visibleYears.length ? previous.selectedYears : [Math.max(...years)];
const visibleNrens = previous.selectedNrens.filter(nren => nrens.has(nren));
const newSelectedNrens = visibleNrens.length ? previous.selectedNrens : [...nrens.keys()];
return { selectedYears: newSelectedYears, selectedNrens: newSelectedNrens };
});
// TODO for budget we could use this fallback instead:
// setFilterSelection(previous => {
// const visibleNrens = previous.selectedNrens.filter(nren => nrens.has(nren));
// const newSelectedNrens = visibleNrens.length ? previous.selectedNrens : [...nrens.keys()];
// return { selectedYears: previous.selectedYears, selectedNrens: newSelectedNrens };
// });
});
}, [loadUrl, setFilterSelection]);
const { years, nrens } = useMemo(
() => getYearsAndNrens(data),
[data]
);
return {data: data, years: years, nrens: nrens};
}
import React, { ReactElement, useContext, useEffect, useMemo, useState } from 'react';
import React, { ReactElement, useContext } from 'react';
import { Row } from "react-bootstrap";
import { Budget } from "../Schema";
import { createBudgetDataset, getYearsAndNrens, loadDataWithFilterNrenSelectionFallback } from "../helpers/dataconversion";
import { createBudgetDataset } from "../helpers/dataconversion";
import DataPage from '../components/DataPage';
import Filter from "../components/graphing/Filter";
import LineGraph from "../components/graphing/LineGraph";
......@@ -11,16 +11,12 @@ import DownloadDataButton from "../components/DownloadDataButton";
import { FilterSelectionContext } from '../helpers/FilterSelectionProvider';
import DownloadImageChartButton from "../components/DownloadImageChartButton";
import ChartContainer from "../components/graphing/ChartContainer";
import { useData } from '../helpers/useData';
function BudgetPage(): ReactElement {
const [budgetResponse, setBudget] = useState<Budget[]>([]);
const {filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
const { nrens } = useMemo(
() => getYearsAndNrens(budgetResponse),
[budgetResponse]
);
const {data: budgetResponse, nrens} = useData<Budget>('/api/budget/', setFilterSelection);
const budgetData = createBudgetDataset(budgetResponse);
......@@ -28,10 +24,6 @@ function BudgetPage(): ReactElement {
dataset.hidden = !filterSelection.selectedNrens.includes(dataset.label);
});
useEffect(() => {
loadDataWithFilterNrenSelectionFallback('/api/budget/', setBudget, setFilterSelection);
}, [setFilterSelection]);
const filterNode = <Filter
filterOptions={{ availableYears: [], availableNrens: [...nrens.values()] }}
filterSelection={filterSelection}
......
import React, { useContext, useMemo, useState } from "react";
import React, { useContext } from "react";
import {Row, Table} from "react-bootstrap";
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
import { ChargingStructure } from "../Schema";
import { createChargingStructureDataLookup, getYearsAndNrens, loadDataWithFilterSelectionFallback } from "../helpers/dataconversion";
import { createChargingStructureDataLookup } from "../helpers/dataconversion";
import ColorPill from "../components/ColorPill";
import DataPage from "../components/DataPage";
import Filter from "../components/graphing/Filter";
......@@ -12,6 +12,7 @@ import DownloadDataButton from "../components/DownloadDataButton";
import { FilterSelectionContext } from "../helpers/FilterSelectionProvider";
import DownloadImageChartButton from "../components/DownloadImageChartButton";
import ChartContainer from "../components/graphing/ChartContainer";
import { useData } from "../helpers/useData";
ChartJS.register(
CategoryScale,
......@@ -23,13 +24,8 @@ ChartJS.register(
);
function ChargingStructurePage(): React.ReactElement {
const [chargingStructureData, setChargingStructureData] = useState<ChargingStructure[]>([]);
const {filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
const { years, nrens } = useMemo(
() => getYearsAndNrens(chargingStructureData),
[chargingStructureData]
);
const {data: chargingStructureData, years, nrens} = useData<ChargingStructure>('/api/charging/', setFilterSelection);
const selectedData = (chargingStructureData).filter(data =>
filterSelection.selectedYears.includes(data.year) && filterSelection.selectedNrens.includes(data.nren)
......@@ -37,10 +33,6 @@ function ChargingStructurePage(): React.ReactElement {
const dataLookup = createChargingStructureDataLookup(selectedData);
React.useEffect(() => {
loadDataWithFilterSelectionFallback('/api/charging/', setChargingStructureData, setFilterSelection);
}, [setFilterSelection]);
const filterNode = <Filter
filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
filterSelection={filterSelection}
......
import React, { ReactElement } from "react";
import React, { ReactElement, useContext } from "react";
import { Container, Row } from "react-bootstrap";
import CollapsibleBox from "../components/CollapsibleBox";
import PageHeader from "../components/global/PageHeader"
import Banner from "../components/global/Banner";
import { Link } from "react-router-dom";
import { Link, useSearchParams } from "react-router-dom";
import { Sections } from "../helpers/constants";
import { PreviewContext } from "../helpers/PreviewProvider";
function CompendiumData(): ReactElement {
const [searchParams] = useSearchParams();
const { setPreview } = useContext(PreviewContext);
if (searchParams.get('preview') !== null) {
setPreview(true);
}
return (
<main className="grow">
<PageHeader type={'data'}/>
......
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useContext } from 'react';
import {Row, Table} from "react-bootstrap";
import { ECProject } from "../Schema";
import { createECProjectsDataLookup, getYearsAndNrens, loadDataWithFilterSelectionFallback } from '../helpers/dataconversion';
import { createECProjectsDataLookup } from '../helpers/dataconversion';
import DataPage from '../components/DataPage';
import Filter from "../components/graphing/Filter"
import {ExportType, Sections} from '../helpers/constants';
......@@ -10,6 +10,7 @@ import DownloadDataButton from "../components/DownloadDataButton";
import { FilterSelectionContext } from '../helpers/FilterSelectionProvider';
import DownloadImageChartButton from "../components/DownloadImageChartButton";
import ChartContainer from "../components/graphing/ChartContainer";
import { useData } from '../helpers/useData';
function getJSXFromMap(data: Map<string, Map<number, ECProject[]>>) {
......@@ -31,23 +32,14 @@ function getJSXFromMap(data: Map<string, Map<number, ECProject[]>>) {
}
function ECProjects() {
const [projectData, setProjectData] = useState<ECProject[]>([]);
const {filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
const { years, nrens } = useMemo(
() => getYearsAndNrens(projectData),
[projectData]
);
const {data: projectData, years, nrens} = useData<ECProject>('/api/ec-project/', setFilterSelection);
const selectedData = (projectData).filter(project =>
filterSelection.selectedYears.includes(project.year) && filterSelection.selectedNrens.includes(project.nren)
);
const projectDataByYear = createECProjectsDataLookup(selectedData);
useEffect(() => {
loadDataWithFilterSelectionFallback('/api/ec-project/', setProjectData, setFilterSelection);
}, [setFilterSelection]);
const filterNode = <Filter
filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
filterSelection={filterSelection}
......
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useContext } from 'react';
import { Bar } from 'react-chartjs-2';
import { Col, Row } from "react-bootstrap";
import { Chart as ChartJS } from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { FundingSource } from "../Schema";
import { createFundingSourceDataset, getYearsAndNrens, loadDataWithFilterSelectionFallback } from "../helpers/dataconversion";
import { createFundingSourceDataset } from "../helpers/dataconversion";
import DataPage from '../components/DataPage';
import Filter from "../components/graphing/Filter"
import {ExportType, Sections} from '../helpers/constants';
......@@ -13,8 +13,8 @@ import ColorBadge from '../components/ColorBadge';
import DownloadDataButton from "../components/DownloadDataButton";
import { FilterSelectionContext } from '../helpers/FilterSelectionProvider';
import DownloadImageChartButton from "../components/DownloadImageChartButton";
import { ChartContainerContext } from "../helpers/ChartContainerProvider";
import ChartContainer from "../components/graphing/ChartContainer";
import { useData } from '../helpers/useData';
export const chartOptions = {
maintainAspectRatio: false,
......@@ -109,13 +109,9 @@ function FundingSourceLegend() {
}
function FundingSourcePage() {
const [fundingSourceData, setFundingSourceData] = useState<FundingSource[]>([]);
const {filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
const {data: fundingSourceData, years, nrens} = useData<FundingSource>('/api/funding/', setFilterSelection);
const { years, nrens } = useMemo(
() => getYearsAndNrens(fundingSourceData),
[fundingSourceData]
);
const fundingSourceDataset = createFundingSourceDataset(fundingSourceData);
fundingSourceDataset.datasets.forEach(dataset => {
......@@ -132,10 +128,6 @@ function FundingSourcePage() {
});
fundingSourceDataset.labels = fundingSourceDataset.labels.filter((e) => filterSelection.selectedNrens.includes(e));
useEffect(() => {
loadDataWithFilterSelectionFallback('/api/funding/', setFundingSourceData, setFilterSelection);
}, [setFilterSelection]);
const filterNode = <Filter
filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
filterSelection={filterSelection}
......
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useContext } from 'react';
import {Row, Table} from "react-bootstrap";
import { Organisation } from "../Schema";
import { createOrganisationDataLookup, getYearsAndNrens, loadDataWithFilterSelectionFallback } from "../helpers/dataconversion";
import { createOrganisationDataLookup } from "../helpers/dataconversion";
import DataPage from '../components/DataPage';
import Filter from "../components/graphing/Filter"
import {ExportType, Sections} from '../helpers/constants';
......@@ -10,6 +10,7 @@ import DownloadDataButton from "../components/DownloadDataButton";
import { FilterSelectionContext } from '../helpers/FilterSelectionProvider';
import DownloadImageChartButton from "../components/DownloadImageChartButton";
import ChartContainer from "../components/graphing/ChartContainer";
import { useData } from '../helpers/useData';
function getJSXFromMap(data: Map<string, Map<number, Organisation[]>>) {
......@@ -24,23 +25,14 @@ function getJSXFromMap(data: Map<string, Map<number, Organisation[]>>) {
})
}
function ParentOrganisation() {
const [organisationData, setOrganisationData] = useState<Organisation[]>([]);
const {filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
const {data: organisationData, years, nrens} = useData<Organisation>('/api/organization/parent', setFilterSelection);
const { years, nrens } = useMemo(
() => getYearsAndNrens(organisationData),
[organisationData]
);
const selectedData = (organisationData).filter(data =>
const selectedData = organisationData.filter(data =>
filterSelection.selectedYears.includes(data.year) && filterSelection.selectedNrens.includes(data.nren)
);
const organisationDataset = createOrganisationDataLookup(selectedData);
useEffect(() => {
loadDataWithFilterSelectionFallback('/api/organization/parent', setOrganisationData, setFilterSelection);
}, [setFilterSelection]);
const filterNode = <Filter
filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
filterSelection={filterSelection}
......
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useContext } from 'react';
import { Table } from "react-bootstrap";
import { Policy } from "../Schema";
import { createPolicyDataLookup, getYearsAndNrens, loadDataWithFilterSelectionFallback } from '../helpers/dataconversion';
import { createPolicyDataLookup } from '../helpers/dataconversion';
import DataPage from '../components/DataPage';
import Filter from "../components/graphing/Filter"
import { Sections } from '../helpers/constants';
import { FilterSelectionContext } from '../helpers/FilterSelectionProvider';
import { useData } from '../helpers/useData';
function getJSXFromMap(data: Map<string, Map<number, Policy>>) {
const policies = [
......@@ -43,23 +44,14 @@ function getJSXFromMap(data: Map<string, Map<number, Policy>>) {
}
function PolicyPage() {
const [policyData, setProjectData] = useState<Policy[]>([]);
const {filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
const {data: policyData, years, nrens} = useData<Policy>('/api/policy/', setFilterSelection);
const { years, nrens } = useMemo(
() => getYearsAndNrens(policyData),
[policyData]
);
const selectedData = (policyData).filter(project =>
const selectedData = policyData.filter(project =>
filterSelection.selectedYears.includes(project.year) && filterSelection.selectedNrens.includes(project.nren)
);
const policyDataByYear = createPolicyDataLookup(selectedData);
useEffect(() => {
loadDataWithFilterSelectionFallback('/api/policy/', setProjectData, setFilterSelection);
}, [setFilterSelection]);
const filterNode = <Filter
filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
filterSelection={filterSelection}
......
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useContext } from 'react';
import { Bar } from 'react-chartjs-2';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
import { NrenStaff } from "../Schema";
import { createNRENStaffDataset, getYearsAndNrens, loadDataWithFilterSelectionFallback } from "../helpers/dataconversion";
import { createNRENStaffDataset } from "../helpers/dataconversion";
import DataPage from '../components/DataPage';
import Filter from "../components/graphing/Filter"
import {ExportType, Sections} from '../helpers/constants';
......@@ -13,6 +13,7 @@ import {Row} from "react-bootstrap";
import DownloadDataButton from "../components/DownloadDataButton";
import { FilterSelectionContext } from '../helpers/FilterSelectionProvider';
import DownloadImageChartButton from "../components/DownloadImageChartButton";
import { useData } from '../helpers/useData';
ChartJS.register(
CategoryScale,
......@@ -107,13 +108,9 @@ interface inputProps {
}
function StaffGraph({ roles = false }: inputProps) {
const [staffData, setStaffData] = useState<NrenStaff[]>([]);
const {filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
const {data: staffData, years, nrens} = useData<NrenStaff>('/api/staff/', setFilterSelection);
const { years, nrens } = useMemo(
() => getYearsAndNrens(staffData),
[staffData]
);
const nrenStaffDataset = createNRENStaffDataset(staffData, roles, filterSelection.selectedYears[0]);
nrenStaffDataset.datasets.forEach(dataset => {
......@@ -130,10 +127,6 @@ function StaffGraph({ roles = false }: inputProps) {
});
nrenStaffDataset.labels = nrenStaffDataset.labels.filter((e) => filterSelection.selectedNrens.includes(e));
useEffect(() => {
loadDataWithFilterSelectionFallback('/api/staff/', setStaffData, setFilterSelection);
}, [setFilterSelection]);
const filterNode = <Filter
max1year
filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
......
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useContext } from 'react';
import {Row, Table} from "react-bootstrap";
import { Organisation } from "../Schema";
import { createOrganisationDataLookup, getYearsAndNrens, loadDataWithFilterSelectionFallback } from "../helpers/dataconversion";
import { createOrganisationDataLookup } from "../helpers/dataconversion";
import DataPage from '../components/DataPage';
import Filter from "../components/graphing/Filter"
import {ExportType, Sections} from '../helpers/constants';
......@@ -10,6 +10,7 @@ import DownloadDataButton from "../components/DownloadDataButton";
import { FilterSelectionContext } from '../helpers/FilterSelectionProvider';
import DownloadImageChartButton from "../components/DownloadImageChartButton";
import ChartContainer from "../components/graphing/ChartContainer";
import { useData } from '../helpers/useData';
function getJSXFromMap(data: Map<string, Map<number, Organisation[]>>) {
return Array.from(data.entries()).map(([nren, nrenMap]) => {
......@@ -30,23 +31,14 @@ function getJSXFromMap(data: Map<string, Map<number, Organisation[]>>) {
}
function SubOrganisation() {
const [organisationData, setOrganisationData] = useState<Organisation[]>([]);
const {filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
const {data: organisationData, years, nrens} = useData<Organisation>('/api/organization/sub', setFilterSelection);
const { years, nrens } = useMemo(
() => getYearsAndNrens(organisationData),
[organisationData]
);
const selectedData = (organisationData).filter(data =>
const selectedData = organisationData.filter(data =>
filterSelection.selectedYears.includes(data.year) && filterSelection.selectedNrens.includes(data.nren)
);
const organisationDataset = createOrganisationDataLookup(selectedData);
useEffect(() => {
loadDataWithFilterSelectionFallback('/api/organization/sub', setOrganisationData, setFilterSelection);
}, [setFilterSelection]);
const filterNode = <Filter
filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
filterSelection={filterSelection}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment