Skip to content
Snippets Groups Projects
Commit ca9854f6 authored by Bjarke Madsen's avatar Bjarke Madsen
Browse files

Merge branch 'feature/COMP-160_suborganisation_page' into 'develop'

first version of organisation pages

See merge request !9
parents ddb65899 4ea87a98
Branches
Tags
1 merge request!9first version of organisation pages
...@@ -10,6 +10,8 @@ import FundingSourcePage from "./pages/FundingSource"; ...@@ -10,6 +10,8 @@ import FundingSourcePage from "./pages/FundingSource";
import ChargingStructurePage from "./pages/ChargingStructure"; import ChargingStructurePage from "./pages/ChargingStructure";
import StaffGraph from "./pages/StaffGraph"; import StaffGraph from "./pages/StaffGraph";
import { FilterSelection } from "./Schema"; import { FilterSelection } from "./Schema";
import SubOrganisation from "./pages/SubOrganisation";
import ParentOrganisation from "./pages/ParentOrganisation";
function App(): ReactElement { function App(): ReactElement {
...@@ -31,6 +33,8 @@ function App(): ReactElement { ...@@ -31,6 +33,8 @@ function App(): ReactElement {
<Route path="/data/employment" element={<StaffGraph filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} /> <Route path="/data/employment" element={<StaffGraph filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
<Route path="/data/roles" element={<StaffGraph roles filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} /> <Route path="/data/roles" element={<StaffGraph roles filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
<Route path="/charging" element={<ChargingStructurePage />} /> <Route path="/charging" element={<ChargingStructurePage />} />
<Route path="/suborganisations" element={<SubOrganisation filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
<Route path="/parentorganisation" element={<ParentOrganisation filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
<Route path="*" element={<Landing />} /> <Route path="*" element={<Landing />} />
</Routes> </Routes>
<GeantFooter /> <GeantFooter />
......
...@@ -122,4 +122,11 @@ export interface NrenStaffDataset { ...@@ -122,4 +122,11 @@ export interface NrenStaffDataset {
stack: string stack: string
hidden: boolean hidden: boolean
}[] }[]
} }
\ No newline at end of file
export interface Organisation {
nren: string,
year: number,
name: string,
role?: string
}
...@@ -3,7 +3,7 @@ import { ...@@ -3,7 +3,7 @@ import {
FundingSource, FundingSource,
FundingSourceDataset, FundingSourceDataset,
ChargingStructure, ChargingStructure,
ChargingStructureDataset, Budget, BasicDataset, NrenStaff, NrenStaffDataset ChargingStructureDataset, Budget, BasicDataset, NrenStaff, NrenStaffDataset, Organisation
} from "../Schema"; } from "../Schema";
const DEFAULT_CHARGING_STRUCTURE_DATA = [ const DEFAULT_CHARGING_STRUCTURE_DATA = [
...@@ -244,6 +244,26 @@ export function createChargingStructureDataset(chargingStructureData: ChargingSt ...@@ -244,6 +244,26 @@ export function createChargingStructureDataset(chargingStructureData: ChargingSt
} }
export function createOrganisationDataLookup(organisationEntries: Organisation[]) {
const nrenMap = new Map<string, Map<number, Organisation[]>>();
organisationEntries.forEach(entry => {
let nrenEntry = nrenMap.get(entry.nren);
if (!nrenEntry) {
nrenEntry = new Map<number, Organisation[]>();
}
let suborgList = nrenEntry.get(entry.year);
if (!suborgList) {
suborgList = [];
}
suborgList.push(entry);
nrenEntry.set(entry.year, suborgList);
nrenMap.set(entry.nren, nrenEntry);
});
return nrenMap;
}
export const createNRENStaffDataset = (data: NrenStaff[], roles: boolean) => { export const createNRENStaffDataset = (data: NrenStaff[], roles: boolean) => {
function CreateDataLookup(data: NrenStaff[]) { function CreateDataLookup(data: NrenStaff[]) {
const dataLookup = new Map<string, Map<string, number>>(); const dataLookup = new Map<string, Map<string, number>>();
......
...@@ -56,8 +56,16 @@ function CompendiumData(): ReactElement { ...@@ -56,8 +56,16 @@ function CompendiumData(): ReactElement {
<span>Types of employment for NRENs</span> <span>Types of employment for NRENs</span>
</Link> </Link>
</Row> </Row>
<Row>
<Link to="/suborganisations" className="link-text-underline">
<span>NREN Suborganisations</span>
</Link>
</Row>
<Row>
<Link to="/parentorganisation" className="link-text-underline">
<span>NREN Parent Organisations</span>
</Link>
</Row>
</div> </div>
</CollapsibleBox> </CollapsibleBox>
</div> </div>
......
import React, { useEffect, useMemo, useState } from 'react';
import { Col, Container, Row, Table } from "react-bootstrap";
import { createOrganisationDataLookup } from "../helpers/dataconversion";
import Filter from "../components/graphing/Filter"
import {
Organisation,
FilterSelection
} from "../Schema";
async function getData(): Promise<Organisation[]> {
try {
const response = await fetch('/api/organization/parent');
return response.json();
} catch (error) {
console.error(`Failed to load data: ${error}`);
throw error;
}
}
function getYearsAndNrens(sourceData: Organisation[]) {
const years = new Set<number>();
const nrens = new Set<string>();
sourceData.forEach(datapoint => {
years.add(datapoint.year);
nrens.add(datapoint.nren);
});
return { years: years, nrens: nrens };
}
interface inputProps {
filterSelection: FilterSelection
setFilterSelection: React.Dispatch<React.SetStateAction<FilterSelection>>
}
function ParentOrganisation({ filterSelection, setFilterSelection }: inputProps) {
const [organisationData, setOrganisationData] = useState<Organisation[]>();
const { years, nrens } = useMemo(
() => getYearsAndNrens(organisationData || []),
[organisationData]
);
const selectedData = (organisationData || []).filter(data =>
filterSelection.selectedYears.includes(data.year) && filterSelection.selectedNrens.includes(data.nren)
);
const organisationDataset = createOrganisationDataLookup(selectedData);
useEffect(() => {
const loadData = async () => {
const data = await getData();
setOrganisationData(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];
return { selectedYears: newSelectedYears, selectedNrens: newSelectedNrens };
});
}
loadData()
}, [setFilterSelection]);
function sortOrganisation(a: Organisation, b: Organisation) {
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
}
return (
<Container>
<Row>
<Col xs={9}>
<Row>
<h3>NREN Parent Organisations</h3>
</Row>
<Row>
<Table>
<thead>
<tr>
<th>NREN</th>
<th>Year</th>
<th>Parent Organisation</th>
</tr>
</thead>
<tbody>
{Array.from(organisationDataset).sort().map(([_, nrenEntry]) => (
Array.from(nrenEntry).sort().map(([_, nrenDataList], yearIndex) => (
nrenDataList.sort(sortOrganisation).map((nrenData, nrenDataIndex) => (
<tr key={nrenData.nren + nrenData.year + nrenData.name}>
<td>{yearIndex == 0 && nrenDataIndex == 0 && nrenData.nren}</td>
<td>{nrenDataIndex == 0 && nrenData.year}</td>
<td>{nrenData.name}</td>
</tr>
))
)
)
))}
</tbody>
</Table>
</Row>
</Col>
<Col xs={3}>
<Filter
filterOptions={{ availableYears: [...years], availableNrens: [...nrens] }}
filterSelection={filterSelection}
setFilterSelection={setFilterSelection}
/>
</Col>
</Row>
</Container>
);
}
export default ParentOrganisation;
import React, { useEffect, useMemo, useState } from 'react';
import { Col, Container, Row, Table } from "react-bootstrap";
import { createOrganisationDataLookup } from "../helpers/dataconversion";
import Filter from "../components/graphing/Filter"
import {
Organisation,
FilterSelection
} from "../Schema";
async function getData(): Promise<Organisation[]> {
try {
const response = await fetch('/api/organization/sub');
return response.json();
} catch (error) {
console.error(`Failed to load data: ${error}`);
throw error;
}
}
function getYearsAndNrens(sourceData: Organisation[]) {
const years = new Set<number>();
const nrens = new Set<string>();
sourceData.forEach(datapoint => {
years.add(datapoint.year);
nrens.add(datapoint.nren);
});
return { years: years, nrens: nrens };
}
interface inputProps {
filterSelection: FilterSelection
setFilterSelection: React.Dispatch<React.SetStateAction<FilterSelection>>
}
function SubOrganisation({ filterSelection, setFilterSelection }: inputProps) {
const [organisationData, setOrganisationData] = useState<Organisation[]>();
const { years, nrens } = useMemo(
() => getYearsAndNrens(organisationData || []),
[organisationData]
);
const selectedData = (organisationData || []).filter(data =>
filterSelection.selectedYears.includes(data.year) && filterSelection.selectedNrens.includes(data.nren)
);
const organisationDataset = createOrganisationDataLookup(selectedData);
useEffect(() => {
const loadData = async () => {
const data = await getData();
setOrganisationData(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];
return { selectedYears: newSelectedYears, selectedNrens: newSelectedNrens };
});
}
loadData()
}, [setFilterSelection]);
function sortOrganisation(a: Organisation, b: Organisation) {
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
}
return (
<Container>
<Row>
<Col xs={9}>
<Row>
<h3>NREN Suborganisations</h3>
</Row>
<Row>
<Table>
<thead>
<tr>
<th>NREN</th>
<th>Year</th>
<th>Suborganisation</th>
<th>Role</th>
</tr>
</thead>
<tbody>
{Array.from(organisationDataset).sort().map(([_, nrenEntry]) => (
Array.from(nrenEntry).sort().map(([_, nrenDataList], yearIndex) => (
nrenDataList.sort(sortOrganisation).map((nrenData, nrenDataIndex) => (
<tr key={nrenData.nren + nrenData.year + nrenData.name}>
<td>{yearIndex == 0 && nrenDataIndex == 0 && nrenData.nren}</td>
<td>{nrenDataIndex == 0 && nrenData.year}</td>
<td>{nrenData.name}</td>
<td>{nrenData.role}</td>
</tr>
))
)
)
))}
</tbody>
</Table>
</Row>
</Col>
<Col xs={3}>
<Filter
filterOptions={{ availableYears: [...years], availableNrens: [...nrens] }}
filterSelection={filterSelection}
setFilterSelection={setFilterSelection}
/>
</Col>
</Row>
</Container>
);
}
export default SubOrganisation;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment