diff --git a/app/hooks/scenarios/index.ts b/app/hooks/scenarios/index.ts index e84166e168..9323523869 100644 --- a/app/hooks/scenarios/index.ts +++ b/app/hooks/scenarios/index.ts @@ -20,6 +20,7 @@ import { useMe } from 'hooks/me'; import { useProjectUsers } from 'hooks/project-users'; import { ItemProps } from 'components/scenarios/item/component'; +import { CostSurface } from 'types/api/cost-surface'; import { Job } from 'types/api/job'; import { Project } from 'types/api/project'; import { Scenario } from 'types/api/scenario'; @@ -1112,3 +1113,25 @@ export function useDownloadSolutionsSummary() { }, }); } + +export function useLinkScenarioToCostSurface() { + const { data: session } = useSession(); + + const queryClient = useQueryClient(); + + const linkScenario = ({ sid, csid }: { sid: Scenario['id']; csid: CostSurface['id'] }) => { + return SCENARIOS.request({ + method: 'POST', + url: `/${sid}/link-cost-surface/${csid}`, + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, + }); + }; + + return useMutation(linkScenario, { + onSuccess: async (data, variables) => { + await queryClient.invalidateQueries(['scenario', variables.sid]); + }, + }); +} 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..d605f25c8b 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 { useLinkScenarioToCostSurface, 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,24 @@ 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 linkScenarioMutation = useLinkScenarioToCostSurface(); + const downloadShapefileTemplateMutation = useDownloadShapefileTemplate(); const uploadMutation = useUploadCostSurface({ requestConfig: { @@ -60,7 +84,6 @@ export const GridSetupCostSurface = (): JSX.Element => { downloadShapefileTemplateMutation.mutate( { pid }, { - onSuccess: () => {}, onError: () => { addToast( 'download-error', @@ -89,7 +112,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 +210,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]) => { + linkScenarioMutation.mutate( + { sid, csid: data.costSurfaceId }, + { + onSuccess: () => { + addToast( + 'scenario-cost-surface-success', + <> +

Cost surface applied successfully

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

Something went wrong

+
    Cost surface could not be updated.
+ , + { + level: 'error', + } + ); + }, + } + ); + }, + [linkScenarioMutation, sid, addToast] + ); + return ( { + + onSubmit={handleCostSurfaceChange} + initialValues={{ + costSurfaceId: scenarioQuery.data?.costSurfaceId || null, + }} + > + {(fprops) => { + formRef.current = fprops.form; + + return ( +
+