diff --git a/src/admin/components/ResourceTabs/MonitoredTab/MonitoredTab.tsx b/src/admin/components/ResourceTabs/MonitoredTab/MonitoredTab.tsx index b95842a46..c9fef6a55 100644 --- a/src/admin/components/ResourceTabs/MonitoredTab/MonitoredTab.tsx +++ b/src/admin/components/ResourceTabs/MonitoredTab/MonitoredTab.tsx @@ -1,19 +1,22 @@ import { FC } from "react"; import { TabbedShowLayout, TabProps } from "react-admin"; +import { EntityName } from "@/types/common"; + import DataCard from "./components/DataCard"; import HeaderMonitoredTab from "./components/HeaderMonitoredTab"; interface IProps extends Omit { label?: string; + type: EntityName; } -const MonitoredTab: FC = ({ label, ...rest }) => { +const MonitoredTab: FC = ({ label, type, ...rest }) => { return (
- - + +
); diff --git a/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx b/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx index f7842e01f..0767c613f 100644 --- a/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx +++ b/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx @@ -1,8 +1,13 @@ import { ColumnDef, RowData } from "@tanstack/react-table"; +import { useT } from "@transifex/react"; import classNames from "classnames"; +import { format } from "date-fns"; import React, { useEffect, useState } from "react"; -import { Else, If, Then, When } from "react-if"; +import { useBasename, useShowContext } from "react-admin"; +import { When } from "react-if"; +import { useNavigate } from "react-router-dom"; +import ExportProcessingAlert from "@/admin/components/Alerts/ExportProcessingAlert"; import CustomChipField from "@/admin/components/Fields/CustomChipField"; import Button from "@/components/elements/Button/Button"; import Dropdown from "@/components/elements/Inputs/Dropdown/Dropdown"; @@ -18,9 +23,28 @@ import Toggle, { TogglePropsItem } from "@/components/elements/Toggle/Toggle"; import Tooltip from "@/components/elements/Tooltip/Tooltip"; import TooltipMapMonitoring from "@/components/elements/TooltipMap/TooltipMapMonitoring"; import Icon, { IconNames } from "@/components/extensive/Icon/Icon"; -import { DUMMY_DATA_TARGET_LAND_USE_TYPES_REPRESENTED } from "@/constants/dashboardConsts"; +import { + CHART_TYPES, + DEFAULT_POLYGONS_DATA, + DEFAULT_POLYGONS_DATA_STRATEGIES, + DUMMY_DATA_FOR_CHART_SIMPLE_BAR_CHART +} from "@/constants/dashboardConsts"; +import { useMonitoredDataContext } from "@/context/monitoredData.provider"; +import { useNotificationContext } from "@/context/notification.provider"; +import { fetchGetV2IndicatorsEntityUuidSlugExport } from "@/generated/apiComponents"; +import SimpleBarChart from "@/pages/dashboard/charts/SimpleBarChart"; import GraphicIconDashboard from "@/pages/dashboard/components/GraphicIconDashboard"; -import { OptionValue } from "@/types/common"; +import SecDashboard from "@/pages/dashboard/components/SecDashboard"; +import { TOTAL_HECTARES_UNDER_RESTORATION_TOOLTIP } from "@/pages/dashboard/constants/tooltips"; +import { EntityName, OptionValue } from "@/types/common"; +import { + isEmptyChartData, + parsePolygonsIndicatorDataForLandUse, + parsePolygonsIndicatorDataForStrategies +} from "@/utils/dashboardUtils"; +import { downloadFileBlob } from "@/utils/network"; + +import { useMonitoredData } from "../hooks/useMonitoredData"; interface TableData { polygonName: string; @@ -50,10 +74,344 @@ export interface DataStructure extends React.HTMLAttributes { tableData: TableData[]; } -const DataCard = ({ ...rest }: React.HTMLAttributes) => { +const COMMON_COLUMNS: ColumnDef[] = [ + { accessorKey: "poly_name", header: "Polygon Name", meta: { style: { width: "13.30%" } } }, + { + accessorKey: "size", + header: "Size (ha)", + meta: { style: { width: "9.01%" } } + }, + { accessorKey: "site_name", header: "Site Name", meta: { style: { width: "9.90%" } } }, + { + accessorKey: "status", + header: "Status", + cell: (props: any) => ( + + ), + meta: { style: { width: "7.65%" } } + }, + { + accessorKey: "plantstart", + header: "Plant Start Date", + cell: (props: any) => { + const value = props.getValue(); + return format(new Date(value), "dd/MM/yyyy"); + }, + meta: { style: { width: "13.65%" } } + }, + { + accessorKey: "base_line", + header: "Baseline", + cell: (props: any) => { + const value = props.getValue(); + return format(new Date(value), "dd/MM/yyyy"); + }, + meta: { style: { width: "8.87%" } } + } +]; + +type CustomColumnDefInternal = ColumnDef & { type?: string }; + +const TABLE_COLUMNS_HECTARES_STRATEGY: ColumnDef[] = [ + ...COMMON_COLUMNS, + { + accessorKey: "data.tree_planting", + header: "Tree Planting", + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "11.95%" } } + }, + { + accessorKey: "data.assisted_natural_regeneration", + header: () => ( + <> + Asst. Nat. +
+ Regeneration + + ), + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "12.09%" } } + }, + { + accessorKey: "data.direct_seeding", + header: () => ( + <> + Direct +
+ Seeding + + ), + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "8.57%" } } + }, + { + accessorKey: "more", + header: "", + enableSorting: false, + cell: props => ( +
+ +
+ ), + meta: { style: { width: "5%" } } + } +]; + +const TABLE_COLUMNS_HECTARES_ECO_REGION: ColumnDef[] = [ + ...COMMON_COLUMNS, + { + accessorKey: "data.australasian", + header: "Australasian", + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "11.45%" } } + }, + { + accessorKey: "data.afrotropical", + header: "Afrotropical", + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "11.05%" } } + }, + { + accessorKey: "data.paleartic", + header: "Paleartic11", + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "10.33%" } } + }, + { + accessorKey: "more", + header: "", + enableSorting: false, + cell: props => ( +
+ +
+ ), + meta: { style: { width: "5%" } } + } +]; + +const TABLE_COLUMNS_HECTARES_LAND_USE: ColumnDef[] = [ + ...COMMON_COLUMNS, + { + accessorKey: "data.agroforest", + header: "Agroforest", + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "11.95%" } } + }, + { + accessorKey: "data.natural_forest", + header: "Natural Forest", + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "12.09%" } } + }, + { + accessorKey: "data.mangrove", + header: "Mangrove", + cell: (props: any) => { + const value = props.getValue(); + return value ?? "-"; + }, + meta: { style: { width: "8.57%" } } + }, + { + accessorKey: "more", + header: "", + enableSorting: false, + cell: props => ( +
+ +
+ ), + meta: { style: { width: "5%" } } + } +]; + +const DROPDOWN_OPTIONS = [ + { + title: "Tree Cover Loss", + value: "1", + slug: "treeCoverLoss" + }, + { + title: "Tree Cover Loss from Fire", + value: "2", + slug: "treeCoverLossFires" + }, + { + title: "Hectares Under Restoration By WWF EcoRegion", + value: "3", + slug: "restorationByEcoRegion" + }, + { + title: "Hectares Under Restoration By Strategy", + value: "4", + slug: "restorationByStrategy" + }, + { + title: "Hectares Under Restoration By Target Land Use System", + value: "5", + slug: "restorationByLandUse" + }, + { + title: "Tree Count", + value: "6", + slug: "treeCount" + } +]; + +const toggleItems: TogglePropsItem[] = [ + { + key: "dashboard", + render: ( + + Table + + ) + }, + { + key: "table", + render: ( + + Graph + + ) + }, + { + key: "table", + render: ( + + Map + + ) + } +]; + +const noDataGraph = ( +
+ + No Data to Display + +
+ + RUN ANALYSUS ON PROJECT POLYGONS TO SEE DATA + + + + +
+
+); + +const indicatorDescription1 = + "From the 23 August 2024 analysis, 12.2M out of 20M hectares are being restored. Of those, Direct Seeding was the most prevalent strategy used with more 765,432ha, followed by Tree Planting with 453,89ha and Assisted Natural Regeneration with 93,345ha."; +const indicatorDescription2 = + "The numbers and reports below display data related to Indicator 2: Hectares Under Restoration described in TerraFund’s MRV framework. Please refer to the linked MRV framework for details on how these numbers are sourced and verified."; + +const POLYGONS = [ + { title: "Agrariala Palma", value: "1" }, + { title: "Agraisa", value: "2" }, + { title: "Agrajaya Batitama", value: "3" }, + { title: "Agoue Iboe", value: "4" }, + { title: "Africas", value: "5" }, + { title: "AEK Torup", value: "6" }, + { title: "AEK Raso", value: "7" }, + { title: "AEK Nabara Selatan", value: "8" }, + { title: "Adison Thaochu A", value: "9" }, + { title: "ABA", value: "10" } +]; + +const noDataMap = ( +
+
+
+ + Indicator Description + +
+ + {indicatorDescription1} + + + {indicatorDescription2} + +
+
+
+
+
+ + No Data to Display + +
+ + RUN ANALYSUS ON PROJECT POLYGONS TO SEE DATA + + + + +
+
+
+
+); + +const DataCard = ({ + type, + ...rest +}: React.HTMLAttributes & { + type?: EntityName; +}) => { const [tabActive, setTabActive] = useState(0); const [selected, setSelected] = useState(["1"]); + const basename = useBasename(); const mapFunctions = useMap(); + const { record } = useShowContext(); + const { polygonsIndicator } = useMonitoredData(type!, record.uuid); + const { setSearchTerm, setIndicatorSlug, indicatorSlug, setSelectPolygonFromMap, selectPolygonFromMap } = + useMonitoredDataContext(); + const navigate = useNavigate(); + const { openNotification } = useNotificationContext(); + const [exporting, setExporting] = useState(false); + const t = useT(); + const totalHectaresRestoredGoal = record?.total_hectares_restored_goal + ? Number(record?.total_hectares_restored_goal) + : +record?.hectares_to_restore_goal; + const landUseData = polygonsIndicator + ? parsePolygonsIndicatorDataForLandUse(polygonsIndicator, totalHectaresRestoredGoal) + : DEFAULT_POLYGONS_DATA; + const strategiesData = polygonsIndicator + ? parsePolygonsIndicatorDataForStrategies(polygonsIndicator) + : DEFAULT_POLYGONS_DATA_STRATEGIES; const [topHeaderFirstTable, setTopHeaderFirstTable] = useState("102px"); const [topHeaderSecondTable, setTopHeaderSecondTable] = useState("70px"); @@ -66,36 +424,48 @@ const DataCard = ({ ...rest }: React.HTMLAttributes) => { } }, []); - const TABLE_COLUMNS: ColumnDef[] = [ + useEffect(() => { + if (typeof window !== "undefined") { + const width = window.innerWidth; + setTopHeaderFirstTable(width > 1900 ? "110px" : "106px"); + setTopHeaderSecondTable(width > 1900 ? "77px" : "72px"); + } + }, []); + + const TABLE_COLUMNS_TREE_COVER_LOSS: CustomColumnDefInternal[] = [ { id: "mainInfo", meta: { style: { top: `${topHeaderSecondTable}`, borderBottomWidth: 0, borderRightWidth: 0 } }, header: "", columns: [ { - accessorKey: "polygonName", + accessorKey: "poly_name", header: "Polygon Name", - meta: { style: { top: `${topHeaderFirstTable}`, borderRadius: "0" } } + meta: { style: { top: `${topHeaderFirstTable}`, borderRadius: "0", width: "11%" } } }, { accessorKey: "size", header: "Size (ha)", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "7%" } } + }, + { + accessorKey: "site_name", + header: "Site Name", + meta: { style: { top: `${topHeaderFirstTable}`, width: "8%" } } }, - { accessorKey: "siteName", header: "Site Name", meta: { style: { top: `${topHeaderFirstTable}` } } }, { accessorKey: "status", header: "Status", - meta: { style: { top: `${topHeaderFirstTable}` } }, + meta: { style: { top: `${topHeaderFirstTable}`, width: "7%" } }, cell: (props: any) => ( ) }, { - accessorKey: "plantDate", + accessorKey: "plantstart", header: () => ( <> Plant @@ -103,7 +473,7 @@ const DataCard = ({ ...rest }: React.HTMLAttributes) => { Start Date ), - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "8%" } } } ] }, @@ -113,68 +483,68 @@ const DataCard = ({ ...rest }: React.HTMLAttributes) => { meta: { style: { top: `${topHeaderSecondTable}`, borderBottomWidth: 0 } }, columns: [ { - accessorKey: "2024-2015", + accessorKey: "data.2015", header: "2015", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2016", + accessorKey: "data.2016", header: "2016", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2017", + accessorKey: "data.2017", header: "2017", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2018", + accessorKey: "data.2018", header: "2018", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2019", + accessorKey: "data.2019", header: "2019", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2020", + accessorKey: "data.2020", header: "2020", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2021", + accessorKey: "data.2021", header: "2021", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2022", + accessorKey: "data.2022", header: "2022", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2023", + accessorKey: "data.2023", header: "2023", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } }, { - accessorKey: "2024-2024", + accessorKey: "data.2024", header: "2024", - meta: { style: { top: `${topHeaderFirstTable}` } } + meta: { style: { top: `${topHeaderFirstTable}`, width: "5.4%" } } } ] }, { id: "moreInfo", header: " ", - meta: { style: { top: `${topHeaderSecondTable}`, borderBottomWidth: 0 } }, + meta: { style: { top: `${topHeaderSecondTable}`, borderBottomWidth: 0, width: "5%" } }, columns: [ { accessorKey: "more", header: "", enableSorting: false, cell: props => ( -
+
), @@ -184,698 +554,180 @@ const DataCard = ({ ...rest }: React.HTMLAttributes) => { } ]; - const TABLE_COLUMNS_HECTARES: ColumnDef[] = [ - { accessorKey: "polygonName", header: "Polygon Name" }, - { - accessorKey: "size", - header: "Size (ha)" - }, - { accessorKey: "siteName", header: "Site Name" }, - { - accessorKey: "status", - header: "Status", - cell: (props: any) => ( - - ) - }, - { - accessorKey: "plantDate", - header: "Plant Start Date" - }, - { - accessorKey: "baseline", - header: "Baseline" - }, - { - accessorKey: "treePlanting", - header: "Tree Planting" - }, - { - accessorKey: "regeneration", - header: () => ( - <> - Asst. Nat. -
- Regeneration - - ) - }, - { - accessorKey: "seeding", - header: () => ( - <> - Direct -
- Seeding - - ) - }, - { - accessorKey: "more", - header: "", - enableSorting: false, - cell: props => ( -
- -
- ) - } - ]; + const TABLE_COLUMNS_MAPPING: Record = { + treeCoverLoss: TABLE_COLUMNS_TREE_COVER_LOSS, + treeCoverLossFires: TABLE_COLUMNS_TREE_COVER_LOSS, + restorationByEcoRegion: TABLE_COLUMNS_HECTARES_ECO_REGION, + restorationByStrategy: TABLE_COLUMNS_HECTARES_STRATEGY, + restorationByLandUse: TABLE_COLUMNS_HECTARES_LAND_USE, + treeCount: [] + }; - const TABLE_DATA = [ - { - polygonName: "ABA", - siteName: "Palm Oil", - status: "Draft", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Adison Thaochu A", - siteName: "Palm Oil", - status: "Submitted", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "AEK Nabara Selatan", - siteName: "Palm Oil", - status: "Needs Info", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "AEK Raso", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "AEK Torup", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Africas", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Agoue Iboe", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Agrajaya Baktitama", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Agralsa", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Africas", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Agoue Iboe", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Agrajaya Baktitama", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Agralsa", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "ABA", - siteName: "Palm Oil", - status: "Draft", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "Adison Thaochu A", - siteName: "Palm Oil", - status: "Submitted", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "AEK Nabara Selatan", - siteName: "Palm Oil", - status: "Needs Info", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "AEK Raso", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - }, - { - polygonName: "AEK Torup", - siteName: "Palm Oil", - status: "Approved", - size: "7,473", - plantDate: "9/26/24", - baseline: "25/4/24", - treePlanting: "0.423", - regeneration: "0.120", - seeding: "0.120", - "2024-2015": "0.423", - "2024-2016": "0.120", - "2024-2017": "0.655", - "2024-2018": "0.208", - "2024-2019": "0.654", - "2024-2020": "0.466", - "2024-2021": "0.151", - "2024-2022": "0.385", - "2024-2023": "0.457", - "2024-2024": "0.966" - } - ]; + const handleExport = async () => { + try { + setExporting(true); + const blob = await fetchGetV2IndicatorsEntityUuidSlugExport({ + pathParams: { entity: type!, uuid: record.uuid, slug: indicatorSlug! } + }); + downloadFileBlob(blob!, `Indicator (${DROPDOWN_OPTIONS.find(item => item.slug === indicatorSlug)?.title}).csv`); - const DROPDOWN_OPTIONS = [ - { - title: "Tree Cover Loss", - value: "1" - }, - { - title: "Tree Cover Loss from Fire", - value: "2" - }, - { - title: "Hectares Under Restoration By WWF EcoRegion", - value: "3" - }, - { - title: "Hectares Under Restoration By Strategy", - value: "4" - }, - { - title: "Hectares Under Restoration By Target Land Use System", - value: "5" - }, - { - title: "Tree Count", - value: "6" + openNotification("success", t("Success! Export completed."), t("The export has been completed successfully.")); + setExporting(false); + } catch (error) { + openNotification("error", t("Error! Export failed."), t("The export has failed. Please try again.")); + setExporting(false); + } finally { + setExporting(false); } - ]; + }; - const toggleItems: TogglePropsItem[] = [ - { - key: "dashboard", - render: ( - - Table - - ) - }, - { - key: "table", - render: ( - - Graph - - ) - }, - { - key: "table", - render: ( - - Map - - ) + useEffect(() => { + if (selectPolygonFromMap?.isOpen) { + setSelectPolygonFromMap?.({ isOpen: false, uuid: "" }); } - ]; - - const POLYGONS = [ - { title: "Agrariala Palma", value: "1" }, - { title: "Agraisa", value: "2" }, - { title: "Agrajaya Batitama", value: "3" }, - { title: "Agoue Iboe", value: "4" }, - { title: "Africas", value: "5" }, - { title: "AEK Torup", value: "6" }, - { title: "AEK Raso", value: "7" }, - { title: "AEK Nabara Selatan", value: "8" }, - { title: "Adison Thaochu A", value: "9" }, - { title: "ABA", value: "10" } - ]; - - const indicatorDescription1 = - "From the 23 August 2024 analysis, 12.2M out of 20M hectares are being restored. Of those, Direct Seeding was the most prevalent strategy used with more 765,432ha, followed by Tree Planting with 453,89ha and Assisted Natural Regeneration with 93,345ha."; - const indicatorDescription2 = - "The numbers and reports below display data related to Indicator 2: Hectares Under Restoration described in TerraFund’s MRV framework. Please refer to the linked MRV framework for details on how these numbers are sourced and verified."; - - const noDataGraph = ( -
- - No Data to Display - -
- - RUN ANALYSUS ON PROJECT POLYGONS TO SEE DATA - - - - -
-
- ); - - const noDataMap = ( -
-
-
- - Indicator Description - -
- - {indicatorDescription1} - - - {indicatorDescription2} - -
-
-
-
-
- - No Data to Display - -
- - RUN ANALYSUS ON PROJECT POLYGONS TO SEE DATA - - - - -
-
-
-
- ); - + }, [selectPolygonFromMap]); return ( -
-
-
-
- - { - setSelected(option); - }} - variant={VARIANT_DROPDOWN_SIMPLE} - inputVariant="text-14-semibold" - className="z-50" - defaultValue={[DROPDOWN_OPTIONS[0].value]} - optionsClassName="w-max z-50" - /> -
- -
- - {}} variant={FILTER_SEARCH_MONITORING} /> - - + <> +
+
+
+
+ + { + setIndicatorSlug?.(DROPDOWN_OPTIONS.find(item => item.value === option[0])?.slug!); + setSelected(option); + }} + variant={VARIANT_DROPDOWN_SIMPLE} + inputVariant="text-14-semibold" + className="z-50" + defaultValue={[DROPDOWN_OPTIONS.find(item => item.slug === indicatorSlug)?.value!]} + optionsClassName="w-max z-50" + /> +
- -
-
- -
- - - + + { + setSearchTerm(e); + }} + variant={FILTER_SEARCH_MONITORING} /> - - -
- - + + + + + - - -
- {}} - /> -
- - Indicator Description - -
- - {indicatorDescription1} - - - {indicatorDescription2} + +
+
{ + navigate(`${basename}/site/${row?.site_id}/show/1`); + setSelectPolygonFromMap?.({ isOpen: true, uuid: row?.poly_id }); + }} + /> + + + +
+ {}} + /> +
+ + Indicator Description +
+ + {indicatorDescription1} + + + {indicatorDescription2} + +
+ + + + + + + + + + +
+ {noDataGraph} + + +
+
+ +
+ +
+
+ {noDataGraph}
- - - - - - - - - - - - - -
- + + +
+
+
- - {noDataGraph} -
-
- -
-
- + + {noDataMap}
- - {noDataMap} -
-
+ +
- + + ); }; diff --git a/src/admin/components/ResourceTabs/MonitoredTab/components/HeaderMonitoredTab.tsx b/src/admin/components/ResourceTabs/MonitoredTab/components/HeaderMonitoredTab.tsx index fab6e2d1a..5c0440fe8 100644 --- a/src/admin/components/ResourceTabs/MonitoredTab/components/HeaderMonitoredTab.tsx +++ b/src/admin/components/ResourceTabs/MonitoredTab/components/HeaderMonitoredTab.tsx @@ -1,3 +1,6 @@ +import { useShowContext } from "react-admin"; +import { When } from "react-if"; + import Button from "@/components/elements/Button/Button"; import LinearProgressBarMonitored from "@/components/elements/ProgressBar/LinearProgressBar/LineProgressBarMonitored"; import Text from "@/components/elements/Text/Text"; @@ -6,29 +9,14 @@ import { ModalId } from "@/components/extensive/Modal/ModalConst"; import ModalNotes from "@/components/extensive/Modal/ModalNotes"; import ModalRunAnalysis from "@/components/extensive/Modal/ModalRunAnalysis"; import { useModalContext } from "@/context/modal.provider"; +import { EntityName } from "@/types/common"; -const HeaderMonitoredTab = () => { - const { openModal, closeModal } = useModalContext(); +import { useMonitoredData } from "../hooks/useMonitoredData"; - const dataPolygonOverview = [ - { - status: "Draft", - count: 12.5, - color: "bg-grey-200" - }, - { - status: "Submitted", - count: 42.5 - }, - { - status: "Needs Info", - count: 22.5 - }, - { - status: "Approved", - count: 22.5 - } - ]; +const HeaderMonitoredTab = ({ type }: { type?: EntityName }) => { + const { openModal, closeModal } = useModalContext(); + const { record } = useShowContext(); + const { headerBarPolygonStatus, totalPolygonsStatus, polygonMissingAnalysis } = useMonitoredData(type, record?.uuid); const openRunAnalysis = () => { openModal( @@ -37,6 +25,9 @@ const HeaderMonitoredTab = () => { title="Update Analysis " content="Project Developers may submit one or all polygons for review." primaryButtonText="Run" + projectName={record?.project ? record?.project?.name : record?.name} + entityType={type} + entityUuid={record?.uuid} primaryButtonProps={{ className: "px-8 py-3", variant: "primary", @@ -60,9 +51,9 @@ const HeaderMonitoredTab = () => { ModalId.MODAL_NOTES,
- +
@@ -96,17 +87,19 @@ graphs and tables below by clicking update analysis button to your right. " No. of Polygons - 45 - -
-
- - No. of Sites - - - 12 + {totalPolygonsStatus}
+ +
+ + No. of Sites + + + {record?.project ? record?.project?.total_sites : record?.total_sites} + +
+
diff --git a/src/admin/components/ResourceTabs/MonitoredTab/hooks/useMonitoredData.ts b/src/admin/components/ResourceTabs/MonitoredTab/hooks/useMonitoredData.ts new file mode 100644 index 000000000..9151b9bfb --- /dev/null +++ b/src/admin/components/ResourceTabs/MonitoredTab/hooks/useMonitoredData.ts @@ -0,0 +1,240 @@ +import { useT } from "@transifex/react"; +import { useEffect, useState } from "react"; + +import { ModalId } from "@/components/extensive/Modal/ModalConst"; +import { useModalContext } from "@/context/modal.provider"; +import { useMonitoredDataContext } from "@/context/monitoredData.provider"; +import { useNotificationContext } from "@/context/notification.provider"; +import { + fetchGetV2IndicatorsEntityUuidSlugVerify, + useGetV2IndicatorsEntityUuid, + useGetV2IndicatorsEntityUuidSlug, + useGetV2IndicatorsEntityUuidSlugVerify, + usePostV2IndicatorsSlug +} from "@/generated/apiComponents"; +import { IndicatorPolygonsStatus, Indicators } from "@/generated/apiSchemas"; +import { EntityName } from "@/types/common"; + +const dataPolygonOverview = [ + { + status: "Draft", + status_key: "draft", + count: 12.5, + color: "bg-grey-200" + }, + { + status: "Submitted", + status_key: "submitted", + count: 42.5 + }, + { + status: "Needs Info", + status_key: "needs-more-information", + count: 22.5 + }, + { + status: "Approved", + status_key: "approved", + count: 22.5 + } +]; + +const DROPDOWN_OPTIONS = [ + { + title: "Tree Cover Loss", + value: "1", + slug: "treeCoverLoss" + }, + { + title: "Tree Cover Loss from Fire", + value: "2", + slug: "treeCoverLossFires" + }, + { + title: "Hectares Under Restoration By WWF EcoRegion", + value: "3", + slug: "restorationByEcoRegion" + }, + { + title: "Hectares Under Restoration By Strategy", + value: "4", + slug: "restorationByStrategy" + }, + { + title: "Hectares Under Restoration By Target Land Use System", + value: "5", + slug: "restorationByLandUse" + }, + { + title: "Tree Count", + value: "6", + slug: "treeCount" + } +]; + +const SLUGS_INDICATORS = [ + "treeCoverLoss", + "treeCoverLossFires", + "restorationByEcoRegion", + "restorationByStrategy", + "restorationByLandUse" +]; + +type InterfaceIndicatorPolygonsStatus = { + draft: number; + submitted: number; + "needs-more-information": number; + approved: number; +}; + +export const useMonitoredData = (entity?: EntityName, entity_uuid?: string) => { + const t = useT(); + const { searchTerm, indicatorSlug, setLoadingAnalysis, setIndicatorSlugAnalysis } = useMonitoredDataContext(); + const { modalOpened } = useModalContext(); + const [isLoadingVerify, setIsLoadingVerify] = useState(false); + const { openNotification } = useNotificationContext(); + const [analysisToSlug, setAnalysisToSlug] = useState({ + treeCoverLoss: [], + treeCoverLossFires: [], + restorationByEcoRegion: [], + restorationByStrategy: [], + restorationByLandUse: [], + treeCount: [] + }); + const [dropdownAnalysisOptions, setDropdownAnalysisOptions] = useState(DROPDOWN_OPTIONS); + + const { mutate, isLoading } = usePostV2IndicatorsSlug({ + onSuccess: () => { + openNotification( + "success", + t("Success! Analysis completed."), + t("The analysis has been completed successfully.") + ); + refetchDataIndicators(); + setLoadingAnalysis?.(false); + setIndicatorSlugAnalysis?.("treeCoverLoss"); + }, + onError: () => { + openNotification("error", t("Error! Analysis failed."), t("The analysis has failed. Please try again.")); + refetchDataIndicators(); + setLoadingAnalysis?.(false); + setIndicatorSlugAnalysis?.("treeCoverLoss"); + } + }); + + const { data: indicatorData, refetch: refetchDataIndicators } = useGetV2IndicatorsEntityUuidSlug( + { + pathParams: { + entity: entity!, + uuid: entity_uuid!, + slug: indicatorSlug! + } + }, + { + enabled: !!indicatorSlug || !!entity_uuid + } + ); + + const { data: indicatorPolygonsStatus } = useGetV2IndicatorsEntityUuid( + { + pathParams: { + entity: entity!, + uuid: entity_uuid! + } + }, + { + enabled: !!entity_uuid + } + ); + + const filteredPolygons = indicatorData?.filter( + (polygon: Indicators) => + polygon?.poly_name?.toLowerCase().includes(searchTerm?.toLowerCase()) || + polygon?.site_name?.toLowerCase().includes(searchTerm?.toLowerCase()) + ); + + const headerBarPolygonStatus = dataPolygonOverview.map(status => { + const key = status.status_key as keyof InterfaceIndicatorPolygonsStatus; + return { + ...status, + count: indicatorPolygonsStatus?.[key] ?? 0 + }; + }); + + const totalPolygonsApproved = headerBarPolygonStatus.find(item => item.status_key === "approved")?.count; + + const { data: dataToMissingPolygonVerify } = useGetV2IndicatorsEntityUuidSlugVerify( + { + pathParams: { + entity: entity!, + uuid: entity_uuid!, + slug: indicatorSlug! + } + }, + { + enabled: !!indicatorSlug + } + ); + + // @ts-ignore + const polygonMissingAnalysis = dataToMissingPolygonVerify?.message + ? totalPolygonsApproved + : totalPolygonsApproved! - Object?.keys(dataToMissingPolygonVerify ?? {})?.length; + + const verifySlug = async (slug: string) => + fetchGetV2IndicatorsEntityUuidSlugVerify({ + pathParams: { + entity: entity!, + uuid: entity_uuid!, + slug: slug! + } + }); + + useEffect(() => { + const fetchSlugs = async () => { + setIsLoadingVerify(true); + const slugVerify = await Promise.all(SLUGS_INDICATORS.map(verifySlug)); + const slugToAnalysis = SLUGS_INDICATORS.reduce>((acc, slug, index) => { + acc[slug] = slugVerify[index]; + return acc; + }, {}); + const updateTitleDropdownOptions = () => { + return DROPDOWN_OPTIONS.map(option => { + if (slugToAnalysis[`${option.slug}`]?.message) { + return { + ...option, + title: `${option.title} (0 polygons not run)` + }; + } + if (!slugToAnalysis[`${option.slug}`]) { + return option; + } + return { + ...option, + title: `${option.title} (${Object?.keys(slugToAnalysis[`${option.slug}`]).length} polygons not run)` + }; + }); + }; + setAnalysisToSlug(slugToAnalysis); + await setDropdownAnalysisOptions(updateTitleDropdownOptions); + setIsLoadingVerify(false); + }; + if (modalOpened(ModalId.MODAL_RUN_ANALYSIS)) { + fetchSlugs(); + } + }, [entity]); + + return { + polygonsIndicator: filteredPolygons, + indicatorPolygonsStatus: indicatorPolygonsStatus, + headerBarPolygonStatus: headerBarPolygonStatus, + totalPolygonsStatus: totalPolygonsApproved, + runAnalysisIndicator: mutate, + loadingAnalysis: isLoading, + loadingVerify: isLoadingVerify, + setIsLoadingVerify: setIsLoadingVerify, + dropdownAnalysisOptions: dropdownAnalysisOptions, + analysisToSlug: analysisToSlug, + polygonMissingAnalysis: polygonMissingAnalysis + }; +}; diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx index 921264199..59f324e90 100644 --- a/src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx +++ b/src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx @@ -34,6 +34,7 @@ import { ModalId } from "@/components/extensive/Modal/ModalConst"; import { useLoading } from "@/context/loaderAdmin.provider"; import { useMapAreaContext } from "@/context/mapArea.provider"; import { useModalContext } from "@/context/modal.provider"; +import { useMonitoredDataContext } from "@/context/monitoredData.provider"; import { useNotificationContext } from "@/context/notification.provider"; import { SitePolygonDataProvider } from "@/context/sitePolygon.provider"; import { @@ -146,6 +147,7 @@ const ContentForApproval = ({ const PolygonReviewTab: FC = props => { const { isLoading: ctxLoading, record, refetch: refreshEntity } = useShowContext(); + const { selectPolygonFromMap } = useMonitoredDataContext(); const [files, setFiles] = useState([]); const [saveFlags, setSaveFlags] = useState(false); const [polygonFromMap, setPolygonFromMap] = useState({ isOpen: false, uuid: "" }); @@ -166,6 +168,12 @@ const PolygonReviewTab: FC = props => { const { openNotification } = useNotificationContext(); + useEffect(() => { + if (selectPolygonFromMap?.uuid) { + setPolygonFromMap(selectPolygonFromMap); + flyToPolygonBounds(selectPolygonFromMap.uuid); + } + }, [polygonList]); const onSave = (geojson: any, record: any) => { storePolygon(geojson, record, refetch, setPolygonFromMap, refreshEntity); }; diff --git a/src/admin/modules/projects/components/ProjectShow.tsx b/src/admin/modules/projects/components/ProjectShow.tsx index aae73d9d2..40fca8a9d 100644 --- a/src/admin/modules/projects/components/ProjectShow.tsx +++ b/src/admin/modules/projects/components/ProjectShow.tsx @@ -43,7 +43,8 @@ const ProjectShow = () => { - + {/* In Progress */} + diff --git a/src/admin/modules/sites/components/SiteShow.tsx b/src/admin/modules/sites/components/SiteShow.tsx index 287325785..fb0bef090 100644 --- a/src/admin/modules/sites/components/SiteShow.tsx +++ b/src/admin/modules/sites/components/SiteShow.tsx @@ -42,7 +42,7 @@ const SiteShow: FC = () => { - + diff --git a/src/components/elements/Drawer/Drawer.tsx b/src/components/elements/Drawer/Drawer.tsx index ac734c19e..dfef681c8 100644 --- a/src/components/elements/Drawer/Drawer.tsx +++ b/src/components/elements/Drawer/Drawer.tsx @@ -2,6 +2,7 @@ import classNames from "classnames"; import React, { ReactNode, useEffect, useState } from "react"; import Icon, { IconNames } from "@/components/extensive/Icon/Icon"; +import { useMonitoredDataContext } from "@/context/monitoredData.provider"; import Button from "../Button/Button"; import { DRAWER_VARIANT_DEFAULT, DrawerVariant } from "./DrawerVariants"; @@ -29,6 +30,7 @@ const Drawer = (props: DrawerProps) => { const [isScrolled, setIsScrolled] = useState(isScrolledDefault); const [isScrollingDown, setIsScrollingDown] = useState(isScrolledDefault); const [prevScrollPos, setPrevScrollPos] = useState(0); + const { setSelectPolygonFromMap } = useMonitoredDataContext(); useEffect(() => { const handleScroll = () => { @@ -70,6 +72,7 @@ const Drawer = (props: DrawerProps) => { onClick={() => { setIsOpen(false); setPolygonFromMap && setPolygonFromMap({ isOpen: false, uuid: "" }); + setSelectPolygonFromMap?.({ uuid: "", isOpen: false }); }} > diff --git a/src/components/elements/Inputs/Map/RHFMap.tsx b/src/components/elements/Inputs/Map/RHFMap.tsx index cd929ad06..2c5bc2301 100644 --- a/src/components/elements/Inputs/Map/RHFMap.tsx +++ b/src/components/elements/Inputs/Map/RHFMap.tsx @@ -6,6 +6,7 @@ import InputWrapper, { InputWrapperProps } from "@/components/elements/Inputs/In import MapContainer from "@/components/elements/Map-mapbox/Map"; import { FORM_POLYGONS } from "@/constants/statuses"; import { useMapAreaContext } from "@/context/mapArea.provider"; +import { useMonitoredDataContext } from "@/context/monitoredData.provider"; import { SitePolygonDataProvider } from "@/context/sitePolygon.provider"; import { fetchGetV2TerrafundPolygonBboxUuid, useGetV2TerrafundProjectPolygon } from "@/generated/apiComponents"; import { SitePolygonsDataResponse } from "@/generated/apiSchemas"; @@ -47,6 +48,7 @@ const RHFMap = ({ const [polygonDataMap, setPolygonDataMap] = useState({}); const [polygonFromMap, setPolygonFromMap] = useState(null); const { setSiteData } = useMapAreaContext(); + const { setSelectPolygonFromMap } = useMonitoredDataContext(); const refetchData = () => { reloadProjectPolygonData(); @@ -84,6 +86,7 @@ const RHFMap = ({ if (!projectPolygon?.project_polygon) { setPolygonDataMap({ [FORM_POLYGONS]: [] }); setPolygonFromMap({ isOpen: false, uuid: "" }); + setSelectPolygonFromMap?.({ uuid: "", isOpen: false }); } else { setBbboxAndZoom(); setPolygonDataMap({ [FORM_POLYGONS]: [projectPolygon?.project_polygon?.poly_uuid] }); diff --git a/src/components/extensive/Modal/ModalRunAnalysis.tsx b/src/components/extensive/Modal/ModalRunAnalysis.tsx index bf1d5fe01..172bcc5f5 100644 --- a/src/components/extensive/Modal/ModalRunAnalysis.tsx +++ b/src/components/extensive/Modal/ModalRunAnalysis.tsx @@ -1,11 +1,16 @@ import { FC } from "react"; import { When } from "react-if"; +import { useMonitoredData } from "@/admin/components/ResourceTabs/MonitoredTab/hooks/useMonitoredData"; import Button from "@/components/elements/Button/Button"; import Dropdown from "@/components/elements/Inputs/Dropdown/Dropdown"; import { VARIANT_DROPDOWN_DEFAULT } from "@/components/elements/Inputs/Dropdown/DropdownVariant"; import Input from "@/components/elements/Inputs/Input/Input"; import Text from "@/components/elements/Text/Text"; +import InlineLoader from "@/components/generic/Loading/InlineLoader"; +import { useMonitoredDataContext } from "@/context/monitoredData.provider"; +import { useNotificationContext } from "@/context/notification.provider"; +import { EntityName } from "@/types/common"; import Icon, { IconNames } from "../Icon/Icon"; import { ModalProps } from "./Modal"; @@ -16,11 +21,17 @@ export interface ModalRunAnalysisProps extends ModalProps { onClose?: () => void; primaryButtonText: string; title: string; + projectName?: string; + entityType?: EntityName; + entityUuid?: string; } const ModalRunAnalysis: FC = ({ primaryButtonProps, title, + projectName, + entityType, + entityUuid, primaryButtonText, secondaryButtonProps, secondaryButtonText, @@ -29,11 +40,41 @@ const ModalRunAnalysis: FC = ({ onClose, ...rest }) => { + const { indicatorSlugAnalysis, setIndicatorSlugAnalysis, setLoadingAnalysis } = useMonitoredDataContext(); + const { runAnalysisIndicator, dropdownAnalysisOptions, loadingVerify, analysisToSlug } = useMonitoredData( + entityType, + entityUuid + ); + const { openNotification } = useNotificationContext(); + const runAnalysis = async () => { + setLoadingAnalysis?.(true); + if (analysisToSlug[`${indicatorSlugAnalysis}`]?.message) { + setLoadingAnalysis?.(false); + return openNotification("warning", "Warning", analysisToSlug[`${indicatorSlugAnalysis}`].message); + } + await runAnalysisIndicator({ + pathParams: { + slug: indicatorSlugAnalysis! + }, + body: { + uuids: analysisToSlug[`${indicatorSlugAnalysis}`] + } + }); + setIndicatorSlugAnalysis?.("treeCoverLoss"); + onClose?.(); + }; + return (
-
@@ -48,7 +89,7 @@ const ModalRunAnalysis: FC = ({ = ({ disabled={true} className="bg-neutral-150" /> - {}} - variant={VARIANT_DROPDOWN_DEFAULT} - className="!min-h-[44px] !py-[9px]" - labelClassName="!capitalize !text-darkCustom" - labelVariant="text-14-light" - /> + + { + const indicator = dropdownAnalysisOptions.find(option => option.value === e[0])?.slug; + setIndicatorSlugAnalysis?.(indicator!); + }} + variant={VARIANT_DROPDOWN_DEFAULT} + className="!min-h-[44px] !py-[9px]" + labelClassName="!capitalize !text-darkCustom" + labelVariant="text-14-light" + /> +


@@ -84,10 +126,11 @@ const ModalRunAnalysis: FC = ({
- diff --git a/src/constants/dashboardConsts.ts b/src/constants/dashboardConsts.ts index 8d7c4f611..b64565569 100644 --- a/src/constants/dashboardConsts.ts +++ b/src/constants/dashboardConsts.ts @@ -44,6 +44,20 @@ export const TERRAFUND_MONITORING_LINK = "https://www.wri.org/update/land-degrad export const TERRAFUND_MRV_LINK = `TerraFund's MRV framework`; +export const DEFAULT_POLYGONS_DATA = { + graphicTargetLandUseTypes: [], + totalSection: { + totalHectaresRestored: 0 + } +}; + +export const DEFAULT_POLYGONS_DATA_STRATEGIES = [ + { label: "Direct Seeding", value: 0 }, + { label: "Assisted Natural Regeneration", value: 0 }, + { label: "Tree Planting", value: 0 }, + { label: "Multiple Strategies", value: 0 } +]; + export const DUMMY_DATA_FOR_CHART_MULTI_LINE_CHART = [ { name: "Total", diff --git a/src/context/modal.provider.tsx b/src/context/modal.provider.tsx index a3b87c747..31a01301e 100644 --- a/src/context/modal.provider.tsx +++ b/src/context/modal.provider.tsx @@ -13,6 +13,7 @@ type ModalContextType = { openModal: (id: string, content: ReactNode, coverToolbar?: boolean) => void; closeModal: (id: string) => void; setModalLoading: (id: string, loading: boolean) => void; + modalOpened: (id: string) => boolean; }; export const ModalContext = React.createContext({ @@ -20,7 +21,8 @@ export const ModalContext = React.createContext({ setModalContent: () => {}, openModal: () => {}, closeModal: () => {}, - setModalLoading: () => {} + setModalLoading: () => {}, + modalOpened: () => false }); type ModalProviderProps = { @@ -48,13 +50,17 @@ const ModalProvider = ({ children }: ModalProviderProps) => { setModals(prevModals => prevModals.map(modal => (modal.id === id ? { ...modal, loading } : modal))); }; + /** Checks if the modal is opened. */ + const modalOpened = (id: string) => modals.some(modal => modal.id === id); + const value = useMemo( () => ({ modals, setModalContent, openModal, closeModal, - setModalLoading + setModalLoading, + modalOpened }), [modals] ); diff --git a/src/context/monitoredData.provider.tsx b/src/context/monitoredData.provider.tsx new file mode 100644 index 000000000..9dc1a1ff5 --- /dev/null +++ b/src/context/monitoredData.provider.tsx @@ -0,0 +1,74 @@ +import React, { createContext, ReactNode, useContext } from "react"; + +import { IpolygonFromMap } from "@/admin/components/ResourceTabs/PolygonReviewTab/components/Polygons"; + +type MonitoredDataType = { + filters: { + uuid: string; + }; + setFilters: React.Dispatch< + React.SetStateAction<{ + uuid: string; + }> + >; + searchTerm: string; + setSearchTerm: React.Dispatch>; + indicatorSlug?: string; + setIndicatorSlug?: React.Dispatch>; + indicatorSlugAnalysis?: string; + setIndicatorSlugAnalysis?: React.Dispatch>; + loadingAnalysis?: boolean; + setLoadingAnalysis?: React.Dispatch>; + setSelectPolygonFromMap?: (value: IpolygonFromMap) => void; + selectPolygonFromMap?: IpolygonFromMap; +}; +const defaultValues: MonitoredDataType = { + filters: { + uuid: "" + }, + setFilters: () => {}, + searchTerm: "", + setSearchTerm: () => {}, + indicatorSlug: "treeCoverLoss", + setIndicatorSlug: () => {}, + indicatorSlugAnalysis: "treeCoverLoss", + setIndicatorSlugAnalysis: () => {}, + loadingAnalysis: false, + setLoadingAnalysis: () => {}, + setSelectPolygonFromMap: () => {}, + selectPolygonFromMap: { isOpen: false, uuid: "" } +}; +const MonitoredDataContext = createContext(defaultValues); + +export const MonitoredDataProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [filters, setFilters] = React.useState(defaultValues.filters); + const [searchTerm, setSearchTerm] = React.useState(""); + const [indicatorSlug, setIndicatorSlug] = React.useState("treeCoverLoss"); + const [indicatorSlugAnalysis, setIndicatorSlugAnalysis] = React.useState("treeCoverLoss"); + const [loadingAnalysis, setLoadingAnalysis] = React.useState(false); + const [selectPolygonFromMap, setSelectPolygonFromMap] = React.useState({ isOpen: false, uuid: "" }); + + const contextValue: MonitoredDataType = { + filters, + setFilters, + searchTerm, + setSearchTerm, + indicatorSlug, + setIndicatorSlug, + indicatorSlugAnalysis, + setIndicatorSlugAnalysis, + loadingAnalysis, + setLoadingAnalysis, + setSelectPolygonFromMap, + selectPolygonFromMap + }; + return {children}; +}; + +export const useMonitoredDataContext = () => { + const context = useContext(MonitoredDataContext); + if (!context) { + throw new Error("useMonitoredDataContext must be used within a MonitoredDataProvider"); + } + return context; +}; diff --git a/src/generated/apiComponents.ts b/src/generated/apiComponents.ts index 651896ee8..a4e07fa0b 100644 --- a/src/generated/apiComponents.ts +++ b/src/generated/apiComponents.ts @@ -38154,6 +38154,314 @@ export const usePostV2TerrafundValidationPolygons = ( ); }; +export type GetV2IndicatorsEntityUuidSlugPathParams = { + /** + * Filter counts and metrics by entity. + */ + entity: string; + /** + * Filter counts and metrics by entity uuid. + */ + uuid: string; + /** + * Filter counts and metrics by slug. + */ + slug: string; +}; + +export type GetV2IndicatorsEntityUuidSlugError = Fetcher.ErrorWrapper; + +export type GetV2IndicatorsEntityUuidSlugResponse = { + ["2015"]?: number; + ["2016"]?: number; + ["2017"]?: number; + ["2018"]?: number; + ["2019"]?: number; + ["2020"]?: number; + ["2021"]?: number; + ["2022"]?: number; + ["2023"]?: number; + ["2024"]?: number; + id?: number; + poly_name?: string; + status?: string; + /** + * @format date + */ + plantstart?: string; + site_name?: string; + size?: void; + /** + * @format date + */ + created_at?: string; + indicator_slug?: string; + year_of_analysis?: number; + value?: Record; +}[]; + +export type GetV2IndicatorsEntityUuidSlugVariables = { + pathParams: GetV2IndicatorsEntityUuidSlugPathParams; +} & ApiContext["fetcherOptions"]; + +export const fetchGetV2IndicatorsEntityUuidSlug = ( + variables: GetV2IndicatorsEntityUuidSlugVariables, + signal?: AbortSignal +) => + apiFetch< + GetV2IndicatorsEntityUuidSlugResponse, + GetV2IndicatorsEntityUuidSlugError, + undefined, + {}, + {}, + GetV2IndicatorsEntityUuidSlugPathParams + >({ url: "/v2/indicators/{entity}/{uuid}/{slug}", method: "get", ...variables, signal }); + +export const useGetV2IndicatorsEntityUuidSlug = ( + variables: GetV2IndicatorsEntityUuidSlugVariables, + options?: Omit< + reactQuery.UseQueryOptions, + "queryKey" | "queryFn" + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options); + return reactQuery.useQuery( + queryKeyFn({ + path: "/v2/indicators/{entity}/{uuid}/{slug}", + operationId: "getV2IndicatorsEntityUuidSlug", + variables + }), + ({ signal }) => fetchGetV2IndicatorsEntityUuidSlug({ ...fetcherOptions, ...variables }, signal), + { + ...options, + ...queryOptions + } + ); +}; + +export type PostV2IndicatorsSlugPathParams = { + /** + * Optional. Filter counts and metrics by slug. + */ + slug: string; +}; + +export type PostV2IndicatorsSlugError = Fetcher.ErrorWrapper; + +export type PostV2IndicatorsSlugResponse = { + uuids?: string[]; +}; + +export type PostV2IndicatorsSlugRequestBody = { + uuids?: string[]; +}; + +export type PostV2IndicatorsSlugVariables = { + body?: PostV2IndicatorsSlugRequestBody; + pathParams: PostV2IndicatorsSlugPathParams; +} & ApiContext["fetcherOptions"]; + +export const fetchPostV2IndicatorsSlug = (variables: PostV2IndicatorsSlugVariables, signal?: AbortSignal) => + apiFetch< + PostV2IndicatorsSlugResponse, + PostV2IndicatorsSlugError, + PostV2IndicatorsSlugRequestBody, + {}, + {}, + PostV2IndicatorsSlugPathParams + >({ url: "/v2/indicators/{slug}", method: "post", ...variables, signal }); + +export const usePostV2IndicatorsSlug = ( + options?: Omit< + reactQuery.UseMutationOptions< + PostV2IndicatorsSlugResponse, + PostV2IndicatorsSlugError, + PostV2IndicatorsSlugVariables + >, + "mutationFn" + > +) => { + const { fetcherOptions } = useApiContext(); + return reactQuery.useMutation( + (variables: PostV2IndicatorsSlugVariables) => fetchPostV2IndicatorsSlug({ ...fetcherOptions, ...variables }), + options + ); +}; + +export type GetV2IndicatorsEntityUuidPathParams = { + /** + * Filter counts and metrics by entity. + */ + entity: string; + /** + * Filter counts and metrics by entity uuid. + */ + uuid: string; +}; + +export type GetV2IndicatorsEntityUuidError = Fetcher.ErrorWrapper; + +export type GetV2IndicatorsEntityUuidResponse = { + draft?: number; + submitted?: number; + approved?: number; + ["needs-more-information"]?: number; +}[]; + +export type GetV2IndicatorsEntityUuidVariables = { + pathParams: GetV2IndicatorsEntityUuidPathParams; +} & ApiContext["fetcherOptions"]; + +export const fetchGetV2IndicatorsEntityUuid = (variables: GetV2IndicatorsEntityUuidVariables, signal?: AbortSignal) => + apiFetch< + GetV2IndicatorsEntityUuidResponse, + GetV2IndicatorsEntityUuidError, + undefined, + {}, + {}, + GetV2IndicatorsEntityUuidPathParams + >({ url: "/v2/indicators/{entity}/{uuid}", method: "get", ...variables, signal }); + +export const useGetV2IndicatorsEntityUuid = ( + variables: GetV2IndicatorsEntityUuidVariables, + options?: Omit< + reactQuery.UseQueryOptions, + "queryKey" | "queryFn" + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options); + return reactQuery.useQuery( + queryKeyFn({ path: "/v2/indicators/{entity}/{uuid}", operationId: "getV2IndicatorsEntityUuid", variables }), + ({ signal }) => fetchGetV2IndicatorsEntityUuid({ ...fetcherOptions, ...variables }, signal), + { + ...options, + ...queryOptions + } + ); +}; + +export type GetV2IndicatorsEntityUuidSlugVerifyPathParams = { + /** + * Filter counts and metrics by entity. + */ + entity: string; + /** + * Filter counts and metrics by entity uuid. + */ + uuid: string; + /** + * Filter counts and metrics by slug. + */ + slug: string; +}; + +export type GetV2IndicatorsEntityUuidSlugVerifyError = Fetcher.ErrorWrapper; + +export type GetV2IndicatorsEntityUuidSlugVerifyResponse = any[]; + +export type GetV2IndicatorsEntityUuidSlugVerifyVariables = { + pathParams: GetV2IndicatorsEntityUuidSlugVerifyPathParams; +} & ApiContext["fetcherOptions"]; + +export const fetchGetV2IndicatorsEntityUuidSlugVerify = ( + variables: GetV2IndicatorsEntityUuidSlugVerifyVariables, + signal?: AbortSignal +) => + apiFetch< + GetV2IndicatorsEntityUuidSlugVerifyResponse, + GetV2IndicatorsEntityUuidSlugVerifyError, + undefined, + {}, + {}, + GetV2IndicatorsEntityUuidSlugVerifyPathParams + >({ url: "/v2/indicators/{entity}/{uuid}/{slug}/verify", method: "get", ...variables, signal }); + +export const useGetV2IndicatorsEntityUuidSlugVerify = ( + variables: GetV2IndicatorsEntityUuidSlugVerifyVariables, + options?: Omit< + reactQuery.UseQueryOptions< + GetV2IndicatorsEntityUuidSlugVerifyResponse, + GetV2IndicatorsEntityUuidSlugVerifyError, + TData + >, + "queryKey" | "queryFn" + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options); + return reactQuery.useQuery< + GetV2IndicatorsEntityUuidSlugVerifyResponse, + GetV2IndicatorsEntityUuidSlugVerifyError, + TData + >( + queryKeyFn({ + path: "/v2/indicators/{entity}/{uuid}/{slug}/verify", + operationId: "getV2IndicatorsEntityUuidSlugVerify", + variables + }), + ({ signal }) => fetchGetV2IndicatorsEntityUuidSlugVerify({ ...fetcherOptions, ...variables }, signal), + { + ...options, + ...queryOptions + } + ); +}; + +export type GetV2IndicatorsEntityUuidSlugExportPathParams = { + /** + * Filter counts and metrics by entity. + */ + entity: string; + /** + * Filter counts and metrics by entity uuid. + */ + uuid: string; + /** + * Filter counts and metrics by slug. + */ + slug: string; +}; + +export type GetV2IndicatorsEntityUuidSlugExportError = Fetcher.ErrorWrapper; + +export type GetV2IndicatorsEntityUuidSlugExportVariables = { + pathParams: GetV2IndicatorsEntityUuidSlugExportPathParams; +} & ApiContext["fetcherOptions"]; + +export const fetchGetV2IndicatorsEntityUuidSlugExport = ( + variables: GetV2IndicatorsEntityUuidSlugExportVariables, + signal?: AbortSignal +) => + apiFetch< + undefined, + GetV2IndicatorsEntityUuidSlugExportError, + undefined, + {}, + {}, + GetV2IndicatorsEntityUuidSlugExportPathParams + >({ url: "/v2/indicators/{entity}/{uuid}/{slug}/export", method: "get", ...variables, signal }); + +export const useGetV2IndicatorsEntityUuidSlugExport = ( + variables: GetV2IndicatorsEntityUuidSlugExportVariables, + options?: Omit< + reactQuery.UseQueryOptions, + "queryKey" | "queryFn" + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options); + return reactQuery.useQuery( + queryKeyFn({ + path: "/v2/indicators/{entity}/{uuid}/{slug}/export", + operationId: "getV2IndicatorsEntityUuidSlugExport", + variables + }), + ({ signal }) => fetchGetV2IndicatorsEntityUuidSlugExport({ ...fetcherOptions, ...variables }, signal), + { + ...options, + ...queryOptions + } + ); +}; + export type QueryOperation = | { path: "/v2/tree-species/{entity}/{UUID}"; @@ -38859,4 +39167,24 @@ export type QueryOperation = path: "/v2/site-polygon/{uuid}/versions"; operationId: "getV2SitePolygonUuidVersions"; variables: GetV2SitePolygonUuidVersionsVariables; + } + | { + path: "/v2/indicators/{entity}/{uuid}/{slug}"; + operationId: "getV2IndicatorsEntityUuidSlug"; + variables: GetV2IndicatorsEntityUuidSlugVariables; + } + | { + path: "/v2/indicators/{entity}/{uuid}"; + operationId: "getV2IndicatorsEntityUuid"; + variables: GetV2IndicatorsEntityUuidVariables; + } + | { + path: "/v2/indicators/{entity}/{uuid}/{slug}/verify"; + operationId: "getV2IndicatorsEntityUuidSlugVerify"; + variables: GetV2IndicatorsEntityUuidSlugVerifyVariables; + } + | { + path: "/v2/indicators/{entity}/{uuid}/{slug}/export"; + operationId: "getV2IndicatorsEntityUuidSlugExport"; + variables: GetV2IndicatorsEntityUuidSlugExportVariables; }; diff --git a/src/generated/apiSchemas.ts b/src/generated/apiSchemas.ts index 0ca65e9b5..c68f2fe35 100644 --- a/src/generated/apiSchemas.ts +++ b/src/generated/apiSchemas.ts @@ -23660,3 +23660,43 @@ export type UserCreateComplete = { export type V2AdminProjectUpdate = { is_test?: boolean; }; + +export type IndicatorPost = { + uuids?: string[]; +}; + +export type Indicators = { + ["2015"]?: number; + ["2016"]?: number; + ["2017"]?: number; + ["2018"]?: number; + ["2019"]?: number; + ["2020"]?: number; + ["2021"]?: number; + ["2022"]?: number; + ["2023"]?: number; + ["2024"]?: number; + id?: number; + poly_name?: string; + status?: string; + /** + * @format date + */ + plantstart?: string; + site_name?: string; + size?: void; + /** + * @format date + */ + created_at?: string; + indicator_slug?: string; + year_of_analysis?: number; + value?: Record; +}; + +export type IndicatorPolygonsStatus = { + draft?: number; + submitted?: number; + approved?: number; + ["needs-more-information"]?: number; +}; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 2161956e3..c0d979182 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -36,6 +36,7 @@ if (typeof window !== "undefined") { } import FloatNotificationProvider from "@/context/floatNotification.provider"; +import { MonitoredDataProvider } from "@/context/monitoredData.provider"; import DashboardAnalyticsWrapper from "./dashboard/DashboardAnalyticsWrapper"; @@ -96,10 +97,12 @@ const _App = ({ Component, ...rest }: AppProps) => { - - - - + + + + + + diff --git a/src/pages/dashboard/components/SecDashboard.tsx b/src/pages/dashboard/components/SecDashboard.tsx index ded2cb9f9..b62a676b4 100644 --- a/src/pages/dashboard/components/SecDashboard.tsx +++ b/src/pages/dashboard/components/SecDashboard.tsx @@ -55,7 +55,8 @@ const SecDashboard = ({ dataForChart, chartType, isUserAllowed = true, - isLoading = false + isLoading = false, + showTreesRestoredGraph = true }: { title: string; type?: "legend" | "toggle"; @@ -73,6 +74,7 @@ const SecDashboard = ({ chartType?: string; isUserAllowed?: boolean; isLoading?: boolean; + showTreesRestoredGraph?: boolean; }) => { const router = useRouter(); const [toggleValue, setToggleValue] = useState(0); @@ -161,7 +163,7 @@ const SecDashboard = ({ {data?.value !== undefined && ( )} - +
diff --git a/src/utils/dashboardUtils.ts b/src/utils/dashboardUtils.ts index dca701d44..8137e4ab2 100644 --- a/src/utils/dashboardUtils.ts +++ b/src/utils/dashboardUtils.ts @@ -1,4 +1,4 @@ -import { CHART_TYPES, MONTHS } from "@/constants/dashboardConsts"; +import { CHART_TYPES, DEFAULT_POLYGONS_DATA, MONTHS } from "@/constants/dashboardConsts"; import { DashboardTreeRestorationGoalResponse } from "@/generated/apiSchemas"; type DataPoint = { @@ -123,6 +123,22 @@ interface ChartCategory { values: ChartDataPoint[]; } +interface PolygonIndicator { + id?: number; + poly_name?: string; + status?: string; + data?: Record; + value?: Record; + [key: string]: any; +} + +export interface ParsedPolygonsData { + graphicTargetLandUseTypes: ParsedLandUseType[]; + totalSection: { + totalHectaresRestored: number; + }; +} + export const formatNumberUS = (value: number) => value ? (value >= 1000000 ? `${(value / 1000000).toFixed(2)}M` : value.toLocaleString("en-US")) : ""; @@ -416,3 +432,90 @@ export const isEmptyChartData = (chartType: string, data: any): boolean => { return false; } }; + +const formatLabel = (key: string): string => { + return key + .split("_") + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +}; + +export const parsePolygonsIndicatorDataForLandUse = ( + polygonsIndicator: PolygonIndicator[], + totalHectares: number +): ParsedPolygonsData => { + if (!polygonsIndicator?.length) { + return DEFAULT_POLYGONS_DATA; + } + + const { aggregatedData } = polygonsIndicator.reduce( + (acc, polygon) => { + if (!polygon.data) return acc; + + Object.entries(polygon.data).forEach(([key, value]) => { + const label = formatLabel(key); + const numericValue = Number(value); + acc.aggregatedData[label] = (acc.aggregatedData[label] || 0) + numericValue; + }); + + return acc; + }, + { + aggregatedData: {} as Record + } + ); + + const graphicTargetLandUseTypes = Object.entries(aggregatedData).map(([label, value]) => { + const percentage = calculatePercentage(value as number, totalHectares); + return { + label, + value: value as number, + valueText: `${Math.round(value as number)} ha ${percentage.toFixed(2)}%` + }; + }); + + return { + graphicTargetLandUseTypes, + totalSection: { + totalHectaresRestored: totalHectares + } + }; +}; + +export const parsePolygonsIndicatorDataForStrategies = (polygonsIndicator: PolygonIndicator[]): ParsedDataItem[] => { + const totals = { + "Tree Planting": 0, + "Direct Seeding": 0, + "Assisted Natural Regeneration": 0, + "Multiple Strategies": 0 + }; + + polygonsIndicator.forEach(polygon => { + const strategies = polygon.data ? Object.keys(polygon.data) : []; + + if (strategies.length === 1) { + const strategy = strategies[0]; + switch (strategy) { + case "tree_planting": + totals["Tree Planting"] += polygon.data?.[strategy] || 0; + break; + case "direct_seeding": + totals["Direct Seeding"] += polygon.data?.[strategy] || 0; + break; + case "assisted_natural_regeneration": + totals["Assisted Natural Regeneration"] += polygon.data?.[strategy] || 0; + break; + } + } else if (strategies.length > 1) { + const totalValue = polygon.data ? Object.values(polygon.data).reduce((sum, value) => sum + (value || 0), 0) : 0; + totals["Multiple Strategies"] += totalValue; + } + }); + + return Object.entries(totals) + .filter(([_, value]) => value > 0) + .map(([label, value]) => ({ + label, + value: Number(value.toFixed(2)) + })); +};