diff --git a/app/layout/project/sidebar/project/inventory-panel/cost-surfaces/bulk-action-menu/utils.ts b/app/layout/project/sidebar/project/inventory-panel/cost-surfaces/bulk-action-menu/utils.ts index 15dc04b69c..d458ead34d 100644 --- a/app/layout/project/sidebar/project/inventory-panel/cost-surfaces/bulk-action-menu/utils.ts +++ b/app/layout/project/sidebar/project/inventory-panel/cost-surfaces/bulk-action-menu/utils.ts @@ -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}`, }, diff --git a/app/layout/project/sidebar/scenario/grid-setup/cost-surface/index.tsx b/app/layout/project/sidebar/scenario/grid-setup/cost-surface/index.tsx index ea16954a94..d2066e6f4a 100644 --- a/app/layout/project/sidebar/scenario/grid-setup/cost-surface/index.tsx +++ b/app/layout/project/sidebar/scenario/grid-setup/cost-surface/index.tsx @@ -1,7 +1,8 @@ -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'; @@ -9,15 +10,18 @@ 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'; @@ -25,6 +29,7 @@ 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'; @@ -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['form']>(null); const { addToast } = useToasts(); const plausible = usePlausible(); @@ -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: { @@ -60,7 +88,6 @@ export const GridSetupCostSurface = (): JSX.Element => { downloadShapefileTemplateMutation.mutate( { pid }, { - onSuccess: () => {}, onError: () => { addToast( 'download-error', @@ -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 }); @@ -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>['onSubmit']>[0]) => { + scenarioQueryMutation.mutate( + { id: sid, data }, + { + onSuccess: () => { + addToast( + 'scenario-cost-surface-success', + <> +

Cost surface updated

+ {/* */} + , + { + level: 'success', + } + ); + }, + onError: () => { + addToast( + 'scenario-cost-surface-error', + <> +

Something went wrong

+ + , + { + level: 'error', + } + ); + }, + } + ); + }, + [scenarioQueryMutation, sid, addToast, dispatch, setSelectedCostSurface, setLayerSettings] + ); + return ( { + + onSubmit={handleCostSurfaceChange} + initialValues={{ + costSurfaceId: scenarioQuery.data?.costSurfaceId || null, + }} + > + {(fprops) => { + formRef.current = fprops.form; + + return ( +
+