diff --git a/app/layout/project/navigation/types.ts b/app/layout/project/navigation/types.ts
index 325b538052..1e8a58aaa3 100644
--- a/app/layout/project/navigation/types.ts
+++ b/app/layout/project/navigation/types.ts
@@ -4,3 +4,5 @@ export type NavigationTreeCategories =
| 'gridSetup'
| 'solutions'
| 'advancedSettings';
+
+export type NavigationInventoryTabs = 'protected-areas' | 'cost-surface' | 'features';
diff --git a/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/header-item/index.tsx b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/header-item/index.tsx
new file mode 100644
index 0000000000..45285a4b0c
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/header-item/index.tsx
@@ -0,0 +1,54 @@
+import { useCallback } from 'react';
+
+import { ArrowDown, ArrowUp } from 'lucide-react';
+
+import { cn } from 'utils/cn';
+
+import { HeaderItem } from './types';
+
+const HeaderItem = ({
+ className,
+ text,
+ name,
+ columns,
+ sorting,
+ onClick,
+}: HeaderItem): JSX.Element => {
+ const sortingMatches = /^(-?)(.+)$/.exec(sorting);
+ const sortField = sortingMatches[2];
+ const sortOrder = sortingMatches[1] === '-' ? 'desc' : 'asc';
+
+ const isActive = columns[name] === sortField;
+
+ const handleClick = useCallback(() => {
+ onClick(columns[name]);
+ }, [onClick, columns, name]);
+
+ return (
+
+
+ {text}
+
+ {sortOrder === 'asc' && isActive ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default HeaderItem;
diff --git a/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/header-item/types.ts b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/header-item/types.ts
new file mode 100644
index 0000000000..b550e8447d
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/header-item/types.ts
@@ -0,0 +1,10 @@
+export type HeaderItem = {
+ className?: string;
+ text: string;
+ name: string;
+ columns: {
+ [key: string]: string;
+ };
+ sorting: string;
+ onClick?: (field: string) => void;
+};
diff --git a/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/index.tsx b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/index.tsx
new file mode 100644
index 0000000000..906bb73aa3
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/index.tsx
@@ -0,0 +1,87 @@
+import Checkbox from 'components/forms/checkbox';
+import Loading from 'components/loading';
+
+import HeaderItem from './header-item';
+import RowItem from './row-item';
+import { InventoryTable } from './types';
+
+const InventoryTable = ({
+ loading,
+ data,
+ noDataMessage,
+ columns,
+ sorting,
+ selectedIds,
+ onSortChange,
+ onToggleSeeOnMap,
+ onSelectRow,
+ onSelectAll,
+ ActionsComponent,
+}: InventoryTable): JSX.Element => {
+ const noData = !loading && data?.length === 0;
+
+ return (
+ <>
+ {loading && !data.length && (
+
+
+
+ )}
+ {noData && {noDataMessage}
}
+ {!!data?.length && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {data.map((item) => (
+
+ ))}
+
+
+ )}
+ >
+ );
+};
+
+export { type DataItem } from './types';
+
+export default InventoryTable;
diff --git a/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/row-item/index.tsx b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/row-item/index.tsx
new file mode 100644
index 0000000000..9facd7eb38
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/row-item/index.tsx
@@ -0,0 +1,85 @@
+import { MoreHorizontal } from 'lucide-react';
+
+import Checkbox from 'components/forms/checkbox';
+import Icon from 'components/icon';
+import { Popover, PopoverContent, PopoverTrigger } from 'components/popover';
+import { cn } from 'utils/cn';
+
+import HIDE_SVG from 'svgs/ui/hide.svg?sprite';
+import SHOW_SVG from 'svgs/ui/show.svg?sprite';
+
+import { RowItem } from './types';
+
+const RowItem = ({
+ item,
+ selectedIds,
+ onSelectRow,
+ onToggleSeeOnMap,
+ ActionsComponent,
+}: RowItem) => {
+ const { id, name, scenarios, tag, isVisibleOnMap } = item;
+
+ return (
+
+
+
+
+
+ {name}
+
+ Currently in use in
+
+ {scenarios}
+ {' '}
+ scenarios.
+
+
+
+ {tag && (
+
+
+ {tag}
+
+
+ )}
+
+
+
+
onToggleSeeOnMap(id)}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default RowItem;
diff --git a/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/row-item/types.ts b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/row-item/types.ts
new file mode 100644
index 0000000000..350ee574ec
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/row-item/types.ts
@@ -0,0 +1,11 @@
+import { ChangeEvent } from 'react';
+
+import { DataItem } from '../types';
+
+export type RowItem = {
+ item: DataItem;
+ selectedIds: string[];
+ onSelectRow: (evt: ChangeEvent) => void;
+ onToggleSeeOnMap: (id: string) => void;
+ ActionsComponent: ({ item }) => JSX.Element;
+};
diff --git a/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/types.ts b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/types.ts
new file mode 100644
index 0000000000..b89b6d68c8
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/components/inventory-table/types.ts
@@ -0,0 +1,25 @@
+import { ChangeEvent } from 'react';
+
+export type DataItem = {
+ id: string;
+ name: string;
+ scenarios: number;
+ tag: string;
+ isVisibleOnMap: boolean;
+};
+
+export type InventoryTable = {
+ loading: boolean;
+ data: DataItem[];
+ noDataMessage: string;
+ columns: {
+ [key: string]: string;
+ };
+ sorting: string;
+ selectedIds: string[];
+ onSortChange: (field: string) => void;
+ onToggleSeeOnMap: (id: string) => void;
+ onSelectRow: (evt: ChangeEvent) => void;
+ onSelectAll: (evt: ChangeEvent) => void;
+ ActionsComponent: ({ item }) => JSX.Element;
+};
diff --git a/app/layout/project/sidebar/project/inventory-panel/constants.ts b/app/layout/project/sidebar/project/inventory-panel/constants.ts
new file mode 100644
index 0000000000..7acacc1f3d
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/constants.ts
@@ -0,0 +1,37 @@
+import { NavigationInventoryTabs } from 'layout/project/navigation/types';
+
+import CostSurfaceTable from './cost-surface';
+import CostSurfaceInfo from './cost-surface/info';
+import FeaturesTable from './features';
+import FeaturesInfo from './features/info';
+import FeatureUploadModal from './features/modals/upload';
+import ProtectedAreasTable from './protected-areas';
+import ProtectedAreasFooter from './protected-areas/footer';
+import { InventoryPanel } from './types';
+
+export const INVENTORY_TABS = {
+ 'protected-areas': {
+ title: 'Protected Areas',
+ search: 'Search protected areas',
+ noData: 'No protected areas found.',
+ TableComponent: ProtectedAreasTable,
+ FooterComponent: ProtectedAreasFooter,
+ },
+ 'cost-surface': {
+ title: 'Cost Surface',
+ search: 'Search cost surfaces',
+ noData: 'No cost surfaces found.',
+ InfoComponent: CostSurfaceInfo,
+ TableComponent: CostSurfaceTable,
+ },
+ features: {
+ title: 'Features',
+ search: 'Search features',
+ noData: 'No features found.',
+ InfoComponent: FeaturesInfo,
+ UploadModalComponent: FeatureUploadModal,
+ TableComponent: FeaturesTable,
+ },
+} satisfies {
+ [key in NavigationInventoryTabs]: InventoryPanel;
+};
diff --git a/app/layout/project/sidebar/project/inventory-panel/cost-surface/index.tsx b/app/layout/project/sidebar/project/inventory-panel/cost-surface/index.tsx
index e54c0cc7bb..20c04506aa 100644
--- a/app/layout/project/sidebar/project/inventory-panel/cost-surface/index.tsx
+++ b/app/layout/project/sidebar/project/inventory-panel/cost-surface/index.tsx
@@ -1,7 +1,5 @@
-import Section from 'layout/section';
-
-const InventoryPanelCostSurface = (): JSX.Element => {
- return InventoryPanelCostSurface ;
+const InventoryPanelCostSurface = ({ noData: noDataMessage }: { noData: string }): JSX.Element => {
+ return {noDataMessage}
;
};
export default InventoryPanelCostSurface;
diff --git a/app/layout/project/sidebar/project/inventory-panel/cost-surface/info/index.tsx b/app/layout/project/sidebar/project/inventory-panel/cost-surface/info/index.tsx
new file mode 100644
index 0000000000..6c5bc50ae0
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/cost-surface/info/index.tsx
@@ -0,0 +1,28 @@
+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';
+
+const CostSurfaceInfo = (): JSX.Element => {
+ return (
+ <>
+ What is a Cost Surface?
+
+
+ Marxan aims to minimize socio-economic impacts and conflicts between uses through what is
+ called the “cost” surface. In conservation planning, cost data may reflect acquisition,
+ management, or opportunity costs ($), but may also reflect non-monetary impacts. Proxies
+ are commonly used in absence of fine-scale socio-economic information. A default value for
+ cost will be the planning unit area but you can upload your cost surface.
+
+
+ In the examples below, we illustrate how distance from a city, road or port can be used as
+ a proxy cost surface. In these examples, areas with many competing activities will make a
+ planning unit cost more than areas further away with less competition for access.
+
+
+
+
+ >
+ );
+};
+
+export default CostSurfaceInfo;
diff --git a/app/layout/project/sidebar/project/inventory-panel/features/list/item/actions/index.tsx b/app/layout/project/sidebar/project/inventory-panel/features/actions-menu/index.tsx
similarity index 65%
rename from app/layout/project/sidebar/project/inventory-panel/features/list/item/actions/index.tsx
rename to app/layout/project/sidebar/project/inventory-panel/features/actions-menu/index.tsx
index c64032733a..9d5cf5c54d 100644
--- a/app/layout/project/sidebar/project/inventory-panel/features/list/item/actions/index.tsx
+++ b/app/layout/project/sidebar/project/inventory-panel/features/actions-menu/index.tsx
@@ -1,27 +1,33 @@
-import { useCallback, useState, ButtonHTMLAttributes } from 'react';
-
-import { FileEdit, Trash2, Tag } from 'lucide-react';
+import { useCallback, useState } from 'react';
+import Icon from 'components/icon';
import Modal from 'components/modal/component';
import DeleteModal from 'layout/project/sidebar/project/inventory-panel/features/modals/delete';
import EditModal from 'layout/project/sidebar/project/inventory-panel/features/modals/edit';
-import { Feature } from 'types/api/feature';
import { cn } from 'utils/cn';
+import DELETE_SVG from 'svgs/ui/new-layout/delete.svg?sprite';
+import TAG_SVG from 'svgs/ui/tag.svg?sprite';
+
const BUTTON_CLASSES =
'flex items-center px-4 py-2 w-full text-sm cursor-pointer bg-gray-700 hover:bg-gray-500 transition transition-colors space-x-2 group';
-const ICON_CLASSES = 'text-gray-400 group-hover:text-white';
+const ICON_CLASSES = 'h-5 w-5 text-gray-400 group-hover:text-white';
-const FeatureActions = ({
- feature,
- onEditName,
- isDeletable,
+const ActionsMenu = ({
+ item,
}: {
- feature: Feature;
- isDeletable: boolean;
- onEditName: (evt: Parameters['onClick']>[0]) => void;
+ item: {
+ id: string;
+ name: string;
+ scenarios: number;
+ tag: string;
+ custom: boolean;
+ };
}): JSX.Element => {
+ const isDeletable = !item.custom && !item.scenarios;
+
+ // item.isCustom && !item.scenarioUsageCount
const [modalState, setModalState] = useState<{ edit: boolean; delete: boolean }>({
edit: false,
delete: false,
@@ -33,39 +39,27 @@ const FeatureActions = ({
return (
-
-
-
- Rename
-
-
handleModal('edit', true)}
className={cn({
[BUTTON_CLASSES]: true,
+ 'rounded-t-2xl': true,
'last:rounded-b-2xl': !isDeletable,
})}
>
-
+
Edit
handleModal('edit', false)}
>
-
+
{isDeletable && (
@@ -80,7 +74,7 @@ const FeatureActions = ({
'rounded-b-2xl': true,
})}
>
-
+
Delete
-
+
)}
@@ -100,4 +94,4 @@ const FeatureActions = ({
);
};
-export default FeatureActions;
+export default ActionsMenu;
diff --git a/app/layout/project/sidebar/project/inventory-panel/features/bulk-action-menu/index.tsx b/app/layout/project/sidebar/project/inventory-panel/features/bulk-action-menu/index.tsx
index b692fed2f6..ab35ef62c6 100644
--- a/app/layout/project/sidebar/project/inventory-panel/features/bulk-action-menu/index.tsx
+++ b/app/layout/project/sidebar/project/inventory-panel/features/bulk-action-menu/index.tsx
@@ -7,7 +7,7 @@ import DeleteModal from 'layout/project/sidebar/project/inventory-panel/features
import EditBulkModal from 'layout/project/sidebar/project/inventory-panel/features/modals/edit-bulk';
import { Feature } from 'types/api/feature';
-import EDIT_SVG from 'svgs/project/edit.svg?sprite';
+import EDIT_SVG from 'svgs/ui/edit.svg?sprite';
import DELETE_SVG from 'svgs/ui/new-layout/delete.svg?sprite';
const BUTTON_CLASSES =
@@ -30,7 +30,7 @@ const FeaturesBulkActionMenu = ({
return (
<>
-
+
{selectedFeaturesIds.length}
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 e032a977ed..2e5f5967a2 100644
--- a/app/layout/project/sidebar/project/inventory-panel/features/index.tsx
+++ b/app/layout/project/sidebar/project/inventory-panel/features/index.tsx
@@ -1,87 +1,131 @@
-import { useCallback, useState } from 'react';
+import { useCallback, useState, ChangeEvent, useEffect } from 'react';
-import { useAppDispatch } from 'store/hooks';
-import { setSearch } from 'store/slices/projects/[id]';
+import { useRouter } from 'next/router';
-import Button from 'components/button';
-import Icon from 'components/icon';
-import InfoButton from 'components/info-button';
-import Search, { SearchProps } from 'components/search';
-import Section from 'layout/section';
+import { useAppDispatch, useAppSelector } from 'store/hooks';
+import { setSelectedFeatures as setVisibleFeatures } from 'store/slices/projects/[id]';
-import FEATURE_ABUND_IMG from 'images/info-buttons/img_abundance_data.png';
-import FEATURE_SOCIAL_IMG from 'images/info-buttons/img_social_uses.png';
-import FEATURE_SPECIES_IMG from 'images/info-buttons/img_species_range.png';
+import { useAllFeatures } from 'hooks/features';
-import UPLOAD_SVG from 'svgs/ui/upload.svg?sprite';
+import { Feature } from 'types/api/feature';
-import ProjectFeatureList from './list';
-import FeatureUploadModal from './modals/upload';
+import InventoryTable, { type DataItem } from '../components/inventory-table';
-const InventoryPanelFeatures = (): JSX.Element => {
+import ActionsMenu from './actions-menu';
+import FeaturesBulkActionMenu from './bulk-action-menu';
+
+const FEATURES_TABLE_COLUMNS = {
+ name: 'featureClassName',
+ tag: 'tag',
+};
+
+const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }): JSX.Element => {
const dispatch = useAppDispatch();
- const handleSearch = useCallback(
- (value: Parameters[0]) => {
- dispatch(setSearch(value));
+ const { selectedFeatures: visibleFeatures, search } = useAppSelector(
+ (state) => state['/projects/[id]']
+ );
+
+ const [filters, setFilters] = useState[1]>({
+ sort: 'featureClassName',
+ });
+ const [selectedFeaturesIds, setSelectedFeaturesIds] = useState([]);
+ const { query } = useRouter();
+ const { pid } = query as { pid: string };
+
+ const allFeaturesQuery = useAllFeatures(
+ pid,
+ {
+ ...filters,
+ search,
},
- [dispatch]
+ {
+ select: ({ data }) =>
+ data?.map((feature) => ({
+ id: feature.id,
+ name: feature.featureClassName,
+ scenarios: feature.scenarioUsageCount,
+ tag: feature.tag,
+ isCustom: feature.isCustom,
+ })),
+ placeholderData: { data: [] },
+ keepPreviousData: true,
+ }
);
- const [isOpenFeatureUploader, setOpenFeatureUploader] = useState(false);
- const handleFeatureUploader = useCallback(() => {
- setOpenFeatureUploader(true);
- }, []);
+ const featureIds = allFeaturesQuery.data?.map((feature) => feature.id);
+
+ const handleSelectAll = useCallback(
+ (evt: ChangeEvent) => {
+ setSelectedFeaturesIds(evt.target.checked ? featureIds : []);
+ },
+ [featureIds]
+ );
- const closeFeatureUploadModal = useCallback(() => {
- setOpenFeatureUploader(false);
+ const handleSelectFeature = useCallback((evt: ChangeEvent) => {
+ if (evt.target.checked) {
+ setSelectedFeaturesIds((prevSelectedFeatures) => [...prevSelectedFeatures, evt.target.value]);
+ } else {
+ setSelectedFeaturesIds((prevSelectedFeatures) =>
+ prevSelectedFeatures.filter((featureId) => featureId !== evt.target.value)
+ );
+ }
}, []);
+ const handleSort = useCallback(
+ (_sortType: (typeof filters)['sort']) => {
+ const sort = filters.sort === _sortType ? `-${_sortType}` : _sortType;
+
+ setFilters((prevFilters) => ({
+ ...prevFilters,
+ sort,
+ }));
+ },
+ [filters.sort]
+ );
+
+ useEffect(() => {
+ setSelectedFeaturesIds([]);
+ }, [search]);
+
+ const toggleSeeOnMap = useCallback(
+ (featureId: Feature['id']) => {
+ const newSelectedFeatures = [...visibleFeatures];
+ if (!newSelectedFeatures.includes(featureId)) {
+ newSelectedFeatures.push(featureId);
+ } else {
+ const i = newSelectedFeatures.indexOf(featureId);
+ newSelectedFeatures.splice(i, 1);
+ }
+ dispatch(setVisibleFeatures(newSelectedFeatures));
+ },
+ [dispatch, visibleFeatures]
+ );
+
+ const displayBulkActions = selectedFeaturesIds.length > 0;
+
+ const data: DataItem[] = allFeaturesQuery.data?.map((feature) => ({
+ ...feature,
+ isVisibleOnMap: visibleFeatures.includes(feature.id),
+ }));
+
return (
-
-
-
-
Inventory Panel
-
- Features
-
- <>
- What are features?
-
-
- Features are the important habitats, species, processes, activities, and
- discrete areas that you want to consider in your planning process. Common
- feature data formats are range maps, polygons, abundances, and continuous scale
- or probability of occurrence maps (e.g. 0-1). Features can include more than
- just ecological data but also be cultural and socio-economic areas like
- community fishing grounds or traditional-use areas, and other human activities
- and industries. Every feature must have a minimum target amount set. Some
- examples include:
-
-
-
-
-
- >
-
-
-
-
- Upload
-
-
-
-
+
-
-
-
+ {displayBulkActions && }
+
);
};
diff --git a/app/layout/project/sidebar/project/inventory-panel/features/info/index.tsx b/app/layout/project/sidebar/project/inventory-panel/features/info/index.tsx
new file mode 100644
index 0000000000..2b3160e600
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/features/info/index.tsx
@@ -0,0 +1,27 @@
+import FEATURE_ABUND_IMG from 'images/info-buttons/img_abundance_data.png';
+import FEATURE_SOCIAL_IMG from 'images/info-buttons/img_social_uses.png';
+import FEATURE_SPECIES_IMG from 'images/info-buttons/img_species_range.png';
+
+const FeaturesInfo = (): JSX.Element => {
+ return (
+ <>
+
What are features?
+
+
+ Features are the important habitats, species, processes, activities, and discrete areas
+ that you want to consider in your planning process. Common feature data formats are range
+ maps, polygons, abundances, and continuous scale or probability of occurrence maps (e.g.
+ 0-1). Features can include more than just ecological data but also be cultural and
+ socio-economic areas like community fishing grounds or traditional-use areas, and other
+ human activities and industries. Every feature must have a minimum target amount set. Some
+ examples include:
+
+
+
+
+
+ >
+ );
+};
+
+export default FeaturesInfo;
diff --git a/app/layout/project/sidebar/project/inventory-panel/features/list/index.tsx b/app/layout/project/sidebar/project/inventory-panel/features/list/index.tsx
deleted file mode 100644
index 34218148fa..0000000000
--- a/app/layout/project/sidebar/project/inventory-panel/features/list/index.tsx
+++ /dev/null
@@ -1,204 +0,0 @@
-import { useCallback, useState, ChangeEvent, useEffect } from 'react';
-
-import { useRouter } from 'next/router';
-
-import { useAppDispatch, useAppSelector } from 'store/hooks';
-import { setSelectedFeatures as setVisibleFeatures } from 'store/slices/projects/[id]';
-
-import { ArrowDown, ArrowUp } from 'lucide-react';
-
-import { useAllFeatures } from 'hooks/features';
-
-import Checkbox from 'components/forms/checkbox';
-import Loading from 'components/loading';
-import { Feature } from 'types/api/feature';
-import { cn } from 'utils/cn';
-
-import FeaturesBulkActionMenu from '../bulk-action-menu';
-
-import FeatureItemList from './item';
-
-export const ProjectFeatureList = (): JSX.Element => {
- const dispatch = useAppDispatch();
- const { selectedFeatures: visibleFeatures, search } = useAppSelector(
- (state) => state['/projects/[id]']
- );
-
- const [filters, setFilters] = useState
[1]>({
- sort: 'featureClassName',
- });
- const [selectedFeaturesIds, setSelectedFeaturesIds] = useState([]);
- const { query } = useRouter();
- const { pid } = query as { pid: string };
- const allFeaturesQuery = useAllFeatures(
- pid,
- {
- ...filters,
- search,
- },
- {
- select: ({ data }) => data,
- placeholderData: { data: [] },
- }
- );
- const featureIds = allFeaturesQuery.data?.map((feature) => feature.id);
-
- const handleSelectAll = useCallback(
- (evt: ChangeEvent) => {
- setSelectedFeaturesIds(evt.target.checked ? featureIds : []);
- },
- [featureIds]
- );
-
- const handleSelectFeature = useCallback((evt: ChangeEvent) => {
- if (evt.target.checked) {
- setSelectedFeaturesIds((prevSelectedFeatures) => [...prevSelectedFeatures, evt.target.value]);
- } else {
- setSelectedFeaturesIds((prevSelectedFeatures) =>
- prevSelectedFeatures.filter((featureId) => featureId !== evt.target.value)
- );
- }
- }, []);
-
- const handleSort = useCallback(
- (_sortType: (typeof filters)['sort']) => {
- const sort = filters.sort === _sortType ? `-${_sortType}` : _sortType;
-
- setFilters((prevFilters) => ({
- ...prevFilters,
- sort,
- }));
- },
- [filters.sort]
- );
-
- const toggleSeeOnMap = useCallback(
- (featureId: Feature['id']) => {
- const newSelectedFeatures = [...visibleFeatures];
-
- if (!newSelectedFeatures.includes(featureId)) {
- newSelectedFeatures.push(featureId);
- } else {
- const i = newSelectedFeatures.indexOf(featureId);
- newSelectedFeatures.splice(i, 1);
- }
- dispatch(setVisibleFeatures(newSelectedFeatures));
- },
- [dispatch, visibleFeatures]
- );
-
- useEffect(() => {
- setSelectedFeaturesIds([]);
- }, [search]);
-
- return (
-
-
-
-
-
handleSort('featureClassName')}
- >
-
- Name
-
- {filters.sort === 'featureClassName' ? (
-
- ) : (
-
- )}
-
-
-
-
handleSort('tag')}
- >
-
- Type
-
- {filters.sort === 'tag' ? (
-
- ) : (
-
- )}
-
-
-
-
- {allFeaturesQuery.isFetching && (
-
-
-
- )}
-
- {!allFeaturesQuery.data?.length && allFeaturesQuery.isFetching === false && (
-
No features found.
- )}
- {!allFeaturesQuery.isFetching && (
-
0,
- })}
- >
-
- {allFeaturesQuery.data?.map((feature) => (
-
- toggleSeeOnMap(feature.id)}
- isShown={visibleFeatures.includes(feature.id)}
- />
-
- ))}
-
-
- {selectedFeaturesIds.length > 0 && (
-
- )}
-
- )}
-
-
- );
-};
-
-export default ProjectFeatureList;
diff --git a/app/layout/project/sidebar/project/inventory-panel/features/list/item/index.tsx b/app/layout/project/sidebar/project/inventory-panel/features/list/item/index.tsx
deleted file mode 100644
index a7ad4068fc..0000000000
--- a/app/layout/project/sidebar/project/inventory-panel/features/list/item/index.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import { useCallback, useState, ChangeEvent, useRef, InputHTMLAttributes } from 'react';
-
-import { useQueryClient } from 'react-query';
-
-import { MoreHorizontal } from 'lucide-react';
-
-import { useEditFeature } from 'hooks/features';
-import { useToasts } from 'hooks/toast';
-
-import Checkbox from 'components/forms/checkbox';
-import Icon from 'components/icon';
-import { Popover, PopoverContent, PopoverTrigger } from 'components/popover';
-import Tag from 'components/tag';
-import { Feature } from 'types/api/feature';
-import { Project } from 'types/api/project';
-import { cn } from 'utils/cn';
-
-import HIDE_SVG from 'svgs/ui/hide.svg?sprite';
-import SHOW_SVG from 'svgs/ui/show.svg?sprite';
-
-import FeatureActions from './actions';
-
-const FeatureItemList = ({
- feature,
- projectId,
- isSelected,
- onSelectFeature,
- toggleSeeOnMap,
- isShown,
-}: {
- feature: Feature;
- projectId: Project['id'];
- isSelected: boolean;
- onSelectFeature: (evt: ChangeEvent) => void;
- toggleSeeOnMap: () => void;
- isShown: boolean;
-}): JSX.Element => {
- const queryClient = useQueryClient();
- const { addToast } = useToasts();
-
- const [isEditable, setEditable] = useState(false);
- const nameInputRef = useRef(null);
- const { mutate: editFeature } = useEditFeature();
-
- const handleRename = useCallback(() => {
- setEditable(true);
- nameInputRef.current?.focus();
- }, [nameInputRef]);
-
- const handleNameChanges = useCallback(
- (evt: Parameters['onKeyDown']>[0]) => {
- if (evt.key === 'Enter') {
- setEditable(false);
- nameInputRef.current?.blur();
-
- editFeature(
- {
- fid: feature.id,
- body: {
- featureClassName: evt.currentTarget.value,
- },
- },
- {
- onSuccess: async () => {
- await queryClient.invalidateQueries(['all-features', projectId]);
-
- addToast(
- 'edit-project-features',
- <>
- Success
- The feature was updated successfully.
- >,
- {
- level: 'success',
- }
- );
- },
- onError: () => {
- addToast(
- 'edit-project-features',
- <>
- Error
- Something went wrong editing the feature.
- >,
- {
- level: 'error',
- }
- );
- },
- }
- );
- }
- },
- [nameInputRef, projectId, feature.id, editFeature, addToast, queryClient]
- );
-
- return (
- <>
-
-
-
-
- {Boolean(feature.scenarioUsageCount) && (
-
- Currently in use in
-
- {feature.scenarioUsageCount}
-
- scenarios.
-
- )}
-
-
-
- {feature.tag && (
-
- {feature.tag}
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-export default FeatureItemList;
diff --git a/app/layout/project/sidebar/project/inventory-panel/features/modals/edit-bulk/index.tsx b/app/layout/project/sidebar/project/inventory-panel/features/modals/edit-bulk/index.tsx
index 3b9b66ca29..33b30b7824 100644
--- a/app/layout/project/sidebar/project/inventory-panel/features/modals/edit-bulk/index.tsx
+++ b/app/layout/project/sidebar/project/inventory-panel/features/modals/edit-bulk/index.tsx
@@ -216,7 +216,7 @@ const EditBulkModal = ({
{values.tag && tagIsDone && (
-
{values.tag}
+
{values.tag}
{
+ const dispatch = useAppDispatch();
+ const { query } = useRouter();
+ const { tab } = query as { tab: NavigationInventoryTabs };
+
+ const panel = INVENTORY_TABS[tab] as InventoryPanel;
+
+ const [isOpenUploader, setOpenUploader] = useState(false);
+
+ // Ensure uploader modals are closed if we change panels
+ useEffect(() => {
+ setOpenUploader(false);
+ }, [panel]);
+
+ // Handle upload modals
+ const handleUploader = useCallback(() => {
+ setOpenUploader(true);
+ }, []);
+
+ const closeUploadModal = useCallback(() => {
+ setOpenUploader(false);
+ }, []);
+
+ // Handle search
+ const handleSearch = useCallback(
+ (value: Parameters[0]) => {
+ dispatch(setSearch(value));
+ },
+ [dispatch]
+ );
+
+ // Shouldn't happen that the panel/tab doesn't exist, but in case it happens let's
+ // add a guard to prevent the app from crashing.
+ if (!panel) return null;
+
+ const {
+ title,
+ search,
+ noData,
+ InfoComponent,
+ UploadModalComponent,
+ TableComponent,
+ FooterComponent,
+ } = panel;
+
+ return (
+
+
+
+ {TableComponent && }
+ {/* TODO: Upload modal won't be optional; currently checking their existence only for development purposes */}
+ {UploadModalComponent && (
+
+ )}
+ {/* Not all panels have a FooterComponent */}
+ {FooterComponent && }
+
+ );
+};
+
+export default InventoryPanel;
diff --git a/app/layout/project/sidebar/project/inventory-panel/protected-areas/footer/index.tsx b/app/layout/project/sidebar/project/inventory-panel/protected-areas/footer/index.tsx
new file mode 100644
index 0000000000..081684cc67
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/protected-areas/footer/index.tsx
@@ -0,0 +1,24 @@
+const ProtectedAreasFooter = (): JSX.Element => {
+ return (
+
+
+ UNEP-WCMC and IUCN (2022), Protected Planet: The World Database on Protected Areas (WDPA)
+ [On-line], [05/2022], Cambridge, UK: UNEP-WCMC and IUCN.
+
+
+
+ Available at:{' '}
+
+ www.protectedplanet.net
+
+
+
+ );
+};
+
+export default ProtectedAreasFooter;
diff --git a/app/layout/project/sidebar/project/inventory-panel/protected-areas/index.tsx b/app/layout/project/sidebar/project/inventory-panel/protected-areas/index.tsx
index 08698c3301..c564d99d16 100644
--- a/app/layout/project/sidebar/project/inventory-panel/protected-areas/index.tsx
+++ b/app/layout/project/sidebar/project/inventory-panel/protected-areas/index.tsx
@@ -1,7 +1,9 @@
-import Section from 'layout/section';
-
-const InventoryPanelProtectedAreas = (): JSX.Element => {
- return InventoryPanelProtectedAreas ;
+const InventoryPanelProtectedAreas = ({
+ noData: noDataMessage,
+}: {
+ noData: string;
+}): JSX.Element => {
+ return {noDataMessage}
;
};
export default InventoryPanelProtectedAreas;
diff --git a/app/layout/project/sidebar/project/inventory-panel/types.ts b/app/layout/project/sidebar/project/inventory-panel/types.ts
new file mode 100644
index 0000000000..644c32bccd
--- /dev/null
+++ b/app/layout/project/sidebar/project/inventory-panel/types.ts
@@ -0,0 +1,17 @@
+export type InventoryPanel = {
+ title: string;
+ search: string;
+ noData: string;
+ InfoComponent?: () => JSX.Element;
+ // TODO: Remove optional when we have upload modals for all tabs
+ UploadModalComponent?: ({
+ isOpen,
+ onDismiss,
+ }: {
+ isOpen?: boolean;
+ onDismiss: () => void;
+ }) => JSX.Element;
+ // TODO: Remove optional when we have table components for all tabs
+ TableComponent?: (props) => JSX.Element;
+ FooterComponent?: () => JSX.Element;
+};
diff --git a/app/layout/sidebar/constants.ts b/app/layout/sidebar/constants.ts
deleted file mode 100644
index 50ead6fd76..0000000000
--- a/app/layout/sidebar/constants.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { SidebarTreeCategories } from './types';
-
-export const SIDEBAR_TREE = {
- user: [],
- inventory: ['protected-areas', 'cost-surface', 'features'],
- gridSetup: ['protected-areas', 'cost-surface', 'planning-unit-status'],
- solutions: ['solutions', 'target-achievement'],
- advancedSettings: ['advanced-settings', 'blm-calibration'],
-} satisfies { [key in SidebarTreeCategories]: string[] };
-
-export const MENU_COMMON_CLASSES = 'flex flex-col items-center space-y-2';
-export const MENU_ITEM_COMMON_CLASSES =
- 'flex group rounded-xl cursor-pointer bg-transparent transition-colors first:mt-2';
-
-export const MENU_ITEM_ACTIVE_CLASSES =
- 'group/active bg-primary-400 border-primary-400 hover:border-primary-400';
-
-export const ICONS_COMMON_CLASSES =
- 'h-5 w-5 text-gray-500 group-hover:text-white group-hover/active:!text-gray-500';
-
-export const MENU_ITEM_BUTTON_COMMON_CLASSES = 'flex p-[10px]';
diff --git a/app/layout/sidebar/hooks.tsx b/app/layout/sidebar/hooks.tsx
deleted file mode 100644
index 4e9d3cdb98..0000000000
--- a/app/layout/sidebar/hooks.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { useRouter } from 'next/router';
-
-import COST_SURFACE_SVG from 'svgs/sidebar/cost-surface.svg?sprite';
-import FEATURES_SVG from 'svgs/sidebar/features.svg?sprite';
-import PLANNING_UNIT_STATUS_SVG from 'svgs/sidebar/planning-unit-status.svg?sprite';
-import PROTECTED_AREA_SVG from 'svgs/sidebar/protected-area.svg?sprite';
-
-import type { SubMenuItem } from './submenu';
-
-const SCENARIO_ROUTE = '/projects/[pid]/scenarios/[sid]/edit';
-
-export const useInventoryItems = (): SubMenuItem[] => {
- const { query, route } = useRouter();
- const { pid, tab } = query as { pid: string; tab: string };
- const isProjectRoute = route === '/projects/[pid]';
-
- return [
- {
- name: 'Protected areas',
- route: `/projects/${pid}?tab=protected-areas`,
- icon: PROTECTED_AREA_SVG,
- selected: isProjectRoute && tab === 'protected-areas',
- },
- {
- name: 'Cost surface',
- route: `/projects/${pid}?tab=cost-surface`,
- icon: COST_SURFACE_SVG,
- selected: isProjectRoute && tab === 'cost-surface',
- },
-
- {
- name: 'Features',
- route: `/projects/${pid}?tab=features`,
- icon: FEATURES_SVG,
- selected: isProjectRoute && tab === 'features',
- },
- ];
-};
-
-export const useGridSetupItems = (): SubMenuItem[] => {
- const { query, route } = useRouter();
- const { pid, sid, tab } = query as { pid: string; sid: string; tab: string };
- const isScenarioRoute = route === SCENARIO_ROUTE;
-
- return [
- {
- name: 'Protected areas',
- route: `/projects/${pid}/scenarios/${sid}/edit?tab=protected-areas`,
- icon: PROTECTED_AREA_SVG,
- selected: isScenarioRoute && tab === 'protected-areas',
- },
- {
- name: 'Cost Surface',
- route: `/projects/${pid}/scenarios/${sid}/edit?tab=cost-surface`,
- icon: COST_SURFACE_SVG,
- selected: isScenarioRoute && tab === 'cost-surface',
- },
- {
- name: 'Planning unit status',
- route: `/projects/${pid}/scenarios/${sid}/edit?tab=planning-unit-status`,
- icon: PLANNING_UNIT_STATUS_SVG,
- selected: isScenarioRoute && tab === 'planning-unit-status',
- },
- {
- name: 'Features',
- route: `/projects/${pid}/scenarios/${sid}/edit?tab=features`,
- icon: PROTECTED_AREA_SVG,
- selected: isScenarioRoute && tab === 'features',
- },
- ];
-};
-
-export const useSolutionItems = (): SubMenuItem[] => {
- const { query, route } = useRouter();
- const { pid, sid, tab } = query as { pid: string; sid: string; tab: string };
- const isScenarioRoute = route === SCENARIO_ROUTE;
-
- return [
- {
- name: 'Overview',
- route: `/projects/${pid}/scenarios/${sid}/edit?tab=solutions`,
- icon: PROTECTED_AREA_SVG,
- selected: isScenarioRoute && tab === 'solutions',
- },
- {
- name: 'Target achievement',
- route: `/projects/${pid}/scenarios/${sid}/edit?tab=target-achievement`,
- icon: PROTECTED_AREA_SVG,
- selected: isScenarioRoute && tab === 'target-achievement',
- },
- ];
-};
-
-export const useAdvancedSettingsItems = (): SubMenuItem[] => {
- const { query, route } = useRouter();
- const { pid, sid, tab } = query as { pid: string; sid: string; tab: string };
- const isScenarioRoute = route === SCENARIO_ROUTE;
-
- return [
- {
- name: 'Overview',
- route: `/projects/${pid}/scenarios/${sid}/edit?tab=advanced-settings`,
- icon: PROTECTED_AREA_SVG,
- selected: isScenarioRoute && tab === 'advanced-settings',
- },
- {
- name: 'BLM calibration',
- route: `/projects/${pid}/scenarios/${sid}/edit?tab=blm-calibration`,
- icon: PROTECTED_AREA_SVG,
- selected: isScenarioRoute && tab === 'blm-calibration',
- },
- ];
-};
diff --git a/app/layout/sidebar/index.tsx b/app/layout/sidebar/index.tsx
deleted file mode 100644
index a83291f674..0000000000
--- a/app/layout/sidebar/index.tsx
+++ /dev/null
@@ -1,295 +0,0 @@
-import { PropsWithChildren, useCallback, useState } from 'react';
-
-import Image from 'next/image';
-import Link from 'next/link';
-import { useRouter } from 'next/router';
-
-import { TippyProps } from '@tippyjs/react/headless';
-
-import Icon from 'components/icon';
-import { Popover, PopoverContent, PopoverTrigger } from 'components/popover';
-import Tooltip from 'components/tooltip';
-import { cn } from 'utils/cn';
-
-import ADVANCED_SETTINGS_SVG from 'svgs/sidebar/advanced-settings.svg?sprite';
-import GRID_SETUP_SVG from 'svgs/sidebar/grid-setup.svg?sprite';
-import INVENTORY_SVG from 'svgs/sidebar/inventory.svg?sprite';
-import WHITE_LOGO_SVG from 'svgs/sidebar/logo-white.svg';
-import MENU_SVG from 'svgs/sidebar/menu.svg?sprite';
-import RUN_SCENARIO_SVG from 'svgs/sidebar/run-scenario.svg?sprite';
-import SCENARIO_LIST_SVG from 'svgs/sidebar/scenario-list.svg?sprite';
-import SOLUTIONS_SVG from 'svgs/sidebar/solutions.svg?sprite';
-
-import {
- MENU_COMMON_CLASSES,
- MENU_ITEM_COMMON_CLASSES,
- MENU_ITEM_ACTIVE_CLASSES,
- MENU_ITEM_BUTTON_COMMON_CLASSES,
- ICONS_COMMON_CLASSES,
- SIDEBAR_TREE,
-} from './constants';
-import {
- useInventoryItems,
- useGridSetupItems,
- useSolutionItems,
- useAdvancedSettingsItems,
-} from './hooks';
-import SubMenu from './submenu';
-import type { SidebarTreeCategories } from './types';
-import UserMenu from './user-menu';
-
-export const MenuTooltip = ({ children }: PropsWithChildren): JSX.Element => {
- return (
-
- {children}
-
- );
-};
-
-export const TOOLTIP_OFFSET: TippyProps['offset'] = [0, 10];
-
-export const Sidebar = (): JSX.Element => {
- const { query, route } = useRouter();
- const { pid, sid, tab } = query as { pid: string; sid: string; tab: string };
-
- const isProjectRoute = route === '/projects/[pid]';
- const isScenarioRoute = route === '/projects/[pid]/scenarios/[sid]/edit';
-
- const [submenuState, setSubmenuState] = useState<{ [key in SidebarTreeCategories]: boolean }>({
- user: false,
- inventory: isProjectRoute && SIDEBAR_TREE.inventory.includes(tab),
- gridSetup: isScenarioRoute && SIDEBAR_TREE.gridSetup.includes(tab),
- solutions: isScenarioRoute && SIDEBAR_TREE.solutions.includes(tab),
- advancedSettings: isScenarioRoute && SIDEBAR_TREE.advancedSettings.includes(tab),
- });
-
- const inventoryItems = useInventoryItems();
- const gridSetupItems = useGridSetupItems();
- const solutionsItems = useSolutionItems();
- const advancedSettingsItems = useAdvancedSettingsItems();
-
- const toggleSubmenu = useCallback((submenuKey: SidebarTreeCategories) => {
- if (submenuKey === 'user') {
- return setSubmenuState((prevState) => ({
- ...prevState,
- [submenuKey]: !prevState[submenuKey],
- }));
- }
-
- return setSubmenuState((prevState) => {
- return Object.keys(prevState).reduce(
- (acc, key) => ({
- ...acc,
- [key]: key === submenuKey ? !prevState[key] : false,
- }),
- prevState
- );
- });
- }, []);
-
- const handleRunScenario = useCallback(() => {
- // todo: define run scenario button behaviour
- console.log('handleRunScenario', sid);
- }, [sid]);
-
- return (
-
-
-
-
-
-
- {/* // ? Common menu */}
-
-
-
- Menu}
- >
-
-
- toggleSubmenu('user')}
- >
-
-
-
- toggleSubmenu('user')}
- >
-
-
-
-
-
-
-
- {/* // ? Project menu */}
- {(isProjectRoute || isScenarioRoute) && (
-
-
- Inventory}
- >
- toggleSubmenu('inventory')}
- >
-
-
-
-
- {submenuState.inventory && (
-
-
-
- )}
-
- Scenario}
- >
-
-
-
-
-
-
- )}
- {/* // ? Scenario menu */}
- {isScenarioRoute && (
-
-
- Grid setup}
- >
- toggleSubmenu('gridSetup')}
- >
-
-
-
-
- {submenuState.gridSetup && (
-
-
-
- )}
-
- Advanced settings}
- >
- toggleSubmenu('advancedSettings')}
- >
-
-
-
-
- {submenuState.advancedSettings && (
-
-
-
- )}
-
- Solutions}
- >
- toggleSubmenu('solutions')}
- >
-
-
-
-
- {submenuState.solutions && (
-
-
-
- )}
-
- )}
-
-
- {sid && (
-
-
-
-
-
- Run scenario
-
-
- )}
-
- );
-};
-
-export default Sidebar;
diff --git a/app/layout/sidebar/submenu/index.tsx b/app/layout/sidebar/submenu/index.tsx
deleted file mode 100644
index 052cbea826..0000000000
--- a/app/layout/sidebar/submenu/index.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import Link from 'next/link';
-
-import { cn } from 'utils/cn';
-
-import Icon, { IconProps } from 'components/icon';
-import Tooltip from 'components/tooltip';
-
-import { MenuTooltip, TOOLTIP_OFFSET } from '../';
-
-export interface SubMenuItem {
- name: string;
- icon: IconProps['icon'];
- route: string;
- selected: boolean;
-}
-
-export const SubMenu = ({ items }: { items: SubMenuItem[] }): JSX.Element => {
- return (
-
- {items.map((item) => (
- {item.name}}
- >
-
-
-
-
-
-
- ))}
-
- );
-};
-
-export default SubMenu;
diff --git a/app/layout/sidebar/types.ts b/app/layout/sidebar/types.ts
deleted file mode 100644
index c1877a3ee4..0000000000
--- a/app/layout/sidebar/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export type SidebarTreeCategories =
- | 'user'
- | 'inventory'
- | 'gridSetup'
- | 'solutions'
- | 'advancedSettings';
diff --git a/app/layout/sidebar/user-menu/constants.ts b/app/layout/sidebar/user-menu/constants.ts
deleted file mode 100644
index fdd86b29fc..0000000000
--- a/app/layout/sidebar/user-menu/constants.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const ITEM_COMMON_CLASSES = 'flex items-center justify-between rounded-3xl bg-gray-50 p-4';
-export const ITEM_TITLE_COMMON_CLASSES = 'flex items-center space-x-2 font-medium text-black';
-export const ITEM_DESCRIPTION_COMMON_CLASSES = 'text-gray-400 leading-normal';
diff --git a/app/layout/sidebar/user-menu/index.tsx b/app/layout/sidebar/user-menu/index.tsx
deleted file mode 100644
index 5e3be10301..0000000000
--- a/app/layout/sidebar/user-menu/index.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-import { useCallback } from 'react';
-
-import { useQuery } from 'react-query';
-
-import Link from 'next/link';
-
-import axios from 'axios';
-import { signOut, useSession } from 'next-auth/react';
-import { usePlausible } from 'next-plausible';
-
-import { useHelp } from 'hooks/help';
-import { useMe } from 'hooks/me';
-import { COLOR_ME } from 'hooks/project-users';
-
-import Avatar from 'components/avatar';
-import Button from 'components/button';
-import { Switch } from 'components/forms/switch';
-import Icon from 'components/icon';
-import { cn } from 'utils/cn';
-
-import HELP_GUIDE_SVG from 'svgs/sidebar/help-guide.svg?sprite';
-import EDIT_PROFILE_SVG from 'svgs/sidebar/pencil.svg?sprite';
-import DOCUMENTATION_SVG from 'svgs/ui/documentation.svg?sprite';
-import SIGN_OUT_SVG from 'svgs/ui/sign-out.svg?sprite';
-
-import {
- ITEM_COMMON_CLASSES,
- ITEM_TITLE_COMMON_CLASSES,
- ITEM_DESCRIPTION_COMMON_CLASSES,
-} from './constants';
-
-export const UserMenu = (): JSX.Element => {
- const { data: session } = useSession();
- const { user } = useMe();
- const { active, onActive } = useHelp();
- const plausible = usePlausible();
-
- const { data: totalProjects } = useQuery(['user-total-projects', user.id], {
- queryFn: () =>
- axios
- .get(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/projects`, {
- headers: {
- Authorization: `Bearer ${session.accessToken}`,
- },
- })
- .then((response) => response.data),
- enabled: Boolean(session),
- select: ({ meta }) => meta.totalItems,
- });
-
- const handleSignOut = useCallback(async () => {
- await signOut();
- }, []);
-
- const onToggleHelpGuide = useCallback(() => {
- onActive(!active);
- if (active) {
- plausible('Activate help guide', {
- props: {
- userId: `${user.id}`,
- userEmail: `${user.email}`,
- },
- });
- }
- }, [active, onActive, plausible, user.id, user.email]);
-
- return (
-
-
-
-
- {!user.avatar && user.displayName.slice(0, 2).toUpperCase()}
-
-
-
-
-
{user.displayName}
- {user.email}
-
-
-
-
- Edit profile
-
-
-
- Log out
-
-
-
-
-
-
- );
-};
-
-export default UserMenu;
diff --git a/app/pages/projects/[pid]/index.tsx b/app/pages/projects/[pid]/index.tsx
index b4a76068a9..a8389d9596 100644
--- a/app/pages/projects/[pid]/index.tsx
+++ b/app/pages/projects/[pid]/index.tsx
@@ -8,9 +8,7 @@ import ProjectLayout from 'layout/project';
import Breadcrumbs from 'layout/project/navigation/breadcrumbs';
import Sidebar from 'layout/project/sidebar';
import InventoryProjectHeader from 'layout/project/sidebar/project/header';
-import InventoryPanelCostSurface from 'layout/project/sidebar/project/inventory-panel/cost-surface';
-import InventoryPanelFeatures from 'layout/project/sidebar/project/inventory-panel/features';
-import InventoryPanelProtectedAreas from 'layout/project/sidebar/project/inventory-panel/protected-areas';
+import InventoryPanel from 'layout/project/sidebar/project/inventory-panel';
import ScenariosList from 'layout/project/sidebar/project/scenarios-list';
import ProjectMap from 'layout/projects/show/map';
import ProjectStatus from 'layout/projects/show/status';
@@ -30,12 +28,8 @@ const ShowProjectsPage = (): JSX.Element => {
-
-
- {tab === 'features' && }
- {tab === 'protected-areas' && }
- {tab === 'cost-surface' && }
+ {tab && }
{!tab && }
diff --git a/app/svgs/ui/edit.svg b/app/svgs/ui/edit.svg
new file mode 100644
index 0000000000..2ce49b14ad
--- /dev/null
+++ b/app/svgs/ui/edit.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/svgs/ui/tag.svg b/app/svgs/ui/tag.svg
new file mode 100644
index 0000000000..a8f174ebd0
--- /dev/null
+++ b/app/svgs/ui/tag.svg
@@ -0,0 +1,3 @@
+
+
+