[#40] Review changes

- add comment about Suspense
- add missing key attributes
- remove redundant fragments
- prettier formatting
- rename React component files with .jsx extension
This commit is contained in:
Matthew Northcott 2023-02-10 14:13:12 +13:00
parent ff197cc1a8
commit 0f36c79dbf
42 changed files with 1020 additions and 785 deletions

View file

@ -1,13 +0,0 @@
import TopNav from './TopNav'
import logo from '../assets/img/logo.png'
export default function Header() {
return (
<div className='d-flex justify-content-between top-header'>
<a href="https://www.b4c3.com/">
<img className='header-logo' src={logo} alt="Biosphere Capital Limited" />
</a>
<TopNav />
</div>
);
}

View file

@ -0,0 +1,17 @@
import TopNav from "./TopNav";
import logo from "../assets/img/logo.png";
export default function Header() {
return (
<div className="d-flex justify-content-between top-header">
<a href="https://www.b4c3.com/">
<img
className="header-logo"
src={logo}
alt="Biosphere Capital Limited"
/>
</a>
<TopNav />
</div>
);
}

View file

@ -56,28 +56,26 @@ export default function StepperWizard({ steps }) {
); );
})} })}
</Stepper> </Stepper>
<> <CurrentStep
<CurrentStep filters={filters}
filters={filters} updateFilterState={updateFilterState}
updateFilterState={updateFilterState} resetFilterState={() => setFilters({})}
resetFilterState={() => setFilters({})} setNextDisabled={setNextDisabled}
setNextDisabled={setNextDisabled} setRedirectBack={setRedirectBack}
setRedirectBack={setRedirectBack} />
/> <Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, pb: 2, paddingRight: '3vw', paddingLeft: '3vw' }}>
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, pb: 2, paddingRight: '3vw', paddingLeft: '3vw' }}> <Button
<Button color="inherit"
color="inherit" disabled={activeStep === 0}
disabled={activeStep === 0} onClick={handleBack}
onClick={handleBack} sx={{ mr: 1 }}
sx={{ mr: 1 }} >
> Back
Back </Button>
</Button> <Box sx={{ flex: '1 1 auto' }} />
<Box sx={{ flex: '1 1 auto' }} /> {activeStep === steps.length - 1 ?
{activeStep === steps.length - 1 ? <Button onClick={handleReset}>Reset</Button> : <Button onClick={handleNext} disabled={nextDisabled}>Next</Button>}
<Button onClick={handleReset}>Reset</Button> : <Button onClick={handleNext} disabled={nextDisabled}>Next</Button>} </Box>
</Box>
</>
</Box> </Box>
); );
} }

View file

@ -1,30 +0,0 @@
export default function TopNav() {
return (
<nav className="navbar navbar-expand-lg">
<div className="container-fluid">
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNavDropdown">
<ul className="navbar-nav">
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com">Advisory</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com">Ecology</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com">Renewables</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com">About</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com/contact">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
);
}

View file

@ -0,0 +1,48 @@
export default function TopNav() {
return (
<nav className="navbar navbar-expand-lg">
<div className="container-fluid">
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNavDropdown">
<ul className="navbar-nav">
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com">
Advisory
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com">
Ecology
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com">
Renewables
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com">
About
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://www.b4c3.com/contact">
Contact
</a>
</li>
</ul>
</div>
</div>
</nav>
);
}

View file

@ -9,7 +9,7 @@ export const HabitatSVG = ({ name, ...rest }) => {
</Stack> </Stack>
); );
return ( return (
<Suspense fallback={loadingComponent}> <Suspense fallback={loadingComponent}> {/* experiemental in React 17. TODO: upgrade to 18 */}
<Diagram {...rest} /> <Diagram {...rest} />
</Suspense>); </Suspense>);
}; };

View file

@ -1,30 +0,0 @@
import { useEffect, useState } from 'react';
import { Row, Col } from 'reactstrap'
import SampleBackground from '../../assets/img/stepBackgrounds/step1.jpg';
export default function Step(props) {
const [stepBackgroundImage, setStepBackground] = useState(SampleBackground)
useEffect(() => setStepBackground(props.backgroundImage || SampleBackground), [props.backgroundImage])
return (
<div className='step-container' style={{backgroundImage: `url(${stepBackgroundImage})`}}>
<div className='step-overlay'>
{props.contentComponent ? (
props.contentComponent
) : (
<Row style={{height: '100%'}}>
<Col lg={{ size: "4" }} md="5" sm="12" style={{display: 'flex', alignItems: 'center', justifyContent: 'center', paddingRight: '50px', paddingLeft: '50px'}}>
{props.informationComponent}
</Col>
<Col lg={{ size: "8" }} md="7" sm="12" style={{display: 'flex', alignItems: 'center'}}>
{props.selectionComponent}
</Col>
</Row>
)}
</div>
</div>
);
}

View file

@ -0,0 +1,50 @@
import { useEffect, useState } from "react";
import { Row, Col } from "reactstrap";
import SampleBackground from "../../assets/img/stepBackgrounds/step1.jpg";
export default function Step(props) {
const [stepBackgroundImage, setStepBackground] = useState(SampleBackground);
useEffect(
() => setStepBackground(props.backgroundImage || SampleBackground),
[props.backgroundImage]
);
return (
<div
className="step-container"
style={{ backgroundImage: `url(${stepBackgroundImage})` }}
>
<div className="step-overlay">
{props.contentComponent ? (
props.contentComponent
) : (
<Row style={{ height: "100%" }}>
<Col
lg={{ size: "4" }}
md="5"
sm="12"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
paddingRight: "50px",
paddingLeft: "50px",
}}
>
{props.informationComponent}
</Col>
<Col
lg={{ size: "8" }}
md="7"
sm="12"
style={{ display: "flex", alignItems: "center" }}
>
{props.selectionComponent}
</Col>
</Row>
)}
</div>
</div>
);
}

View file

@ -1,9 +0,0 @@
export default function StepInformation(props) {
return (
<div className='ph-4'>
<h3>{props.title}</h3>
<br/>
{props.description}
</div>
)
}

View file

@ -0,0 +1,9 @@
export default function StepInformation(props) {
return (
<div className="ph-4">
<h3>{props.title}</h3>
<br />
{props.description}
</div>
);
}

View file

