[#44] Loading Indicator #87

Merged
mattn merged 1 commit from matt/spinner into main 2023-02-08 14:30:17 +13:00
2 changed files with 213 additions and 132 deletions

View file

@ -1,25 +1,27 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableFooter from '@mui/material/TableFooter';
import TablePagination from '@mui/material/TablePagination';
import TableRow from '@mui/material/TableRow';
import TableHead from '@mui/material/TableHead';
import Paper from '@mui/material/Paper';
import IconButton from '@mui/material/IconButton';
import FirstPageIcon from '@mui/icons-material/FirstPage';
import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft';
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
import LastPageIcon from '@mui/icons-material/LastPage';
import { styled } from '@mui/material/styles';
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';
import staticText from '../../../assets/data/staticText.json'
import forestGraphic from '../../../assets/img/habitats/1a_Forest_Section.png';
import * as React from "react";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material/styles";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableFooter from "@mui/material/TableFooter";
import TablePagination from "@mui/material/TablePagination";
import TableRow from "@mui/material/TableRow";
import TableHead from "@mui/material/TableHead";
import Paper from "@mui/material/Paper";
import IconButton from "@mui/material/IconButton";
import FirstPageIcon from "@mui/icons-material/FirstPage";
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
import LastPageIcon from "@mui/icons-material/LastPage";
import { styled } from "@mui/material/styles";
import Tooltip, { tooltipClasses } from "@mui/material/Tooltip";
import staticText from "../../../assets/data/staticText.json";
import forestGraphic from "../../../assets/img/habitats/1a_Forest_Section.png";
import { CircularProgress } from "@mui/material";
function TablePaginationActions(props) {
const theme = useTheme();
@ -48,28 +50,36 @@ function TablePaginationActions(props) {
disabled={page === 0}
aria-label="first page"
>
{theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
{theme.direction === "rtl" ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
{theme.direction === "rtl" ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
);
@ -86,6 +96,14 @@ export default function PlantResultsTable(props) {
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(25);
let rows = [];
if (props.rows) {
rows =
rowsPerPage > 0
? props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
: props.rows;
}
// Avoid a layout jump when reaching the last page with empty rows.
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - props.rows.length) : 0;
@ -108,47 +126,64 @@ export default function PlantResultsTable(props) {
});
const forestDiagramInformation = (
<div style={{ "width": "100%" }}>
<div style={{ width: "100%" }}>
<p>{staticText.steps.results.forestDiagramDescription}</p>
<img src={forestGraphic} style={{ "width": "100%" }} />
<img
src={forestGraphic}
style={{ width: "100%" }}
alt={staticText.steps.results.forestDiagramDescription}
/>
</div>
)
);
return (
<TableContainer component={Paper}>
<Table size="small" stickyHeader aria-label="Plant table">
<TableHead>
<TableHead>
<TableRow>
<TableCell>Names</TableCell>
<CustomWidthTooltip arrow title={forestDiagramInformation} >
<TableCell align="right">Growth Form / Max Height (m) / Spacing (m) / Forest Position</TableCell>
<CustomWidthTooltip arrow title={forestDiagramInformation}>
<TableCell align="right">
Growth Form / Max Height (m) / Spacing (m) / Forest Position
</TableCell>
</CustomWidthTooltip>
<TableCell align="right">Moisture Preferences</TableCell>
<TableCell align="right">Tolerances (Water / Drought / Frost / Salinity)</TableCell>
<TableCell align="right">
Tolerances (Water / Drought / Frost / Salinity)
</TableCell>
<TableCell align="right">Ecosystem Services</TableCell>
<TableCell align="right">Carbon Sequestration Rate</TableCell>
<TableCell align="right">Planting Stage</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(rowsPerPage > 0
? props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
: props.rows
).map((row) => (
<TableRow key={row.name}>
<TableCell component="th" scope="row">{row.name}</TableCell>
<TableCell align="right">{row.growthForm}</TableCell>
<TableCell align="right">{row.moisturePreferences}</TableCell>
<TableCell align="right">{row.plantTolerances}</TableCell>
<TableCell align="right">{row.ecosystemServices}</TableCell>
<TableCell align="right">{row.carbonSequestration}</TableCell>
<TableCell align="right">{row.plantingStage}</TableCell>
{rows.length === 0 && (
<TableRow style={{ height: 150 }}>
<TableCell colSpan={7}>
<Stack alignItems="center">
<CircularProgress />
</Stack>
</TableCell>
</TableRow>
))}
)}
{rows.length > 0 &&
rows.map((row) => (
<TableRow key={row.name}>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">{row.growthForm}</TableCell>
<TableCell align="right">{row.moisturePreferences}</TableCell>
<TableCell align="right">{row.plantTolerances}</TableCell>
<TableCell align="right">{row.ecosystemServices}</TableCell>
<TableCell align="right">{row.carbonSequestration}</TableCell>
<TableCell align="right">{row.plantingStage}</TableCell>
</TableRow>
))}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
<TableCell colSpan={7} />
</TableRow>
)}
</TableBody>
@ -156,14 +191,14 @@ export default function PlantResultsTable(props) {
<TableRow>
<TablePagination
className="plant-list-pagination"
rowsPerPageOptions={[5, 10, 25, { label: 'All', value: -1 }]}
rowsPerPageOptions={[5, 10, 25, { label: "All", value: -1 }]}
colSpan={7}
count={props.rows.length}
rowsPerPage={rowsPerPage}
page={page}
SelectProps={{
inputProps: {
'aria-label': 'rows per page',
"aria-label": "rows per page",
},
native: true,
}}

View file

@ -1,95 +1,141 @@
import { useEffect, useState } from 'react';
import { useEffect, useState } from "react";
import Step from '../Step';
import PlantList from './PlantList'
import Stack from '@mui/material/Stack';
import Button from '@mui/material/Button';
import PlantRepository from '../../../repository/PlantRepository'
import { Typography, Box } from '@mui/material';
import resultsBackgroundImage from '../../../assets/img/stepBackgrounds/step6.jpg';
import staticText from '../../../assets/data/staticText.json'
import Step from "../Step";
import PlantList from "./PlantList";
import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";
import PlantRepository from "../../../repository/PlantRepository";
import { Typography, Box } from "@mui/material";
import resultsBackgroundImage from "../../../assets/img/stepBackgrounds/step6.jpg";
import staticText from "../../../assets/data/staticText.json";
const RESULTS_DESCRIPTION = <Typography>Please review the plant species list it can be saved as a CSV file or appended to a planting plan guidebook. Additional information on each species can be obtained by entering the plant species name into the search box at <a href='https://inaturalist.nz' target='blank'>inaturalist.nz</a> or in the "search flora" box at <a href="https://www.nzpcn.org.nz/" target="blank">www.nzpcn.org.nz</a>. Click the "Download PDF" button to create a planting plan guidebook specific to your project site..</Typography>
const RESULTS_DESCRIPTION = (
<Typography>
Please review the plant species list. It can be saved as a CSV file or
appended to a planting plan guidebook. Additional information on each
species can be obtained by entering the plant species name into the search
box at{" "}
<a href="https://inaturalist.nz" target="blank">
inaturalist.nz
</a>{" "}
or in the "search flora" box at{" "}
<a href="https://www.nzpcn.org.nz/" target="blank">
www.nzpcn.org.nz
</a>
. Click the "Download PDF" button to create a planting plan guidebook
specific to your project site.
</Typography>
);
export default function ResultsStep(props) {
const [plants, setPlants] = useState([]);
const [plants, setPlants] = useState([]);
useEffect(() => {
const updatePlants = () => {
PlantRepository.getFilteredPlants(props.filters).then(response => {
if (response.status === 200) {
setPlants(response.data)
}
}).catch(e => {
this.setState({ plants: ["No plants found."] });
})
}
updatePlants()},
[props.filters]);
function createData(name, growthForm, moisturePreferences, plantTolerances, ecosystemServices, carbonSequestration, plantingStage) {
return {
name,
growthForm,
moisturePreferences,
plantTolerances,
ecosystemServices,
carbonSequestration,
plantingStage
};
}
const getTableRows = () => {
return plants.map((plant) => {
return createData(
plant.display_name,
plant.display_growth_form,
plant.moisture_preferences,
plant.plant_tolerances,
plant.ecosystem_services,
plant.carbon_sequestration,
plant.stage
);
useEffect(() => {
const updatePlants = () => {
PlantRepository.getFilteredPlants(props.filters)
.then((response) => {
if (response.status === 200) {
setPlants(response.data);
}
})
}
.catch((e) => {
this.setState({ plants: ["No plants found."] });
});
};
updatePlants();
}, [props.filters]);
const download = (response, fileType, fileName) => {
const url = window.URL.createObjectURL(new Blob([response.data], {type : fileType }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
}
function createData(
name,
growthForm,
moisturePreferences,
plantTolerances,
ecosystemServices,
carbonSequestration,
plantingStage
) {
return {
name,
growthForm,
moisturePreferences,
plantTolerances,
ecosystemServices,
carbonSequestration,
plantingStage,
};
}
const downloadCSV = () => {
PlantRepository.getPlantsCSV(props.filters).then(response => {
download(response, "text/csv", "plants.csv")
})
}
const getTableRows = () => {
return plants.map((plant) => {
return createData(
plant.display_name,
plant.display_growth_form,
plant.moisture_preferences,
plant.plant_tolerances,
plant.ecosystem_services,
plant.carbon_sequestration,
plant.stage
);
});
};
const downloadPDF = () => {
PlantRepository.getPlantsPDF(props.filters).then(response => {
download(response, "application/pdf", "planting_guide.pdf")
})
}
const download = (response, fileType, fileName) => {
const url = window.URL.createObjectURL(
new Blob([response.data], { type: fileType })
);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
};
const stepContent = (
<div className="py-4">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} className="pb-4">
<Typography variant='h5'>{staticText.steps.results.title}</Typography>
<Stack spacing={2} direction="row" justifyContent="end">
<Button variant="contained" onClick={() => downloadPDF()}>Download PDF</Button>
<Button variant="contained" onClick={() => downloadCSV()}>Download CSV</Button>
</Stack>
</Box>
<Typography variant='body2' sx={{ paddingRight: '100px', paddingBottom: '20px' }}>{RESULTS_DESCRIPTION}</Typography>
<PlantList rows={getTableRows()}/>
</div>
)
const downloadCSV = () => {
PlantRepository.getPlantsCSV(props.filters).then((response) => {
download(response, "text/csv", "plants.csv");
});
};
return (
<Step contentComponent={stepContent} backgroundImage={resultsBackgroundImage} />
)
const downloadPDF = () => {
PlantRepository.getPlantsPDF(props.filters).then((response) => {
download(response, "application/pdf", "planting_guide.pdf");
});
};
const stepContent = (
<div className="py-4">
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
className="pb-4"
>
<Typography variant="h5">{staticText.steps.results.title}</Typography>
<Stack spacing={2} direction="row" justifyContent="end">
<Button variant="contained" onClick={() => downloadPDF()}>
Download PDF
</Button>
<Button variant="contained" onClick={() => downloadCSV()}>
Download CSV
</Button>
</Stack>
</Box>
<Typography
variant="body2"
sx={{ paddingRight: "100px", paddingBottom: "20px" }}
>
{RESULTS_DESCRIPTION}
</Typography>
<PlantList rows={getTableRows()} />
</div>
);
return (
<Step
contentComponent={stepContent}
backgroundImage={resultsBackgroundImage}
/>
);
}