diff --git a/components/balanceHistory/BalanceHistoryChart.tsx b/components/balanceHistory/BalanceHistoryChart.tsx index d526507ffb98df1f0104a66eca4c0566b71fe844..ed013f102d765cd000da01aba5f5029d6723555e 100644 --- a/components/balanceHistory/BalanceHistoryChart.tsx +++ b/components/balanceHistory/BalanceHistoryChart.tsx @@ -25,6 +25,8 @@ import useDynamicGlobal from "@/hooks/api/homePage/useDynamicGlobal"; import { useHiveChainContext } from "@/contexts/HiveChainContext"; import { convertVestsToHive } from "@/utils/Calculations"; import { grabNumericValue } from "@/utils/StringUtils"; +import { Switch } from "@/components/ui/switch"; +import { useSettings } from "@/contexts/SettingsContext"; interface BalanceHistoryChartProps { aggregatedAccountBalanceHistory?: { @@ -63,10 +65,19 @@ const BalanceHistoryChart: React.FC = ({ const { t, dir, locale } = useI18n(); const { hiveChain } = useHiveChainContext(); const { dynamicGlobalData } = useDynamicGlobal(); + const { settings } = useSettings(); // <-- Get user's VESTS/HP setting 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" + ); // <-- Added VESTS/HP toggle state + + // Sync unit with user settings + useEffect(() => { + setUnit(settings.displayVestHpMode === "hp" ? "hp" : "vests"); + }, [settings.displayVestHpMode]); // State to store available coins const availableCoins = ["HIVE", "VESTS", "HBD"]; @@ -78,66 +89,109 @@ const BalanceHistoryChart: React.FC = ({ 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"; - - const hiveAmount = grabNumericValue( - convertVestsToHive( - hiveChain, - vests, - dynamicGlobalData.headBlockDetails.rawTotalVestingFundHive, - dynamicGlobalData.headBlockDetails.rawTotalVestingShares - ) - ); - - const dollarValueFull = - !isNaN(hiveAmount) && !isNaN(hivePrice) - ? hiveAmount * hivePrice - : 0; - - return { - ...item, - convertedHive: hiveAmount, - dollarValue: dollarValueFull, - }; - } + // Process data for chart depending on selectedCoinType +const processedData = useCallback( + (data: any, type: string) => { + if (!dynamicGlobalData || !hiveChain || !data) return []; + + const parseAssetField = (field: any): number => { + if (field == null) return 0; + + if (typeof field === "number" && !Number.isNaN(field)) return field; - if (type === "HIVE") { - const dollarValue = - !isNaN(item.balance) && !isNaN(hivePrice) - ? item.balance * hivePrice - : 0; + if (typeof field === "string") { + const numericStr = String(grabNumericValue(field)); + const parsed = Number(numericStr); + if (!Number.isNaN(parsed)) return parsed; - return { - ...item, - dollarValue, - }; + const cleaned = field.replace(/[^0-9.\-]/g, ""); + const parsedAlt = Number(cleaned); + return Number.isNaN(parsedAlt) ? 0 : parsedAlt; + } + + if (typeof field === "object") { + if ("amount" in field) { + const amount = Number(field.amount ?? 0); + const precision = Number(field.precision ?? 0); + if (!Number.isNaN(amount)) { + return amount / Math.pow(10, precision); + } } - if (type === "HBD") { - return { - ...item, - dollarValue: !isNaN(item.balance) ? item.balance : 0, - }; + try { + const asString = JSON.stringify(field); + const numericStr = String(grabNumericValue(asString)); + const parsed = Number(numericStr); + if (!Number.isNaN(parsed)) return parsed; + } catch { + return 0; } + } + + return 0; + }; + + // Safely extract total vesting fund and shares + const fundHive = + parseAssetField(dynamicGlobalData.total_vesting_fund_hive) || + parseAssetField(dynamicGlobalData.headBlockDetails?.rawTotalVestingFundHive) || + 0; + + const totalVests = + parseAssetField(dynamicGlobalData.total_vesting_shares) || + parseAssetField(dynamicGlobalData.headBlockDetails?.rawTotalVestingShares) || + 0; + + return data.map((item: any) => { + const hivePrice = parseFloat(item.hivePrice || "0"); + const parsedItemBalance = parseAssetField(item.balance ?? item.vests ?? item.value ?? 0); + + if (type === "VESTS") { + let displayValue = parsedItemBalance; + + if (fundHive > 0 && totalVests > 0) { + const convertedHP = (fundHive * parsedItemBalance) / totalVests; + displayValue = unit === "hp" ? convertedHP : parsedItemBalance; + } + + const dollarValue = !Number.isNaN(displayValue) && !Number.isNaN(hivePrice) + ? displayValue * hivePrice + : 0; + + return { + ...item, + convertedHive: displayValue, + dollarValue, + displayBalance: displayValue, + }; + } + + if (type === "HIVE") { + const balanceValue = parseAssetField(item.balance ?? item.hive ?? 0); + const dollarValue = !Number.isNaN(balanceValue) && !Number.isNaN(hivePrice) + ? balanceValue * hivePrice + : 0; + + return { ...item, displayBalance: balanceValue, dollarValue }; + } + + if (type === "HBD") { + const balanceValue = parseAssetField(item.balance ?? item.hbd ?? 0); + return { ...item, displayBalance: balanceValue, dollarValue: balanceValue }; + } + + return { ...item, displayBalance: parseAssetField(item.balance ?? 0) }; + }); + }, + [dynamicGlobalData, hiveChain, unit] +); + + - return item; - }); - }, - [dynamicGlobalData, hiveChain] - ); const dataMap: Record< string, @@ -150,6 +204,7 @@ const BalanceHistoryChart: React.FC = ({ hivePrice: string; dollarValue?: number; convertedHive?: number; + displayBalance?: number; }[] > = useMemo(() => { return { @@ -164,9 +219,9 @@ const BalanceHistoryChart: React.FC = ({ const displayData = useMemo(() => { return dataMap[selectedCoinType]; - //eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedCoinType]); + }, [dataMap, selectedCoinType]); + // Custom tooltip const CustomTooltip = ({ active, payload, @@ -182,7 +237,7 @@ const BalanceHistoryChart: React.FC = ({ const selectedData = displayData?.find((item) => item.timestamp === label); if (!selectedData) return null; - const actualBalance = selectedData?.balance ?? 0; + const actualBalance = selectedData?.displayBalance ?? 0; // <-- Use displayBalance const balanceChange = selectedData?.balance_change ?? 0; const savingsBalance = selectedData?.savings_balance ?? undefined; const savingsBalanceChange = selectedData?.savings_balance_change ?? 0; @@ -294,28 +349,50 @@ const BalanceHistoryChart: React.FC = ({ ); }; + // Render buttons for coin selection const renderCoinButtons = () => { - return availableCoins.map((coinType) => ( - + ))} + + {/* VESTS / HP toggle */} + {selectedCoinType === "VESTS" && ( +
+ {t("common.vests")} + + setUnit(checked ? "hp" : "vests") + } + /> + {t("common.hp")} +
)} - > - {coinType} - - )); + + ); }; + const getMinMax = ( data: { balance: number; savings_balance?: number; dollarValue?: number; convertedHive?: number; + displayBalance?: number; }[] ): [number, number] => { if (!data || data.length === 0) { @@ -330,8 +407,12 @@ const BalanceHistoryChart: React.FC = ({ .filter((v): v is number => typeof v === "number" && !Number.isNaN(v)); allValues = allValues.concat(dollarValues); + const balances = data + .map((item) => item.displayBalance) + .filter((v): v is number => typeof v === "number" && !Number.isNaN(v)); + allValues = allValues.concat(balances); } else { - allValues = data.map((item) => item.balance); + allValues = data.map((item) => item.displayBalance ?? item.balance); const dollarValues = data .map((item) => item.dollarValue) @@ -355,16 +436,8 @@ const BalanceHistoryChart: React.FC = ({ 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 - ) { - // Reset zoom if brush is cleared or start/end index is undefined + const handleBrushAreaChange = (domain: { startIndex?: number; endIndex?: number }) => { + if (!domain || domain.startIndex === undefined || domain.endIndex === undefined) { setZoomedDomain([fullDataMin, fullDataMax]); return; } @@ -378,7 +451,7 @@ const BalanceHistoryChart: React.FC = ({ } }; - if (!displayData || !displayData.length) return; + if (!displayData || !displayData.length) return null; const isDualAxis = selectedCoinType === "VESTS"; const primaryAxisId = isRTL ? "right" : "left"; @@ -386,13 +459,7 @@ const BalanceHistoryChart: React.FC = ({ return (
- {quickView && ( -
- {renderCoinButtons()} -
- )} + {quickView &&
{renderCoinButtons()}
} = ({ = ({ const dataKey = event.dataKey as string; const isHidden = hiddenDataKeys.includes(dataKey as string); if (isHidden) { - setHiddenDataKeys( - hiddenDataKeys.filter((key) => key !== dataKey) - ); + setHiddenDataKeys(hiddenDataKeys.filter((key) => key !== dataKey)); } else { setHiddenDataKeys([...hiddenDataKeys, dataKey]); }