@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { InputAdornment, TextField } from '@mui/material'; import { InputAdornment, TextField } from '@mui/material';
import PlaceIcon from '@mui/icons-material/Place'; import PlaceIcon from '@mui/icons-material/Place';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
@ -14,8 +14,8 @@ const AddressSearchSuggestions = ({results, onClick}) => (
(Array.isArray(results) && results.length > 0) (Array.isArray(results) && results.length > 0)
? <Box sx={{ width: '100%', bgcolor: 'background.paper' }}> ? <Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<List> <List>
{results.map((r) => ( {results.map((r, idx) => (
<ListItem disablePadding> <ListItem disablePadding key={idx}>
<ListItemButton onClick={() => onClick(r)}> <ListItemButton onClick={() => onClick(r)}>
<ListItemText primary={r.address} /> <ListItemText primary={r.address} />
</ListItemButton> </ListItemButton>

View file

@ -1,133 +0,0 @@
import { useEffect, useState } from 'react';
import SiteRepository from '../../../repository/SiteRepository';
import * as React from 'react';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormLabel from '@mui/material/FormLabel';
import FormControl from '@mui/material/FormControl';
import staticText from '../../../assets/data/staticText.json'
import { HabitatSVG } from '../HabitatSVG'
export default function HabitatSelector(props) {
const [habitats, setHabitats] = useState([])
const [habitatsMap, setHabitatsMap] = useState({})
const [value, setValue] = React.useState(props.filters.habitat && props.filters.habitat.id);
const [selectedHabitat, setSelectedHabitat] = React.useState({});
const [imageValue, setImageValue] = React.useState(props.filters.habitatImage);
const getHabitats = () => {
SiteRepository.getHabitats().then(response => {
if (response.status === 200) {
// Create a mapping from the habitat id to the object
let habitatsJson = {}
response.data.forEach(habitat => {
habitatsJson[habitat.id] = habitat
})
setHabitats(response.data);
setHabitatsMap(habitatsJson);
}
})
}
useEffect(() => {
// Retrieves the habitats from the api if they are not loaded already
habitats.length === 0 && getHabitats()
// Sets the selected habitat if its already set in filters
if (props.filters.habitat && props.filters.habitat.id) {
setSelectedHabitat(habitatsMap[props.filters.habitat.id])
}
// If both the habitat and the image is selected, then enable the next step
if (props.filters.habitat && props.filters.habitatImage) {
props.setNextDisabled(false);
}
}, [habitats.length, props, habitatsMap]);
const setHabitatImage = (imageId) => {
// Sets the selected image radio, updates filter state for image and enable the next button
setImageValue(imageId);
props.updateFilterState({ "habitatImage": imageId });
props.updateFilterState({ "zone": null });
props.setNextDisabled(false);
}
const handleRadioChange = (event) => {
// Retrieve and set the value of the radio button
const habitatSelection = event.target.value;
setValue(habitatSelection);
setHabitatImage(null)
// Set the image default selection if there is only one image variation available
const habitatObject = habitatsMap[habitatSelection]
setSelectedHabitat(habitatObject)
if (habitatObject.images.length === 1) {
setHabitatImage(habitatObject.images[0].id)
} else {
props.setNextDisabled(true);
}
// Update the filters for the selected habitat
props.updateFilterState({ "habitat": {"id": habitatObject.id, "name": habitatObject.name} });
};
const handleImageRadioChange = (event) => {
const habitatImageSelection = event.target.value;
setHabitatImage(habitatImageSelection)
};
const getImageLabel = (image) => {
return (
<div>
<p>{image.name}</p>
<HabitatSVG name={image.image_filename} style={{ "height": 'auto', "width": "300px" }}/>
</div>)
}
return (
<div className='d-flex align-top justify-space-between'>
<form >
<FormControl
sx={{ m: 3 }}
component="fieldset"
variant="standard"
>
<FormLabel component="legend">{staticText.steps.habitat.optionsLabel}</FormLabel>
<RadioGroup
aria-label="habitat-selection"
name="habitat selection"
value={value ?? ""}
onChange={handleRadioChange}
>
{habitats && Array.isArray(habitats) && habitats.map(habitat =>
<FormControlLabel key={habitat.id} value={habitat.id} control={<Radio />} label={habitat.name} />
)}
</RadioGroup>
</FormControl>
</form>
<form >
<FormControl
sx={{ m: 3 }}
component="fieldset"
variant="standard"
>
<FormLabel component="legend">{staticText.steps.habitat.imageOptionsLabel}</FormLabel>
<RadioGroup
aria-label="habitat-image-selection"
name="habitat image selection"
value={imageValue ?? ""}
onChange={handleImageRadioChange}
>
{selectedHabitat && selectedHabitat.images && Array.isArray(selectedHabitat.images) && selectedHabitat.images.map(image =>
<FormControlLabel key={image.id} value={image.id} control={<Radio />} label={getImageLabel(image)} sx={{ paddingTop: '15px', paddingBottom: '15px' }} />
)}
{(!selectedHabitat || (selectedHabitat.images && selectedHabitat.images.length === 0)) && <p>No images available.</p>}
</RadioGroup>
</FormControl>
</form>
</div>
);
}

View file

@ -0,0 +1,157 @@
import { useEffect, useState } from "react";
import SiteRepository from "../../../repository/SiteRepository";
import * as React from "react";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormLabel from "@mui/material/FormLabel";
import FormControl from "@mui/material/FormControl";
import staticText from "../../../assets/data/staticText.json";
import { HabitatSVG } from "../HabitatSVG";
export default function HabitatSelector(props) {
const [habitats, setHabitats] = useState([]);
const [habitatsMap, setHabitatsMap] = useState({});
const [value, setValue] = React.useState(
props.filters.habitat && props.filters.habitat.id
);
const [selectedHabitat, setSelectedHabitat] = React.useState({});
const [imageValue, setImageValue] = React.useState(
props.filters.habitatImage
);
const getHabitats = () => {
SiteRepository.getHabitats().then((response) => {
if (response.status === 200) {
// Create a mapping from the habitat id to the object
let habitatsJson = {};
response.data.forEach((habitat) => {
habitatsJson[habitat.id] = habitat;
});
setHabitats(response.data);
setHabitatsMap(habitatsJson);
}
});
};
useEffect(() => {
// Retrieves the habitats from the api if they are not loaded already
habitats.length === 0 && getHabitats();
// Sets the selected habitat if its already set in filters
if (props.filters.habitat && props.filters.habitat.id) {
setSelectedHabitat(habitatsMap[props.filters.habitat.id]);
}
// If both the habitat and the image is selected, then enable the next step
if (props.filters.habitat && props.filters.habitatImage) {
props.setNextDisabled(false);
}
}, [habitats.length, props, habitatsMap]);
const setHabitatImage = (imageId) => {
// Sets the selected image radio, updates filter state for image and enable the next button
setImageValue(imageId);
props.updateFilterState({ habitatImage: imageId });
props.updateFilterState({ zone: null });
props.setNextDisabled(false);
};
const handleRadioChange = (event) => {
// Retrieve and set the value of the radio button
const habitatSelection = event.target.value;
setValue(habitatSelection);
setHabitatImage(null);
// Set the image default selection if there is only one image variation available
const habitatObject = habitatsMap[habitatSelection];
setSelectedHabitat(habitatObject);
if (habitatObject.images.length === 1) {
setHabitatImage(habitatObject.images[0].id);
} else {
props.setNextDisabled(true);
}
// Update the filters for the selected habitat
props.updateFilterState({
habitat: { id: habitatObject.id, name: habitatObject.name },
});
};
const handleImageRadioChange = (event) => {
const habitatImageSelection = event.target.value;
setHabitatImage(habitatImageSelection);
};
const getImageLabel = (image) => {
return (
<div>
<p>{image.name}</p>
<HabitatSVG
name={image.image_filename}
style={{ height: "auto", width: "300px" }}
/>
</div>
);
};
return (
<div className="d-flex align-top justify-space-between">
<form>
<FormControl sx={{ m: 3 }} component="fieldset" variant="standard">
<FormLabel component="legend">
{staticText.steps.habitat.optionsLabel}
</FormLabel>
<RadioGroup
aria-label="habitat-selection"
name="habitat selection"
value={value ?? ""}
onChange={handleRadioChange}
>
{habitats &&
Array.isArray(habitats) &&
habitats.map((habitat) => (
<FormControlLabel
key={habitat.id}
value={habitat.id}
control={<Radio />}
label={habitat.name}
/>
))}
</RadioGroup>
</FormControl>
</form>
<form>
<FormControl sx={{ m: 3 }} component="fieldset" variant="standard">
<FormLabel component="legend">
{staticText.steps.habitat.imageOptionsLabel}
</FormLabel>
<RadioGroup
aria-label="habitat-image-selection"
name="habitat image selection"
value={imageValue ?? ""}
onChange={handleImageRadioChange}
>
{selectedHabitat &&
selectedHabitat.images &&
Array.isArray(selectedHabitat.images) &&
selectedHabitat.images.map((image) => (
<FormControlLabel
key={image.id}
value={image.id}
control={<Radio />}
label={getImageLabel(image)}
sx={{ paddingTop: "15px", paddingBottom: "15px" }}
/>
))}
{(!selectedHabitat ||
(selectedHabitat.images &&
selectedHabitat.images.length === 0)) && (
<p>No images available.</p>
)}
</RadioGroup>
</FormControl>
</form>
</div>
);
}

View file

@ -1,22 +0,0 @@
import Step from '../Step';
import HabitatSelector from './HabitatSelector'
import StepInformation from '../StepInformation';
import staticText from '../../../assets/data/staticText.json'
import habitatBackgroundImage from '../../../assets/img/stepBackgrounds/step3.jpg';
export default function HabitatStep(props) {
const habitatInfoPanel = (
<StepInformation
title={staticText.steps.habitat.title}
description={<p>{staticText.steps.habitat.description}</p>}
/>
)
const habitatSelectionPanel = (
<HabitatSelector {...props} />
)
return (
<Step informationComponent={habitatInfoPanel} selectionComponent={habitatSelectionPanel} backgroundImage={habitatBackgroundImage} />
)
}

View file

@ -0,0 +1,24 @@
import Step from "../Step";
import HabitatSelector from "./HabitatSelector";
import StepInformation from "../StepInformation";
import staticText from "../../../assets/data/staticText.json";
import habitatBackgroundImage from "../../../assets/img/stepBackgrounds/step3.jpg";
export default function HabitatStep(props) {
const habitatInfoPanel = (
<StepInformation
title={staticText.steps.habitat.title}
description={<p>{staticText.steps.habitat.description}</p>}
/>
);
const habitatSelectionPanel = <HabitatSelector {...props} />;
return (
<Step
informationComponent={habitatInfoPanel}
selectionComponent={habitatSelectionPanel}
backgroundImage={habitatBackgroundImage}
/>
);
}

View file

@ -1,23 +0,0 @@
import Step from '../Step';
import LocationSelectorMap from './Map'
import StepInformation from '../StepInformation';
import staticText from '../../../assets/data/staticText.json'
import locationBackgroundImage from '../../../assets/img/stepBackgrounds/step1.jpg';
export default function LocationStep(props) {
const locationInfoPanel = (
<StepInformation
title={staticText.steps.location.title}
description={<p>{staticText.steps.location.description}</p>}
/>
)
const locationSelectionPanel = (
<LocationSelectorMap {...props} />
)
return (
<Step informationComponent={locationInfoPanel} selectionComponent={locationSelectionPanel} backgroundImage={locationBackgroundImage} />
)
}

View file

@ -0,0 +1,24 @@
import Step from "../Step";
import LocationSelectorMap from "./Map";
import StepInformation from "../StepInformation";
import staticText from "../../../assets/data/staticText.json";
import locationBackgroundImage from "../../../assets/img/stepBackgrounds/step1.jpg";
export default function LocationStep(props) {
const locationInfoPanel = (
<StepInformation
title={staticText.steps.location.title}
description={<p>{staticText.steps.location.description}</p>}
/>
);
const locationSelectionPanel = <LocationSelectorMap {...props} />;
return (
<Step
informationComponent={locationInfoPanel}
selectionComponent={locationSelectionPanel}
backgroundImage={locationBackgroundImage}
/>
);
}

View file

@ -1,72 +1,72 @@
import { useState, useEffect } from "react" import { useState, useEffect } from "react";
import { MapContainer, TileLayer, Marker, useMapEvents } from 'react-leaflet' import { MapContainer, TileLayer, Marker, useMapEvents } from "react-leaflet";
import LocationRepository from '../../../repository/LocationRepository' import LocationRepository from "../../../repository/LocationRepository";
const NZ_BOUNDS = [ const NZ_BOUNDS = [
[-47.204642, 165.344238], [-47.204642, 165.344238],
[-34.307144, 179.824219] [-34.307144, 179.824219],
] ];
function LocationMarker(props) { function LocationMarker(props) {
const [position, setPosition] = useState(null) const [position, setPosition] = useState(null);
const map = useMapEvents({ const map = useMapEvents({
click(e) { click(e) {
const newPosition = e.latlng; const newPosition = e.latlng;
setPosition(newPosition); setPosition(newPosition);
props.updateFilterState({ "coordinates": newPosition }); props.updateFilterState({ coordinates: newPosition });
props.setNextDisabled(false); props.setNextDisabled(false);
} },
}) });
map.whenReady(() => { map.whenReady(() => {
const savedCoordinates = props.filters["coordinates"]; const savedCoordinates = props.filters["coordinates"];
if (!position && savedCoordinates) { if (!position && savedCoordinates) {
setPosition(savedCoordinates); setPosition(savedCoordinates);
props.setNextDisabled(false); props.setNextDisabled(false);
map.flyTo(savedCoordinates, 9) map.flyTo(savedCoordinates, 9);
} }
}) });
return position === null ? null : ( return position === null ? null : <Marker position={position} />;
<Marker position={position} />
)
} }
const fitNZBounds = (map) => { const fitNZBounds = (map) => {
map.fitBounds(NZ_BOUNDS); map.fitBounds(NZ_BOUNDS);
} };
function LocationDetailsDisplay(props) { function LocationDetailsDisplay(props) {
const [locationDetails, setLocationDetails] = useState({}) const [locationDetails, setLocationDetails] = useState({});
useEffect(() => { useEffect(() => {
if (props.filters["coordinates"]) { if (props.filters["coordinates"]) {
LocationRepository.getLocationData(props.filters).then(result => { LocationRepository.getLocationData(props.filters).then((result) => {
setLocationDetails(result); setLocationDetails(result);
}) });
} }
}, [props.filters, props.filters.coordinates] ); }, [props.filters, props.filters.coordinates]);
const savedCoordinates = props.filters["coordinates"]; const savedCoordinates = props.filters["coordinates"];
if (savedCoordinates) { if (savedCoordinates) {
const soilString = `${locationDetails.soil_name} (${locationDetails.soil_code})` const soilString = `${locationDetails.soil_name} (${locationDetails.soil_code})`;
return ( return (
<div className='coordinates-display'> <div className="coordinates-display">
<strong>Latitude:</strong> {savedCoordinates.lat} <br /> <strong>Latitude:</strong> {savedCoordinates.lat} <br />
<strong>Longitude:</strong> {savedCoordinates.lng} <br /> <strong>Longitude:</strong> {savedCoordinates.lng} <br />
<strong>Address:</strong> {locationDetails.full_address} <br /> <strong>Address:</strong> {locationDetails.full_address} <br />
<strong>Ecological Region:</strong> {locationDetails.ecological_region} <br /> <strong>Ecological Region:</strong> {locationDetails.ecological_region}{" "}
<strong>Ecological District:</strong> {locationDetails.ecological_district} <br /> <br />
<strong>Soil Order:</strong> {locationDetails.soil_name ? soilString : ""} <strong>Ecological District:</strong>{" "}
{locationDetails.ecological_district} <br />
<strong>Soil Order:</strong>{" "}
{locationDetails.soil_name ? soilString : ""}
</div> </div>
) );
} }
return null return null;
} }
export default function Map(props) { export default function Map(props) {
return ( return (
<div className="map-container"> <div className="map-container">
@ -79,5 +79,5 @@ export default function Map(props) {
<LocationMarker {...props} /> <LocationMarker {...props} />
</MapContainer> </MapContainer>
</div> </div>
) );
} }

View file

@ -1,63 +0,0 @@
import * as React from 'react';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormLabel from '@mui/material/FormLabel';
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import Tooltip from '@mui/material/Tooltip';
import staticText from '../../../assets/data/staticText.json'
const WET_SOIL_DESCRIPTION = (<p>
Is your ground/soil <strong>VERY WET</strong>? i.e. is it soft and squishy under foot with ground water at or near surface for three months of year or more? Your soil is considered wet if the <Tooltip arrow title="Topsoil is the uppermost layer of soil usually 5-20cm of dark soil with high concentrations of organic matter. Subsoil is the layer under the topsoil and generally contains less organic matter and more of the small particles such as sand, silt and clay that characterize the mineral and geology of the location."><strong>subsoil</strong></Tooltip> is either very dark brown and <Tooltip arrow title="Peat is an accumulation of partially decayed vegetation or organic matter, usually brown and often formed in waterlogged areas."><strong>peat</strong></Tooltip>-like, or grey rather than a red, brown, or yellow rusty colour?
</p>);
const DRY_SOIL_DESCRIPTION = (<p>
Is your ground/soil <strong>VERY DRY</strong>? i.e. If your soil is very well drained, stony, sandy or shallow (less than one metre depth to bed rock, stones or other hard/artificial surface), or is it completely dry for three months of year or more, and not irrigated then we consider it dry.
</p>);
const MESIC_SOIL_DESCRIPTION = (<p>
If your ground/soil does not meet either of the two previous definitions, wed likely classify it as <strong>MOIST, or somewhere in between very wet and very dry</strong>. Your ground is considered moist if it has compacted soil that is fine or has a soft/crumbly texture. The sand, silt and clay mixture known as loam is considered moist in this context. Moist soils are more than one metre above an impervious layer, and are never water-logged nor are they completely dry for more than three months. If you are unsure, or if you plan to irrigate your site, then select this option.
</p>);
export default function SoilSelector(props) {
const [value, setValue] = React.useState(props.filters.soilVariant);
const [helperText, setHelperText] = React.useState(staticText.steps.soil.optionsHelperText);
React.useEffect(() => {
if (props.filters.soilVariant) {
props.setNextDisabled(false);
}
});
const handleRadioChange = (event) => {
const soilVariantSelection = event.target.value;
setValue(soilVariantSelection);
setHelperText(' ');
props.updateFilterState({ "soilVariant": soilVariantSelection });
props.setNextDisabled(false);
};
return (
<form >
<FormControl
sx={{ m: 3 }}
component="fieldset"
variant="standard"
>
<FormLabel component="legend">{staticText.steps.soil.optionsLabel}</FormLabel>
<RadioGroup
aria-label="soil-varient-selection"
name="soil varient selection"
value={value ?? ""}
onChange={handleRadioChange}
>
<FormControlLabel value="D" control={<Radio />} label={DRY_SOIL_DESCRIPTION} sx={{ paddingTop: '15px', paddingBottom: '15px' }}/>
<FormControlLabel value="W" control={<Radio />} label={WET_SOIL_DESCRIPTION} sx={{ paddingTop: '15px', paddingBottom: '15px' }} />
<FormControlLabel value="M" control={<Radio />} label={MESIC_SOIL_DESCRIPTION} sx={{ paddingTop: '15px', paddingBottom: '15px' }} />
</RadioGroup>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
</form>
);
}

View file

@ -0,0 +1,111 @@
import * as React from "react";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormLabel from "@mui/material/FormLabel";
import FormControl from "@mui/material/FormControl";
import FormHelperText from "@mui/material/FormHelperText";
import Tooltip from "@mui/material/Tooltip";
import staticText from "../../../assets/data/staticText.json";
const WET_SOIL_DESCRIPTION = (
<p>
Is your ground/soil <strong>VERY WET</strong>? i.e. is it soft and squishy
under foot with ground water at or near surface for three months of year or
more? Your soil is considered wet if the{" "}
<Tooltip
arrow
title="Topsoil is the uppermost layer of soil usually 5-20cm of dark soil with high concentrations of organic matter. Subsoil is the layer under the topsoil and generally contains less organic matter and more of the small particles such as sand, silt and clay that characterize the mineral and geology of the location."
>
<strong>subsoil</strong>
</Tooltip>{" "}
is either very dark brown and{" "}
<Tooltip
arrow
title="Peat is an accumulation of partially decayed vegetation or organic matter, usually brown and often formed in waterlogged areas."
>
<strong>peat</strong>
</Tooltip>
-like, or grey rather than a red, brown, or yellow rusty colour?
</p>
);
const DRY_SOIL_DESCRIPTION = (
<p>
Is your ground/soil <strong>VERY DRY</strong>? i.e. If your soil is very
well drained, stony, sandy or shallow (less than one metre depth to bed
rock, stones or other hard/artificial surface), or is it completely dry for
three months of year or more, and not irrigated then we consider it dry.
</p>
);
const MESIC_SOIL_DESCRIPTION = (
<p>
If your ground/soil does not meet either of the two previous definitions,
wed likely classify it as{" "}
<strong>MOIST, or somewhere in between very wet and very dry</strong>. Your
ground is considered moist if it has compacted soil that is fine or has a
soft/crumbly texture. The sand, silt and clay mixture known as loam is
considered moist in this context. Moist soils are more than one metre above
an impervious layer, and are never water-logged nor are they completely dry
for more than three months. If you are unsure, or if you plan to irrigate
your site, then select this option.
</p>
);
export default function SoilSelector(props) {
const [value, setValue] = React.useState(props.filters.soilVariant);
const [helperText, setHelperText] = React.useState(
staticText.steps.soil.optionsHelperText
);
React.useEffect(() => {
if (props.filters.soilVariant) {
props.setNextDisabled(false);
}
});
const handleRadioChange = (event) => {
const soilVariantSelection = event.target.value;
setValue(soilVariantSelection);
setHelperText(" ");
props.updateFilterState({ soilVariant: soilVariantSelection });
props.setNextDisabled(false);
};
return (
<form>
<FormControl sx={{ m: 3 }} component="fieldset" variant="standard">
<FormLabel component="legend">
{staticText.steps.soil.optionsLabel}
</FormLabel>
<RadioGroup
aria-label="soil-varient-selection"
name="soil varient selection"
value={value ?? ""}
onChange={handleRadioChange}
>
<FormControlLabel
value="D"
control={<Radio />}
label={DRY_SOIL_DESCRIPTION}
sx={{ paddingTop: "15px", paddingBottom: "15px" }}
/>
<FormControlLabel
value="W"
control={<Radio />}
label={WET_SOIL_DESCRIPTION}
sx={{ paddingTop: "15px", paddingBottom: "15px" }}
/>
<FormControlLabel
value="M"
control={<Radio />}
label={MESIC_SOIL_DESCRIPTION}
sx={{ paddingTop: "15px", paddingBottom: "15px" }}
/>
</RadioGroup>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
</form>
);
}

View file

@ -1,30 +0,0 @@
import Step from '../Step';
import SoilSelector from './SoilSelector';
import StepInformation from '../StepInformation';
import staticText from '../../../assets/data/staticText.json'
import soilBackgroundImage from '../../../assets/img/stepBackgrounds/step2.jpg';
export default function SoilVariantStep(props) {
const SOIL_DESCRIPTION = (<p>
From your site location, we use <a href="https://soils.landcareresearch.co.nz/describing-soils/nzsc/" target="blank">New Zealand Soil Classification</a> data and additional <a href="https://www.landcareresearch.co.nz" target="blank">Manaaki Whenua Landcare Research</a> data to determine overall soil type. However we also need to know as much as possible about the moisture content of the soil at your planting site location.
<br /><br />
Please select an option from the choices listed to the right:
</p>)
const soilVarientInfoPanel = (
<StepInformation
title={staticText.steps.soil.title}
description={SOIL_DESCRIPTION}
/>
)
const soilVarientSelectionPanel = (
<div className='p-5'>
<SoilSelector {...props} />
</div>
)
return (
<Step informationComponent={soilVarientInfoPanel} selectionComponent={soilVarientSelectionPanel} backgroundImage={soilBackgroundImage} />
)
}

View file

@ -0,0 +1,50 @@
import Step from "../Step";
import SoilSelector from "./SoilSelector";
import StepInformation from "../StepInformation";
import staticText from "../../../assets/data/staticText.json";
import soilBackgroundImage from "../../../assets/img/stepBackgrounds/step2.jpg";
export default function SoilVariantStep(props) {
const SOIL_DESCRIPTION = (
<p>
From your site location, we use{" "}
<a
href="https://soils.landcareresearch.co.nz/describing-soils/nzsc/"
target="blank"
>
New Zealand Soil Classification
</a>{" "}
data and additional{" "}
<a href="https://www.landcareresearch.co.nz" target="blank">
Manaaki Whenua Landcare Research
</a>{" "}
data to determine overall soil type. However we also need to know as much
as possible about the moisture content of the soil at your planting site
location.
<br />
<br />
Please select an option from the choices listed to the right:
</p>
);
const soilVarientInfoPanel = (
<StepInformation
title={staticText.steps.soil.title}
description={SOIL_DESCRIPTION}
/>
);
const soilVarientSelectionPanel = (
<div className="p-5">
<SoilSelector {...props} />
</div>
);
return (
<Step
informationComponent={soilVarientInfoPanel}
selectionComponent={soilVarientSelectionPanel}
backgroundImage={soilBackgroundImage}
/>
);
}

View file

@ -1,12 +0,0 @@
import { useEffect } from 'react';
export default function ProjectSpecificsSelector(props) {
useEffect(() => {
props.setNextDisabled(false);
});
return (
<h2>Project Specifics Selector</h2>
)
}

View file

@ -0,0 +1,9 @@
import { useEffect } from "react";
export default function ProjectSpecificsSelector(props) {
useEffect(() => {
props.setNextDisabled(false);
});
return <h2>Project Specifics Selector</h2>;
}

View file

@ -1,21 +0,0 @@
import Step from '../Step';
import ProjectSpecificsSelector from './ProjectSpecificsSelector'
import StepInformation from '../StepInformation';
import staticText from '../../../assets/data/staticText.json'
export default function ProjectSpecificsStep(props) {
const projectSpecificsInfoPanel = (
<StepInformation
title={staticText.steps.projectSpecifics.title}
description={<p>{staticText.steps.projectSpecifics.description}</p>}
/>
)
const projectSpecificsSelectionPanel = (
<ProjectSpecificsSelector {...props} />
)
return (
<Step informationComponent={projectSpecificsInfoPanel} selectionComponent={projectSpecificsSelectionPanel} />
)
}

View file

@ -0,0 +1,24 @@
import Step from "../Step";
import ProjectSpecificsSelector from "./ProjectSpecificsSelector";
import StepInformation from "../StepInformation";
import staticText from "../../../assets/data/staticText.json";
export default function ProjectSpecificsStep(props) {
const projectSpecificsInfoPanel = (
<StepInformation
title={staticText.steps.projectSpecifics.title}
description={<p>{staticText.steps.projectSpecifics.description}</p>}
/>
);
const projectSpecificsSelectionPanel = (
<ProjectSpecificsSelector {...props} />
);
return (
<Step
informationComponent={projectSpecificsInfoPanel}
selectionComponent={projectSpecificsSelectionPanel}
/>
);
}

View file

@ -1,101 +0,0 @@
import * as React from 'react';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import SummaryTable from './SummaryTable'
import LocationRepository from '../../../repository/LocationRepository'
export default function SummaryContent(props) {
const [expanded, setExpanded] = React.useState(null);
const [locationDetails, setLocationDetails] = React.useState({})
const getLocationDetails = () => {
LocationRepository.getLocationData(props.filters).then(result => {
setLocationDetails(result);
})
}
const handleChange = (panel) => (event, isExpanded) => {
setExpanded(isExpanded ? panel : false);
};
React.useEffect(() => {
props.setNextDisabled(false);
!Object.keys(locationDetails).length && getLocationDetails()
});
function createData(name, value) {
return { name, value };
}
const locationData = [
createData('Geographical Coordinates (latitude, longitude)', `(${props.filters.coordinates.lat}, ${props.filters.coordinates.lng})`),
createData('Ecological Region', locationDetails.ecological_region || ''),
createData('Ecological District', locationDetails.ecological_district || ''),
createData('Property Name', locationDetails.full_address || ''),
];
const soilData = [
createData('Soil Order', `${locationDetails.soil_name} (${locationDetails.soil_code})` || ''),
createData('Soil Variant', props.filters.soilVariant)
];
const siteData = [
createData('Habitat', props.filters.habitat.name ?? ""),
createData('Zone Name', (props.filters.zone && props.filters.zone.name) ?? ""),
createData('Zone Variant', (props.filters.zone && props.filters.zone.variant) ?? ""),
createData('Zone Refined Variant', (props.filters.zone && props.filters.zone.refined_variant) ?? "")
];
const regionInformation = () => {
if (locationDetails.in_chch) {
return <Typography variant='body2'>Your location falls within the ecosystem type covered by the Christchurch Council ecosystem maps - further information can be obtained from <a href="https://ccc.govt.nz/environment/land/ecosystem-map" target="blank" >Ōtautahi/Christchurch ecosystems map</a>.</Typography>
} else if (locationDetails.in_auckland) {
return <Typography variant='body2'>"Your location falls within the ecosystem type covered by the Auckland Council Tiaki Tāmaki Makaurau Conservation map - further information can be obtained from <a href="https://www.tiakitamakimakaurau.nz/conservation-map/" target="blank" >tiaki Tāmaki Makaurau conservation Auckland</a></Typography>
}
}
return (
<div className='summary-content'>
<Typography mb={2} mt={2} >Please review your choices presented below: </Typography>
<Accordion expanded={expanded === 'panel1'} onChange={handleChange('panel1')} >
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>
<Typography>1. Location Data</Typography>
</AccordionSummary>
<AccordionDetails>
<SummaryTable rows={locationData} />
<Box ml={2} mt={2}>
{regionInformation()}
</Box>
</AccordionDetails>
</Accordion>
<Accordion expanded={expanded === 'panel2'} onChange={handleChange('panel2')} >
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>
<Typography>2. Soil Properties</Typography>
</AccordionSummary>
<AccordionDetails>
<SummaryTable rows={soilData} />
</AccordionDetails>
</Accordion>
<Accordion expanded={expanded === 'panel3'} onChange={handleChange('panel3')} >
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>
<Typography>3. Project Site Details</Typography>
</AccordionSummary>
<AccordionDetails>
<SummaryTable rows={siteData} />
</AccordionDetails>
</Accordion>
</div>
)
}

View file

@ -0,0 +1,148 @@
import * as React from "react";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import SummaryTable from "./SummaryTable";
import LocationRepository from "../../../repository/LocationRepository";
export default function SummaryContent(props) {
const [expanded, setExpanded] = React.useState(null);
const [locationDetails, setLocationDetails] = React.useState({});
const getLocationDetails = () => {
LocationRepository.getLocationData(props.filters).then((result) => {
setLocationDetails(result);
});
};
const handleChange = (panel) => (event, isExpanded) => {
setExpanded(isExpanded ? panel : false);
};
React.useEffect(() => {
props.setNextDisabled(false);
!Object.keys(locationDetails).length && getLocationDetails();
});
function createData(name, value) {
return { name, value };
}
const locationData = [
createData(
"Geographical Coordinates (latitude, longitude)",
`(${props.filters.coordinates.lat}, ${props.filters.coordinates.lng})`
),
createData("Ecological Region", locationDetails.ecological_region || ""),
createData(
"Ecological District",
locationDetails.ecological_district || ""
),
createData("Property Name", locationDetails.full_address || ""),
];
const soilData = [
createData(
"Soil Order",
`${locationDetails.soil_name} (${locationDetails.soil_code})` || ""
),
createData("Soil Variant", props.filters.soilVariant),
];
const siteData = [
createData("Habitat", props.filters.habitat.name ?? ""),
createData(
"Zone Name",
(props.filters.zone && props.filters.zone.name) ?? ""
),
createData(
"Zone Variant",
(props.filters.zone && props.filters.zone.variant) ?? ""
),
createData(
"Zone Refined Variant",
(props.filters.zone && props.filters.zone.refined_variant) ?? ""
),
];
const regionInformation = () => {
if (locationDetails.in_chch) {
return (
<Typography variant="body2">
Your location falls within the ecosystem type covered by the
Christchurch Council ecosystem maps - further information can be
obtained from{" "}
<a
href="https://ccc.govt.nz/environment/land/ecosystem-map"
target="blank"
>
Ōtautahi/Christchurch ecosystems map
</a>
.
</Typography>
);
} else if (locationDetails.in_auckland) {
return (
<Typography variant="body2">
"Your location falls within the ecosystem type covered by the Auckland
Council Tiaki Tāmaki Makaurau Conservation map - further information
can be obtained from{" "}
<a
href="https://www.tiakitamakimakaurau.nz/conservation-map/"
target="blank"
>
tiaki Tāmaki Makaurau conservation Auckland
</a>
</Typography>
);
}
};
return (
<div className="summary-content">
<Typography mb={2} mt={2}>
Please review your choices presented below:{" "}
</Typography>
<Accordion
expanded={expanded === "panel1"}
onChange={handleChange("panel1")}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>1. Location Data</Typography>
</AccordionSummary>
<AccordionDetails>
<SummaryTable rows={locationData} />
<Box ml={2} mt={2}>
{regionInformation()}
</Box>
</AccordionDetails>
</Accordion>
<Accordion
expanded={expanded === "panel2"}
onChange={handleChange("panel2")}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>2. Soil Properties</Typography>
</AccordionSummary>
<AccordionDetails>
<SummaryTable rows={soilData} />
</AccordionDetails>
</Accordion>
<Accordion
expanded={expanded === "panel3"}
onChange={handleChange("panel3")}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>3. Project Site Details</Typography>
</AccordionSummary>
<AccordionDetails>
<SummaryTable rows={siteData} />
</AccordionDetails>
</Accordion>
</div>
);
}

View file

@ -1,22 +0,0 @@
import Step from '../Step';
import SummaryContent from './SummaryContent'
import StepInformation from '../StepInformation';
import staticText from '../../../assets/data/staticText.json'
import summaryBackgroundImage from '../../../assets/img/stepBackgrounds/step5.jpg';
export default function SummaryStep(props) {
const summaryInfoPanel = (
<StepInformation
title={staticText.steps.summary.title}
description={<p>{staticText.steps.summary.description}</p>}
/>
)
const summaryContent = (
<SummaryContent {...props} />
)
return (
<Step informationComponent={summaryInfoPanel} selectionComponent={summaryContent} backgroundImage={summaryBackgroundImage} />
)
}

View file

@ -0,0 +1,24 @@
import Step from "../Step";
import SummaryContent from "./SummaryContent";
import StepInformation from "../StepInformation";
import staticText from "../../../assets/data/staticText.json";
import summaryBackgroundImage from "../../../assets/img/stepBackgrounds/step5.jpg";
export default function SummaryStep(props) {
const summaryInfoPanel = (
<StepInformation
title={staticText.steps.summary.title}
description={<p>{staticText.steps.summary.description}</p>}
/>
);
const summaryContent = <SummaryContent {...props} />;
return (
<Step
informationComponent={summaryInfoPanel}
selectionComponent={summaryContent}
backgroundImage={summaryBackgroundImage}
/>
);
}

View file

@ -1,19 +1,19 @@
import * as React from 'react'; import * as React from "react";
import Table from '@mui/material/Table'; import Table from "@mui/material/Table";
import TableBody from '@mui/material/TableBody'; import TableBody from "@mui/material/TableBody";
import TableCell from '@mui/material/TableCell'; import TableCell from "@mui/material/TableCell";
import TableContainer from '@mui/material/TableContainer'; import TableContainer from "@mui/material/TableContainer";
import TableRow from '@mui/material/TableRow'; import TableRow from "@mui/material/TableRow";
export default function BasicTable(props) { export default function BasicTable(props) {
return ( return (
<TableContainer > <TableContainer>
<Table sx={{ minWidth: 650 }} size="small" aria-label="simple table"> <Table sx={{ minWidth: 650 }} size="small" aria-label="simple table">
<TableBody> <TableBody>
{props.rows.map((row) => ( {props.rows.map((row) => (
<TableRow <TableRow
key={row.name} key={row.name}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
> >
<TableCell component="th" scope="row"> <TableCell component="th" scope="row">
{row.name} {row.name}

View file

@ -1,117 +0,0 @@
import { useEffect, useState } from 'react';
import SiteRepository from '../../../repository/SiteRepository';
import { HabitatSVG } from '../HabitatSVG'
export default function ZoneSelector(props) {
const [habitatImageObject, setHabitatImageObject] = useState({});
const [segmentMapping, setSegmentMapping] = useState({});
const [selectedZoneSegment, setZoneSegment] = useState(null);
const setZoneOrRedirect = (zone) => {
const redirectHabitat = zone && zone.redirect_habitat;
// If there is a redirect habitat set then redirect otherwise set the zone as state
const newFilterState = redirectHabitat ? { "habitat": { "id": redirectHabitat.id, "name": redirectHabitat.name } } : { "zone": zone };
props.updateFilterState(newFilterState);
props.setRedirectBack(Boolean(redirectHabitat));
props.setNextDisabled(false);
}
useEffect(() => {
const getHabitatImage = () => {
SiteRepository.getHabitatImage(props.filters.habitatImage).then(response => {
if (response.status === 200) {
const imageData = response.data
setHabitatImageObject(imageData)
}
})
}
const setInitialZone = () => {
if (props.filters.zone) {
const zone = props.filters.zone
const zoneSegment = document.querySelectorAll(`.zone-selector-svg svg path[inkscapelabel="${zone.related_svg_segment}"]`)[0];
// If the previously selected zone segment is located in the diagram then highlight it
if (zoneSegment) {
setZoneSegment(zoneSegment)
zoneSegment.style.fill = "#eeeeee"
zoneSegment.style['fill-opacity'] = 0.5;
props.setNextDisabled(false);
}
}
}
const getZones = () => {
SiteRepository.getZones().then(response => {
if (response.status === 200) {
const zoneData = response.data
let newSegmentMapping = {}
zoneData.forEach(zone => {
newSegmentMapping[zone.related_svg_segment] = zone
});
setSegmentMapping(newSegmentMapping);
setInitialZone()
}
})
}
// Retrieves the habitat image from the api if it's not loaded already
Object.keys(habitatImageObject).length === 0 && getHabitatImage()
// Retrieves the habitat image from the api if it's not loaded already
Object.keys(segmentMapping).length === 0 && getZones()
}, [habitatImageObject, segmentMapping, props, props.filters.zone]);
const selectZone = (element) => {
if (['path', 'rect'].includes(element.tagName) && element.attributes.inkscapelabel && element.attributes.inkscapelabel.nodeValue) {
if (selectedZoneSegment) {
selectedZoneSegment.style['fill-opacity'] = 0;
}
setZoneSegment(element)
element.style.fill = "#eeeeee"
element.style['fill-opacity'] = 0.5;
const zone = segmentMapping[element.attributes.inkscapelabel.nodeValue]
setZoneOrRedirect(zone)
}
}
const showTooltip = (evt) => {
const element = evt.target;
if (['path', 'rect'].includes(element.tagName) && element.attributes.inkscapelabel && element.attributes.inkscapelabel.nodeValue) {
const zone = segmentMapping[element.attributes.inkscapelabel.nodeValue]
if (zone) {
let tooltip = document.getElementById("svg-tooltip");
tooltip.innerHTML = zone.tooltip_display_text;
tooltip.style.display = "block";
tooltip.style.left = evt.pageX + 10 + 'px';
tooltip.style.top = evt.pageY + 10 + 'px';
tooltip.style.opacity = 1;
}
}
}
const hideTooltip = () => {
var tooltip = document.getElementById("svg-tooltip");
tooltip.style.display = "none";
}
return Object.keys(habitatImageObject).length > 0 ? (
<div style={{ width: '100%' }}>
<p>{habitatImageObject.name}</p>
<div className="zone-selector-svg">
<div id="svg-tooltip" className="MuiTooltip-tooltip MuiTooltip-tooltipArrow MuiTooltip-tooltipPlacementBottom" display="none" style={{ "position": 'absolute', "display": "none" }} ></div>
<HabitatSVG onClick={(event) => selectZone(event.target)} onMouseMove={(event) => showTooltip(event)} onMouseOut={() => hideTooltip()} name={habitatImageObject.image_filename} style={{ "height": 'auto', "width": "100%" }} />
</div>
</div>) : <div></div>
}

View file

@ -0,0 +1,141 @@
import { useEffect, useState } from "react";
import SiteRepository from "../../../repository/SiteRepository";
import { HabitatSVG } from "../HabitatSVG";
export default function ZoneSelector(props) {
const [habitatImageObject, setHabitatImageObject] = useState({});
const [segmentMapping, setSegmentMapping] = useState({});
const [selectedZoneSegment, setZoneSegment] = useState(null);
const setZoneOrRedirect = (zone) => {
const redirectHabitat = zone && zone.redirect_habitat;
// If there is a redirect habitat set then redirect otherwise set the zone as state
const newFilterState = redirectHabitat
? { habitat: { id: redirectHabitat.id, name: redirectHabitat.name } }
: { zone: zone };
props.updateFilterState(newFilterState);
props.setRedirectBack(Boolean(redirectHabitat));
props.setNextDisabled(false);
};
useEffect(() => {
const getHabitatImage = () => {
SiteRepository.getHabitatImage(props.filters.habitatImage).then(
(response) => {
if (response.status === 200) {
const imageData = response.data;
setHabitatImageObject(imageData);
}
}
);
};
const setInitialZone = () => {
if (props.filters.zone) {
const zone = props.filters.zone;
const zoneSegment = document.querySelectorAll(
`.zone-selector-svg svg path[inkscapelabel="${zone.related_svg_segment}"]`
)[0];
// If the previously selected zone segment is located in the diagram then highlight it
if (zoneSegment) {
setZoneSegment(zoneSegment);
zoneSegment.style.fill = "#eeeeee";
zoneSegment.style["fill-opacity"] = 0.5;
props.setNextDisabled(false);
}
}
};
const getZones = () => {
SiteRepository.getZones().then((response) => {
if (response.status === 200) {
const zoneData = response.data;
let newSegmentMapping = {};
zoneData.forEach((zone) => {
newSegmentMapping[zone.related_svg_segment] = zone;
});
setSegmentMapping(newSegmentMapping);
setInitialZone();
}
});
};
// Retrieves the habitat image from the api if it's not loaded already
Object.keys(habitatImageObject).length === 0 && getHabitatImage();
// Retrieves the habitat image from the api if it's not loaded already
Object.keys(segmentMapping).length === 0 && getZones();
}, [habitatImageObject, segmentMapping, props, props.filters.zone]);
const selectZone = (element) => {
if (
["path", "rect"].includes(element.tagName) &&
element.attributes.inkscapelabel &&
element.attributes.inkscapelabel.nodeValue
) {
if (selectedZoneSegment) {
selectedZoneSegment.style["fill-opacity"] = 0;
}
setZoneSegment(element);
element.style.fill = "#eeeeee";
element.style["fill-opacity"] = 0.5;
const zone = segmentMapping[element.attributes.inkscapelabel.nodeValue];
setZoneOrRedirect(zone);
}
};
const showTooltip = (evt) => {
const element = evt.target;
if (
["path", "rect"].includes(element.tagName) &&
element.attributes.inkscapelabel &&
element.attributes.inkscapelabel.nodeValue
) {
const zone = segmentMapping[element.attributes.inkscapelabel.nodeValue];
if (zone) {
let tooltip = document.getElementById("svg-tooltip");
tooltip.innerHTML = zone.tooltip_display_text;
tooltip.style.display = "block";
tooltip.style.left = evt.pageX + 10 + "px";
tooltip.style.top = evt.pageY + 10 + "px";
tooltip.style.opacity = 1;
}
}
};
const hideTooltip = () => {
var tooltip = document.getElementById("svg-tooltip");
tooltip.style.display = "none";
};
return Object.keys(habitatImageObject).length > 0 ? (
<div style={{ width: "100%" }}>
<p>{habitatImageObject.name}</p>
<div className="zone-selector-svg">
<div
id="svg-tooltip"
className="MuiTooltip-tooltip MuiTooltip-tooltipArrow MuiTooltip-tooltipPlacementBottom"
display="none"
style={{ position: "absolute", display: "none" }}
></div>
<HabitatSVG
onClick={(event) => selectZone(event.target)}
onMouseMove={(event) => showTooltip(event)}
onMouseOut={() => hideTooltip()}
name={habitatImageObject.image_filename}
style={{ height: "auto", width: "100%" }}
/>
</div>
</div>
) : (
<div></div>
);
}

View file

@ -1,24 +0,0 @@
import Step from '../Step';
import ZoneSelector from './ZoneSelector'
import StepInformation from '../StepInformation';
import staticText from '../../../assets/data/staticText.json'
import zoneBackgroundImage from '../../../assets/img/stepBackgrounds/step4.jpg';
export default function ZoneStep(props) {
const zoneInfoPanel = (
<StepInformation
title={staticText.steps.zone.title}
description={<p>{staticText.steps.zone.description}</p>}
/>
)
const zoneSelectionPanel = (
<div style={{padding: '30px', height: "100%", width: "90%", display: "flex", justifyContent: 'center', alignItems: "center"}}>
<ZoneSelector {...props} />
</div>
)
return (
<Step informationComponent={zoneInfoPanel} selectionComponent={zoneSelectionPanel} backgroundImage={zoneBackgroundImage} />
)
}

View file

@ -0,0 +1,37 @@
import Step from "../Step";
import ZoneSelector from "./ZoneSelector";
import StepInformation from "../StepInformation";
import staticText from "../../../assets/data/staticText.json";
import zoneBackgroundImage from "../../../assets/img/stepBackgrounds/step4.jpg";
export default function ZoneStep(props) {
const zoneInfoPanel = (
<StepInformation
title={staticText.steps.zone.title}
description={<p>{staticText.steps.zone.description}</p>}
/>
);
const zoneSelectionPanel = (
<div
style={{
padding: "30px",
height: "100%",
width: "90%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<ZoneSelector {...props} />
</div>
);
return (
<Step
informationComponent={zoneInfoPanel}
selectionComponent={zoneSelectionPanel}
backgroundImage={zoneBackgroundImage}
/>
);
}

View file

@ -1,17 +1,14 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import { createTheme, ThemeProvider } from '@mui/material/styles'; import { createTheme, ThemeProvider } from "@mui/material/styles";
import reportWebVitals from './reportWebVitals'; import reportWebVitals from "./reportWebVitals";
import { import { createBrowserRouter, RouterProvider } from "react-router-dom";
createBrowserRouter, import MainPage from "./pages/MainPage";
RouterProvider,
} from 'react-router-dom';
import MainPage from './pages/MainPage';
// Styles // Styles
import './assets/styles/main.scss'; import "./assets/styles/main.scss";
import 'bootstrap/dist/css/bootstrap.min.css'; import "bootstrap/dist/css/bootstrap.min.css";
import ApplyPage from './pages/ApplyPage'; import ApplyPage from "./pages/ApplyPage";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@ -26,10 +23,10 @@ const router = createBrowserRouter([
const darkTheme = createTheme({ const darkTheme = createTheme({
palette: { palette: {
mode: 'dark', mode: "dark",
}, },
typography: { typography: {
fontFamily: 'Poppins, sans-serif', fontFamily: "Poppins, sans-serif",
}, },
}); });
@ -41,7 +38,7 @@ ReactDOM.render(
</ThemeProvider> </ThemeProvider>
</div> </div>
</React.StrictMode>, </React.StrictMode>,
document.getElementById('root') document.getElementById("root")
); );
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function

View file

@ -1,40 +1,55 @@
import API from "./Repository"; import API from "./Repository";
const LocationRepsostory = { const LocationRepsostory = {
getSoilDetails(filters) {
return API.get(`/soil/`, { params: filters });
},
getSoilDetails(filters) { getEcologicalDistrictDetails(filters) {
return API.get(`/soil/`, { params: filters }); return API.get(`/ecologicaldistrict/`, { params: filters });
}, },
getEcologicalDistrictDetails(filters) { getRegionDetails(filters) {
return API.get(`/ecologicaldistrict/`, { params: filters }); return API.get(`/region/`, { params: filters });
}, },
getRegionDetails(filters) { getPropertyDetails(filters) {
return API.get(`/region/`, { params: filters }); return API.get(`/address/`, { params: filters });
}, },
getPropertyDetails(filters) { async getLocationData(filters) {
return API.get(`/address/`, { params: filters }); const [
}, soilDetails,
ecologicalDistrictDetails,
propertyDetails,
regionDetails,
] = await Promise.all([
this.getSoilDetails(filters),
this.getEcologicalDistrictDetails(filters),
this.getPropertyDetails(filters),
this.getRegionDetails(filters),
]);
async getLocationData(filters) { let locationData = {};
const [soilDetails, ecologicalDistrictDetails, propertyDetails, regionDetails] = await Promise.all([ locationData =
this.getSoilDetails(filters), soilDetails.status === 200
this.getEcologicalDistrictDetails(filters), ? Object.assign(locationData, soilDetails.data[0])
this.getPropertyDetails(filters), : locationData;
this.getRegionDetails(filters) locationData =
]); ecologicalDistrictDetails.status === 200
? Object.assign(locationData, ecologicalDistrictDetails.data[0])
: locationData;
locationData =
propertyDetails.status === 200
? Object.assign(locationData, propertyDetails.data)
: locationData;
locationData =
regionDetails.status === 200
? Object.assign(locationData, regionDetails.data)
: locationData;
let locationData = {}; return locationData;
locationData = soilDetails.status === 200 ? Object.assign(locationData, soilDetails.data[0]) : locationData; },
locationData = ecologicalDistrictDetails.status === 200 ? Object.assign(locationData, ecologicalDistrictDetails.data[0]) : locationData; };
locationData = propertyDetails.status === 200 ? Object.assign(locationData, propertyDetails.data) : locationData;
locationData = regionDetails.status === 200 ? Object.assign(locationData, regionDetails.data) : locationData;
return locationData
}
}
export default LocationRepsostory; export default LocationRepsostory;

View file

@ -1,19 +1,20 @@
import Repository from "./Repository"; import Repository from "./Repository";
const PlantRepository = { const PlantRepository = {
getFilteredPlants(filters) {
return Repository.get(`/plants/`, { params: filters });
},
getFilteredPlants(filters) { getPlantsCSV(filters) {
return Repository.get(`/plants/`, { params: filters }); return Repository.get("/download/csv/", { params: filters });
}, },
getPlantsCSV(filters) { getPlantsPDF(filters) {
return Repository.get('/download/csv/', { params: filters }) return Repository.get("/download/pdf/", {
}, params: filters,
responseType: "arraybuffer",
getPlantsPDF(filters) { });
return Repository.get('/download/pdf/', { params: filters, responseType: 'arraybuffer' }) },
} };
}
export default PlantRepository; export default PlantRepository;

View file

@ -1,8 +1,11 @@
import axios from "axios" import axios from "axios";
// Create the axios object // Create the axios object
const repo = axios.create({ const repo = axios.create({
baseURL: window.location.hostname === 'localhost' ? "http://localhost:9000/api" : "/api", baseURL:
window.location.hostname === "localhost"
? "http://localhost:9000/api"
: "/api",
}); });
repo.defaults.headers.post["access-control-allow-origin"] = "*"; repo.defaults.headers.post["access-control-allow-origin"] = "*";

View file

@ -1,19 +1,17 @@
import Repository from "./Repository"; import Repository from "./Repository";
const SiteRepository = { const SiteRepository = {
getHabitats() {
return Repository.get(`/habitats/`);
},
getHabitats() { getZones() {
return Repository.get(`/habitats/`); return Repository.get(`/zones/`);
}, },
getZones() { getHabitatImage(habitatImageID) {
return Repository.get(`/zones/`); return Repository.get(`/habitatimage/${habitatImageID}/`);
}, },
};
getHabitatImage(habitatImageID) {
return Repository.get(`/habitatimage/${habitatImageID}/`);
}
}
export default SiteRepository; export default SiteRepository;