[#40] Bulk PDF export - frontend #91

Merged
mattn merged 1 commit from matt/40-batch-frontend into main 2023-02-22 16:41:48 +13:00
24 changed files with 393 additions and 301 deletions

View file

@ -35,6 +35,10 @@
"results": {
"title": "Plant List Results",
"forestDiagramDescription": "Forest Position Information Diagram"
},
"complete": {
"title": "Application Complete",
"description": "You have completed your application and submitted your results. You may now return to the homepage or fill out another application for a different habitat or zone."
}
}
}

View file

@ -1,81 +0,0 @@
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

@ -0,0 +1,37 @@
import { createContext, useState, useContext } from 'react';
import Repository from '../../repository/Repository';
const FilterContext = createContext(null);
const FilterProvider = ({children}) => {
const [filters, setFilters] = useState({});
const resetFilters = () => setFilters({});
const updateFilters = newFilters => setFilters(oldFilters => ({...oldFilters, ...newFilters}));
const submit = async () => {
return await Repository.post("/questionnaire/", {
location: `SRID=4326;POINT (${filters.coordinates.lng} ${filters.coordinates.lat})`,
soil_variant: filters.soilVariant,
zone: filters.zone.id,
});
};
const value = {
filters,
setFilters,
resetFilters,
updateFilters,
submit,
};
return <FilterContext.Provider value={value}>{children}</FilterContext.Provider>;
};
const useFilter = () => useContext(FilterContext);
export {
FilterProvider,
useFilter,
};

View file

@ -0,0 +1,100 @@
import { createContext, useState, useContext } 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 Tooltip from '@mui/material/Tooltip';
import Button from '@mui/material/Button';
import { useFilter } from './FilterProvider';
const StepContext = createContext(null);
const StepperWizard = ({children}) => {
const [step, setStep] = useState(0);
const isStep = n => (0 <= n && n < children.length);
const setStepNext = () => setStep(n => isStep(n + 1) ? n + 1 : n);
const setStepBack = () => setStep(n => isStep(n - 1) ? n - 1 : n);
const value = {
step,
setStep,
setStepNext,
setStepBack,
isStep,
};
return (
<StepContext.Provider value={value}>
<Box sx={{ width: '100%', height: '100%', display: "flex", flexDirection: "column", overflow: "hidden" }}>
<Stepper activeStep={step} sx={{ paddingRight: '3vw', paddingLeft: '3vw', marginBottom: '2vw' }}>
{children.map(child => (
<Tooltip title={child.props.tooltip}>
<Step key={child.props.label}>
<StepLabel>{child.props.label}</StepLabel>
</Step>
</Tooltip>)
)}
</Stepper>
{children[step]}
</Box>
</StepContext.Provider>
);
};
const useStepper = () => useContext(StepContext);
const StepperFooter = ({nextDisabled, backDisabled, onBack = null, onNext = null, onSubmit = () => {}}) => {
const { step, isStep, setStepNext, setStepBack, setStep } = useStepper();
const { resetFilters } = useFilter();
const isSubmit = !isStep(step + 2);
const _onBack = () => {
if (isStep(step + 1)) {
setStepBack();
} else {
resetFilters();
setStep(0);
}
};
const _onNext = () => {
setStepNext();
};
const _onSubmit = () => {
onSubmit();
if (onNext) {
onNext();
} else {
_onNext();
}
};
return (
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, pb: 2, paddingRight: '3vw', paddingLeft: '3vw', minHeight: '68px' }}>
{isStep(step - 1) &&
<Button
onClick={onBack ?? _onBack}
disabled={backDisabled}
>
{isStep(step + 1) ? "Back" : "Reset"}
</Button>
}
<Box sx={{ flex: '1 1 auto' }} />
{isStep(step + 1) &&
<Button
onClick={isSubmit ? _onSubmit : (onNext ?? _onNext)}
disabled={nextDisabled}
>
{isSubmit ? "Submit" : "Next"}
</Button>
}
</Box>);
};
export {
StepperWizard,
StepperFooter,
useStepper,
};

