diff --git a/app/hooks/map/constants.tsx b/app/hooks/map/constants.tsx index b1b2d2e050..91cd0faa40 100644 --- a/app/hooks/map/constants.tsx +++ b/app/hooks/map/constants.tsx @@ -65,7 +65,7 @@ export const COLORS = { include: '#03E7D1', exclude: '#FF472E', available: '#FFCA42', - cost: ['#FFBFB7', '#C21701'], + cost: ['#3C1002', '#FF440A'], frequency: ['#0C2C32', '#006D83', '#008B8C', '#0BC6C2'], compare: { '#1F1F1F': ['00'], @@ -182,21 +182,26 @@ export const LEGEND_LAYERS = { }), // WDPA - 'wdpa-preview': () => ({ - id: 'wdpa-preview', - name: 'Protected areas preview', - icon: ( - - ), - settingsManager: { - opacity: true, - visibility: true, - }, - }), + 'wdpa-preview': (options: { onChangeVisibility: () => void }) => { + const { onChangeVisibility } = options; + + return { + id: 'wdpa-preview', + name: 'Protected areas preview', + icon: ( + + ), + settingsManager: { + opacity: true, + visibility: true, + }, + onChangeVisibility, + }; + }, 'wdpa-percentage': () => ({ id: 'wdpa-percentage', name: 'Protected areas', @@ -245,22 +250,20 @@ export const LEGEND_LAYERS = { }) => { const { items, onChangeVisibility } = options; - return items.map(({ name, id, color }) => { - return { - id, - name, - type: 'basic' as LegendItemType, - icon: , - settingsManager: { - opacity: true, - visibility: true, - }, - items: [], - onChangeVisibility: () => { - onChangeVisibility?.(id); - }, - }; - }); + return items.map(({ name, id, color }) => ({ + id, + name, + type: 'basic' as LegendItemType, + icon: , + settingsManager: { + opacity: true, + visibility: true, + }, + items: [], + onChangeVisibility: () => { + onChangeVisibility?.(id); + }, + })); }, 'features-preview': (options: UseLegend['options']) => { @@ -363,12 +366,15 @@ export const LEGEND_LAYERS = { // }; }, // !this config aims to replace the original cost config - 'cost-surface': (options: { items: { min: number; max: number; name: string }[] }) => { - const { items } = options; + 'cost-surface': (options: { + costSurface: { name: string; min: number; max: number }; + onChangeVisibility: () => void; + }) => { + const { costSurface, onChangeVisibility } = options; - return items.map(({ name, min, max }) => ({ - id: `cost-surface-${name}`, - name, + return { + id: 'cost-surface', + name: costSurface.name, type: 'gradient' as LegendItemType, settingsManager: { opacity: true, @@ -377,34 +383,15 @@ export const LEGEND_LAYERS = { items: [ { color: COLORS.cost[0], - value: `${min === max ? 0 : min}`, + value: `${costSurface.min === costSurface.max ? 0 : costSurface.min}`, }, { color: COLORS.cost[1], - value: `${max}`, + value: `${costSurface.max}`, }, ], - })); - - // return { - // id: 'cost-surface', - // name: 'Cost Surface', - // type: 'gradient', - // settingsManager: { - // opacity: true, - // visibility: true, - // }, - // items: [ - // { - // color: COLORS.cost[0], - // value: `${min === max ? 0 : min}`, - // }, - // { - // color: COLORS.cost[1], - // value: `${max}`, - // }, - // ], - // }; + onChangeVisibility, + }; }, cost: (options) => { const { diff --git a/app/layout/project/sidebar/project/inventory-panel/features/index.tsx b/app/layout/project/sidebar/project/inventory-panel/features/index.tsx index 4be2fc53db..c1973c35cc 100644 --- a/app/layout/project/sidebar/project/inventory-panel/features/index.tsx +++ b/app/layout/project/sidebar/project/inventory-panel/features/index.tsx @@ -8,7 +8,10 @@ import { setLayerSettings, } from 'store/slices/projects/[id]'; +import chroma from 'chroma-js'; + import { useAllFeatures } from 'hooks/features'; +import { COLORS } from 'hooks/map/constants'; import ActionsMenu from 'layout/project/sidebar/project/inventory-panel/features/actions-menu'; import FeaturesBulkActionMenu from 'layout/project/sidebar/project/inventory-panel/features/bulk-action-menu'; @@ -31,9 +34,11 @@ const FEATURES_TABLE_COLUMNS = [ const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }): JSX.Element => { const dispatch = useAppDispatch(); - const { selectedFeatures: visibleFeatures, search } = useAppSelector( - (state) => state['/projects/[id]'] - ); + const { + selectedFeatures: visibleFeatures, + search, + layerSettings, + } = useAppSelector((state) => state['/projects/[id]']); const [filters, setFilters] = useState[1]>({ sort: FEATURES_TABLE_COLUMNS[0].name, @@ -49,14 +54,23 @@ const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }): search, }, { - select: ({ data }) => - data?.map((feature) => ({ - id: feature.id, - name: feature.featureClassName, - scenarios: feature.scenarioUsageCount, - tag: feature.tag, - isCustom: feature.isCustom, - })), + select: ({ data }) => { + return data?.map((feature, index) => { + const color = + data.length > COLORS['features-preview'].ramp.length + ? chroma.scale(COLORS['features-preview'].ramp).colors(data.length)[index] + : COLORS['features-preview'].ramp[index]; + + return { + id: feature.id, + name: feature.featureClassName, + scenarios: feature.scenarioUsageCount, + tag: feature.tag, + isCustom: feature.isCustom, + color, + }; + }); + }, placeholderData: { data: [] }, keepPreviousData: true, } @@ -108,23 +122,27 @@ const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }): } dispatch(setVisibleFeatures(newSelectedFeatures)); + const selectedFeature = allFeaturesQuery.data.find(({ id }) => featureId === id); + const { color } = selectedFeature || {}; + dispatch( setLayerSettings({ id: featureId, settings: { visibility: !isIncluded, + color, }, }) ); }, - [dispatch, visibleFeatures] + [dispatch, visibleFeatures, allFeaturesQuery.data] ); const displayBulkActions = selectedFeaturesIds.length > 0; const data: DataItem[] = allFeaturesQuery.data?.map((feature) => ({ ...feature, - isVisibleOnMap: visibleFeatures.includes(feature.id), + isVisibleOnMap: layerSettings[feature.id]?.visibility ?? false, })); return ( diff --git a/app/layout/projects/show/map/legend/hooks/index.ts b/app/layout/projects/show/map/legend/hooks/index.ts index 9b13a9b432..5b100fda92 100644 --- a/app/layout/projects/show/map/legend/hooks/index.ts +++ b/app/layout/projects/show/map/legend/hooks/index.ts @@ -11,12 +11,17 @@ import { COLORS, LEGEND_LAYERS } from 'hooks/map/constants'; import { useProjectWDPAs } from 'hooks/wdpa'; import { Feature } from 'types/api/feature'; +import { WDPA } from 'types/api/wdpa'; export const useCostSurfaceLegend = () => { const { selectedCostSurfaces } = useAppSelector((state) => state['/projects/[id]']); const { query } = useRouter(); - const { pid } = query as { pid: string }; + const { pid } = query as { pid: string; sid: string }; + + const dispatch = useAppDispatch(); + const { layerSettings } = useAppSelector((state) => state['/projects/[id]']); + // TODO: pending on API to provide the cost surfaces associated to the project. const costSurfaceQuery = useProjectCostSurfaces( pid, {}, @@ -25,31 +30,35 @@ export const useCostSurfaceLegend = () => { } ); - // todo: uncomment when API is ready - // return LEGEND_LAYERS['cost-surface']({ - // items: costSurfaceQuery.data?.map(({ name, min = 1, max = 8 }) => ({ name, min, max })) || [], - // }); - return LEGEND_LAYERS['cost-surface']({ - items: [ - { name: 'Cost Surface 2', min: 1, max: 22 }, - { name: 'Cost Surface 4', min: 1, max: 11 }, - { name: 'Cost Surface 5', min: 1, max: 5 }, - ], + costSurface: { name: 'Cost Surface 4', min: 1, max: 11 }, + onChangeVisibility: () => { + dispatch( + setLayerSettings({ + id: 'cost-surface', + settings: { + visibility: !layerSettings['cost-surface']?.visibility, + }, + }) + ); + }, }); }; export const useConservationAreasLegend = () => { const { query } = useRouter(); const { pid } = query as { pid: string }; - const { selectedWDPAs } = useAppSelector((state) => state['/projects/[id]']); + const dispatch = useAppDispatch(); + const { layerSettings } = useAppSelector((state) => state['/projects/[id]']); const protectedAreaQuery = useProjectWDPAs( pid, - {}, - { select: (data) => data.filter(({ id }) => selectedWDPAs.includes(id)) } + {} + // { select: (data) => data.map(({ }) => )} ); + console.log({ data: protectedAreaQuery?.data }); + // todo: uncomment when API is ready // return LEGEND_LAYERS['designated-areas']({ // items: protectedAreaQuery.data?.map(({ id }) => ({ name: id })) || [], @@ -61,7 +70,16 @@ export const useConservationAreasLegend = () => { { name: 'WDPA 2', id: '2' }, { name: 'WDPA 3', id: '3' }, ], - onChangeVisibility: () => {}, + onChangeVisibility: (WDPAId: WDPA['id']) => { + dispatch( + setLayerSettings({ + id: WDPAId, + settings: { + visibility: !layerSettings[WDPAId]?.visibility, + }, + }) + ); + }, }); }; @@ -106,7 +124,7 @@ export const useFeatureAbundanceLegend = () => { }); }; -export const useFeatureLegend = () => { +export const useFeaturesLegend = () => { const { selectedFeatures } = useAppSelector((state) => state['/projects/[id]']); const { query } = useRouter(); const { pid } = query as { pid: string }; @@ -114,7 +132,7 @@ export const useFeatureLegend = () => { const dispatch = useAppDispatch(); const projectFeaturesQuery = useAllFeatures( pid, - {}, + { sort: 'feature_class_name' }, { select: ({ data }) => data, } @@ -149,11 +167,14 @@ export const useFeatureLegend = () => { } dispatch(setSelectedFeatures(newSelectedFeatures)); + const { color } = items.find(({ id }) => id === featureId) || {}; + dispatch( setLayerSettings({ id: featureId, settings: { visibility: !isIncluded, + color, }, }) ); @@ -169,7 +190,7 @@ export const useInventoryLegend = () => { subgroups: [ { name: 'Cost Surface', - layers: useCostSurfaceLegend(), + layers: [useCostSurfaceLegend()], }, ], }, @@ -178,21 +199,17 @@ export const useInventoryLegend = () => { subgroups: [ { name: 'Conservation Areas', - layers: useConservationAreasLegend(), - }, - { - name: 'Conservation Areas 2', - layers: useConservationAreasLegend(), + layers: [useConservationAreasLegend()], }, ], }, { name: 'Features (Continuous)', - layers: useFeatureAbundanceLegend(), + layers: [useFeatureAbundanceLegend()], }, { name: 'Features (Binary)', - layers: useFeatureLegend(), + layers: useFeaturesLegend(), }, ]; }; diff --git a/app/layout/scenarios/edit/map/legend/hooks/index.ts b/app/layout/scenarios/edit/map/legend/hooks/index.ts index 45b99a9a7a..6956469d96 100644 --- a/app/layout/scenarios/edit/map/legend/hooks/index.ts +++ b/app/layout/scenarios/edit/map/legend/hooks/index.ts @@ -19,23 +19,32 @@ import { WDPA } from 'types/api/wdpa'; export const useCostSurfaceLegend = () => { const { query } = useRouter(); const { pid, sid } = query as { pid: string; sid: string }; - const { selectedCostSurfaces } = useAppSelector((state) => state[`/scenarios/${sid}/edit`]); - const costSurfaceQuery = useProjectCostSurfaces( - pid, - {}, - { - select: (data) => data.filter((cs) => selectedCostSurfaces.includes(cs.id)), - } - ); + const dispatch = useAppDispatch(); + const scenarioSlice = getScenarioEditSlice(sid); + const { setLayerSettings } = scenarioSlice.actions; + const { layerSettings } = useAppSelector((state) => state[`/scenarios/${sid}/edit`]); - // todo: uncomment when API is ready - // return LEGEND_LAYERS['cost-surface']({ - // items: costSurfaceQuery.data?.map(({ name, min = 1, max = 8 }) => ({ name, min, max })) || [], - // }); + // const costSurfaceQuery = useProjectCostSurfaces( + // pid, + // {}, + // { + // select: (data) => data.filter((cs) => selectedCostSurfaces.includes(cs.id)), + // } + // ); return LEGEND_LAYERS['cost-surface']({ - items: [{ name: 'Cost Surface 2', min: 1, max: 22 }], + costSurface: { name: 'Cost Surface 2', min: 1, max: 22 }, + onChangeVisibility: () => { + dispatch( + setLayerSettings({ + id: 'cost-surface', + settings: { + visibility: !layerSettings['cost-surface']?.visibility, + }, + }) + ); + }, }); }; @@ -257,6 +266,29 @@ export const useLockAvailableLegend = () => { }); }; +export const useWDPAPreviewLegend = () => { + const { query } = useRouter(); + const { sid } = query as { sid: string }; + + const dispatch = useAppDispatch(); + const scenarioSlice = getScenarioEditSlice(sid); + const { setLayerSettings } = scenarioSlice.actions; + const { layerSettings } = useAppSelector((state) => state[`/scenarios/${sid}/edit`]); + + return LEGEND_LAYERS['wdpa-preview']({ + onChangeVisibility: () => { + dispatch( + setLayerSettings({ + id: 'wdpa-preview', + settings: { + visibility: !layerSettings['wdpa-preview']?.visibility, + }, + }) + ); + }, + }); +}; + export const useFrequencyLegend = () => { const { query } = useRouter(); const { sid } = query as { sid: string }; @@ -315,22 +347,18 @@ export const useScenarioLegend = () => { return [ { name: 'Planning Grid', - subgroups: [ - { - name: 'Planning Grid', - layers: [ - LEGEND_LAYERS['pugrid'](), - useLockInLegend(), - useLockOutLegend(), - useLockAvailableLegend(), - ], - }, - { - name: 'Features', - layers: [LEGEND_LAYERS['wdpa-preview'](), useFrequencyLegend(), useSolutionsLegend()], - }, + layers: [ + LEGEND_LAYERS['pugrid'](), + useCostSurfaceLegend(), + useLockInLegend(), + useLockOutLegend(), + useLockAvailableLegend(), ], }, + { + name: 'Features', + layers: [useWDPAPreviewLegend(), useFrequencyLegend(), useSolutionsLegend()], + }, { name: 'Designated Areas', subgroups: [