Skip to content

Commit

Permalink
[TM-1425] reload and no data for monitored charts (#747)
Browse files Browse the repository at this point in the history
* [TM-1425] add loader and no data div for monitored charts

* [TM-1425]  fix overflow chart TreeLossBarChart

* [TM-1425] add dropdown with polygon filtering

---------

Co-authored-by: Dotty <[email protected]>
  • Loading branch information
cesarLima1 and dottyy authored Dec 13, 2024
1 parent 5f2d873 commit bbc3a18
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 70 deletions.
112 changes: 51 additions & 61 deletions src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ import {
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 SecDashboard from "@/pages/dashboard/components/SecDashboard";
import { TOTAL_HECTARES_UNDER_RESTORATION_TOOLTIP } from "@/pages/dashboard/constants/tooltips";
import { EntityName, OptionValue } from "@/types/common";
import {
parsePolygonsIndicatorDataForEcoRegion,
Expand All @@ -45,8 +41,7 @@ import {
import { downloadFileBlob } from "@/utils/network";

import { useMonitoredData } from "../hooks/useMonitoredData";
import EcoRegionDoughnutChart from "./EcoRegionDoughnutChart";
import TreeLossBarChart from "./TreesLossBarChart";
import MonitoredCharts from "./MonitoredCharts";

interface TableData {
polygonName: string;
Expand Down Expand Up @@ -348,19 +343,6 @@ const indicatorDescription1 =
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 = (
<div className="absolute top-0 flex h-full w-full">
<div className="relative flex w-[23vw] flex-col gap-3 p-6">
Expand Down Expand Up @@ -407,11 +389,30 @@ const DataCard = ({
}) => {
const [tabActive, setTabActive] = useState(0);
const [selected, setSelected] = useState<OptionValue[]>(["1"]);
const [selectedPolygonUuid, setSelectedPolygonUuid] = useState<any>("0");
const basename = useBasename();
const mapFunctions = useMap();
const { record } = useShowContext();
const { polygonsIndicator, treeCoverLossData, treeCoverLossFiresData } = useMonitoredData(type!, record.uuid);
const parsedData = parseTreeCoverData(treeCoverLossData, treeCoverLossFiresData);
const { polygonsIndicator, treeCoverLossData, treeCoverLossFiresData, isLoadingIndicator } = useMonitoredData(
type!,
record.uuid
);
const filteredPolygonsIndicator =
selectedPolygonUuid !== "0"
? polygonsIndicator?.filter((polygon: any) => polygon.poly_id === selectedPolygonUuid)
: polygonsIndicator;

const filteredTreeCoverLossData =
selectedPolygonUuid !== "0"
? treeCoverLossData?.filter((data: any) => data.poly_id === selectedPolygonUuid)
: treeCoverLossData;

const filteredTreeCoverLossFiresData =
selectedPolygonUuid !== "0"
? treeCoverLossFiresData?.filter((data: any) => data.poly_id === selectedPolygonUuid)
: treeCoverLossFiresData;

const parsedData = parseTreeCoverData(filteredTreeCoverLossData, filteredTreeCoverLossFiresData);
const { setSearchTerm, setIndicatorSlug, indicatorSlug, setSelectPolygonFromMap, selectPolygonFromMap } =
useMonitoredDataContext();
const navigate = useNavigate();
Expand All @@ -421,20 +422,28 @@ const DataCard = ({
const totalHectaresRestoredGoal = record?.total_hectares_restored_goal
? Number(record?.total_hectares_restored_goal)
: +record?.hectares_to_restore_goal;
const landUseData = polygonsIndicator
? parsePolygonsIndicatorDataForLandUse(polygonsIndicator, totalHectaresRestoredGoal)
const landUseData = filteredPolygonsIndicator
? parsePolygonsIndicatorDataForLandUse(filteredPolygonsIndicator, totalHectaresRestoredGoal)
: DEFAULT_POLYGONS_DATA;
const strategiesData = polygonsIndicator
? parsePolygonsIndicatorDataForStrategies(polygonsIndicator)
const strategiesData = filteredPolygonsIndicator
? parsePolygonsIndicatorDataForStrategies(filteredPolygonsIndicator)
: DEFAULT_POLYGONS_DATA_STRATEGIES;

const ecoRegionData: any = polygonsIndicator
? parsePolygonsIndicatorDataForEcoRegion(polygonsIndicator)
const ecoRegionData: any = filteredPolygonsIndicator
? parsePolygonsIndicatorDataForEcoRegion(filteredPolygonsIndicator)
: DEFAULT_POLYGONS_DATA_ECOREGIONS;

const [topHeaderFirstTable, setTopHeaderFirstTable] = useState("102px");
const [topHeaderSecondTable, setTopHeaderSecondTable] = useState("70px");
const totalElemIndicator = polygonsIndicator?.length ? polygonsIndicator?.length - 1 : null;
const totalElemIndicator = filteredPolygonsIndicator?.length ? filteredPolygonsIndicator?.length - 1 : null;

const polygonsList = [
{ title: "All Polygons", value: "0" },
...(polygonsIndicator ?? []).map((item: any) => ({
title: item.poly_name,
value: item.poly_id
}))
];

useEffect(() => {
if (typeof window !== "undefined") {
Expand Down Expand Up @@ -659,14 +668,14 @@ const DataCard = ({
<When condition={tabActive === 1}>
<div className="relative z-auto flex w-full gap-8 px-6 pb-6 pt-2">
<Dropdown
containerClassName={classNames("absolute left-full -translate-x-full pr-6 z-50", {
containerClassName={classNames("absolute left-full -translate-x-full pr-6 z-[1]", {
hidden: selected.includes("6")
})}
optionsClassName="!w-max right-0"
className="w-max"
options={POLYGONS}
defaultValue={["1"]}
onChange={() => {}}
options={polygonsList}
defaultValue={["0"]}
onChange={option => setSelectedPolygonUuid(option[0])}
/>
<div className="sticky top-[77px] flex h-[calc(100vh-320px)] w-1/4 min-w-[25%] flex-col gap-3">
<Text
Expand All @@ -684,35 +693,16 @@ const DataCard = ({
</Text>
</div>
</div>
<When condition={selected.includes("1")}>
<TreeLossBarChart data={parsedData} />
</When>
<When condition={selected.includes("2") || selected.includes("2")}>
<TreeLossBarChart data={parsedData} />
</When>
<When condition={selected.includes("3")}>
<EcoRegionDoughnutChart data={ecoRegionData} />
</When>
<When condition={selected.includes("4")}>
<div className="flex w-full flex-col gap-6 lg:ml-[35px]">
<SecDashboard
title={"Total Hectares Under Restoration"}
data={{ value: record.total_hectares_restored_sum, totalValue: totalHectaresRestoredGoal }}
className="w-full place-content-center pl-8"
tooltip={TOTAL_HECTARES_UNDER_RESTORATION_TOOLTIP}
showTreesRestoredGraph={false}
/>
<SimpleBarChart data={strategiesData} />
</div>
</When>
<When condition={selected.includes("5")}>
<div className="w-[73%] pt-12">
<GraphicIconDashboard
data={landUseData.graphicTargetLandUseTypes}
maxValue={totalHectaresRestoredGoal}
/>
</div>
</When>
<MonitoredCharts
selected={selected}
isLoadingIndicator={isLoadingIndicator}
parsedData={parsedData}
ecoRegionData={ecoRegionData}
strategiesData={strategiesData}
landUseData={landUseData}
record={record}
totalHectaresRestoredGoal={totalHectaresRestoredGoal}
/>
<When condition={selected.includes("6")}>{noDataGraph}</When>
</div>
</When>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React from "react";
import { ReactNode } from "react";
import { When } from "react-if";

import SimpleBarChart from "@/pages/dashboard/charts/SimpleBarChart";
import GraphicIconDashboard from "@/pages/dashboard/components/GraphicIconDashboard";
import SecDashboard from "@/pages/dashboard/components/SecDashboard";
import { TOTAL_HECTARES_UNDER_RESTORATION_TOOLTIP } from "@/pages/dashboard/constants/tooltips";

import EcoRegionDoughnutChart from "./EcoRegionDoughnutChart";
import { LoadingState } from "./MonitoredLoading";
import { NoDataState } from "./NoDataState";
import TreeLossBarChart from "./TreesLossBarChart";

const ChartContainer = ({
children,
isLoading,
hasNoData
}: {
children: ReactNode;
isLoading: boolean;
hasNoData: boolean;
}): JSX.Element | null => {
if (isLoading) {
return <LoadingState />;
}

if (hasNoData) {
return <NoDataState />;
}

return <>{children}</>;
};

interface RecordType {
total_hectares_restored_sum: number;
}

const RestorationMetrics = ({
record,
totalHectaresRestoredGoal,
strategiesData
}: {
record: RecordType;
totalHectaresRestoredGoal: number;
strategiesData: any[];
}) => (
<div className="flex w-full flex-col gap-6 lg:ml-[35px]">
<SecDashboard
title="Total Hectares Under Restoration"
data={{
value: record.total_hectares_restored_sum,
totalValue: totalHectaresRestoredGoal
}}
className="w-full place-content-center pl-8"
tooltip={TOTAL_HECTARES_UNDER_RESTORATION_TOOLTIP}
showTreesRestoredGraph={false}
/>
<SimpleBarChart data={strategiesData} />
</div>
);

interface MonitoredChartsProps {
selected: React.Key[];
isLoadingIndicator: boolean;
parsedData: any[];
ecoRegionData: any;
strategiesData: any[];
landUseData: any;
record: RecordType;
totalHectaresRestoredGoal: number;
}

const MonitoredCharts = ({
selected,
isLoadingIndicator,
parsedData,
ecoRegionData,
strategiesData,
landUseData,
record,
totalHectaresRestoredGoal
}: MonitoredChartsProps) => {
const renderChart = (chartId: React.Key) => {
switch (chartId) {
case "1":
case "2":
return (
<ChartContainer isLoading={isLoadingIndicator} hasNoData={!parsedData?.length}>
<TreeLossBarChart data={parsedData} className="flex flex-col" />
</ChartContainer>
);

case "3":
return (
<ChartContainer isLoading={isLoadingIndicator} hasNoData={!ecoRegionData?.chartData?.length}>
<EcoRegionDoughnutChart data={ecoRegionData} />
</ChartContainer>
);

case "4":
return (
<ChartContainer isLoading={isLoadingIndicator} hasNoData={!strategiesData?.length}>
<RestorationMetrics
record={record}
totalHectaresRestoredGoal={totalHectaresRestoredGoal}
strategiesData={strategiesData}
/>
</ChartContainer>
);

case "5":
return (
<ChartContainer isLoading={isLoadingIndicator} hasNoData={!landUseData?.graphicTargetLandUseTypes?.length}>
<div className="w-full pt-12">
<GraphicIconDashboard data={landUseData.graphicTargetLandUseTypes} maxValue={totalHectaresRestoredGoal} />
</div>
</ChartContainer>
);

default:
return null;
}
};

return (
<div className="flex w-full flex-col gap-6">
{selected.map(
(id: React.Key | null | undefined) =>
id != null && (
<When key={id} condition={selected.includes(id)}>
{renderChart(id)}
</When>
)
)}
</div>
);
};

export default MonitoredCharts;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";

import Loader from "@/components/generic/Loading/Loader";

export const LoadingState = () => (
<div className="flex w-full items-start justify-center">
<Loader />
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

import Text from "@/components/elements/Text/Text";
import Tooltip from "@/components/elements/Tooltip/Tooltip";
import Icon from "@/components/extensive/Icon/Icon";
import { IconNames } from "@/components/extensive/Icon/Icon";

export const NoDataState = () => (
<div className="flex w-full flex-col items-center justify-center gap-2 rounded-xl border border-grey-1000">
<Text variant="text-32-semibold" className="text-blueCustom">
No Data to Display
</Text>
<div className="flex items-center gap-1">
<Text variant="text-14" className="text-darkCustom">
RUN ANALYSIS ON PROJECT POLYGONS TO SEE DATA
</Text>
<Tooltip content="Tooltip">
<Icon name={IconNames.IC_INFO} className="h-4 w-4" />
</Tooltip>
</div>
</div>
);
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ const TreeLossBarChart = ({ data, className = "" }: TreeLossBarChartProps) => {
const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: any[]; label?: string }) => {
if (active && payload && payload.length) {
return (
<div className="border-gray-200 min-w-[200px] rounded-md border bg-white p-4">
<p className="text-16-bold mb-2 font-bold">{label}</p>
<div className="border-gray-200 min-w-[200px] rounded-md border bg-white p-2">
<p className="text-12-semibold mb-2">{label}</p>
{payload.map((entry, index) => (
<div key={index} className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: entry.color }} />
<span className="text-gray-700">{entry.name}</span>
<span className="ml-auto font-medium">{Number(entry.value).toLocaleString()} ha</span>
<span className="text-12-light">{entry.name}</span>
<span className="text-12-semibold ml-auto font-medium">
{Number(entry.value).toFixed(1).toLocaleString()} ha
</span>
</div>
))}
</div>
Expand All @@ -35,8 +37,8 @@ const TreeLossBarChart = ({ data, className = "" }: TreeLossBarChartProps) => {

return (
<div className={`h-[500px] w-full p-4 ${className}`}>
<h2 className="text-16 mb-2 pl-10 font-semibold">Tree Loss Retrospective (ha)</h2>
<h3 className="text-16-bold mb-4 pl-10">2015-2024</h3>
<h2 className="text-12 mb-1 pl-10">Tree Loss Retrospective (ha)</h2>
<h3 className="text-14-semibold mb-4 pl-10">2015-2024</h3>
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={data}
Expand All @@ -50,7 +52,12 @@ const TreeLossBarChart = ({ data, className = "" }: TreeLossBarChartProps) => {
>
<CartesianGrid vertical={false} stroke="#E1E4E9" />
<XAxis dataKey="name" axisLine={false} tickLine={false} dy={10} />
<YAxis axisLine={false} tickLine={false} tickFormatter={value => `${value.toLocaleString()}`} />
<YAxis
axisLine={false}
tickLine={false}
tickFormatter={value => `${value.toLocaleString()}`}
className="text-12"
/>
<Tooltip content={<CustomTooltip />} cursor={{ fill: "rgba(0, 0, 0, 0.05)" }} />
<Legend
wrapperStyle={{
Expand Down
Loading

0 comments on commit bbc3a18

Please sign in to comment.