[#44] Loading Indicator

- add loading indicator to plant list
- format PlantList.js and ResultsStep.js
This commit is contained in:
Matthew Northcott 2023-02-03 09:27:31 +13:00
parent ddcff48f5b
commit 47b6c48aea
2 changed files with 213 additions and 132 deletions

View file

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

View file

@ -1,35 +1,51 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import Step from '../Step'; import Step from "../Step";
import PlantList from './PlantList' import PlantList from "./PlantList";
import Stack from '@mui/material/Stack'; import Stack from "@mui/material/Stack";
import Button from '@mui/material/Button'; import Button from "@mui/material/Button";
import PlantRepository from '../../../repository/PlantRepository' import PlantRepository from "../../../repository/PlantRepository";
import { Typography, Box } from '@mui/material'; import { Typography, Box } from "@mui/material";
import resultsBackgroundImage from '../../../assets/img/stepBackgrounds/step6.jpg'; import resultsBackgroundImage from "../../../assets/img/stepBackgrounds/step6.jpg";
import staticText from '../../../assets/data/staticText.json' 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) { export default function ResultsStep(props) {
const [plants, setPlants] = useState([]); const [plants, setPlants] = useState([]);
useEffect(() => { useEffect(() => {
const updatePlants = () => { const updatePlants = () => {
PlantRepository.getFilteredPlants(props.filters).then(response => { PlantRepository.getFilteredPlants(props.filters)
.then((response) => {
if (response.status === 200) { if (response.status === 200) {
setPlants(response.data) setPlants(response.data);
} }
}).catch(e => {
this.setState({ plants: ["No plants found."] });
}) })
} .catch((e) => {
updatePlants()}, this.setState({ plants: ["No plants found."] });
[props.filters]); });
};
updatePlants();
}, [props.filters]);
function createData(
function createData(name, growthForm, moisturePreferences, plantTolerances, ecosystemServices, carbonSequestration, plantingStage) {
return {
name, name,
growthForm, growthForm,
moisturePreferences, moisturePreferences,
@ -37,6 +53,15 @@ export default function ResultsStep(props) {
ecosystemServices, ecosystemServices,
carbonSequestration, carbonSequestration,
plantingStage plantingStage
) {
return {
name,
growthForm,
moisturePreferences,
plantTolerances,
ecosystemServices,
carbonSequestration,
plantingStage,
}; };
} }
@ -51,45 +76,66 @@ export default function ResultsStep(props) {
plant.carbon_sequestration, plant.carbon_sequestration,
plant.stage plant.stage
); );
}) });
} };
const download = (response, fileType, fileName) => { const download = (response, fileType, fileName) => {
const url = window.URL.createObjectURL(new Blob([response.data], {type : fileType })); const url = window.URL.createObjectURL(
const link = document.createElement('a'); new Blob([response.data], { type: fileType })
);
const link = document.createElement("a");
link.href = url; link.href = url;
link.setAttribute('download', fileName); link.setAttribute("download", fileName);
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
} };
const downloadCSV = () => { const downloadCSV = () => {
PlantRepository.getPlantsCSV(props.filters).then(response => { PlantRepository.getPlantsCSV(props.filters).then((response) => {
download(response, "text/csv", "plants.csv") download(response, "text/csv", "plants.csv");
}) });
} };
const downloadPDF = () => { const downloadPDF = () => {
PlantRepository.getPlantsPDF(props.filters).then(response => { PlantRepository.getPlantsPDF(props.filters).then((response) => {
download(response, "application/pdf", "planting_guide.pdf") download(response, "application/pdf", "planting_guide.pdf");
}) });
} };
const stepContent = ( const stepContent = (
<div className="py-4"> <div className="py-4">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} className="pb-4"> <Box
<Typography variant='h5'>{staticText.steps.results.title}</Typography> 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"> <Stack spacing={2} direction="row" justifyContent="end">
<Button variant="contained" onClick={() => downloadPDF()}>Download PDF</Button> <Button variant="contained" onClick={() => downloadPDF()}>
<Button variant="contained" onClick={() => downloadCSV()}>Download CSV</Button> Download PDF
</Button>
<Button variant="contained" onClick={() => downloadCSV()}>
Download CSV
</Button>
</Stack> </Stack>
</Box> </Box>
<Typography variant='body2' sx={{ paddingRight: '100px', paddingBottom: '20px' }}>{RESULTS_DESCRIPTION}</Typography> <Typography
variant="body2"
sx={{ paddingRight: "100px", paddingBottom: "20px" }}
>
{RESULTS_DESCRIPTION}
</Typography>
<PlantList rows={getTableRows()} /> <PlantList rows={getTableRows()} />
</div> </div>
) );
return ( return (
<Step contentComponent={stepContent} backgroundImage={resultsBackgroundImage} /> <Step
) contentComponent={stepContent}
backgroundImage={resultsBackgroundImage}
/>
);
} }