[#40] Address search #89
9 changed files with 240 additions and 72 deletions
|
@ -1,5 +1,9 @@
|
||||||
{
|
{
|
||||||
"steps": {
|
"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": {
|
"location": {
|
||||||
"title": "Right Plant Right Place Right Time\nPlant Selector Tool for New Zealand.",
|
"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."
|
"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."
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as React from 'react';
|
import { useState } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Stepper from '@mui/material/Stepper';
|
import Stepper from '@mui/material/Stepper';
|
||||||
import Step from '@mui/material/Step';
|
import Step from '@mui/material/Step';
|
||||||
|
@ -6,26 +6,11 @@ import StepLabel from '@mui/material/StepLabel';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
|
||||||
import LocationStep from './steps/location/LocationStep'
|
export default function StepperWizard({ steps }) {
|
||||||
import SoilStep from './steps/soilvariant/SoilStep'
|
const [filters, setFilters] = useState({});
|
||||||
import HabitatStep from './steps/habitat/HabitatStep';
|
const [activeStep, setActiveStep] = useState(0);
|
||||||
import ZoneStep from './steps/zone/ZoneStep';
|
const [nextDisabled, setNextDisabled] = useState(true);
|
||||||
import SummaryStep from './steps/summary/SummaryStep';
|
const [redirectBack, setRedirectBack] = useState(false);
|
||||||
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 = () => {
|
const resetStepState = () => {
|
||||||
setNextDisabled(true);
|
setNextDisabled(true);
|
||||||
|
@ -49,7 +34,11 @@ export default function StepperWizard(props) {
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setActiveStep(0);
|
setActiveStep(0);
|
||||||
resetStepState();
|
resetStepState();
|
||||||
props.resetFilterState()
|
setFilters({});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFilterState = (newFilters) => {
|
||||||
|
setFilters(f => ({...f, ...newFilters}));
|
||||||
};
|
};
|
||||||
|
|
||||||
let CurrentStep = activeStep >= steps.length ? steps[steps.length - 1].component : steps[activeStep].component;
|
let CurrentStep = activeStep >= steps.length ? steps[steps.length - 1].component : steps[activeStep].component;
|
||||||
|
@ -67,8 +56,14 @@ export default function StepperWizard(props) {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Stepper>
|
</Stepper>
|
||||||
<React.Fragment>
|
<>
|
||||||
<CurrentStep {...props} setNextDisabled={setNextDisabled} setRedirectBack={setRedirectBack} />
|
<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' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, pb: 2, paddingRight: '3vw', paddingLeft: '3vw' }}>
|
||||||
<Button
|
<Button
|
||||||
color="inherit"
|
color="inherit"
|
||||||
|
@ -82,7 +77,7 @@ export default function StepperWizard(props) {
|
||||||
{activeStep === steps.length - 1 ?
|
{activeStep === steps.length - 1 ?
|
||||||
<Button onClick={handleReset}>Reset</Button> : <Button onClick={handleNext} disabled={nextDisabled}>Next</Button>}
|
<Button onClick={handleReset}>Reset</Button> : <Button onClick={handleNext} disabled={nextDisabled}>Next</Button>}
|
||||||
</Box>
|
</Box>
|
||||||
</React.Fragment>
|
</>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
88
frontend/src/components/steps/address/AddressSearch.jsx
Normal file
88
frontend/src/components/steps/address/AddressSearch.jsx
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { useState, useRef, 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) => (
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<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;
|
29
frontend/src/components/steps/address/AddressStep.jsx
Normal file
29
frontend/src/components/steps/address/AddressStep.jsx
Normal 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;
|
|
@ -11,7 +11,7 @@ import MainPage from './pages/MainPage';
|
||||||
// Styles
|
// Styles
|
||||||
import './assets/styles/main.scss';
|
import './assets/styles/main.scss';
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import RegisterPage from './pages/RegisterPage';
|
import ApplyPage from './pages/ApplyPage';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
|
@ -19,8 +19,8 @@ const router = createBrowserRouter([
|
||||||
element: <MainPage />,
|
element: <MainPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/register",
|
path: "/apply",
|
||||||
element: <RegisterPage />,
|
element: <ApplyPage />,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
45
frontend/src/pages/ApplyPage.jsx
Normal file
45
frontend/src/pages/ApplyPage.jsx
Normal 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;
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
51
frontend/src/pages/MainPage.jsx
Normal file
51
frontend/src/pages/MainPage.jsx
Normal 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;
|
|
@ -1,5 +0,0 @@
|
||||||
const RegisterPage = () => {
|
|
||||||
return <></>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RegisterPage;
|
|
Loading…
Reference in a new issue