From 0e6e5c20e80c23689502a675916bdccb757a2539 Mon Sep 17 00:00:00 2001 From: Ghina Al Rashwani Date: Wed, 29 Oct 2025 18:41:01 +0200 Subject: [PATCH 1/2] ginar/ issue #665 fiding toggle switch and legend and views --- .../balanceHistory/BalanceHistoryChart.tsx | 580 ++++++++++++------ 1 file changed, 391 insertions(+), 189 deletions(-) diff --git a/components/balanceHistory/BalanceHistoryChart.tsx b/components/balanceHistory/BalanceHistoryChart.tsx index c5bbd2a17..a8b85c686 100644 --- a/components/balanceHistory/BalanceHistoryChart.tsx +++ b/components/balanceHistory/BalanceHistoryChart.tsx @@ -23,11 +23,11 @@ import { ArrowDown, ArrowUp, Minus } from "lucide-react"; import { useI18n } from "@/i18n/i18n"; import useDynamicGlobal from "@/hooks/api/homePage/useDynamicGlobal"; import { useHiveChainContext } from "@/contexts/HiveChainContext"; -import { convertVestsToHive, convertVestsToHP } from "@/utils/Calculations"; +import { convertVestsToHive } from "@/utils/Calculations"; import { grabNumericValue } from "@/utils/StringUtils"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; -import { useSettings } from "@/contexts/SettingsContext"; +import { Payload } from "recharts/types/component/DefaultLegendContent"; // Import Payload type interface BalanceHistoryChartProps { aggregatedAccountBalanceHistory?: { @@ -63,99 +63,99 @@ const BalanceHistoryChart: React.FC = ({ selectedCoinType, setSelectedCoinType, }) => { - const { t, dir, locale } = useI18n(); + const { t, dir } = useI18n(); const { hiveChain } = useHiveChainContext(); const { dynamicGlobalData } = useDynamicGlobal(); - const { settings } = useSettings(); const isRTL = dir === "rtl"; - const [isMobile, setIsMobile] = useState(window.innerWidth < 480); - const [hiddenDataKeys, setHiddenDataKeys] = useState([]); - const [unit, setUnit] = useState<"vests" | "hp">( - settings.displayVestHpMode === "hp" ? "hp" : "vests" + const [isMobile, setIsMobile] = useState( + typeof window !== "undefined" ? window.innerWidth < 480 : false ); - - useEffect(() => { - setUnit(settings.displayVestHpMode === "hp" ? "hp" : "vests"); - }, [settings.displayVestHpMode]); + const [hiddenDataKeys, setHiddenDataKeys] = useState([]); + const [vestsHpUnit, setVestsHpUnit] = useState<"vests" | "hp">("hp"); const availableCoins = ["HIVE", "VESTS", "HBD"]; - const [zoomedDomain, setZoomedDomain] = useState<[number, number] | null>(null); + const [zoomedDomain, setZoomedDomain] = useState<[number, number] | null>( + null + ); useEffect(() => { + if (typeof window === "undefined") return; + const handleResize = () => { setIsMobile(window.innerWidth < 480); }; + window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); - const processedData = useCallback( - (data: any, type: string) => { - if (!dynamicGlobalData || !hiveChain || !data) return []; - - return data.map((item: any) => { - const hivePrice = parseFloat(item.hivePrice || "0"); - - if (type === "VESTS") { - const vests = item.balance?.toString() || "0"; - - // Convert VESTS to HP or Hive - let convertedHPRaw = unit === "hp" - ? hiveChain.vestsToHp( - vests, - dynamicGlobalData.headBlockDetails.rawTotalVestingFundHive, - dynamicGlobalData.headBlockDetails.rawTotalVestingShares - ) - : hiveChain.vestsToHp( // using vestsToHp also for Hive conversion - vests, - dynamicGlobalData.headBlockDetails.rawTotalVestingFundHive, - dynamicGlobalData.headBlockDetails.rawTotalVestingShares - ); - - // Ensure convertedValue is a number - let convertedValue: number; - if (typeof convertedHPRaw === "number") { - convertedValue = convertedHPRaw; - } else if (convertedHPRaw && typeof convertedHPRaw === "object" && "amount" in convertedHPRaw) { - convertedValue = parseFloat(convertedHPRaw.amount); - } else if (typeof convertedHPRaw === "string") { - convertedValue = parseFloat(convertedHPRaw); - } else { - convertedValue = 0; + const processedData = useCallback( + (data: any, type: string) => { + if (!dynamicGlobalData || !hiveChain || !data) return []; + + return data.map((item: any) => { + const hivePrice = parseFloat(item.hivePrice || "0"); + + if (type === "VESTS") { + const vests = item.balance?.toString() || "0"; + const convertedHP = hiveChain.vestsToHp( + vests, + dynamicGlobalData.headBlockDetails.rawTotalVestingFundHive, + dynamicGlobalData.headBlockDetails.rawTotalVestingShares + ); + + let convertedHPNumeric: number; + if (typeof convertedHP === "number") { + convertedHPNumeric = convertedHP; + } else if ( + convertedHP && + typeof convertedHP === "object" && + "amount" in convertedHP + ) { + convertedHPNumeric = parseFloat(String(convertedHP.amount)); + } else if (typeof convertedHP === "string") { + convertedHPNumeric = parseFloat(convertedHP); + } else { + convertedHPNumeric = 0; + } + + const dollarValueFull = + !isNaN(convertedHPNumeric) && !isNaN(hivePrice) + ? convertedHPNumeric * hivePrice + : 0; + + return { + ...item, + convertedHive: convertedHPNumeric, + dollarValue: dollarValueFull, + }; } - const dollarValueFull = - !isNaN(convertedValue) && !isNaN(hivePrice) - ? convertedValue * hivePrice /100 - : 0; - - return { - ...item, - convertedHive: convertedValue, - dollarValue: dollarValueFull, - }; - } - - if (type === "HIVE") { - const dollarValue = - !isNaN(item.balance) && !isNaN(hivePrice) - ? item.balance * hivePrice - : 0; + if (type === "HIVE") { + const dollarValue = + !isNaN(item.balance) && !isNaN(hivePrice) + ? item.balance * hivePrice + : 0; - return { ...item, dollarValue }; - } - - if (type === "HBD") { - return { ...item, dollarValue: !isNaN(item.balance) ? item.balance : 0 }; - } + return { + ...item, + dollarValue, + }; + } - return item; - }); - }, - [dynamicGlobalData, hiveChain, unit] -); + if (type === "HBD") { + return { + ...item, + dollarValue: !isNaN(item.balance) ? item.balance : 0, + }; + } + return item; + }); + }, + [dynamicGlobalData, hiveChain] + ); const dataMap: Record< string, @@ -180,17 +180,31 @@ const BalanceHistoryChart: React.FC = ({ setSelectedCoinType(coinType); }; - const displayData = useMemo(() => dataMap[selectedCoinType], [selectedCoinType]); - - // ---------------- Tooltip ---------------- - const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: any[]; label?: string; }) => { + const displayData = useMemo(() => { + return dataMap[selectedCoinType]; + //eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedCoinType, vestsHpUnit]); + + const CustomTooltip = ({ + active, + payload, + label, + }: { + active?: boolean; + payload?: any[]; + label?: string; + }) => { const { dir: tooltipDir } = useI18n(); if (quickView || !active || !payload || payload.length === 0) return null; const isTooltipRTL = tooltipDir === "rtl"; const selectedData = displayData?.find((item) => item.timestamp === label); if (!selectedData) return null; - const actualBalance = selectedData?.balance ?? 0; + const actualBalance = + selectedCoinType === "VESTS" && vestsHpUnit === "hp" + ? selectedData?.convertedHive ?? 0 + : selectedData?.balance ?? 0; + const balanceChange = selectedData?.balance_change ?? 0; const savingsBalance = selectedData?.savings_balance ?? undefined; const savingsBalanceChange = selectedData?.savings_balance_change ?? 0; @@ -207,118 +221,175 @@ const BalanceHistoryChart: React.FC = ({

