Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
andresgnlez committed Oct 17, 2023
1 parent 2ce00ea commit 2ff24d8
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function bulkDeleteCostSurfaceFromProject(
pid: Project['id'];
csid: CostSurface['id'];
}) => {
return PROJECTS.delete(`/${pid}/cost-surface/${csid}`, {
return PROJECTS.delete(`/${pid}/cost-surfaces/${csid}`, {
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
Expand Down
126 changes: 119 additions & 7 deletions app/layout/project/sidebar/scenario/grid-setup/cost-surface/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
import { useCallback, useState } from 'react';
import { ComponentProps, useCallback, useRef, useState } from 'react';

import { useDropzone, DropzoneProps } from 'react-dropzone';
import { Form, Field } from 'react-final-form';
import { Form, Field, FormProps } from 'react-final-form';
import { Form as FormRFF } from 'react-final-form';
import { useDispatch } from 'react-redux';

import { useRouter } from 'next/router';

import { getScenarioEditSlice } from 'store/slices/scenarios/edit';

import { motion } from 'framer-motion';
import { sortBy } from 'lodash';
import { usePlausible } from 'next-plausible';

import { useProjectCostSurfaces } from 'hooks/cost-surface';
import { useMe } from 'hooks/me';
import { useCanEditScenario } from 'hooks/permissions';
import { useDownloadShapefileTemplate } from 'hooks/projects';
import { useUploadCostSurface } from 'hooks/scenarios';
import { useSaveScenario, useScenario, useUploadCostSurface } from 'hooks/scenarios';
import { useToasts } from 'hooks/toast';

import Button from 'components/button';
import Select from 'components/forms/select';
import { composeValidators } from 'components/forms/validations';
import Icon from 'components/icon';
import InfoButton from 'components/info-button';
import Loading from 'components/loading';
import Uploader from 'components/uploader';
import { COST_SURFACE_UPLOADER_MAX_SIZE } from 'constants/file-uploader-size-limits';
import Section from 'layout/section';
import { Scenario } from 'types/api/scenario';
import { cn } from 'utils/cn';
import { bytesToMegabytes } from 'utils/units';

Expand All @@ -33,10 +38,15 @@ import COST_SEA_IMG from 'images/info-buttons/img_cost_surface_terrestrial.png';

import CLOSE_SVG from 'svgs/ui/close.svg?sprite';

export type FormFields = {
costSurfaceId: Scenario['costSurfaceId'];
};

export const GridSetupCostSurface = (): JSX.Element => {
const [opened, setOpened] = useState(false);
const [loading, setLoading] = useState(false);
const [successFile, setSuccessFile] = useState<{ name: string }>(null);
const formRef = useRef<FormProps<FormFields>['form']>(null);

const { addToast } = useToasts();
const plausible = usePlausible();
Expand All @@ -45,10 +55,28 @@ export const GridSetupCostSurface = (): JSX.Element => {

const dispatch = useDispatch();
const scenarioSlice = getScenarioEditSlice(sid);
const { setJob } = scenarioSlice.actions;
const { setJob, setSelectedCostSurface, setLayerSettings } = scenarioSlice.actions;

const { data: user } = useMe();
const editable = useCanEditScenario(pid, sid);
const costSurfaceQuery = useProjectCostSurfaces(
pid,
{},
{
select: (data) =>
sortBy(
data.filter(({ isDefault }) => !isDefault),
'name'
)?.map(({ id, name }) => ({ value: id, label: name })),
}
);
const scenarioQuery = useScenario(sid);
const scenarioQueryMutation = useSaveScenario({
requestConfig: {
method: 'PATCH',
},
});

const downloadShapefileTemplateMutation = useDownloadShapefileTemplate();
const uploadMutation = useUploadCostSurface({
requestConfig: {
Expand All @@ -60,7 +88,6 @@ export const GridSetupCostSurface = (): JSX.Element => {
downloadShapefileTemplateMutation.mutate(
{ pid },
{
onSuccess: () => {},
onError: () => {
addToast(
'download-error',
Expand Down Expand Up @@ -89,7 +116,7 @@ export const GridSetupCostSurface = (): JSX.Element => {
uploadMutation.mutate(
{ id: `${sid}`, data },
{
onSuccess: ({ data: { data: g, meta } }) => {
onSuccess: ({ data: { meta } }) => {
dispatch(setJob(new Date(meta.isoDate).getTime()));
setLoading(false);
setSuccessFile({ name: f.name });
Expand Down Expand Up @@ -187,6 +214,58 @@ export const GridSetupCostSurface = (): JSX.Element => {
// resetCustomArea();
}, []);

const onChangeCostSurface = useCallback(
(value: string) => {
formRef.current.change('costSurfaceId', value);

dispatch(setSelectedCostSurface(value));
dispatch(
setLayerSettings({
id: value,
settings: {
visibility: true,
},
})
);
},
[dispatch, setSelectedCostSurface, setLayerSettings]
);

const handleCostSurfaceChange = useCallback(
(data: Parameters<ComponentProps<typeof FormRFF<FormFields>>['onSubmit']>[0]) => {
scenarioQueryMutation.mutate(
{ id: sid, data },
{
onSuccess: () => {
addToast(
'scenario-cost-surface-success',
<>
<h2 className="font-medium">Cost surface updated</h2>
{/* <ul className="text-sm">Template not downloaded</ul> */}
</>,
{
level: 'success',
}
);
},
onError: () => {
addToast(
'scenario-cost-surface-error',
<>
<h2 className="font-medium">Something went wrong</h2>
<ul className="text-sm">Cost surface could not be updated.</ul>
</>,
{
level: 'error',
}
);
},
}
);
},
[scenarioQueryMutation, sid, addToast, dispatch, setSelectedCostSurface, setLayerSettings]
);

return (
<motion.div
key="cost-surface"
Expand Down Expand Up @@ -226,8 +305,41 @@ export const GridSetupCostSurface = (): JSX.Element => {
</div>
</div>

<FormRFF<FormFields>
onSubmit={handleCostSurfaceChange}
initialValues={{
costSurfaceId: scenarioQuery.data?.costSurfaceId || null,
}}
>
{(fprops) => {
formRef.current = fprops.form;

return (
<form
id="form-cost-surface-scenario"
onSubmit={fprops.handleSubmit}
autoComplete="off"
className="space-y-3"
>
<Select
maxHeight={300}
size="base"
theme="dark"
selected={fprops.values.costSurfaceId}
options={costSurfaceQuery.data}
clearSelectionActive
onChange={onChangeCostSurface}
/>
<Button type="submit" theme="primary-alt" size="base" className="w-full">
Apply cost surface
</Button>
</form>
);
}}
</FormRFF>

<div className="relative mt-1 flex min-h-0 w-full flex-grow flex-col overflow-hidden text-sm">
<p className="pt-2">
<p className="mt-2 text-xs">
By default all projects have an equal area cost surface which means that planning units
with the same area have the same cost
</p>
Expand Down
21 changes: 19 additions & 2 deletions app/layout/scenarios/edit/map/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { sortBy } from 'lodash';
import { FiLayers } from 'react-icons/fi';

import { useAccessToken } from 'hooks/auth';
import { useProjectCostSurface } from 'hooks/cost-surface';
import { useSelectedFeatures, useTargetedFeatures } from 'hooks/features';
import { useAllGapAnalysis } from 'hooks/gap-analysis';
import {
Expand All @@ -22,6 +23,7 @@ import {
// useLegend,
useBBOX,
useTargetedPreviewLayers,
useCostSurfaceLayer,
} from 'hooks/map';
import { useProject } from 'hooks/projects';
import { useCostSurfaceRange, useScenario, useScenarioPU } from 'hooks/scenarios';
Expand Down Expand Up @@ -50,6 +52,9 @@ import { centerMap } from 'utils/map';

import { useScenarioLegend } from './legend/hooks';

const minZoom = 2;
const maxZoom = 20;

export const ScenariosEditMap = (): JSX.Element => {
const [open, setOpen] = useState(true);
const [mapInteractive, setMapInteractive] = useState(false);
Expand Down Expand Up @@ -200,8 +205,8 @@ export const ScenariosEditMap = (): JSX.Element => {
});
const bestSolution = bestSolutionData;

const minZoom = 2;
const maxZoom = 20;
const costSurfaceQuery = useProjectCostSurface(pid, selectedCostSurface);

const [viewport, setViewport] = useState({});
const [bounds, setBounds] = useState<MapProps['bounds']>(null);

Expand Down Expand Up @@ -307,10 +312,22 @@ export const ScenariosEditMap = (): JSX.Element => {
},
});

const costSurfaceLayer = useCostSurfaceLayer({
active: Boolean(selectedCostSurface) && costSurfaceQuery.isSuccess,
pid,
costSurfaceId: selectedCostSurface,
layerSettings: {
...layerSettings[selectedCostSurface],
min: costSurfaceQuery.data?.min,
max: costSurfaceQuery.data?.max,
} as Parameters<typeof useCostSurfaceLayer>[0]['layerSettings'],
});

const LAYERS = [
// PUGridPreviewLayer,
// AdminPreviewLayer,
PUGridLayer,
costSurfaceLayer,
WDPApreviewLayer,
...FeaturePreviewLayers,
...TargetedPreviewLayers,
Expand Down
1 change: 1 addition & 0 deletions app/types/api/scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Scenario {
lastUpdate: string;
lock?: Record<string, any>;
lastUpdateDistance: string;
costSurfaceId: string;
name: string;
numberOfRuns: number;
metadata: {
Expand Down

0 comments on commit 2ff24d8

Please sign in to comment.