[#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:
parent
ff197cc1a8
commit
0f36c79dbf
42 changed files with 1020 additions and 785 deletions
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
17
frontend/src/components/Header.jsx
Normal file
17
frontend/src/components/Header.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -56,7 +56,6 @@ export default function StepperWizard({ steps }) {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Stepper>
|
</Stepper>
|
||||||
<>
|
|
||||||
<CurrentStep
|
<CurrentStep
|
||||||
filters={filters}
|
filters={filters}
|
||||||
updateFilterState={updateFilterState}
|
updateFilterState={updateFilterState}
|
||||||
|
@ -77,7 +76,6 @@ export default function StepperWizard({ steps }) {
|
||||||
{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>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
48
frontend/src/components/TopNav.jsx
Normal file
48
frontend/src/components/TopNav.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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>);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
50
frontend/src/components/steps/Step.jsx
Normal file
50
frontend/src/components/steps/Step.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
export default function StepInformation(props) {
|
|
||||||
return (
|
|
||||||
<div className='ph-4'>
|
|
||||||
<h3>{props.title}</h3>
|
|
||||||
<br/>
|
|
||||||
{props.description}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
9
frontend/src/components/steps/StepInformation.jsx
Normal file
9
frontend/src/components/steps/StepInformation.jsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export default function StepInformation(props) {
|
||||||
|
return (
|
||||||
|
<div className="ph-4">
|
||||||
|
<h3>{props.title}</h3>
|
||||||
|
<br />
|
||||||
|
{props.description}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
157
frontend/src/components/steps/habitat/HabitatSelector.jsx
Normal file
157
frontend/src/components/steps/habitat/HabitatSelector.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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} />
|
|
||||||
)
|
|
||||||
}
|
|
24
frontend/src/components/steps/habitat/HabitatStep.jsx
Normal file
24
frontend/src/components/steps/habitat/HabitatStep.jsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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} />
|
|
||||||
)
|
|
||||||
}
|
|
24
frontend/src/components/steps/location/LocationStep.jsx
Normal file
24
frontend/src/components/steps/location/LocationStep.jsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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>
|
||||||
)
|
);
|
||||||
}
|
}
|
|
@ -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, we’d 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>
|
|
||||||
);
|
|
||||||
}
|
|
111
frontend/src/components/steps/soilvariant/SoilSelector.jsx
Normal file
111
frontend/src/components/steps/soilvariant/SoilSelector.jsx
Normal 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,
|
||||||
|
we’d 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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} />
|
|
||||||
)
|
|
||||||
}
|
|
50
frontend/src/components/steps/soilvariant/SoilStep.jsx
Normal file
50
frontend/src/components/steps/soilvariant/SoilStep.jsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export default function ProjectSpecificsSelector(props) {
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
props.setNextDisabled(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<h2>Project Specifics Selector</h2>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export default function ProjectSpecificsSelector(props) {
|
||||||
|
useEffect(() => {
|
||||||
|
props.setNextDisabled(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <h2>Project Specifics Selector</h2>;
|
||||||
|
}
|
|
@ -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} />
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
148
frontend/src/components/steps/summary/SummaryContent.jsx
Normal file
148
frontend/src/components/steps/summary/SummaryContent.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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} />
|
|
||||||
)
|
|
||||||
}
|
|
24
frontend/src/components/steps/summary/SummaryStep.jsx
Normal file
24
frontend/src/components/steps/summary/SummaryStep.jsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
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 (
|
||||||
|
@ -13,7 +13,7 @@ export default function BasicTable(props) {
|
||||||
{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}
|
|
@ -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>
|
|
||||||
}
|
|
141
frontend/src/components/steps/zone/ZoneSelector.jsx
Normal file
141
frontend/src/components/steps/zone/ZoneSelector.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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} />
|
|
||||||
)
|
|
||||||
}
|
|
37
frontend/src/components/steps/zone/ZoneStep.jsx
Normal file
37
frontend/src/components/steps/zone/ZoneStep.jsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import API from "./Repository";
|
import API from "./Repository";
|
||||||
|
|
||||||
|
|
||||||
const LocationRepsostory = {
|
const LocationRepsostory = {
|
||||||
|
|
||||||
getSoilDetails(filters) {
|
getSoilDetails(filters) {
|
||||||
return API.get(`/soil/`, { params: filters });
|
return API.get(`/soil/`, { params: filters });
|
||||||
},
|
},
|
||||||
|
@ -20,21 +18,38 @@ const LocationRepsostory = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async getLocationData(filters) {
|
async getLocationData(filters) {
|
||||||
const [soilDetails, ecologicalDistrictDetails, propertyDetails, regionDetails] = await Promise.all([
|
const [
|
||||||
|
soilDetails,
|
||||||
|
ecologicalDistrictDetails,
|
||||||
|
propertyDetails,
|
||||||
|
regionDetails,
|
||||||
|
] = await Promise.all([
|
||||||
this.getSoilDetails(filters),
|
this.getSoilDetails(filters),
|
||||||
this.getEcologicalDistrictDetails(filters),
|
this.getEcologicalDistrictDetails(filters),
|
||||||
this.getPropertyDetails(filters),
|
this.getPropertyDetails(filters),
|
||||||
this.getRegionDetails(filters)
|
this.getRegionDetails(filters),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let locationData = {};
|
let locationData = {};
|
||||||
locationData = soilDetails.status === 200 ? Object.assign(locationData, soilDetails.data[0]) : locationData;
|
locationData =
|
||||||
locationData = ecologicalDistrictDetails.status === 200 ? Object.assign(locationData, ecologicalDistrictDetails.data[0]) : locationData;
|
soilDetails.status === 200
|
||||||
locationData = propertyDetails.status === 200 ? Object.assign(locationData, propertyDetails.data) : locationData;
|
? Object.assign(locationData, soilDetails.data[0])
|
||||||
locationData = regionDetails.status === 200 ? Object.assign(locationData, regionDetails.data) : locationData;
|
: 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
|
return locationData;
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export default LocationRepsostory;
|
export default LocationRepsostory;
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import Repository from "./Repository";
|
import Repository from "./Repository";
|
||||||
|
|
||||||
const PlantRepository = {
|
const PlantRepository = {
|
||||||
|
|
||||||
getFilteredPlants(filters) {
|
getFilteredPlants(filters) {
|
||||||
return Repository.get(`/plants/`, { params: filters });
|
return Repository.get(`/plants/`, { params: filters });
|
||||||
},
|
},
|
||||||
|
|
||||||
getPlantsCSV(filters) {
|
getPlantsCSV(filters) {
|
||||||
return Repository.get('/download/csv/', { params: filters })
|
return Repository.get("/download/csv/", { params: filters });
|
||||||
},
|
},
|
||||||
|
|
||||||
getPlantsPDF(filters) {
|
getPlantsPDF(filters) {
|
||||||
return Repository.get('/download/pdf/', { params: filters, responseType: 'arraybuffer' })
|
return Repository.get("/download/pdf/", {
|
||||||
}
|
params: filters,
|
||||||
|
responseType: "arraybuffer",
|
||||||
}
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default PlantRepository;
|
export default PlantRepository;
|
||||||
|
|
|
@ -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"] = "*";
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import Repository from "./Repository";
|
import Repository from "./Repository";
|
||||||
|
|
||||||
const SiteRepository = {
|
const SiteRepository = {
|
||||||
|
|
||||||
getHabitats() {
|
getHabitats() {
|
||||||
return Repository.get(`/habitats/`);
|
return Repository.get(`/habitats/`);
|
||||||
},
|
},
|
||||||
|
@ -12,8 +11,7 @@ const SiteRepository = {
|
||||||
|
|
||||||
getHabitatImage(habitatImageID) {
|
getHabitatImage(habitatImageID) {
|
||||||
return Repository.get(`/habitatimage/${habitatImageID}/`);
|
return Repository.get(`/habitatimage/${habitatImageID}/`);
|
||||||
}
|
},
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default SiteRepository;
|
export default SiteRepository;
|
Loading…
Reference in a new issue