{`${t("common.date")}: ${label}`}

-
+
{isPositiveChange ? ( ) : isZeroChange ? ( - + ) : ( )} - {` ${formatNumber(balanceChange, selectedCoinType === "VESTS" ? unit === "vests" : false)}`} + {` ${formatNumber( + balanceChange, + selectedCoinType === "VESTS" && vestsHpUnit === "vests" + )}`}
-
{`${t("common.balance")}: ${formatNumber(actualBalance, selectedCoinType === "VESTS" ? unit === "vests" : false)}`}
- {dollarValue ? ( +
{`${t( + "common.balance" + )}: ${formatNumber( + actualBalance, + selectedCoinType === "VESTS" && vestsHpUnit === "vests" + )}`}
+ + {dollarValue || dollarValue === 0 ? (
- Dollar Value: ${formatNumber(dollarValue, false, selectedCoinType === "VESTS")} + Dollar Value: $ + {formatNumber(dollarValue, false, selectedCoinType === "VESTS")}
) : null}
- {showSavingsBalance === "yes" && savingsBalance !== undefined && selectedCoinType !== "VESTS" && ( -
-
- {isSavingsPositiveChange ? ( - - ) : isSavingsZeroChange ? ( - - ) : ( - - )} - {` ${formatNumber(savingsBalanceChange, selectedCoinType === "VESTS" ? unit === "vests" : false)}`} -
-
- {`${t("balanceHistoryChart.savingsBalance")}: ${formatNumber(savingsBalance, selectedCoinType === "VESTS" ? unit === "vests" : false)}`} + {showSavingsBalance === "yes" && + savingsBalance !== undefined && + selectedCoinType !== "VESTS" && ( +
+
+ {isSavingsPositiveChange ? ( + + ) : isSavingsZeroChange ? ( + + ) : ( + + )} + {` ${formatNumber( + savingsBalanceChange, + selectedCoinType === "VESTS" && vestsHpUnit === "vests" + )}`} +
+
+ {`${t("balanceHistoryChart.savingsBalance")}: ${formatNumber( + savingsBalance, + selectedCoinType === "VESTS" && vestsHpUnit === "vests" + )}`} +
-
- )} + )}
); }; - // ---------------- Coin toggle buttons ---------------- -const renderCoinButtons = () => ( -
- {/* Toggle on the far left */} - {selectedCoinType === "VESTS" && ( -
- - setUnit(checked ? "hp" : "vests")} - /> - -
- )} - - {/* Coin buttons always on the right */} - {availableCoins.map((coinType) => ( - - ))} -
- -); - - + const renderCoinButtons = () => { + return ( +
+ {availableCoins.map((coinType) => ( + + ))} +
+ ); + }; - // ---------------- Min/Max for Y-axis ---------------- - const getMinMax = (data: any[]): [number, number] => { - if (!data || data.length === 0) return [0, 1]; + const getMinMax = ( + data: { + balance: number; + savings_balance?: number; + dollarValue?: number; + convertedHive?: number; + }[] + ): [number, number] => { + if (!data || data.length === 0) { + return [0, 1]; + } let allValues: number[] = []; if (selectedCoinType === "VESTS") { - allValues = data.map((item) => item.convertedHive || 0); - const dollarValues = data.map((item) => item.dollarValue).filter((v): v is number => typeof v === "number" && !Number.isNaN(v)); + allValues = data.map((item) => + vestsHpUnit === "hp" ? item.convertedHive || 0 : item.balance + ); + + const dollarValues = data + .map((item) => item.dollarValue) + .filter((v): v is number => typeof v === "number" && !Number.isNaN(v)); + allValues = allValues.concat(dollarValues); } else { allValues = data.map((item) => item.balance); - const dollarValues = data.map((item) => item.dollarValue).filter((v): v is number => typeof v === "number" && !Number.isNaN(v)); + + const dollarValues = data + .map((item) => item.dollarValue) + .filter((v): v is number => typeof v === "number" && !Number.isNaN(v)); allValues = allValues.concat(dollarValues); + if (showSavingsBalance === "yes") { - const savingsValues = data.map((item) => item.savings_balance).filter((v): v is number => typeof v === "number"); + const savingsValues = data + .map((item) => item.savings_balance) + .filter((v): v is number => typeof v === "number"); allValues = allValues.concat(savingsValues); } } - return [Math.min(...allValues), Math.max(...allValues)]; + const minValue = Math.min(...allValues); + const maxValue = Math.max(...allValues); + + return [minValue, maxValue]; }; const [fullDataMin, fullDataMax] = getMinMax(displayData); const [minValue, maxValue] = zoomedDomain || [fullDataMin, fullDataMax]; - const handleBrushAreaChange = (domain: { startIndex?: number; endIndex?: number; }) => { - if (!domain || domain.startIndex === undefined || domain.endIndex === undefined) { + const handleBrushAreaChange = (domain: { + startIndex?: number; + endIndex?: number; + }) => { + if ( + !domain || + domain.startIndex === undefined || + domain.endIndex === undefined + ) { setZoomedDomain([fullDataMin, fullDataMax]); return; } - const visibleData = (displayData || []).slice(domain.startIndex, domain.endIndex + 1); + + const { startIndex, endIndex } = domain; + const visibleData = (displayData || []).slice(startIndex, endIndex + 1); + if (visibleData.length > 0) { const [min, max] = getMinMax(visibleData); setZoomedDomain([min, max]); @@ -331,25 +402,80 @@ const renderCoinButtons = () => ( const primaryAxisId = isRTL ? "right" : "left"; const secondaryAxisId = isRTL ? "left" : "right"; - const leftMargin = isMobile - ? 10 - : selectedCoinType === "VESTS" - ? 50 - : 30; + const secondaryAxisDomain = + selectedCoinType === "HBD" ? [minValue, maxValue] : undefined; + + const chartBottomMargin = useMemo(() => { + let margin = 60; + margin += 50; + if (isMobile) { + margin += 30; + } + return margin; + }, [isMobile]); + + // Construct the payload for the Legend component + const legendPayload: Payload[] = useMemo(() => { + const payloadItems: Payload[] = [ + { + value: + selectedCoinType === "VESTS" && vestsHpUnit === "hp" + ? "HP" + : selectedCoinType, + id: + selectedCoinType === "VESTS" && vestsHpUnit === "hp" + ? "convertedHive" + : "balance", // Use the actual dataKey as the id for toggling + type: "line", // Shape for the legend item + color: colorMap[selectedCoinType], + }, + { + value: "DOLLAR", + id: "dollarValue", // Use the actual dataKey as the id for toggling + type: "line", + color: colorMap.DOLLAR, + }, + ]; + + if (showSavingsBalance === "yes" && selectedCoinType !== "VESTS") { + payloadItems.push({ + value: t("balanceHistoryChart.savingsBalance"), + id: "savings_balance", // Use the actual dataKey as the id for toggling + type: "line", + color: colorMap.SAVINGS, + }); + } + + // Filter based on hiddenDataKeys using the 'id' of the payload + return payloadItems.filter((item) => !hiddenDataKeys.includes(item.id as string)); + }, [selectedCoinType, vestsHpUnit, showSavingsBalance, hiddenDataKeys, t]); return ( -
- {renderCoinButtons()} - +
+ {quickView && ( +
+ {renderCoinButtons()} +
+ )} + + + ( dx={isMobile ? -8 : 0} dy={isMobile ? 20 : 10} reversed={isRTL} + interval="preserveStartEnd" /> ( tickCount={6} tickFormatter={(tick) => { if (selectedCoinType === "VESTS") { - if (unit === "hp") return `${formatNumber(tick, false, false)}`; + if (vestsHpUnit === "hp") { + return formatNumber(tick, false, false); + } const valueInK = tick / 1_000; return `${formatNumber(valueInK, true, false).split(".")[0]} K`; } return formatNumber(tick, false, false); }} /> - {isDualAxis && ( - `$${Math.round(tick)}`} - /> - )} + + `$${formatNumber(tick, false, false)}`} + /> + } /> + ( dot={false} hide={hiddenDataKeys.includes("dollarValue")} /> + {showSavingsBalance === "yes" && selectedCoinType !== "VESTS" && ( ( hide={hiddenDataKeys.includes("savings_balance")} /> )} + {!quickView && ( ( fill="var(--color-background)" travellerWidth={10} tickFormatter={(value) => moment(value).format("MMM D")} - y={380} + y={350} x={50} className="text-xs" onChange={handleBrushAreaChange} /> )} - { - const dataKey = event.dataKey as string; - const isHidden = hiddenDataKeys.includes(dataKey); - if (isHidden) setHiddenDataKeys(hiddenDataKeys.filter((key) => key !== dataKey)); - else setHiddenDataKeys([...hiddenDataKeys, dataKey]); - }} - /> + + {/* Container for Legend and VESTS/HP toggle */} +
+ { + // The event.payload.id contains the dataKey we manually assigned + const clickedId = event.payload?.id; + if (clickedId) { + const isHidden = hiddenDataKeys.includes(clickedId as string); + if (isHidden) { + setHiddenDataKeys( + hiddenDataKeys.filter((key) => key !== clickedId) + ); + } else { + setHiddenDataKeys([...hiddenDataKeys, clickedId]); + } + } + }} + /> + + {selectedCoinType === "VESTS" && ( +
+ + + setVestsHpUnit(checked ? "hp" : "vests") + } + /> + +
+ )} +
); }; -export default BalanceHistoryChart; +export default BalanceHistoryChart; \ No newline at end of file -- GitLab From c1708f806025c6e32ef70fa3c2d18d8ab8b4c869 Mon Sep 17 00:00:00 2001 From: Ghina Al Rashwani Date: Wed, 29 Oct 2025 23:21:22 +0200 Subject: [PATCH 2/2] ginar/ still fixing tooltip --- .../balanceHistory/BalanceHistoryChart.tsx | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/components/balanceHistory/BalanceHistoryChart.tsx b/components/balanceHistory/BalanceHistoryChart.tsx index a8b85c686..205232052 100644 --- a/components/balanceHistory/BalanceHistoryChart.tsx +++ b/components/balanceHistory/BalanceHistoryChart.tsx @@ -183,7 +183,7 @@ const BalanceHistoryChart: React.FC = ({ const displayData = useMemo(() => { return dataMap[selectedCoinType]; //eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedCoinType, vestsHpUnit]); + }, [selectedCoinType, vestsHpUnit, dataMap]); const CustomTooltip = ({ active, @@ -371,40 +371,13 @@ const BalanceHistoryChart: React.FC = ({ return [minValue, maxValue]; }; - const [fullDataMin, fullDataMax] = getMinMax(displayData); + // Move useMemo calls outside of any conditional returns + const [fullDataMin, fullDataMax] = useMemo( + () => getMinMax(displayData), + [displayData, selectedCoinType, vestsHpUnit, showSavingsBalance] + ); const [minValue, maxValue] = zoomedDomain || [fullDataMin, fullDataMax]; - const handleBrushAreaChange = (domain: { - startIndex?: number; - endIndex?: number; - }) => { - if ( - !domain || - domain.startIndex === undefined || - domain.endIndex === undefined - ) { - setZoomedDomain([fullDataMin, fullDataMax]); - return; - } - - const { startIndex, endIndex } = domain; - const visibleData = (displayData || []).slice(startIndex, endIndex + 1); - - if (visibleData.length > 0) { - const [min, max] = getMinMax(visibleData); - setZoomedDomain([min, max]); - } - }; - - if (!displayData || !displayData.length) return null; - - const isDualAxis = selectedCoinType === "VESTS"; - const primaryAxisId = isRTL ? "right" : "left"; - const secondaryAxisId = isRTL ? "left" : "right"; - - const secondaryAxisDomain = - selectedCoinType === "HBD" ? [minValue, maxValue] : undefined; - const chartBottomMargin = useMemo(() => { let margin = 60; margin += 50; @@ -450,6 +423,41 @@ const BalanceHistoryChart: React.FC = ({ return payloadItems.filter((item) => !hiddenDataKeys.includes(item.id as string)); }, [selectedCoinType, vestsHpUnit, showSavingsBalance, hiddenDataKeys, t]); + + const handleBrushAreaChange = (domain: { + startIndex?: number; + endIndex?: number; + }) => { + if ( + !domain || + domain.startIndex === undefined || + domain.endIndex === undefined + ) { + setZoomedDomain([fullDataMin, fullDataMax]); + return; + } + + const { startIndex, endIndex } = domain; + const visibleData = (displayData || []).slice(startIndex, endIndex + 1); + + if (visibleData.length > 0) { + const [min, max] = getMinMax(visibleData); + setZoomedDomain([min, max]); + } else { + setZoomedDomain([fullDataMin, fullDataMax]); // Reset if no data visible + } + }; + + if (!displayData || !displayData.length) return null; + + const isDualAxis = selectedCoinType === "VESTS"; + const primaryAxisId = isRTL ? "right" : "left"; + const secondaryAxisId = isRTL ? "left" : "right"; + + const secondaryAxisDomain = + selectedCoinType === "HBD" ? [minValue, maxValue] : undefined; + + return (
{quickView && ( @@ -473,9 +481,9 @@ const BalanceHistoryChart: React.FC = ({ left: isMobile ? 20 : 12, bottom: chartBottomMargin, }} - + > - +