View file

@ -25,9 +25,10 @@ const AddressSearchSuggestions = ({results, onClick}) => (
</Box>
: null);
const AddressSearch = ({filters, updateFilterState, resetFilterState, setNextDisabled, setRedirectBack, classNames}) => {
const AddressSearch = ({onSelect, classNames}) => {
const [value, setValue] = useState("");
const [enable, setEnable] = useState(true);
const [selected, setSelected] = useState(null);
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
@ -35,7 +36,6 @@ const AddressSearch = ({filters, updateFilterState, resetFilterState, setNextDis
setResults([]);
if (enable && value && value.length > 5) {
setNextDisabled(true);
const timer = setTimeout(() => {
setIsLoading(true);
LocationRepsostory.getPropertyDetails({search: value}).then(resp => {
@ -46,7 +46,11 @@ const AddressSearch = ({filters, updateFilterState, resetFilterState, setNextDis
return () => clearTimeout(timer);
}
}, [value, enable, setNextDisabled]);
}, [value, enable]);
useEffect(() => {
onSelect(selected);
}, [selected, onSelect]);
return (
<div classNames={classNames}>
@ -65,6 +69,9 @@ const AddressSearch = ({filters, updateFilterState, resetFilterState, setNextDis
onChange={(e) => {
setEnable(true);
setValue(e.target.value);
if (selected && e.target.value !== selected.address) {
setSelected(null);
}
}}
value={value}
/>
@ -72,14 +79,8 @@ const AddressSearch = ({filters, updateFilterState, resetFilterState, setNextDis
results={results}
onClick={(r) => {
setValue(r.address);
updateFilterState({
coordinates: {
lng: r.coordinates[0],
lat: r.coordinates[1],
},
});
setNextDisabled(false);
setEnable(false);
setSelected(r);
}}
/>
</div>);

View file

@ -1,11 +1,16 @@
import { useState } from 'react';
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';
import { StepperFooter } from '../../providers/StepperProvider';
import { useFilter } from '../../providers/FilterProvider';
const AddressStep = (props) => {
const AddressStep = () => {
const [nextDisabled, setNextDisabled] = useState(true);
const { updateFilters } = useFilter();
const addressPanel = (
<div className="p-5">
@ -14,16 +19,31 @@ const AddressStep = (props) => {
description={<p>{staticText.steps.address.description}</p>}
/>
<div className="p-4">
<AddressSearch {...props} />
<AddressSearch onSelect={address => {
if (address) {
setNextDisabled(false);
updateFilters({
coordinates: {
lat: address.coordinates[1],
lng: address.coordinates[0],
},
});
} else {
setNextDisabled(true);
}
}}/>
</div>
</div>
);
return (
<Step
contentComponent={addressPanel}
backgroundImage={addressBackgroundImage}
/>);
<>
<Step
contentComponent={addressPanel}
backgroundImage={addressBackgroundImage}
/>
<StepperFooter nextDisabled={nextDisabled} />
</>);
};
export default AddressStep;

View file

@ -0,0 +1,24 @@
import Step from "../Step";
import StepInformation from "../StepInformation";
import completeBackgroundImage from "../../../assets/img/stepBackgrounds/step6.jpg";
import staticText from "../../../assets/data/staticText.json";
import { StepperFooter } from "../../providers/StepperProvider";
export default function CompleteStep() {
const completeInfoPanel = (
<StepInformation
title={staticText.steps.complete.title}
description={<p>{staticText.steps.complete.description}</p>}
/>
);
return (
<>
<Step
informationComponent={completeInfoPanel}
backgroundImage={completeBackgroundImage}
/>
<StepperFooter />
</>
);
};

View file

@ -8,16 +8,18 @@ import FormLabel from "@mui/material/FormLabel";
import FormControl from "@mui/material/FormControl";
import staticText from "../../../assets/data/staticText.json";
import { HabitatSVG } from "../HabitatSVG";
import { useFilter } from '../../providers/FilterProvider';
export default function HabitatSelector(props) {
export default function HabitatSelector({setNextDisabled}) {
const { filters, updateFilters } = useFilter();
const [habitats, setHabitats] = useState([]);
const [habitatsMap, setHabitatsMap] = useState({});
const [value, setValue] = React.useState(
props.filters.habitat && props.filters.habitat.id
filters.habitat && filters.habitat.id
);
const [selectedHabitat, setSelectedHabitat] = React.useState({});
const [imageValue, setImageValue] = React.useState(
props.filters.habitatImage
filters.habitatImage
);
const getHabitats = () => {
@ -39,22 +41,22 @@ export default function HabitatSelector(props) {
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 (filters.habitat && filters.habitat.id) {
setSelectedHabitat(habitatsMap[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);
if (filters.habitat && filters.habitatImage) {
setNextDisabled(false);
}
}, [habitats.length, props, habitatsMap]);
}, [habitats.length, setNextDisabled, habitatsMap, filters]);
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);
updateFilters({ habitatImage: imageId });
updateFilters({ zone: null });
setNextDisabled(false);
};
const handleRadioChange = (event) => {
@ -69,11 +71,11 @@ export default function HabitatSelector(props) {
if (habitatObject.images.length === 1) {
setHabitatImage(habitatObject.images[0].id);
} else {
props.setNextDisabled(true);
setNextDisabled(true);
}
// Update the filters for the selected habitat
props.updateFilterState({
updateFilters({
habitat: { id: habitatObject.id, name: habitatObject.name },
});
};

View file

@ -1,10 +1,14 @@
import { useState } from 'react';
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";
import { StepperFooter } from '../../providers/StepperProvider';
export default function HabitatStep(props) {
const [nextDisabled, setNextDisabled] = useState(true);
const habitatInfoPanel = (
<StepInformation
title={staticText.steps.habitat.title}
@ -12,13 +16,16 @@ export default function HabitatStep(props) {
/>
);
const habitatSelectionPanel = <HabitatSelector {...props} />;
const habitatSelectionPanel = <HabitatSelector setNextDisabled={setNextDisabled} />;
return (
<Step
informationComponent={habitatInfoPanel}
selectionComponent={habitatSelectionPanel}
backgroundImage={habitatBackgroundImage}
/>
<>
<Step
informationComponent={habitatInfoPanel}
selectionComponent={habitatSelectionPanel}
backgroundImage={habitatBackgroundImage}
/>
<StepperFooter nextDisabled={nextDisabled} />
</>
);
}

View file

@ -1,10 +1,14 @@
import { useState } from "react";
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";
import { StepperFooter } from '../../providers/StepperProvider';
export default function LocationStep(props) {
const [nextDisabled, setNextDisabled] = useState(true);
const locationInfoPanel = (
<StepInformation
title={staticText.steps.location.title}
@ -12,13 +16,16 @@ export default function LocationStep(props) {
/>
);
const locationSelectionPanel = <LocationSelectorMap {...props} />;
const locationSelectionPanel = <LocationSelectorMap setNextDisabled={setNextDisabled} />;
return (
<Step
informationComponent={locationInfoPanel}
selectionComponent={locationSelectionPanel}
backgroundImage={locationBackgroundImage}
/>
<>
<Step
informationComponent={locationInfoPanel}
selectionComponent={locationSelectionPanel}
backgroundImage={locationBackgroundImage}
/>
<StepperFooter nextDisabled={nextDisabled} />
</>
);
}

View file

@ -1,28 +1,31 @@
import { useState, useEffect } from "react";
import { MapContainer, TileLayer, Marker, useMapEvents } from "react-leaflet";
import LocationRepository from "../../../repository/LocationRepository";
import { useFilter } from "../../providers/FilterProvider";
const NZ_BOUNDS = [
[-47.204642, 165.344238],
[-34.307144, 179.824219],
];
function LocationMarker(props) {
function LocationMarker({setNextDisabled}) {
const [position, setPosition] = useState(null);
const { filters, updateFilters } = useFilter();
const map = useMapEvents({
click(e) {
const newPosition = e.latlng;
setPosition(newPosition);
props.updateFilterState({ coordinates: newPosition });
props.setNextDisabled(false);
updateFilters({ coordinates: newPosition });
setNextDisabled(false);
},
});
map.whenReady(() => {
const savedCoordinates = props.filters["coordinates"];
const savedCoordinates = filters.coordinates;
if (!position && savedCoordinates) {
setPosition(savedCoordinates);
props.setNextDisabled(false);
setNextDisabled(false);
map.flyTo(savedCoordinates, 9);
}
});
@ -36,16 +39,17 @@ const fitNZBounds = (map) => {
function LocationDetailsDisplay(props) {
const [locationDetails, setLocationDetails] = useState({});
const { filters } = useFilter();
useEffect(() => {
if (props.filters["coordinates"]) {
LocationRepository.getLocationData(props.filters).then((result) => {
if (filters.coordinates) {
LocationRepository.getLocationData(filters).then((result) => {
setLocationDetails(result);
});
}
}, [props.filters, props.filters.coordinates]);
}, [filters]);
const savedCoordinates = props.filters["coordinates"];
const savedCoordinates = filters.coordinates;
if (savedCoordinates) {
const soilString = `${locationDetails.soil_name} (${locationDetails.soil_code})`;

View file

@ -8,6 +8,8 @@ import PlantRepository from "../../../repository/PlantRepository";
import { Typography, Box } from "@mui/material";
import resultsBackgroundImage from "../../../assets/img/stepBackgrounds/step6.jpg";
import staticText from "../../../assets/data/staticText.json";
import { useFilter } from "../../providers/FilterProvider";
import { StepperFooter } from "../../providers/StepperProvider";
const RESULTS_DESCRIPTION = (
<Typography>
@ -29,10 +31,11 @@ const RESULTS_DESCRIPTION = (
export default function ResultsStep(props) {
const [plants, setPlants] = useState([]);
const { filters } = useFilter();
useEffect(() => {
const updatePlants = () => {
PlantRepository.getFilteredPlants(props.filters)
PlantRepository.getFilteredPlants(filters)
.then((response) => {
if (response.status === 200) {
setPlants(response.data);
@ -43,7 +46,7 @@ export default function ResultsStep(props) {
});
};
updatePlants();
}, [props.filters]);
}, [filters]);
function createData(
name,
@ -91,13 +94,13 @@ export default function ResultsStep(props) {
};
const downloadCSV = () => {
PlantRepository.getPlantsCSV(props.filters).then((response) => {
PlantRepository.getPlantsCSV(filters).then((response) => {
download(response, "text/csv", "plants.csv");
});
};
const downloadPDF = () => {
PlantRepository.getPlantsPDF(props.filters).then((response) => {
PlantRepository.getPlantsPDF(filters).then((response) => {
download(response, "application/pdf", "planting_guide.pdf");
});
};
@ -133,9 +136,12 @@ export default function ResultsStep(props) {
);
return (
<Step
contentComponent={stepContent}
backgroundImage={resultsBackgroundImage}
/>
<>
<Step
contentComponent={stepContent}
backgroundImage={resultsBackgroundImage}
/>
<StepperFooter />
</>
);
}

View file

@ -7,6 +7,7 @@ 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";
import { useFilter } from "../../providers/FilterProvider";
const WET_SOIL_DESCRIPTION = (
<p>
@ -53,24 +54,26 @@ const MESIC_SOIL_DESCRIPTION = (
</p>
);
export default function SoilSelector(props) {
const [value, setValue] = React.useState(props.filters.soilVariant);
export default function SoilSelector({setNextDisabled}) {
const { filters, updateFilters } = useFilter();
const [value, setValue] = React.useState(filters.soilVariant);
const [helperText, setHelperText] = React.useState(
staticText.steps.soil.optionsHelperText
);
React.useEffect(() => {
if (props.filters.soilVariant) {
props.setNextDisabled(false);
if (filters.soilVariant) {
setNextDisabled(false);
}
});
}, [filters, setNextDisabled]);
const handleRadioChange = (event) => {
const soilVariantSelection = event.target.value;
setValue(soilVariantSelection);
setHelperText(" ");
props.updateFilterState({ soilVariant: soilVariantSelection });
props.setNextDisabled(false);
updateFilters({ soilVariant: soilVariantSelection });
setNextDisabled(false);
};
return (

View file

@ -1,10 +1,15 @@
import { useState } from 'react';
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";
import { StepperFooter } from '../../providers/StepperProvider';
export default function SoilVariantStep(props) {
const [nextDisabled, setNextDisabled] = useState(true);
const SOIL_DESCRIPTION = (
<p>
From your site location, we use{" "}
@ -36,15 +41,18 @@ export default function SoilVariantStep(props) {
const soilVarientSelectionPanel = (
<div className="p-5">
<SoilSelector {...props} />
<SoilSelector setNextDisabled={setNextDisabled} />
</div>
);
return (
<Step
informationComponent={soilVarientInfoPanel}
selectionComponent={soilVarientSelectionPanel}
backgroundImage={soilBackgroundImage}
/>
<>
<Step
informationComponent={soilVarientInfoPanel}
selectionComponent={soilVarientSelectionPanel}
backgroundImage={soilBackgroundImage}
/>
<StepperFooter nextDisabled={nextDisabled} />
</>
);
}

View file

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

View file

@ -1,24 +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

@ -8,13 +8,15 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import SummaryTable from "./SummaryTable";
import LocationRepository from "../../../repository/LocationRepository";
import { useFilter } from "../../providers/FilterProvider";
export default function SummaryContent(props) {
export default function SummaryContent() {
const [expanded, setExpanded] = React.useState(null);
const [locationDetails, setLocationDetails] = React.useState({});
const { filters } = useFilter();
const getLocationDetails = () => {
LocationRepository.getLocationData(props.filters).then((result) => {
LocationRepository.getLocationData(filters).then((result) => {
setLocationDetails(result);
});
};
@ -24,7 +26,6 @@ export default function SummaryContent(props) {
};
React.useEffect(() => {
props.setNextDisabled(false);
!Object.keys(locationDetails).length && getLocationDetails();
});
@ -35,7 +36,7 @@ export default function SummaryContent(props) {
const locationData = [
createData(
"Geographical Coordinates (latitude, longitude)",
`(${props.filters.coordinates.lat}, ${props.filters.coordinates.lng})`
`(${filters.coordinates.lat}, ${filters.coordinates.lng})`
),
createData("Ecological Region", locationDetails.ecological_region || ""),
createData(
@ -50,22 +51,22 @@ export default function SummaryContent(props) {
"Soil Order",
`${locationDetails.soil_name} (${locationDetails.soil_code})` || ""
),
createData("Soil Variant", props.filters.soilVariant),
createData("Soil Variant", filters.soilVariant),
];
const siteData = [
createData("Habitat", props.filters.habitat.name ?? ""),
createData("Habitat", filters.habitat.name ?? ""),
createData(
"Zone Name",
(props.filters.zone && props.filters.zone.name) ?? ""
(filters.zone && filters.zone.name) ?? ""
),
createData(
"Zone Variant",
(props.filters.zone && props.filters.zone.variant) ?? ""
(filters.zone && filters.zone.variant) ?? ""
),
createData(
"Zone Refined Variant",
(props.filters.zone && props.filters.zone.refined_variant) ?? ""
(filters.zone && filters.zone.refined_variant) ?? ""
),
];

View file

@ -3,8 +3,10 @@ import SummaryContent from "./SummaryContent";
import StepInformation from "../StepInformation";
import staticText from "../../../assets/data/staticText.json";
import summaryBackgroundImage from "../../../assets/img/stepBackgrounds/step5.jpg";
import { StepperFooter } from '../../providers/StepperProvider';
export default function SummaryStep(props) {
export default function SummaryStep({ onSubmit }) {
const summaryInfoPanel = (
<StepInformation
title={staticText.steps.summary.title}
@ -12,13 +14,16 @@ export default function SummaryStep(props) {
/>
);
const summaryContent = <SummaryContent {...props} />;
const summaryContent = <SummaryContent />;
return (
<Step
informationComponent={summaryInfoPanel}
selectionComponent={summaryContent}
backgroundImage={summaryBackgroundImage}
/>
<>
<Step
informationComponent={summaryInfoPanel}
selectionComponent={summaryContent}
backgroundImage={summaryBackgroundImage}
/>
<StepperFooter onSubmit={onSubmit} />
</>
);
}

View file

@ -1,11 +1,13 @@
import { useEffect, useState } from "react";
import SiteRepository from "../../../repository/SiteRepository";
import { useFilter } from "../../providers/FilterProvider";
import { HabitatSVG } from "../HabitatSVG";
export default function ZoneSelector(props) {
export default function ZoneSelector({setNextDisabled}) {
const [habitatImageObject, setHabitatImageObject] = useState({});
const [segmentMapping, setSegmentMapping] = useState({});
const [selectedZoneSegment, setZoneSegment] = useState(null);
const { filters, updateFilters } = useFilter();
const setZoneOrRedirect = (zone) => {
const redirectHabitat = zone && zone.redirect_habitat;
@ -15,15 +17,13 @@ export default function ZoneSelector(props) {
? { habitat: { id: redirectHabitat.id, name: redirectHabitat.name } }
: { zone: zone };
props.updateFilterState(newFilterState);
props.setRedirectBack(Boolean(redirectHabitat));
props.setNextDisabled(false);
updateFilters(newFilterState);
setNextDisabled(false);
};
useEffect(() => {
const getHabitatImage = () => {
SiteRepository.getHabitatImage(props.filters.habitatImage).then(
SiteRepository.getHabitatImage(filters.habitatImage).then(
(response) => {
if (response.status === 200) {
const imageData = response.data;
@ -34,8 +34,8 @@ export default function ZoneSelector(props) {
};
const setInitialZone = () => {
if (props.filters.zone) {
const zone = props.filters.zone;
if (filters.zone) {
const zone = filters.zone;
const zoneSegment = document.querySelectorAll(
`.zone-selector-svg svg path[inkscapelabel="${zone.related_svg_segment}"]`
)[0];
@ -45,7 +45,7 @@ export default function ZoneSelector(props) {
setZoneSegment(zoneSegment);
zoneSegment.style.fill = "#eeeeee";
zoneSegment.style["fill-opacity"] = 0.5;
props.setNextDisabled(false);
setNextDisabled(false);
}
}
};
@ -69,7 +69,7 @@ export default function ZoneSelector(props) {
// 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]);
}, [habitatImageObject, segmentMapping, setNextDisabled, filters]);
const selectZone = (element) => {
if (

View file

@ -1,10 +1,20 @@
import { useState } from 'react';
import Button from '@mui/material/Button';
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";
import { StepperFooter, useStepper } from '../../providers/StepperProvider';
import { useFilter } from '../../providers/FilterProvider';
export default function ZoneStep({repeatable}) {
const [nextDisabled, setNextDisabled] = useState(true);
const { setStepBack, setStepNext } = useStepper();
const { filters } = useFilter();
const redirect = !nextDisabled && !filters.zone;
export default function ZoneStep(props) {
const zoneInfoPanel = (
<StepInformation
title={staticText.steps.zone.title}
@ -23,15 +33,21 @@ export default function ZoneStep(props) {
alignItems: "center",
}}
>
<ZoneSelector {...props} />
<ZoneSelector setNextDisabled={setNextDisabled} />
</div>
);
return (
<Step
informationComponent={zoneInfoPanel}
selectionComponent={zoneSelectionPanel}
backgroundImage={zoneBackgroundImage}
/>
<>
<Step
informationComponent={zoneInfoPanel}
selectionComponent={zoneSelectionPanel}
backgroundImage={zoneBackgroundImage}
/>
<StepperFooter
nextDisabled={nextDisabled}
onNext={redirect ? setStepBack : setStepNext}
/>
</>
);
}
};

View file

@ -4,6 +4,7 @@ import { createTheme, ThemeProvider } from "@mui/material/styles";
import reportWebVitals from "./reportWebVitals";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import MainPage from "./pages/MainPage";
import { FilterProvider } from "./components/providers/FilterProvider";
// Styles
import "./assets/styles/main.scss";
@ -33,9 +34,11 @@ const darkTheme = createTheme({
ReactDOM.render(
<React.StrictMode>
<div className="App">
<ThemeProvider theme={darkTheme}>
<RouterProvider router={router} />
</ThemeProvider>
<FilterProvider>
<ThemeProvider theme={darkTheme}>
<RouterProvider router={router} />
</ThemeProvider>
</FilterProvider>
</div>
</React.StrictMode>,
document.getElementById("root")

View file

@ -1,45 +1,29 @@
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";
import { StepperWizard } from "../components/providers/StepperProvider";
import CompleteStep from "../components/steps/complete/CompleteStep";
import { useFilter } from "../components/providers/FilterProvider";
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>
);
const ApplyPage = () => {
const { submit } = useFilter();
return (
<Container fluid className="main-container p-0">
<Header />
<StepperWizard>
<AddressStep label="Enter address" tooltip="Enter your address" />
<SoilStep label="Choose soil" tooltip="Describe the moisture content of your soil" />
<HabitatStep label="Choose habitat" tooltip="Specify type of landscape to be planted" />
<ZoneStep label="Select zone" tooltip="Specify geographical detail" />
<SummaryStep label="Summary" tooltip="Check your inputs" onSubmit={submit} />
<CompleteStep label="Complete" tooltip="Complete your application" />
</StepperWizard>
</Container>);
};
export default ApplyPage;

View file

@ -1,5 +1,4 @@
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";
@ -7,45 +6,21 @@ 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";
import { StepperWizard } from "../components/providers/StepperProvider";
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>
);
const MainPage = () => {
return (
<Container fluid className="main-container p-0">
<Header />
<StepperWizard>
<LocationStep label="Select location" tooltip="Click on a location on the map" />
<SoilStep label="Choose soil" tooltip="Describe the moisture content of your soil" />
<HabitatStep label="Choose habitat" tooltip="Specify type of landscape to be planted" />
<ZoneStep label="Select zone" tooltip="Specify geographical detail" />
<SummaryStep label="Summary" tooltip="Check your inputs" />
<ResultsStep label="Results" tooltip="List of plant species and user guide" />
</StepperWizard>
</Container>);
};
export default MainPage;

View file

@ -2,10 +2,9 @@ import axios from "axios";
// Create the axios object
const repo = axios.create({
baseURL:
window.location.hostname === "localhost"
? "http://localhost:9000/api"
: "/api",
baseURL: "/api",
xsrfHeaderName: "X-CSRFToken",
xsrfCookieName: "csrftoken",
});
repo.defaults.headers.post["access-control-allow-origin"] = "*";