[#40] Address search #89

Merged
mattn merged 6 commits from matt/40-batch into main 2023-02-10 14:22:49 +13:00
68 changed files with 18028 additions and 22275 deletions

View file

@ -8,7 +8,7 @@ from right_tree.api.models import Habitat, HabitatImage, Plant, EcologicalDistri
from right_tree.api.serializers import HabitatImageSerializer, HabitatSerializer, PlantSerializer, SoilOrderSerializer, EcologicalDistrictLayerSerializer, AddressSerializer, ZoneSerializer
from .filters import *
from .wms_utils import get_address_from_coordinates
from .wms_utils import get_address_from_coordinates, search_address
from .resource_generation_utils import create_plant_csv_file, get_plant_resource_filepath, create_planting_guide_pdf, PLANTING_GUIDE_PDF_FILENAME
@ -61,12 +61,17 @@ class LINZPropertyViewSet(viewsets.ViewSet):
def list(self, request):
coordinates = self.request.query_params.get('coordinates')
if coordinates is not None:
address = self.request.query_params.get('search')
if address is not None:
results = search_address(address)
return Response(results)
elif coordinates is not None:
address_data = get_address_from_coordinates(coordinates)
serializer = AddressSerializer(address_data)
return Response(serializer.data)
else:
return HttpResponseBadRequest("No coordinate given.")
return HttpResponseBadRequest("No parameters given.")
class AuckCHCHRegionInformation(viewsets.ViewSet):
""" Filtered viewset defining if coordinate falls inside auckland and chch regions.

View file

@ -1,7 +1,9 @@
import json
import os
import re
import requests
from urllib.parse import urlencode
from unicodedata import normalize
from django.contrib.gis.geos import Point, GEOSGeometry
from django.conf import settings
@ -12,6 +14,10 @@ PROPERTY_TILE_LAYER = "layer-50804"
PROPERTY_INFO_LAYER = "layer-53353"
search_num = re.compile(r'((?<!\S)\d+)')
search_str = re.compile(r'((?<!\S)[a-zA-Z\-]+)')
class WFSError(Exception):
pass
@ -85,3 +91,44 @@ def get_address_from_coordinates(coordinates):
if propertyPolygon is not None:
prop_geom = GEOSGeometry(json.dumps(propertyPolygon['geometry']))
return get_address(prop_geom)
def search_address(address):
# normalize accent characters etc.
address = normalize(
"NFKD",
address.strip().lower(),
).encode("ascii", "ignore").decode()
nums = search_num.findall(address)
strings = search_str.findall(address)
num_terms = [f"address_number = {n}" for n in nums]
string_terms = []
for s in strings:
string_terms += [
f"full_road_name_ascii ILIKE '{s}%'",
f"town_city_ascii ILIKE '{s}%'",
f"suburb_locality_ascii ILIKE '{s}%'",
]
num_expr = " OR ".join(f"({term})" for term in num_terms)
string_expr = " OR ".join(f"({term})" for term in string_terms)
cql_filter = f"({num_expr}) AND ({string_expr})"
resp = linz_wfs_getfeature(
typeNames=PROPERTY_INFO_LAYER,
cql_filter=cql_filter,
count=10,
)
features = resp.get("features", [])
return [
{
'coordinates': feature['geometry']['coordinates'],
'address': feature['properties']['full_address'],
} for feature in features
]

37949
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -13,11 +13,12 @@
"axios": "^0.22.0",
"bootstrap": "^5.1.2",
"leaflet": "^1.7.1",
"node-sass": "^6.0.1",
"node-sass": "^8.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-leaflet": "^3.2.2",
"react-scripts": "4.0.3",
"react-router-dom": "^6.8.0",
"react-scripts": "^5.0.1",
"reactstrap": "^8.10.0",
"web-vitals": "^1.1.2"
},

View file

@ -1,23 +0,0 @@
import MainPage from './pages/MainPage';
import { createTheme, ThemeProvider } from '@mui/material/styles';
function App() {
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
typography: {
fontFamily: 'Poppins, sans-serif',
},
});
return (
<div className="App">
<ThemeProvider theme={darkTheme}>
<MainPage />
</ThemeProvider>
</div>
);
}
export default App;

View file

@ -1,8 +0,0 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View file

@ -1,5 +1,9 @@
{
"steps": {
"address": {
"title": "Right Plant Right Place Right Time\nPlant Selector Tool for New Zealand.",
"description": "Your native plant selection starts here! Register by entering your address. On the following pages you will provide more details on your project until the system has enough information to create your plant species list and planting plan. To start, click on the map and pan and zoom to the site location. Once the location is selected, click on the “next step” button to complete the process. Repeat this process for sites at different locations."
},
"location": {
"title": "Right Plant Right Place Right Time\nPlant Selector Tool for New Zealand.",
"description": "Your native plant selection starts here! Use the map to select a planting site location within New Zealand. On the following pages you will provide more details on your project until the system has enough information to create your plant species list and planting plan. To start, click on the map and pan and zoom to the site location. Once the location is selected, click on the “next step” button to complete the process. Repeat this process for sites at different locations."

View file

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

View file

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

View file

@ -1,88 +0,0 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepLabel from '@mui/material/StepLabel';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import LocationStep from './steps/location/LocationStep'
import SoilStep from './steps/soilvariant/SoilStep'
import HabitatStep from './steps/habitat/HabitatStep';
import ZoneStep from './steps/zone/ZoneStep';
import SummaryStep from './steps/summary/SummaryStep';
import ResultsStep from './steps/results/ResultsStep'
const steps = [
{ 'label': 'Select location', 'component': LocationStep, 'tooltip': "Click on a location on the map" },
{ 'label': 'Choose soil', 'component': SoilStep, 'tooltip': "Describe the moisture content of your soil" },
{ 'label': 'Choose habitat', 'component': HabitatStep, 'tooltip': "Specify type of landscape to be planted" },
{ 'label': 'Select zone', 'component': ZoneStep, 'tooltip': "Specify geographical detail" },
{ 'label': 'Summary', 'component': SummaryStep, 'tooltip': "Check your inputs" },
{ 'label': 'Results', 'component': ResultsStep, 'tooltip': "List of plant species and user guide" }
];
export default function StepperWizard(props) {
const [activeStep, setActiveStep] = React.useState(0);
const [nextDisabled, setNextDisabled] = React.useState(true);
const [redirectBack, setRedirectBack] = React.useState(false);
const resetStepState = () => {
setNextDisabled(true);
setRedirectBack(false);
}
const handleNext = () => {
if (redirectBack) {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
} else {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
}
resetStepState();
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
resetStepState();
};
const handleReset = () => {
setActiveStep(0);
resetStepState();
props.resetFilterState()
};
let CurrentStep = activeStep >= steps.length ? steps[steps.length - 1].component : steps[activeStep].component;
return (
<Box sx={{ width: '100%', height: '100%', display: "flex", flexDirection: "column", overflow: "hidden" }}>
<Stepper activeStep={activeStep} sx={{ paddingRight: '3vw', paddingLeft: '3vw', marginBottom: '2vw' }}>
{steps.map((step) => {
return (
<Tooltip title={step.tooltip}>
<Step key={step.label}>
<StepLabel>{step.label}</StepLabel>
</Step>
</Tooltip>
);
})}
</Stepper>
<React.Fragment>
<CurrentStep {...props} setNextDisabled={setNextDisabled} setRedirectBack={setRedirectBack} />
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, pb: 2, paddingRight: '3vw', paddingLeft: '3vw' }}>
<Button
color="inherit"
disabled={activeStep === 0}
onClick={handleBack}
sx={{ mr: 1 }}
>
Back
</Button>
<Box sx={{ flex: '1 1 auto' }} />
{activeStep === steps.length - 1 ?
<Button onClick={handleReset}>Reset</Button> : <Button onClick={handleNext} disabled={nextDisabled}>Next</Button>}
</Box>
</React.Fragment>
</Box>
);
}

View file

@ -0,0 +1,81 @@
import { useState } from 'react';
import Box from '@mui/material/Box';
import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepLabel from '@mui/material/StepLabel';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
export default function StepperWizard({ steps }) {
const [filters, setFilters] = useState({});
const [activeStep, setActiveStep] = useState(0);
const [nextDisabled, setNextDisabled] = useState(true);
const [redirectBack, setRedirectBack] = useState(false);
const resetStepState = () => {
setNextDisabled(true);
setRedirectBack(false);
}
const handleNext = () => {
if (redirectBack) {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
} else {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
}
resetStepState();
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
resetStepState();
};
const handleReset = () => {
setActiveStep(0);
resetStepState();
setFilters({});
};
const updateFilterState = (newFilters) => {
setFilters(f => ({...f, ...newFilters}));
};
let CurrentStep = activeStep >= steps.length ? steps[steps.length - 1].component : steps[activeStep].component;
return (
<Box sx={{ width: '100%', height: '100%', display: "flex", flexDirection: "column", overflow: "hidden" }}>
<Stepper activeStep={activeStep} sx={{ paddingRight: '3vw', paddingLeft: '3vw', marginBottom: '2vw' }}>
{steps.map((step) => {
return (
<Tooltip title={step.tooltip}>
<Step key={step.label}>
<StepLabel>{step.label}</StepLabel>
</Step>
</Tooltip>
);
})}
</Stepper>
<CurrentStep
filters={filters}
updateFilterState={updateFilterState}
resetFilterState={() => setFilters({})}
setNextDisabled={setNextDisabled}
setRedirectBack={setRedirectBack}
/>
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, pb: 2, paddingRight: '3vw', paddingLeft: '3vw' }}>
<Button
color="inherit"
disabled={activeStep === 0}
onClick={handleBack}
sx={{ mr: 1 }}
>
Back
</Button>
<Box sx={{ flex: '1 1 auto' }} />
{activeStep === steps.length - 1 ?
<Button onClick={handleReset}>Reset</Button> : <Button onClick={handleNext} disabled={nextDisabled}>Next</Button>}
</Box>
</Box>
);
}

View file

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

View file

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

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/coastal_dune_and_bank_system.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/hill_slopes.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/hill_slopes_features.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/mangroves_section.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/maori_garden.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/open_wetland_lake.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/riparian.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/rural_section.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/salt_marsh.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/sand_dunes.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/stormwater_treatment.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/transport_corridor_rail_section.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/transport_corridor_road_section.svg';
export default ReactComponent;

View file

@ -0,0 +1,2 @@
import { ReactComponent } from '../../assets/img/habitatSVG/urban_section.svg';
export default ReactComponent;

View file

@ -1,27 +1,15 @@
import React from 'react';
import { lazy, Suspense } from 'react';
import { CircularProgress, Stack } from "@mui/material";
export const HabitatSVG = ({ name, ...rest }) => {
const ImportedIconRef = React.useRef(null);
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
setLoading(true);
const importIcon = async () => {
try {
ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!../../assets/img/habitatSVG/${name}.svg`)).default;
} catch (err) {
throw err;
} finally {
setLoading(false);
}
};
importIcon();
}, [name]);
if (!loading && ImportedIconRef.current) {
const { current: ImportedIcon } = ImportedIconRef;
return <ImportedIcon {...rest} />;
}
return null;
const Diagram = lazy(() => import(`../diagrams/${name}`));
const loadingComponent = (
<Stack alignItems="center">
<CircularProgress />
</Stack>
);
return (
<Suspense fallback={loadingComponent}> {/* experiemental in React 17. TODO: upgrade to 18 */}
<Diagram {...rest} />
</Suspense>);
};

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,88 @@
import { useState, useEffect } from 'react';
import { InputAdornment, TextField } from '@mui/material';
import PlaceIcon from '@mui/icons-material/Place';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import LocationRepsostory from '../../../repository/LocationRepository';
import { CircularProgress } from "@mui/material";
const AddressSearchSuggestions = ({results, onClick}) => (
(Array.isArray(results) && results.length > 0)
? <Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<List>
{results.map((r, idx) => (
<ListItem disablePadding key={idx}>
<ListItemButton onClick={() => onClick(r)}>
<ListItemText primary={r.address} />
</ListItemButton>
</ListItem>
))}
</List>
</Box>
: null);
const AddressSearch = ({filters, updateFilterState, resetFilterState, setNextDisabled, setRedirectBack, classNames}) => {
const [value, setValue] = useState("");
const [enable, setEnable] = useState(true);
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setResults([]);
if (enable && value && value.length > 5) {
setNextDisabled(true);
const timer = setTimeout(() => {
setIsLoading(true);
LocationRepsostory.getPropertyDetails({search: value}).then(resp => {
setResults(resp.data);
setIsLoading(false);
});
}, 500);
return () => clearTimeout(timer);
}
}, [value, enable, setNextDisabled]);
return (
<div classNames={classNames}>
<TextField
fullWidth
variant="outlined"
placeholder="Enter address..."
InputProps={{
startAdornment: (
<InputAdornment position="start">
{isLoading ? <CircularProgress size="1.2rem" color="inherit" /> : <PlaceIcon />}
</InputAdornment>
)
}}
autoComplete="off"
onChange={(e) => {
setEnable(true);
setValue(e.target.value);
}}
value={value}
/>
<AddressSearchSuggestions
results={results}
onClick={(r) => {
setValue(r.address);
updateFilterState({
coordinates: {
lng: r.coordinates[0],
lat: r.coordinates[1],
},
});
setNextDisabled(false);
setEnable(false);
}}
/>
</div>);
};
export default AddressSearch;

View file

@ -0,0 +1,29 @@
import Step from '../Step';
import StepInformation from '../StepInformation';
import staticText from '../../../assets/data/staticText.json'
import addressBackgroundImage from '../../../assets/img/stepBackgrounds/step1.jpg';
import AddressSearch from './AddressSearch';
const AddressStep = (props) => {
const addressPanel = (
<div className="p-5">
<StepInformation
title={staticText.steps.address.title}
description={<p>{staticText.steps.address.description}</p>}
/>
<div className="p-4">
<AddressSearch {...props} />
</div>
</div>
);
return (
<Step
contentComponent={addressPanel}
backgroundImage={addressBackgroundImage}
/>);
};
export default AddressStep;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,72 +1,72 @@
import { useState, useEffect } from "react"
import { 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 : (
<Marker position={position} />
)
return position === null ? null : <Marker position={position} />;
}
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 (
<div className='coordinates-display'>
<div className="coordinates-display">
<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>Ecological Region:</strong> {locationDetails.ecological_region} <br />
<strong>Ecological District:</strong> {locationDetails.ecological_district} <br />
<strong>Soil Order:</strong> {locationDetails.soil_name ? soilString : ""}
<strong>Ecological Region:</strong> {locationDetails.ecological_region}{" "}
<br />
<strong>Ecological District:</strong>{" "}
{locationDetails.ecological_district} <br />
<strong>Soil Order:</strong>{" "}
{locationDetails.soil_name ? soilString : ""}
</div>
)
);
}
return null
return null;
}
export default function Map(props) {
return (
<div className="map-container">
@ -79,5 +79,5 @@ export default function Map(props) {
<LocationMarker {...props} />
</MapContainer>
</div>
)
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,19 +1,19 @@
import * as React from 'react';
import 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 (
<TableContainer >
<TableContainer>
<Table sx={{ minWidth: 650 }} size="small" aria-label="simple table">
<TableBody>
{props.rows.map((row) => (
<TableRow
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">
{row.name}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,44 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
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 "./assets/styles/main.scss";
import "bootstrap/dist/css/bootstrap.min.css";
import ApplyPage from "./pages/ApplyPage";
const router = createBrowserRouter([
{
path: "/",
element: <MainPage />,
},
{
path: "/apply",
element: <ApplyPage />,
},
]);
const darkTheme = createTheme({
palette: {
mode: "dark",
},
typography: {
fontFamily: "Poppins, sans-serif",
},
});
ReactDOM.render(
<React.StrictMode>
<App />
<div className="App">
<ThemeProvider theme={darkTheme}>
<RouterProvider router={router} />
</ThemeProvider>
</div>
</React.StrictMode>,
document.getElementById('root')
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function

View file

@ -0,0 +1,45 @@
import { Container } from "reactstrap";
import Stepper from "../components/Stepper";
import Header from "../components/Header";
import AddressStep from "../components/steps/address/AddressStep";
import SoilStep from "../components/steps/soilvariant/SoilStep";
import HabitatStep from "../components/steps/habitat/HabitatStep";
import ZoneStep from "../components/steps/zone/ZoneStep";
import SummaryStep from "../components/steps/summary/SummaryStep";
const ApplyPage = () => (
<Container fluid className="main-container p-0">
<Header />
<Stepper
steps={[
{
label: "Enter address",
component: AddressStep,
tooltip: "Enter your address",
},
{
label: "Choose soil",
component: SoilStep,
tooltip: "Describe the moisture content of your soil",
},
{
label: "Choose habitat",
component: HabitatStep,
tooltip: "Specify type of landscape to be planted",
},
{
label: "Select zone",
component: ZoneStep,
tooltip: "Specify geographical detail",
},
{
label: "Submit",
component: SummaryStep,
tooltip: "Submit your application",
},
]}
/>
</Container>
);
export default ApplyPage;

View file

@ -1,39 +0,0 @@
import React from 'react'
import { Container } from 'reactstrap';
import Stepper from '../components/Stepper'
import Header from '../components/Header'
export default class MainPage extends React.Component {
constructor(props) {
super(props);
this.state = {
filters: {}
}
this.updateFilterState = this.updateFilterState.bind(this);
this.resetFilterState = this.resetFilterState.bind(this);
}
updateFilterState(newFilter) {
this.setState({ filters: Object.assign(this.state.filters, newFilter) })
}
resetFilterState() {
this.setState({ filters: {} })
}
render() {
return (
<Container fluid className='main-container p-0'>
<Header/>
<Stepper
filters={this.state.filters}
updateFilterState={this.updateFilterState}
resetFilterState={this.resetFilterState} />
</Container>
)
}
}

View file

@ -0,0 +1,51 @@
import { Container } from "reactstrap";
import Stepper from "../components/Stepper";
import Header from "../components/Header";
import LocationStep from "../components/steps/location/LocationStep";
import SoilStep from "../components/steps/soilvariant/SoilStep";
import HabitatStep from "../components/steps/habitat/HabitatStep";
import ZoneStep from "../components/steps/zone/ZoneStep";
import SummaryStep from "../components/steps/summary/SummaryStep";
import ResultsStep from "../components/steps/results/ResultsStep";
const MainPage = () => (
<Container fluid className="main-container p-0">
<Header />
<Stepper
steps={[
{
label: "Select location",
component: LocationStep,
tooltip: "Click on a location on the map",
},
{
label: "Choose soil",
component: SoilStep,
tooltip: "Describe the moisture content of your soil",
},
{
label: "Choose habitat",
component: HabitatStep,
tooltip: "Specify type of landscape to be planted",
},
{
label: "Select zone",
component: ZoneStep,
tooltip: "Specify geographical detail",
},
{
label: "Summary",
component: SummaryStep,
tooltip: "Check your inputs",
},
{
label: "Results",
component: ResultsStep,
tooltip: "List of plant species and user guide",
},
]}
/>
</Container>
);
export default MainPage;

View file

@ -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;

View file

@ -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;

View file

@ -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"] = "*";

View file

@ -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;
export default SiteRepository;