From 0f36c79dbf74d260ba8207d4eb359e98254fffe9 Mon Sep 17 00:00:00 2001 From: Matthew Northcott Date: Fri, 10 Feb 2023 14:13:12 +1300 Subject: [PATCH] [#40] Review changes - add comment about Suspense - add missing key attributes - remove redundant fragments - prettier formatting - rename React component files with .jsx extension --- frontend/src/components/Header.js | 13 -- frontend/src/components/Header.jsx | 17 ++ .../components/{Stepper.js => Stepper.jsx} | 42 +++-- frontend/src/components/TopNav.js | 30 ---- frontend/src/components/TopNav.jsx | 48 ++++++ frontend/src/components/steps/HabitatSVG.jsx | 2 +- frontend/src/components/steps/Step.js | 30 ---- frontend/src/components/steps/Step.jsx | 50 ++++++ .../src/components/steps/StepInformation.js | 9 - .../src/components/steps/StepInformation.jsx | 9 + .../steps/address/AddressSearch.jsx | 6 +- .../steps/habitat/HabitatSelector.js | 133 --------------- .../steps/habitat/HabitatSelector.jsx | 157 ++++++++++++++++++ .../components/steps/habitat/HabitatStep.js | 22 --- .../components/steps/habitat/HabitatStep.jsx | 24 +++ .../components/steps/location/LocationStep.js | 23 --- .../steps/location/LocationStep.jsx | 24 +++ .../steps/location/{Map.js => Map.jsx} | 60 +++---- .../results/{PlantList.js => PlantList.jsx} | 0 .../{ResultsStep.js => ResultsStep.jsx} | 0 .../steps/soilvariant/SoilSelector.js | 63 ------- .../steps/soilvariant/SoilSelector.jsx | 111 +++++++++++++ .../components/steps/soilvariant/SoilStep.js | 30 ---- .../components/steps/soilvariant/SoilStep.jsx | 50 ++++++ .../specifics/ProjectSpecificsSelector.js | 12 -- .../specifics/ProjectSpecificsSelector.jsx | 9 + .../steps/specifics/ProjectSpecificsStep.js | 21 --- .../steps/specifics/ProjectSpecificsStep.jsx | 24 +++ .../steps/summary/SummaryContent.js | 101 ----------- .../steps/summary/SummaryContent.jsx | 148 +++++++++++++++++ .../components/steps/summary/SummaryStep.js | 22 --- .../components/steps/summary/SummaryStep.jsx | 24 +++ .../{SummaryTable.js => SummaryTable.jsx} | 16 +- .../src/components/steps/zone/ZoneSelector.js | 117 ------------- .../components/steps/zone/ZoneSelector.jsx | 141 ++++++++++++++++ .../src/components/steps/zone/ZoneStep.js | 24 --- .../src/components/steps/zone/ZoneStep.jsx | 37 +++++ frontend/src/index.js | 27 ++- frontend/src/repository/LocationRepository.js | 73 ++++---- frontend/src/repository/PlantRepository.js | 25 +-- frontend/src/repository/Repository.js | 7 +- frontend/src/repository/SiteRepository.js | 24 ++- 42 files changed, 1020 insertions(+), 785 deletions(-) delete mode 100644 frontend/src/components/Header.js create mode 100644 frontend/src/components/Header.jsx rename frontend/src/components/{Stepper.js => Stepper.jsx} (69%) delete mode 100644 frontend/src/components/TopNav.js create mode 100644 frontend/src/components/TopNav.jsx delete mode 100644 frontend/src/components/steps/Step.js create mode 100644 frontend/src/components/steps/Step.jsx delete mode 100644 frontend/src/components/steps/StepInformation.js create mode 100644 frontend/src/components/steps/StepInformation.jsx delete mode 100644 frontend/src/components/steps/habitat/HabitatSelector.js create mode 100644 frontend/src/components/steps/habitat/HabitatSelector.jsx delete mode 100644 frontend/src/components/steps/habitat/HabitatStep.js create mode 100644 frontend/src/components/steps/habitat/HabitatStep.jsx delete mode 100644 frontend/src/components/steps/location/LocationStep.js create mode 100644 frontend/src/components/steps/location/LocationStep.jsx rename frontend/src/components/steps/location/{Map.js => Map.jsx} (58%) rename frontend/src/components/steps/results/{PlantList.js => PlantList.jsx} (100%) rename frontend/src/components/steps/results/{ResultsStep.js => ResultsStep.jsx} (100%) delete mode 100644 frontend/src/components/steps/soilvariant/SoilSelector.js create mode 100644 frontend/src/components/steps/soilvariant/SoilSelector.jsx delete mode 100644 frontend/src/components/steps/soilvariant/SoilStep.js create mode 100644 frontend/src/components/steps/soilvariant/SoilStep.jsx delete mode 100644 frontend/src/components/steps/specifics/ProjectSpecificsSelector.js create mode 100644 frontend/src/components/steps/specifics/ProjectSpecificsSelector.jsx delete mode 100644 frontend/src/components/steps/specifics/ProjectSpecificsStep.js create mode 100644 frontend/src/components/steps/specifics/ProjectSpecificsStep.jsx delete mode 100644 frontend/src/components/steps/summary/SummaryContent.js create mode 100644 frontend/src/components/steps/summary/SummaryContent.jsx delete mode 100644 frontend/src/components/steps/summary/SummaryStep.js create mode 100644 frontend/src/components/steps/summary/SummaryStep.jsx rename frontend/src/components/steps/summary/{SummaryTable.js => SummaryTable.jsx} (59%) delete mode 100644 frontend/src/components/steps/zone/ZoneSelector.js create mode 100644 frontend/src/components/steps/zone/ZoneSelector.jsx delete mode 100644 frontend/src/components/steps/zone/ZoneStep.js create mode 100644 frontend/src/components/steps/zone/ZoneStep.jsx diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js deleted file mode 100644 index 2e85058..0000000 --- a/frontend/src/components/Header.js +++ /dev/null @@ -1,13 +0,0 @@ -import TopNav from './TopNav' -import logo from '../assets/img/logo.png' - -export default function Header() { - return ( -
- - Biosphere Capital Limited - - -
- ); -} diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx new file mode 100644 index 0000000..04888be --- /dev/null +++ b/frontend/src/components/Header.jsx @@ -0,0 +1,17 @@ +import TopNav from "./TopNav"; +import logo from "../assets/img/logo.png"; + +export default function Header() { + return ( +
+ + Biosphere Capital Limited + + +
+ ); +} diff --git a/frontend/src/components/Stepper.js b/frontend/src/components/Stepper.jsx similarity index 69% rename from frontend/src/components/Stepper.js rename to frontend/src/components/Stepper.jsx index b659cd6..2bc1041 100644 --- a/frontend/src/components/Stepper.js +++ b/frontend/src/components/Stepper.jsx @@ -56,28 +56,26 @@ export default function StepperWizard({ steps }) { ); })} - <> - setFilters({})} - setNextDisabled={setNextDisabled} - setRedirectBack={setRedirectBack} - /> - - - - {activeStep === steps.length - 1 ? - : } - - + setFilters({})} + setNextDisabled={setNextDisabled} + setRedirectBack={setRedirectBack} + /> + + + + {activeStep === steps.length - 1 ? + : } + ); } diff --git a/frontend/src/components/TopNav.js b/frontend/src/components/TopNav.js deleted file mode 100644 index a06fd87..0000000 --- a/frontend/src/components/TopNav.js +++ /dev/null @@ -1,30 +0,0 @@ -export default function TopNav() { - return ( - - ); -} diff --git a/frontend/src/components/TopNav.jsx b/frontend/src/components/TopNav.jsx new file mode 100644 index 0000000..11bd604 --- /dev/null +++ b/frontend/src/components/TopNav.jsx @@ -0,0 +1,48 @@ +export default function TopNav() { + return ( + + ); +} diff --git a/frontend/src/components/steps/HabitatSVG.jsx b/frontend/src/components/steps/HabitatSVG.jsx index 4db5933..2d537f2 100644 --- a/frontend/src/components/steps/HabitatSVG.jsx +++ b/frontend/src/components/steps/HabitatSVG.jsx @@ -9,7 +9,7 @@ export const HabitatSVG = ({ name, ...rest }) => { ); return ( - + {/* experiemental in React 17. TODO: upgrade to 18 */} ); }; diff --git a/frontend/src/components/steps/Step.js b/frontend/src/components/steps/Step.js deleted file mode 100644 index 047b3c7..0000000 --- a/frontend/src/components/steps/Step.js +++ /dev/null @@ -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 ( -
-
- {props.contentComponent ? ( - props.contentComponent - ) : ( - - - {props.informationComponent} - - - {props.selectionComponent} - - - - )} -
-
- ); -} diff --git a/frontend/src/components/steps/Step.jsx b/frontend/src/components/steps/Step.jsx new file mode 100644 index 0000000..bf54fb2 --- /dev/null +++ b/frontend/src/components/steps/Step.jsx @@ -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 ( +
+
+ {props.contentComponent ? ( + props.contentComponent + ) : ( + + + {props.informationComponent} + + + {props.selectionComponent} + + + )} +
+
+ ); +} diff --git a/frontend/src/components/steps/StepInformation.js b/frontend/src/components/steps/StepInformation.js deleted file mode 100644 index 1c5c174..0000000 --- a/frontend/src/components/steps/StepInformation.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function StepInformation(props) { - return ( -
-

{props.title}

-
- {props.description} -
- ) -} diff --git a/frontend/src/components/steps/StepInformation.jsx b/frontend/src/components/steps/StepInformation.jsx new file mode 100644 index 0000000..d02a71a --- /dev/null +++ b/frontend/src/components/steps/StepInformation.jsx @@ -0,0 +1,9 @@ +export default function StepInformation(props) { + return ( +
+

{props.title}

+
+ {props.description} +
+ ); +} diff --git a/frontend/src/components/steps/address/AddressSearch.jsx b/frontend/src/components/steps/address/AddressSearch.jsx index 1bf9b2a..8ebc18b 100644 --- a/frontend/src/components/steps/address/AddressSearch.jsx +++ b/frontend/src/components/steps/address/AddressSearch.jsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { InputAdornment, TextField } from '@mui/material'; import PlaceIcon from '@mui/icons-material/Place'; import Box from '@mui/material/Box'; @@ -14,8 +14,8 @@ const AddressSearchSuggestions = ({results, onClick}) => ( (Array.isArray(results) && results.length > 0) ? - {results.map((r) => ( - + {results.map((r, idx) => ( + onClick(r)}> diff --git a/frontend/src/components/steps/habitat/HabitatSelector.js b/frontend/src/components/steps/habitat/HabitatSelector.js deleted file mode 100644 index c1abd1a..0000000 --- a/frontend/src/components/steps/habitat/HabitatSelector.js +++ /dev/null @@ -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 ( -
-

{image.name}

- -
) - } - - return ( -
-
- - {staticText.steps.habitat.optionsLabel} - - {habitats && Array.isArray(habitats) && habitats.map(habitat => - } label={habitat.name} /> - )} - - -
-
- - {staticText.steps.habitat.imageOptionsLabel} - - {selectedHabitat && selectedHabitat.images && Array.isArray(selectedHabitat.images) && selectedHabitat.images.map(image => - } label={getImageLabel(image)} sx={{ paddingTop: '15px', paddingBottom: '15px' }} /> - )} - {(!selectedHabitat || (selectedHabitat.images && selectedHabitat.images.length === 0)) &&

No images available.

} -
-
-
-
- ); -} diff --git a/frontend/src/components/steps/habitat/HabitatSelector.jsx b/frontend/src/components/steps/habitat/HabitatSelector.jsx new file mode 100644 index 0000000..33df9b8 --- /dev/null +++ b/frontend/src/components/steps/habitat/HabitatSelector.jsx @@ -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 ( +
+

{image.name}

+ +
+ ); + }; + + return ( +
+
+ + + {staticText.steps.habitat.optionsLabel} + + + {habitats && + Array.isArray(habitats) && + habitats.map((habitat) => ( + } + label={habitat.name} + /> + ))} + + +
+
+ + + {staticText.steps.habitat.imageOptionsLabel} + + + {selectedHabitat && + selectedHabitat.images && + Array.isArray(selectedHabitat.images) && + selectedHabitat.images.map((image) => ( + } + label={getImageLabel(image)} + sx={{ paddingTop: "15px", paddingBottom: "15px" }} + /> + ))} + {(!selectedHabitat || + (selectedHabitat.images && + selectedHabitat.images.length === 0)) && ( +

No images available.

+ )} +
+
+
+
+ ); +} diff --git a/frontend/src/components/steps/habitat/HabitatStep.js b/frontend/src/components/steps/habitat/HabitatStep.js deleted file mode 100644 index 52686da..0000000 --- a/frontend/src/components/steps/habitat/HabitatStep.js +++ /dev/null @@ -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 = ( - {staticText.steps.habitat.description}

} - /> - ) - - const habitatSelectionPanel = ( - - ) - - return ( - - ) -} diff --git a/frontend/src/components/steps/habitat/HabitatStep.jsx b/frontend/src/components/steps/habitat/HabitatStep.jsx new file mode 100644 index 0000000..165fe36 --- /dev/null +++ b/frontend/src/components/steps/habitat/HabitatStep.jsx @@ -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 = ( + {staticText.steps.habitat.description}

} + /> + ); + + const habitatSelectionPanel = ; + + return ( + + ); +} diff --git a/frontend/src/components/steps/location/LocationStep.js b/frontend/src/components/steps/location/LocationStep.js deleted file mode 100644 index 870e669..0000000 --- a/frontend/src/components/steps/location/LocationStep.js +++ /dev/null @@ -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 = ( - {staticText.steps.location.description}

} - /> - ) - - const locationSelectionPanel = ( - - ) - - return ( - - ) -} diff --git a/frontend/src/components/steps/location/LocationStep.jsx b/frontend/src/components/steps/location/LocationStep.jsx new file mode 100644 index 0000000..4e4c0b4 --- /dev/null +++ b/frontend/src/components/steps/location/LocationStep.jsx @@ -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 = ( + {staticText.steps.location.description}

} + /> + ); + + const locationSelectionPanel = ; + + return ( + + ); +} diff --git a/frontend/src/components/steps/location/Map.js b/frontend/src/components/steps/location/Map.jsx similarity index 58% rename from frontend/src/components/steps/location/Map.js rename to frontend/src/components/steps/location/Map.jsx index 993f9c1..a2a5690 100644 --- a/frontend/src/components/steps/location/Map.js +++ b/frontend/src/components/steps/location/Map.jsx @@ -1,72 +1,72 @@ -import { useState, useEffect } from "react" -import { MapContainer, TileLayer, Marker, useMapEvents } from 'react-leaflet' -import LocationRepository from '../../../repository/LocationRepository' +import { useState, useEffect } from "react"; +import { MapContainer, TileLayer, Marker, useMapEvents } from "react-leaflet"; +import LocationRepository from "../../../repository/LocationRepository"; const NZ_BOUNDS = [ [-47.204642, 165.344238], - [-34.307144, 179.824219] -] + [-34.307144, 179.824219], +]; function LocationMarker(props) { - const [position, setPosition] = useState(null) + const [position, setPosition] = useState(null); const map = useMapEvents({ click(e) { const newPosition = e.latlng; setPosition(newPosition); - props.updateFilterState({ "coordinates": newPosition }); + props.updateFilterState({ coordinates: newPosition }); props.setNextDisabled(false); - } - }) + }, + }); map.whenReady(() => { const savedCoordinates = props.filters["coordinates"]; if (!position && savedCoordinates) { setPosition(savedCoordinates); props.setNextDisabled(false); - map.flyTo(savedCoordinates, 9) + map.flyTo(savedCoordinates, 9); } - }) + }); - return position === null ? null : ( - - ) + return position === null ? null : ; } const fitNZBounds = (map) => { map.fitBounds(NZ_BOUNDS); -} +}; function LocationDetailsDisplay(props) { - const [locationDetails, setLocationDetails] = useState({}) + const [locationDetails, setLocationDetails] = useState({}); useEffect(() => { if (props.filters["coordinates"]) { - LocationRepository.getLocationData(props.filters).then(result => { + LocationRepository.getLocationData(props.filters).then((result) => { setLocationDetails(result); - }) - } - }, [props.filters, props.filters.coordinates] ); + }); + } + }, [props.filters, props.filters.coordinates]); const savedCoordinates = props.filters["coordinates"]; if (savedCoordinates) { - const soilString = `${locationDetails.soil_name} (${locationDetails.soil_code})` + const soilString = `${locationDetails.soil_name} (${locationDetails.soil_code})`; return ( -
+
Latitude: {savedCoordinates.lat}
- Longitude: {savedCoordinates.lng}
+ Longitude: {savedCoordinates.lng}
Address: {locationDetails.full_address}
- Ecological Region: {locationDetails.ecological_region}
- Ecological District: {locationDetails.ecological_district}
- Soil Order: {locationDetails.soil_name ? soilString : ""} + Ecological Region: {locationDetails.ecological_region}{" "} +
+ Ecological District:{" "} + {locationDetails.ecological_district}
+ Soil Order:{" "} + {locationDetails.soil_name ? soilString : ""}
- ) + ); } - return null + return null; } - export default function Map(props) { return (
@@ -79,5 +79,5 @@ export default function Map(props) {
- ) + ); } diff --git a/frontend/src/components/steps/results/PlantList.js b/frontend/src/components/steps/results/PlantList.jsx similarity index 100% rename from frontend/src/components/steps/results/PlantList.js rename to frontend/src/components/steps/results/PlantList.jsx diff --git a/frontend/src/components/steps/results/ResultsStep.js b/frontend/src/components/steps/results/ResultsStep.jsx similarity index 100% rename from frontend/src/components/steps/results/ResultsStep.js rename to frontend/src/components/steps/results/ResultsStep.jsx diff --git a/frontend/src/components/steps/soilvariant/SoilSelector.js b/frontend/src/components/steps/soilvariant/SoilSelector.js deleted file mode 100644 index 378bde3..0000000 --- a/frontend/src/components/steps/soilvariant/SoilSelector.js +++ /dev/null @@ -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 = (

- Is your ground/soil VERY WET? 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 subsoil is either very dark brown and peat-like, or grey rather than a red, brown, or yellow rusty colour? -

); - -const DRY_SOIL_DESCRIPTION = (

- Is your ground/soil VERY DRY? 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. -

); - -const MESIC_SOIL_DESCRIPTION = (

- If your ground/soil does not meet either of the two previous definitions, we’d likely classify it as MOIST, or somewhere in between very wet and very dry. 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. -

); - -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 ( -
- - {staticText.steps.soil.optionsLabel} - - } label={DRY_SOIL_DESCRIPTION} sx={{ paddingTop: '15px', paddingBottom: '15px' }}/> - } label={WET_SOIL_DESCRIPTION} sx={{ paddingTop: '15px', paddingBottom: '15px' }} /> - } label={MESIC_SOIL_DESCRIPTION} sx={{ paddingTop: '15px', paddingBottom: '15px' }} /> - - {helperText} - -
- ); -} diff --git a/frontend/src/components/steps/soilvariant/SoilSelector.jsx b/frontend/src/components/steps/soilvariant/SoilSelector.jsx new file mode 100644 index 0000000..a0a0fac --- /dev/null +++ b/frontend/src/components/steps/soilvariant/SoilSelector.jsx @@ -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 = ( +

+ Is your ground/soil VERY WET? 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{" "} + + subsoil + {" "} + is either very dark brown and{" "} + + peat + + -like, or grey rather than a red, brown, or yellow rusty colour? +

+); + +const DRY_SOIL_DESCRIPTION = ( +

+ Is your ground/soil VERY DRY? 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. +

+); + +const MESIC_SOIL_DESCRIPTION = ( +

+ If your ground/soil does not meet either of the two previous definitions, + we’d likely classify it as{" "} + MOIST, or somewhere in between very wet and very dry. 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. +

+); + +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 ( +
+ + + {staticText.steps.soil.optionsLabel} + + + } + label={DRY_SOIL_DESCRIPTION} + sx={{ paddingTop: "15px", paddingBottom: "15px" }} + /> + } + label={WET_SOIL_DESCRIPTION} + sx={{ paddingTop: "15px", paddingBottom: "15px" }} + /> + } + label={MESIC_SOIL_DESCRIPTION} + sx={{ paddingTop: "15px", paddingBottom: "15px" }} + /> + + {helperText} + +
+ ); +} diff --git a/frontend/src/components/steps/soilvariant/SoilStep.js b/frontend/src/components/steps/soilvariant/SoilStep.js deleted file mode 100644 index b152a29..0000000 --- a/frontend/src/components/steps/soilvariant/SoilStep.js +++ /dev/null @@ -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 = (

- From your site location, we use New Zealand Soil Classification data and additional Manaaki Whenua Landcare Research 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. -

- Please select an option from the choices listed to the right: -

) - - const soilVarientInfoPanel = ( - - ) - - const soilVarientSelectionPanel = ( -
- -
- ) - - return ( - - ) -} diff --git a/frontend/src/components/steps/soilvariant/SoilStep.jsx b/frontend/src/components/steps/soilvariant/SoilStep.jsx new file mode 100644 index 0000000..bd087c5 --- /dev/null +++ b/frontend/src/components/steps/soilvariant/SoilStep.jsx @@ -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 = ( +

+ From your site location, we use{" "} + + New Zealand Soil Classification + {" "} + data and additional{" "} + + Manaaki Whenua Landcare Research + {" "} + 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. +
+
+ Please select an option from the choices listed to the right: +

+ ); + + const soilVarientInfoPanel = ( + + ); + + const soilVarientSelectionPanel = ( +
+ +
+ ); + + return ( + + ); +} diff --git a/frontend/src/components/steps/specifics/ProjectSpecificsSelector.js b/frontend/src/components/steps/specifics/ProjectSpecificsSelector.js deleted file mode 100644 index 8ce008b..0000000 --- a/frontend/src/components/steps/specifics/ProjectSpecificsSelector.js +++ /dev/null @@ -1,12 +0,0 @@ -import { useEffect } from 'react'; - -export default function ProjectSpecificsSelector(props) { - - useEffect(() => { - props.setNextDisabled(false); - }); - - return ( -

Project Specifics Selector

- ) -} diff --git a/frontend/src/components/steps/specifics/ProjectSpecificsSelector.jsx b/frontend/src/components/steps/specifics/ProjectSpecificsSelector.jsx new file mode 100644 index 0000000..0b16c56 --- /dev/null +++ b/frontend/src/components/steps/specifics/ProjectSpecificsSelector.jsx @@ -0,0 +1,9 @@ +import { useEffect } from "react"; + +export default function ProjectSpecificsSelector(props) { + useEffect(() => { + props.setNextDisabled(false); + }); + + return

Project Specifics Selector

; +} diff --git a/frontend/src/components/steps/specifics/ProjectSpecificsStep.js b/frontend/src/components/steps/specifics/ProjectSpecificsStep.js deleted file mode 100644 index 9bfc6b9..0000000 --- a/frontend/src/components/steps/specifics/ProjectSpecificsStep.js +++ /dev/null @@ -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 = ( - {staticText.steps.projectSpecifics.description}

} - /> - ) - - const projectSpecificsSelectionPanel = ( - - ) - - return ( - - ) -} diff --git a/frontend/src/components/steps/specifics/ProjectSpecificsStep.jsx b/frontend/src/components/steps/specifics/ProjectSpecificsStep.jsx new file mode 100644 index 0000000..22e0da9 --- /dev/null +++ b/frontend/src/components/steps/specifics/ProjectSpecificsStep.jsx @@ -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 = ( + {staticText.steps.projectSpecifics.description}

} + /> + ); + + const projectSpecificsSelectionPanel = ( + + ); + + return ( + + ); +} diff --git a/frontend/src/components/steps/summary/SummaryContent.js b/frontend/src/components/steps/summary/SummaryContent.js deleted file mode 100644 index c0f28ec..0000000 --- a/frontend/src/components/steps/summary/SummaryContent.js +++ /dev/null @@ -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 Your location falls within the ecosystem type covered by the Christchurch Council ecosystem maps - further information can be obtained from Ōtautahi/Christchurch ecosystems map. - } else if (locationDetails.in_auckland) { - return "Your location falls within the ecosystem type covered by the Auckland Council Tiaki Tāmaki Makaurau Conservation map - further information can be obtained from tiaki Tāmaki Makaurau conservation Auckland - } - } - - return ( -
- Please review your choices presented below: - - } - > - 1. Location Data - - - - - {regionInformation()} - - - - - } - > - 2. Soil Properties - - - - - - - } - > - 3. Project Site Details - - - - - -
- ) -} diff --git a/frontend/src/components/steps/summary/SummaryContent.jsx b/frontend/src/components/steps/summary/SummaryContent.jsx new file mode 100644 index 0000000..a50d1a8 --- /dev/null +++ b/frontend/src/components/steps/summary/SummaryContent.jsx @@ -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 ( + + Your location falls within the ecosystem type covered by the + Christchurch Council ecosystem maps - further information can be + obtained from{" "} + + Ōtautahi/Christchurch ecosystems map + + . + + ); + } else if (locationDetails.in_auckland) { + return ( + + "Your location falls within the ecosystem type covered by the Auckland + Council Tiaki Tāmaki Makaurau Conservation map - further information + can be obtained from{" "} + + tiaki Tāmaki Makaurau conservation Auckland + + + ); + } + }; + + return ( +
+ + Please review your choices presented below:{" "} + + + }> + 1. Location Data + + + + + {regionInformation()} + + + + + }> + 2. Soil Properties + + + + + + + }> + 3. Project Site Details + + + + + +
+ ); +} diff --git a/frontend/src/components/steps/summary/SummaryStep.js b/frontend/src/components/steps/summary/SummaryStep.js deleted file mode 100644 index ddc38e2..0000000 --- a/frontend/src/components/steps/summary/SummaryStep.js +++ /dev/null @@ -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 = ( - {staticText.steps.summary.description}

} - /> - ) - - const summaryContent = ( - - ) - - return ( - - ) -} diff --git a/frontend/src/components/steps/summary/SummaryStep.jsx b/frontend/src/components/steps/summary/SummaryStep.jsx new file mode 100644 index 0000000..ef7aa02 --- /dev/null +++ b/frontend/src/components/steps/summary/SummaryStep.jsx @@ -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 = ( + {staticText.steps.summary.description}

} + /> + ); + + const summaryContent = ; + + return ( + + ); +} diff --git a/frontend/src/components/steps/summary/SummaryTable.js b/frontend/src/components/steps/summary/SummaryTable.jsx similarity index 59% rename from frontend/src/components/steps/summary/SummaryTable.js rename to frontend/src/components/steps/summary/SummaryTable.jsx index 8342d4a..f921c87 100644 --- a/frontend/src/components/steps/summary/SummaryTable.js +++ b/frontend/src/components/steps/summary/SummaryTable.jsx @@ -1,19 +1,19 @@ -import * as React from 'react'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableRow from '@mui/material/TableRow'; +import * as React from "react"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableRow from "@mui/material/TableRow"; export default function BasicTable(props) { return ( - + {props.rows.map((row) => ( {row.name} diff --git a/frontend/src/components/steps/zone/ZoneSelector.js b/frontend/src/components/steps/zone/ZoneSelector.js deleted file mode 100644 index aae3e2d..0000000 --- a/frontend/src/components/steps/zone/ZoneSelector.js +++ /dev/null @@ -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 ? ( -
-

{habitatImageObject.name}

-
-
- selectZone(event.target)} onMouseMove={(event) => showTooltip(event)} onMouseOut={() => hideTooltip()} name={habitatImageObject.image_filename} style={{ "height": 'auto', "width": "100%" }} /> -
-
) :
-} diff --git a/frontend/src/components/steps/zone/ZoneSelector.jsx b/frontend/src/components/steps/zone/ZoneSelector.jsx new file mode 100644 index 0000000..b2463f0 --- /dev/null +++ b/frontend/src/components/steps/zone/ZoneSelector.jsx @@ -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 ? ( +
+

{habitatImageObject.name}

+
+
+ selectZone(event.target)} + onMouseMove={(event) => showTooltip(event)} + onMouseOut={() => hideTooltip()} + name={habitatImageObject.image_filename} + style={{ height: "auto", width: "100%" }} + /> +
+
+ ) : ( +
+ ); +} diff --git a/frontend/src/components/steps/zone/ZoneStep.js b/frontend/src/components/steps/zone/ZoneStep.js deleted file mode 100644 index 2ac3fe5..0000000 --- a/frontend/src/components/steps/zone/ZoneStep.js +++ /dev/null @@ -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 = ( - {staticText.steps.zone.description}

} - /> - ) - - const zoneSelectionPanel = ( -
- -
- ) - - return ( - - ) -} diff --git a/frontend/src/components/steps/zone/ZoneStep.jsx b/frontend/src/components/steps/zone/ZoneStep.jsx new file mode 100644 index 0000000..e4ebf3b --- /dev/null +++ b/frontend/src/components/steps/zone/ZoneStep.jsx @@ -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 = ( + {staticText.steps.zone.description}

} + /> + ); + + const zoneSelectionPanel = ( +
+ +
+ ); + + return ( + + ); +} diff --git a/frontend/src/index.js b/frontend/src/index.js index c904682..f7fe04d 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,17 +1,14 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; -import reportWebVitals from './reportWebVitals'; -import { - createBrowserRouter, - RouterProvider, -} from 'react-router-dom'; -import MainPage from './pages/MainPage'; +import React from "react"; +import ReactDOM from "react-dom"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; +import reportWebVitals from "./reportWebVitals"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import MainPage from "./pages/MainPage"; // Styles -import './assets/styles/main.scss'; -import 'bootstrap/dist/css/bootstrap.min.css'; -import ApplyPage from './pages/ApplyPage'; +import "./assets/styles/main.scss"; +import "bootstrap/dist/css/bootstrap.min.css"; +import ApplyPage from "./pages/ApplyPage"; const router = createBrowserRouter([ { @@ -26,10 +23,10 @@ const router = createBrowserRouter([ const darkTheme = createTheme({ palette: { - mode: 'dark', + mode: "dark", }, typography: { - fontFamily: 'Poppins, sans-serif', + fontFamily: "Poppins, sans-serif", }, }); @@ -41,7 +38,7 @@ ReactDOM.render( , - document.getElementById('root') + document.getElementById("root") ); // If you want to start measuring performance in your app, pass a function diff --git a/frontend/src/repository/LocationRepository.js b/frontend/src/repository/LocationRepository.js index aa63193..bfcdb48 100644 --- a/frontend/src/repository/LocationRepository.js +++ b/frontend/src/repository/LocationRepository.js @@ -1,40 +1,55 @@ import API from "./Repository"; - const LocationRepsostory = { + getSoilDetails(filters) { + return API.get(`/soil/`, { params: filters }); + }, - getSoilDetails(filters) { - return API.get(`/soil/`, { params: filters }); - }, + getEcologicalDistrictDetails(filters) { + return API.get(`/ecologicaldistrict/`, { params: filters }); + }, - getEcologicalDistrictDetails(filters) { - return API.get(`/ecologicaldistrict/`, { params: filters }); - }, + getRegionDetails(filters) { + return API.get(`/region/`, { params: filters }); + }, - getRegionDetails(filters) { - return API.get(`/region/`, { params: filters }); - }, + getPropertyDetails(filters) { + return API.get(`/address/`, { params: filters }); + }, - getPropertyDetails(filters) { - return API.get(`/address/`, { params: filters }); - }, + async getLocationData(filters) { + const [ + soilDetails, + ecologicalDistrictDetails, + propertyDetails, + regionDetails, + ] = await Promise.all([ + this.getSoilDetails(filters), + this.getEcologicalDistrictDetails(filters), + this.getPropertyDetails(filters), + this.getRegionDetails(filters), + ]); - async getLocationData(filters) { - const [soilDetails, ecologicalDistrictDetails, propertyDetails, regionDetails] = await Promise.all([ - this.getSoilDetails(filters), - this.getEcologicalDistrictDetails(filters), - this.getPropertyDetails(filters), - this.getRegionDetails(filters) - ]); + let 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; - let 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 - } -} + return locationData; + }, +}; export default LocationRepsostory; diff --git a/frontend/src/repository/PlantRepository.js b/frontend/src/repository/PlantRepository.js index d321df9..59589e4 100644 --- a/frontend/src/repository/PlantRepository.js +++ b/frontend/src/repository/PlantRepository.js @@ -1,19 +1,20 @@ import Repository from "./Repository"; const PlantRepository = { + getFilteredPlants(filters) { + return Repository.get(`/plants/`, { params: filters }); + }, - getFilteredPlants(filters) { - return Repository.get(`/plants/`, { params: filters }); - }, + getPlantsCSV(filters) { + return Repository.get("/download/csv/", { params: filters }); + }, - getPlantsCSV(filters) { - return Repository.get('/download/csv/', { params: filters }) - }, - - getPlantsPDF(filters) { - return Repository.get('/download/pdf/', { params: filters, responseType: 'arraybuffer' }) - } - -} + getPlantsPDF(filters) { + return Repository.get("/download/pdf/", { + params: filters, + responseType: "arraybuffer", + }); + }, +}; export default PlantRepository; diff --git a/frontend/src/repository/Repository.js b/frontend/src/repository/Repository.js index 555a907..0bc49af 100644 --- a/frontend/src/repository/Repository.js +++ b/frontend/src/repository/Repository.js @@ -1,8 +1,11 @@ -import axios from "axios" +import axios from "axios"; // Create the axios object 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"] = "*"; diff --git a/frontend/src/repository/SiteRepository.js b/frontend/src/repository/SiteRepository.js index a3c2b6d..d298d62 100644 --- a/frontend/src/repository/SiteRepository.js +++ b/frontend/src/repository/SiteRepository.js @@ -1,19 +1,17 @@ import Repository from "./Repository"; const SiteRepository = { + getHabitats() { + return Repository.get(`/habitats/`); + }, - getHabitats() { - return Repository.get(`/habitats/`); - }, + getZones() { + return Repository.get(`/zones/`); + }, - getZones() { - return Repository.get(`/zones/`); - }, + getHabitatImage(habitatImageID) { + return Repository.get(`/habitatimage/${habitatImageID}/`); + }, +}; - getHabitatImage(habitatImageID) { - return Repository.get(`/habitatimage/${habitatImageID}/`); - } - -} - -export default SiteRepository; \ No newline at end of file +export default SiteRepository;