diff --git a/app/hooks/scenarios/index.ts b/app/hooks/scenarios/index.ts index 5e7576ba44..50afaa0ee4 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'; @@ -474,11 +475,11 @@ export function useScenarios(pId, options: UseScenariosOptionsProps = {}) { ]); } -export function useScenario(id: Scenario['id']) { +export function useScenario(id: Scenario['id'], params = {}) { const { data: session } = useSession(); return useQuery({ - queryKey: ['scenario', id], + queryKey: ['scenario', id, params], queryFn: async () => SCENARIOS.request<{ data: Scenario }>({ method: 'GET', @@ -486,6 +487,7 @@ export function useScenario(id: Scenario['id']) { headers: { Authorization: `Bearer ${session.accessToken}`, }, + params, }).then((response) => response.data), enabled: !!id, placeholderData: { @@ -1080,3 +1082,45 @@ 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}/cost-surface/${csid}`, + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, + }); + }; + + return useMutation(linkScenario, { + onSuccess: async (data, variables) => { + await queryClient.invalidateQueries(['scenario', variables.sid]); + }, + }); +} + +export function useUnlinkScenarioToCostSurface() { + const { data: session } = useSession(); + const queryClient = useQueryClient(); + + const unlinkScenario = ({ sid }: { sid: Scenario['id'] }) => { + return SCENARIOS.request({ + method: 'DELETE', + url: `/${sid}/cost-surface`, + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, + }); + }; + + return useMutation(unlinkScenario, { + 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/project/inventory-panel/cost-surfaces/index.tsx b/app/layout/project/sidebar/project/inventory-panel/cost-surfaces/index.tsx index 60f2d46bbe..e82991610c 100644 --- a/app/layout/project/sidebar/project/inventory-panel/cost-surfaces/index.tsx +++ b/app/layout/project/sidebar/project/inventory-panel/cost-surfaces/index.tsx @@ -5,7 +5,7 @@ import { useRouter } from 'next/router'; import { useAppDispatch, useAppSelector } from 'store/hooks'; import { setLayerSettings, - setSelectedCostSurfaces as setVisibleCostSurface, + setSelectedCostSurface as setVisibleCostSurface, } from 'store/slices/projects/[id]'; import { orderBy } from 'lodash'; 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 f01c6c8a66..e5af34d1bb 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,34 +1,74 @@ -import { useCallback, useState } from 'react'; +import { ComponentProps, useCallback, useEffect, useRef, useState } from 'react'; + +import { Form as FormRFF, FormProps, Field } from 'react-final-form'; import { useRouter } from 'next/router'; +import { useAppDispatch } from 'store/hooks'; +import { getScenarioEditSlice } from 'store/slices/scenarios/edit'; + import { motion } from 'framer-motion'; +import { sortBy } from 'lodash'; import { HiOutlineArrowUpOnSquareStack } from 'react-icons/hi2'; +import { useProjectCostSurfaces } from 'hooks/cost-surface'; import { useCanEditScenario } from 'hooks/permissions'; import { useDownloadShapefileTemplate } from 'hooks/projects'; +import { + useLinkScenarioToCostSurface, + useUnlinkScenarioToCostSurface, + useScenario, +} from 'hooks/scenarios'; import { useToasts } from 'hooks/toast'; import Button from 'components/button'; +import Select from 'components/forms/select'; import Icon from 'components/icon'; import InfoButton from 'components/info-button'; import CostSurfaceUploadModal from 'layout/project/sidebar/project/inventory-panel/cost-surfaces/modals/upload'; import Section from 'layout/section'; +import { Scenario } from 'types/api/scenario'; import COST_LAND_IMG from 'images/info-buttons/img_cost_surface_marine.png'; 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['costSurface']['id']; +}; + export const GridSetupCostSurface = (): JSX.Element => { const [opened, setOpened] = useState(false); const [successFile, setSuccessFile] = useState<{ name: string }>(null); - - const { addToast } = useToasts(); const { query } = useRouter(); const { pid, sid } = query as { pid: string; sid: string }; + const formRef = useRef['form']>(null); + const dispatch = useAppDispatch(); + const scenarioSlice = getScenarioEditSlice(sid); + const { setSelectedCostSurface, setLayerSettings } = scenarioSlice.actions; + + const { addToast } = useToasts(); + 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, { + include: 'costSurface', + }); + const linkScenarioMutation = useLinkScenarioToCostSurface(); + const unlinkScenarioMutation = useUnlinkScenarioToCostSurface(); + const downloadShapefileTemplateMutation = useDownloadShapefileTemplate(); const onDownload = useCallback(() => { @@ -55,6 +95,104 @@ export const GridSetupCostSurface = (): JSX.Element => { setSuccessFile(null); }, []); + 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]) => { + if (!data.costSurfaceId) { + return unlinkScenarioMutation.mutate( + { sid }, + { + onSuccess: () => { + addToast( + 'scenario-cost-surface-unlink-success', + <> +

Cost surface unlinked successfully

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

Something went wrong

+
    Cost surface could not be unlinke.
+ , + { + level: 'error', + } + ); + }, + } + ); + } + + linkScenarioMutation.mutate( + { sid, csid: data.costSurfaceId }, + { + onSuccess: () => { + addToast( + 'scenario-cost-surface-success', + <> +

Cost surface applied successfully

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

Something went wrong

+
    Cost surface could not be updated
+ , + { + level: 'error', + } + ); + }, + } + ); + }, + [linkScenarioMutation, unlinkScenarioMutation, sid, addToast] + ); + + useEffect(() => { + if (scenarioQuery.isSuccess) { + const costSurfaceId = scenarioQuery.data.costSurface?.id; + dispatch(setSelectedCostSurface(costSurfaceId)); + + dispatch( + setLayerSettings({ + id: costSurfaceId, + settings: { + visibility: true, + }, + }) + ); + } + }, [scenarioQuery, dispatch, setSelectedCostSurface, setLayerSettings]); + return ( { + + onSubmit={handleCostSurfaceChange} + initialValues={{ + costSurfaceId: scenarioQuery.data.costSurface?.id || null, + }} + keepDirtyOnReinitialize + > + {(fprops) => { + formRef.current = fprops.form; + + return ( +
+ + {() => ( +