diff --git a/app/components/confirmation-prompt/component.tsx b/app/components/confirmation-prompt/component.tsx index b0e6883553..6a7c432e53 100644 --- a/app/components/confirmation-prompt/component.tsx +++ b/app/components/confirmation-prompt/component.tsx @@ -33,7 +33,7 @@ export const ConfirmationPrompt: React.FC = ({ className={cn({ 'my-4 text-sm sm:pr-32': true, 'text-black underline': !!danger, - 'text-gray-100': !danger, + 'text-gray-900': !danger, })} > {description} diff --git a/app/hooks/cost-surface/index.ts b/app/hooks/cost-surface/index.ts index 33c14c2d77..7fac58c35b 100644 --- a/app/hooks/cost-surface/index.ts +++ b/app/hooks/cost-surface/index.ts @@ -31,6 +31,28 @@ export function useProjectCostSurfaces( }); } +export function useProjectCostSurface( + pid: Project['id'], + csid: CostSurface['id'], + queryOptions: QueryObserverOptions = {} +) { + const { data: session } = useSession(); + + return useQuery({ + queryKey: ['cost-surface', csid], + queryFn: async () => + JSONAPI.request<{ data: CostSurface }>({ + method: 'GET', + url: `/projects/${pid}/cost-surfaces/${csid}`, + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, + }).then(({ data }) => data?.data), + enabled: Boolean(csid), + ...queryOptions, + }); +} + export function useEditProjectCostSurface() { const { data: session } = useSession(); @@ -43,7 +65,6 @@ export function useEditProjectCostSurface() { projectId: Project['id']; body: Record; }) => { - // TODO: change this to the correct endpoint return API.patch( `projects/${projectId}/cost-surfaces/${costSurfaceId}`, { @@ -66,7 +87,6 @@ export function useUploadProjectCostSurface() { const uploadProjectCostSurface = ({ id, data }: { id: CostSurface['id']; data: FormData }) => { return UPLOADS.request({ method: 'POST', - // TODO: change this to the correct endpoint url: `/projects/${id}/cost-surfaces/shapefile`, data, headers: { diff --git a/app/hooks/map/index.ts b/app/hooks/map/index.ts index dd01084a89..9616dc9f86 100644 --- a/app/hooks/map/index.ts +++ b/app/hooks/map/index.ts @@ -1,6 +1,10 @@ import { useMemo } from 'react'; import chroma from 'chroma-js'; +import { Layer } from 'mapbox-gl'; + +import { CostSurface } from 'types/api/cost-surface'; +import { Project } from 'types/api/project'; import { COLORS, LEGEND_LAYERS } from './constants'; import { @@ -171,6 +175,56 @@ export function useGridPreviewLayer({ active, gridId, cache = 0 }: UseGridPrevie }, [active, gridId, cache]); } +export function useCostSurfaceLayer({ + active, + pid, + costSurfaceId, + layerSettings, +}: { + active: boolean; + pid: Project['id']; + costSurfaceId: CostSurface['id']; + layerSettings: UsePUGridLayer['options']['settings']['cost-surface']; +}): Layer { + return useMemo(() => { + if (!active) return null; + + return { + id: `cost-surface-layer-${pid}-${costSurfaceId}`, + type: 'vector', + source: { + type: 'vector', + tiles: [ + `${process.env.NEXT_PUBLIC_API_URL}/api/v1/projects/${pid}/cost-surfaces/${costSurfaceId}/preview/tiles/{z}/{x}/{y}.mvt`, + ], + }, + render: { + layers: [ + { + type: 'fill', + 'source-layer': 'layer0', + layout: { + visibility: layerSettings.visibility ? 'visible' : 'none', + }, + paint: { + 'fill-color': [ + 'interpolate', + ['linear'], + ['get', 'cost'], + layerSettings.min === layerSettings.max ? 0 : layerSettings.min, + COLORS.cost[0], + layerSettings.max, + COLORS.cost[1], + ], + 'fill-opacity': 0.75 * (layerSettings.opacity || 1), + }, + }, + ], + }, + }; + }, [active, pid, costSurfaceId, layerSettings]); +} + // WDPA preview layer export function useWDPAPreviewLayer({ pid, @@ -601,14 +655,9 @@ export function usePUGridLayer({ const { wdpaIucnCategories = [], wdpaThreshold = 0, - cost = { - min: 0, - max: 100, - }, puIncludedValue, puExcludedValue, puAvailableValue, - // features = [], preHighlightFeatures = [], postHighlightFeatures = [], runId, @@ -617,8 +666,6 @@ export function usePUGridLayer({ const { pugrid: PUgridSettings = {}, 'wdpa-percentage': WdpaPercentageSettings = {}, - // features: FeaturesSettings = {}, - 'cost-surface': CostSettings = {}, 'lock-in': LockInSettings = {}, 'lock-out': LockOutSettings = {}, 'lock-available': LockAvailableSettings = {}, @@ -630,9 +677,6 @@ export function usePUGridLayer({ const { opacity: PUgridOpacity = 1, visibility: PUgridVisibility = true } = PUgridSettings; const { opacity: WdpaPercentageOpacity = 1, visibility: WdpaPercentageVisibility = true } = WdpaPercentageSettings; - // const { opacity: FeaturesOpacity = 1, visibility: FeaturesVisibility = true } = - // FeaturesSettings; - const { opacity: CostOpacity = 1, visibility: CostVisibility = true } = CostSettings; const { opacity: LockInOpacity = 1, visibility: LockInVisibility = true } = LockInSettings; const { opacity: LockOutOpacity = 1, visibility: LockOutVisibility = true } = LockOutSettings; const { opacity: LockAvailableOpacity = 1, visibility: LockAvailableVisibility = true } = @@ -754,31 +798,6 @@ export function usePUGridLayer({ ] : []), - // ANALYSIS - COST SURFACE - ...(sublayers.includes('cost') - ? [ - { - type: 'fill', - 'source-layer': 'layer0', - layout: { - visibility: getLayerVisibility(CostVisibility), - }, - paint: { - 'fill-color': [ - 'interpolate', - ['linear'], - ['get', 'costValue'], - cost.min === cost.max ? 0 : cost.min, - COLORS.cost[0], - cost.max, - COLORS.cost[1], - ], - 'fill-opacity': 0.75 * CostOpacity, - }, - }, - ] - : []), - // PROTECTED AREAS ...(sublayers.includes('wdpa-percentage') && wdpaThreshold !== null && diff --git a/app/hooks/map/types.ts b/app/hooks/map/types.ts index 3216d50036..be11d396be 100644 --- a/app/hooks/map/types.ts +++ b/app/hooks/map/types.ts @@ -1,6 +1,7 @@ import { PUAction } from 'store/slices/scenarios/types'; import { TargetSPFItemProps } from 'components/features/target-spf-item/types'; +import { CostSurface } from 'types/api/cost-surface'; import { Feature } from 'types/api/feature'; import { Scenario } from 'types/api/scenario'; import { WDPA } from 'types/api/wdpa'; @@ -145,6 +146,8 @@ export interface UsePUGridLayer { 'cost-surface'?: { opacity?: number; visibility?: boolean; + min: CostSurface['min']; + max: CostSurface['max']; }; 'lock-in'?: { opacity?: number; diff --git a/app/layout/projects/show/map/index.tsx b/app/layout/projects/show/map/index.tsx index 8db9c9dab6..24a369e10c 100644 --- a/app/layout/projects/show/map/index.tsx +++ b/app/layout/projects/show/map/index.tsx @@ -13,6 +13,7 @@ import { FiLayers } from 'react-icons/fi'; import { HiOutlinePrinter } from 'react-icons/hi'; import { useAccessToken } from 'hooks/auth'; +import { useProjectCostSurface } from 'hooks/cost-surface'; import { useAllFeatures } from 'hooks/features'; import { usePUCompareLayer, @@ -21,6 +22,7 @@ import { useBBOX, useFeaturePreviewLayers, useWDPAPreviewLayer, + useCostSurfaceLayer, } from 'hooks/map'; import { useDownloadScenarioComparisonReport, useProject } from 'hooks/projects'; import { useScenarios } from 'hooks/scenarios'; @@ -105,6 +107,8 @@ export const ProjectMap = (): JSX.Element => { } ); + const costSurfaceQuery = useProjectCostSurface(pid, selectedCostSurface); + const selectedFeaturesData = useMemo(() => { return allFeaturesQuery.data?.data.filter((f) => selectedFeaturesIds?.includes(f.id)); }, [selectedFeaturesIds, allFeaturesQuery.data?.data]); @@ -134,7 +138,7 @@ export const ProjectMap = (): JSX.Element => { const PUGridLayer = usePUGridLayer({ active: rawScenariosIsFetched && rawScenariosData && !!rawScenariosData.length && !sid2, sid: sid ? `${sid}` : null, - include: 'results,cost', + include: 'results', sublayers: [ ...(sid1 && !sid2 ? ['frequency'] : []), ...(!!selectedCostSurface ? ['cost'] : []), @@ -145,12 +149,22 @@ export const ProjectMap = (): JSX.Element => { pugrid: layerSettings.pugrid, 'wdpa-percentage': layerSettings['wdpa-percentage'], features: layerSettings.features, - 'cost-surface': layerSettings[selectedCostSurface], frequency: layerSettings.frequency, }, }, }); + const costSurfaceLayer = useCostSurfaceLayer({ + active: Boolean(selectedCostSurface) && costSurfaceQuery.isSuccess, + pid, + costSurfaceId: selectedCostSurface, + layerSettings: { + ...layerSettings[selectedCostSurface], + min: costSurfaceQuery.data?.min, + max: costSurfaceQuery.data?.max, + } as Parameters[0]['layerSettings'], + }); + const PUCompareLayer = usePUCompareLayer({ active: isComparisonEnabled, sid: sid1, @@ -192,6 +206,7 @@ export const ProjectMap = (): JSX.Element => { const LAYERS = [ PUGridLayer, + costSurfaceLayer, PUCompareLayer, PlanningAreaLayer, WDPAsPreviewLayers, diff --git a/app/layout/projects/show/map/legend/hooks/index.ts b/app/layout/projects/show/map/legend/hooks/index.ts index 40a8343b77..c246dc378b 100644 --- a/app/layout/projects/show/map/legend/hooks/index.ts +++ b/app/layout/projects/show/map/legend/hooks/index.ts @@ -59,8 +59,7 @@ export const useCostSurfaceLegend = () => { setLayerSettings({ id, settings: { - visibility: - id !== costSurfaceId ? false : !layerSettings[costSurfaceId]?.visibility || true, + visibility: id !== costSurfaceId ? false : !layerSettings[costSurfaceId]?.visibility, }, }) ); diff --git a/app/package.json b/app/package.json index bf3d6e0d12..5e2e7acc67 100644 --- a/app/package.json +++ b/app/package.json @@ -97,6 +97,7 @@ "@types/chroma-js": "2.4.1", "@types/d3": "^6.3.0", "@types/lodash": "4.14.192", + "@types/mapbox-gl": "2.7.15", "@types/node": "18.15.11", "@types/react": "18.0.32", "@types/react-map-gl": "6.1.3", diff --git a/app/yarn.lock b/app/yarn.lock index 4ba2301e5f..b638d528ef 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -3541,6 +3541,15 @@ __metadata: languageName: node linkType: hard +"@types/mapbox-gl@npm:2.7.15": + version: 2.7.15 + resolution: "@types/mapbox-gl@npm:2.7.15" + dependencies: + "@types/geojson": "*" + checksum: 39d5ef3341d2cbc6bf254c08e14ac2a09c7e6192cf18c838a0b7ce7ca6db195376f5c0357edd1b7f2afe132a0ba5bd262842b8274c4cd091a413057221e4322b + languageName: node + linkType: hard + "@types/mapbox-gl@npm:^2.0.3": version: 2.1.2 resolution: "@types/mapbox-gl@npm:2.1.2" @@ -4126,6 +4135,7 @@ __metadata: "@types/chroma-js": 2.4.1 "@types/d3": ^6.3.0 "@types/lodash": 4.14.192 + "@types/mapbox-gl": 2.7.15 "@types/node": 18.15.11 "@types/react": 18.0.32 "@types/react-map-gl": 6.1.3