From bd5a2661d4945a1b1d4b5313861060655bbbbb59 Mon Sep 17 00:00:00 2001 From: Dima Rifai <dima.rifai@gmail.com> Date: Fri, 27 Dec 2024 09:09:54 +0200 Subject: [PATCH] Issue 394 - Create the logic for balance history charts --- .../account/AccountBalanceHistoryCard.tsx | 138 ++++++++++ components/account/AccountDetailsSection.tsx | 7 +- .../balanceHistory/BalanceHistoryChart.tsx | 254 ++++++++++++++++++ .../home/searches/BalanceHistorySearch.tsx | 94 +++++-- hooks/api/balanceHistory/useBalanceHistory.ts | 2 +- pages/balanceHistory/[accountName].tsx | 138 ++++++++-- 6 files changed, 583 insertions(+), 50 deletions(-) create mode 100644 components/account/AccountBalanceHistoryCard.tsx create mode 100644 components/balanceHistory/BalanceHistoryChart.tsx diff --git a/components/account/AccountBalanceHistoryCard.tsx b/components/account/AccountBalanceHistoryCard.tsx new file mode 100644 index 00000000..1fe5785c --- /dev/null +++ b/components/account/AccountBalanceHistoryCard.tsx @@ -0,0 +1,138 @@ +import React, { useState, useMemo } from "react"; +import { ArrowDown, ArrowUp } from "lucide-react"; +import { Card, CardContent, CardHeader } from "../ui/card"; +import Explorer from "@/types/Explorer"; +import Link from "next/link"; +import { + Tooltip, + TooltipProvider, + TooltipTrigger, + TooltipContent, +} from "@radix-ui/react-tooltip"; +import useBalanceHistory from "@/hooks/api/balanceHistory/useBalanceHistory"; +import BalanceHistoryChart from "../balanceHistory/BalanceHistoryChart"; +import moment from "moment"; +import { useRouter } from "next/router"; + +// Define the type for balance operation data + +type AccountBalanceHistoryCardProps = { + header: string; + userDetails: Explorer.FormattedAccountDetails; +}; + +const AccountBalanceHistoryCard: React.FC<AccountBalanceHistoryCardProps> = ({ + header, + userDetails, +}) => { + const [isBalancesHidden, setIsBalancesHidden] = useState(false); + const defaultFromDate = React.useMemo( + () => moment().subtract(1, "month").toDate(), + [] + ); + const router = useRouter(); + const accountNameFromRoute = (router.query.accountName as string)?.slice(1); + + const { + accountBalanceHistory: hiveBalanceHistory, + isAccountBalanceHistoryLoading: hiveBalanceHistoryLoading, + isAccountBalanceHistoryError: hiveBalanceHistoryError, + } = useBalanceHistory( + accountNameFromRoute, + "HIVE", + undefined, + undefined, + "asc", + defaultFromDate + ); + + const { + accountBalanceHistory: vestsBalanceHistory, + isAccountBalanceHistoryLoading: vestsBalanceHistoryLoading, + isAccountBalanceHistoryError: vestsBalanceHistoryError, + } = useBalanceHistory( + accountNameFromRoute, + "VESTS", + undefined, + undefined, + "asc", + defaultFromDate + ); + + const { + accountBalanceHistory: hbdBalanceHistory, + isAccountBalanceHistoryLoading: hbdBalanceHistoryLoading, + isAccountBalanceHistoryError: hbdBalanceHistoryError, + } = useBalanceHistory( + accountNameFromRoute, + "HBD", + undefined, + undefined, + "asc", + defaultFromDate + ); + + const staticData = [ + { timestamp: "2023-01-01", balance: 100 }, + { timestamp: "2023-02-01", balance: 110 }, + { timestamp: "2023-03-01", balance: 120 }, + ]; + + + const handleBalancesVisibility = () => { + setIsBalancesHidden(!isBalancesHidden); + }; + + return ( + <Card data-testid="properties-dropdown" className="overflow-hidden pb-0"> + <CardHeader className="p-0"> + <div + onClick={handleBalancesVisibility} + className="flex justify-between items-center p-2 hover:bg-rowHover cursor-pointer px-4" + > + <div className="text-lg">{header}</div> + <div className="flex"> + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <Link + href={`/balanceHistory/@${userDetails.name}`} + data-testid="balance-history-link" + className="text-link text-sm underline" + onClick={(e) => e.stopPropagation()} + > + <span>Details</span> + </Link> + </TooltipTrigger> + <TooltipContent + side="top" + align="start" + sideOffset={5} + alignOffset={10} + className="border-0" + > + <div className="bg-theme text-text p-2 text-sm"> + <p>Click Here for Balance History</p> + </div> + </TooltipContent> + </Tooltip> + </TooltipProvider> + <span>{isBalancesHidden ? <ArrowDown /> : <ArrowUp />}</span> + </div> + </div> + </CardHeader> + <CardContent hidden={isBalancesHidden} data-testid="balance-history-content"> + <BalanceHistoryChart + hiveBalanceHistoryData={hiveBalanceHistory?.operations_result || []} + vestsBalanceHistoryData={vestsBalanceHistory?.operations_result || []} + hbdBalanceHistoryData={hbdBalanceHistory?.operations_result || []} + quickView={true} + className="h-[340px]" + /> + + </CardContent> + </Card> + ); +}; + +export default AccountBalanceHistoryCard; diff --git a/components/account/AccountDetailsSection.tsx b/components/account/AccountDetailsSection.tsx index 719fccb2..76749644 100644 --- a/components/account/AccountDetailsSection.tsx +++ b/components/account/AccountDetailsSection.tsx @@ -15,7 +15,7 @@ import AccountVestingDelegationsCard from "./AccountVestingDelegationsCard"; import AccountRcDelegationsCard from "./AccountRcDelegationsCard"; import AccountBalanceCard from "./AccountBalanceCard"; import Explorer from "@/types/Explorer"; - +import AccountBalanceHistoryCard from "./AccountBalanceHistoryCard"; interface AccountDetailsSectionProps { accountName: string; refetchAccountOperations: QueryObserverResult<Hive.AccountOperationsResponse>["refetch"]; @@ -66,6 +66,11 @@ const AccountDetailsSection: React.FC<AccountDetailsSectionProps> = ({ header="Wallet" userDetails={accountDetails} /> + <AccountBalanceHistoryCard + header="Balance History" + userDetails={accountDetails} + /> + <AccountDetailsCard header="Properties" userDetails={accountDetails} diff --git a/components/balanceHistory/BalanceHistoryChart.tsx b/components/balanceHistory/BalanceHistoryChart.tsx new file mode 100644 index 00000000..e69c1ccf --- /dev/null +++ b/components/balanceHistory/BalanceHistoryChart.tsx @@ -0,0 +1,254 @@ +import React, { useState, useEffect } from "react"; +import { Line, LineChart, CartesianGrid, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, Brush } from "recharts"; +import { formatNumber } from "@/lib/utils"; +import { cn } from "@/lib/utils"; +import moment from "moment"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowDown,faArrowUp } from "@fortawesome/free-solid-svg-icons"; +interface BalanceHistoryChartProps { + hiveBalanceHistoryData?: { timestamp: string; balance_change: number; balance: number }[]; + vestsBalanceHistoryData?: { timestamp: string; balance_change: number; balance: number }[]; + hbdBalanceHistoryData?: { timestamp: string; balance_change: number; balance: number }[]; + className?: string; + quickView?: boolean; +} + +const BalanceHistoryChart: React.FC<BalanceHistoryChartProps> = ({ + hiveBalanceHistoryData, + vestsBalanceHistoryData, + hbdBalanceHistoryData, + className = "", + quickView = false, +}) => { + const availableCoins: string[] = []; + + if (hiveBalanceHistoryData && hiveBalanceHistoryData.length > 0) availableCoins.push("HIVE"); + if (vestsBalanceHistoryData && vestsBalanceHistoryData.length > 0) availableCoins.push("VESTS"); + if (hbdBalanceHistoryData && hbdBalanceHistoryData.length > 0) availableCoins.push("HBD"); + + // Ensure we render a message only if there's no data available at all + /* if (availableCoins.length === 0) { + return ( + <div className="w-full text-center py-8"> + <p className="text-sm">No balance information found.</p> + </div> + ); + }*/ + + const defaultSelectedCoinType = availableCoins.length === 1 ? availableCoins[0] : "HIVE"; + const [selectedCoinType, setSelectedCoinType] = useState<string>(defaultSelectedCoinType); + const [isMobile, setIsMobile] = useState<boolean>(window.innerWidth < 480); + const [hiddenDataKeys, setHiddenDataKeys] = useState<string[]>([]); + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < 480); + }; + + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + /*useEffect(() => { + if (availableCoins.length === 1) { + setSelectedCoinType(availableCoins[0]); + } else if (availableCoins.length > 1 && !availableCoins.includes(selectedCoinType)) { + setSelectedCoinType(availableCoins[0]); + } + }, [availableCoins,selectedCoinType]);*/ + + const colorMap: Record<string, string> = { + "HIVE": "#8884d8", + "VESTS": "#82ca9d", + "HBD": "#ff7300", + }; + + const dataMap: Record<string, { timestamp: string; balance_change: number; balance: number }[]> = { + HIVE: hiveBalanceHistoryData || [], + VESTS: vestsBalanceHistoryData || [], + HBD: hbdBalanceHistoryData || [], + }; + + const handleCoinTypeChange = (coinType: string) => { + setSelectedCoinType(coinType); + }; + + const CustomTooltip = ({ + active, + payload, + label, + }: { + active?: boolean; + payload?: any[]; + label?: string; + }) => { + if (quickView || !active || !payload || payload.length === 0) return null; + + // Get the actual balance from the data + const actualBalance = dataMap[selectedCoinType]?.find( + (item) => item.timestamp === label + )?.balance ?? 0; + const balanceChange = payload[0]?.payload.balance_change ?? 0; + + // Determine if the balance change is positive or negative + const isPositiveChange = balanceChange > 0; + + return ( + <div className="bg-theme dark:bg-theme p-2 rounded border border-explorer-light-gray"> + <p className="font-bold">{`Date: ${label}`}</p> + {payload.map((pld, index) => ( + <div key={index} style={{ color: pld.stroke }}> + <div> + {/* Displaying arrow icon instead of balance change text */} + {isPositiveChange ? ( + <FontAwesomeIcon + icon={faArrowUp} + size="sm" + className="bg-green-400 p-[1.2px]" // Green for positive + /> + ) : ( + <FontAwesomeIcon + icon={faArrowDown} + size="sm" + color="red" + className="bg-red-400 p-[1.2px]" // Red for negative + /> + + )} + {` ${formatNumber( + balanceChange, + selectedCoinType === "VESTS", + false + )}`} + </div> + {/* Show the actual balance */} + <div>{`Balance: ${formatNumber( + actualBalance, + selectedCoinType === "VESTS", + false + )}`}</div> + </div> + ))} + </div> + ); + }; + + + const renderCoinButtons = () => { + return availableCoins.map((coinType) => ( + <button + key={coinType} + onClick={() => handleCoinTypeChange(coinType)} + className={cn( + "px-2 py-1 text-sm rounded m-[1px]", + selectedCoinType === coinType + ? "bg-blue-500 text-white" + : "bg-gray-200 text-black hover:bg-gray-300 dark:bg-gray-600 dark:text-white hover:dark:bg-gray-500" + )} + > + {coinType} + </button> + )); + }; + + const getMinMax = (data: { balance: number }[]) => { + const balance = data.map(item => item.balance); + const minValue = Math.min(...balance); + const maxValue = Math.max(...balance); + return [minValue, maxValue]; + }; + + if (!selectedCoinType) { + return null; + } + + const [minValue, maxValue] = getMinMax(dataMap[selectedCoinType]); + + // Determine the interval based on the length of the data + const tickInterval = Math.ceil(dataMap[selectedCoinType].length / 10); + + return ( + <div className={cn("w-full", className)}> + {availableCoins.length > 1 && ( + <div className="flex justify-end mb-4">{renderCoinButtons()}</div> + )} + + <ResponsiveContainer width="100%" height="100%"> + <LineChart + data={dataMap[selectedCoinType] || []} + margin={{ top: 20, right: 30, left: 20, bottom: isMobile ? 100 : 60 }} + > + <CartesianGrid strokeDasharray="3 3" /> + <XAxis + dataKey="timestamp" + tick={quickView ? false : true} + interval={tickInterval} + tickFormatter={(value) => moment(value).format("MMM D")} // Formatting dates to be more readable + style={{ fontSize: "10px" }} + angle={isMobile ? -90 : 0} + dx={isMobile ? -8 : 0} + dy={isMobile ? 20 : 10} + /> + <YAxis + domain={[minValue, maxValue]} + tickFormatter={(tick) => { + if (selectedCoinType === "VESTS") { + const valueInK = tick / 1000; + + let formattedValue = formatNumber(valueInK, true, false); + + formattedValue = formattedValue.split(".")[0]; + + return `${formattedValue} K`; + } + + // For other coin types, use the usual formatting + return formatNumber(tick, selectedCoinType === "VESTS", false); + }} + style={{ fontSize: "10px" }} + tickCount={6} + /> + <Tooltip content={<CustomTooltip />} /> + + <Line + type="monotone" + dataKey="balance" + stroke={colorMap[selectedCoinType]} + activeDot={{ r: 6 }} + name={selectedCoinType} + dot={false} + hide={hiddenDataKeys.includes("balance")} + /> + <Legend + onClick={(event) => { + const { dataKey } = event; + const isHidden = hiddenDataKeys.includes(dataKey); + if (isHidden) { + setHiddenDataKeys( + hiddenDataKeys.filter((key) => key !== dataKey) + ); + } else { + setHiddenDataKeys([...hiddenDataKeys, dataKey]); + } + }} + /> + {!quickView && ( + <Brush + dataKey="timestamp" + height={30} + stroke="var(--color-switch-off)" + fill="var(--color-background)" + travellerWidth={10} + tickFormatter={(value) => moment(value).format("MMM D")} + y={380} + className="text-xs" + /> + )} + + </LineChart> + </ResponsiveContainer> + </div> + ); +}; + +export default BalanceHistoryChart; \ No newline at end of file diff --git a/components/home/searches/BalanceHistorySearch.tsx b/components/home/searches/BalanceHistorySearch.tsx index a3afb6e8..de75d003 100644 --- a/components/home/searches/BalanceHistorySearch.tsx +++ b/components/home/searches/BalanceHistorySearch.tsx @@ -6,6 +6,8 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import useSearchRanges from "@/hooks/common/useSearchRanges"; import useURLParams from "@/hooks/common/useURLParams"; +import OperationTypesDialog from "@/components/OperationTypesDialog"; +import useAccountOperationTypes from "@/hooks/api/accountPage/useAccountOperationTypes"; interface AccountSearchParams { accountName?: string | undefined; @@ -19,7 +21,7 @@ interface AccountSearchParams { rangeSelectKey: string | undefined; page: number | undefined; filters: boolean[]; - coinType?: string; + coinType?: string; } const defaultSearchParams: AccountSearchParams = { @@ -42,6 +44,30 @@ const BalanceHistorySearch = () => { const COIN_TYPES = ["HIVE", "VESTS", "HBD"]; const router = useRouter(); const accountNameFromRoute = (router.query.accountName as string)?.slice(1); + const { accountOperationTypes } = + useAccountOperationTypes(accountNameFromRoute); + const [selectedOperationTypes, setSelectedOperationTypes] = useState< + number[] + >([]); + const [singleOperationTypeId, setSingleOperationTypeId] = useState< + number | undefined + >(undefined); + const [fieldContent, setFieldContent] = useState<string>(""); + const [selectedKeys, setSelectedKeys] = useState<string[] | undefined>( + undefined + ); + const [selectedIndex, setSelectedIndex] = useState<string>(""); + + const changeSelectedOperationTypes = (operationTypesIds: number[]) => { + if (operationTypesIds.length === 1) { + setSingleOperationTypeId(operationTypesIds[0]); + } else { + setSingleOperationTypeId(undefined); + } + setSelectedKeys(undefined); + setFieldContent(""); + setSelectedOperationTypes(operationTypesIds); + }; const { paramsState, setParams } = useURLParams( { @@ -65,7 +91,6 @@ const BalanceHistorySearch = () => { const [initialSearch, setInitialSearch] = useState<boolean>(false); const [filters, setFilters] = useState<boolean[]>([]); - const searchRanges = useSearchRanges(); @@ -129,8 +154,11 @@ const BalanceHistorySearch = () => { const handleCoinTypeChange = (newCoinType: string) => { setCoinType(newCoinType); - setParams({ ...paramsState, coinType: newCoinType }); - + setParams({ + ...paramsState, + coinType: newCoinType, + page: undefined, // Reset the page when the coin type changes + }); }; const handleFilterClear = () => { @@ -144,48 +172,66 @@ const BalanceHistorySearch = () => { setFilters([]); }; - useEffect(() => { + useEffect(() => { if (paramsState.coinType) { setCoinType(paramsState.coinType); - } + } if (paramsState && !initialSearch) { handleSearch(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [paramsState]); - return ( <> <p className="m-2 mb-6 mt-6"> Find balance history of given account by coin and range. </p> - + <Card className="mb-4"> <CardHeader> - <CardTitle>Filters</CardTitle> + <CardTitle className="">Filters</CardTitle> </CardHeader> <CardContent> <div className="flex items-center mb-3"> - <select - value={coinType} - onChange={(e) => handleCoinTypeChange(e.target.value)} - className="w-[180px] border border-gray-300 p-2 rounded bg-theme dark:bg-theme" - > - {COIN_TYPES.map((type) => ( - <option key={type} value={type}> - {type} - </option> - ))} - </select> - + <select + value={coinType} + onChange={(e) => handleCoinTypeChange(e.target.value)} + className="w-[180px] border border-gray-300 p-2 rounded bg-theme dark:bg-theme" + > + {COIN_TYPES.map((type) => ( + <option key={type} value={type}> + {type} + </option> + ))} + </select> </div> <SearchRanges rangesProps={searchRanges} /> - <div className="flex items-center justify-between m-2"> - <Button onClick={() => handleSearch(true)} data-testid="apply-filters"> + {/* Operations Types commented for now + <div className="flex items-center mb-10 mt-2"> + <OperationTypesDialog + operationTypes={accountOperationTypes} + selectedOperations={selectedOperationTypes} + setSelectedOperations={/*changeSelectedOperationTypes} + buttonClassName="bg-gray-500" + triggerTitle={/*getOperationButtonTitle( + selectedOperationTypes, + accountOperationTypes + )} + /> + </div> */} + <div> + <Button + onClick={() => handleSearch(true)} + data-testid="apply-filters" + > <span>Apply filters</span> </Button> - <Button onClick={() => handleFilterClear()} data-testid="clear-filters"> + <Button + onClick={() => handleFilterClear()} + data-testid="clear-filters" + className="ml-2" + > <span>Clear filters</span> </Button> </div> diff --git a/hooks/api/balanceHistory/useBalanceHistory.ts b/hooks/api/balanceHistory/useBalanceHistory.ts index 011554b9..d4bacfa3 100644 --- a/hooks/api/balanceHistory/useBalanceHistory.ts +++ b/hooks/api/balanceHistory/useBalanceHistory.ts @@ -20,7 +20,7 @@ const useBalanceHistory = ( return await fetchingService.geAccounttBalanceHistory( accountName, coinType, - page ? page : 1, + page, pageSize, direction, fromDate ? fromDate : undefined, diff --git a/pages/balanceHistory/[accountName].tsx b/pages/balanceHistory/[accountName].tsx index 6cca43e3..3225736f 100644 --- a/pages/balanceHistory/[accountName].tsx +++ b/pages/balanceHistory/[accountName].tsx @@ -2,7 +2,8 @@ import { useRouter } from "next/router"; import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; - +import React from "react"; +import moment from "moment"; import { config } from "@/Config"; import { Loader2 } from "lucide-react"; @@ -17,13 +18,66 @@ import { getHiveAvatarUrl } from "@/utils/HiveBlogUtils"; import BalanceHistoryTable from "@/components/balanceHistory/BalanceHistoryTable"; import BalanceHistorySearch from "@/components/home/searches/BalanceHistorySearch"; import { Card, CardHeader } from "@/components/ui/card"; +import BalanceHistoryChart from "@/components/balanceHistory/BalanceHistoryChart"; + +interface Operation { + timestamp: number; // Timestamp in seconds + balance: number; // Balance associated with the operation +} + +const prepareData = (operations: Operation[]) => { + if (!operations || operations.length === 0) return []; + + // Create a map to store the balance and balance change for each day + const dailyData = new Map<string, { balance: number; balance_change: number }>(); + + operations.forEach((operation: any) => { // Adjusted the type to match the structure of the data you provided + let date; + if (typeof operation.timestamp === 'string') { + date = new Date(operation.timestamp); + } else if (typeof operation.timestamp === 'number') { + date = new Date(operation.timestamp * 1000); + } else { + return; // Skip this operation if the timestamp is invalid + } + + if (!isNaN(date.getTime())) { + const dateString = date.toISOString().split('T')[0]; // Get date in YYYY-MM-DD format + + let balance_change = parseInt(operation.balance_change, 10); + let balance = parseInt(operation.balance, 10); + + // Update the map with the latest balance and balance change for the day + if (dailyData.has(dateString)) { + dailyData.get(dateString)!.balance_change += balance_change; // Accumulate balance changes for the same day + dailyData.get(dateString)!.balance = balance; // Update the balance for the day + } else { + dailyData.set(dateString, { balance, balance_change }); + } + } + }); + + // Convert the map to an array of objects with the required fields + const preparedData = Array.from(dailyData.entries()).map(([date, data]) => ({ + timestamp: date, // Use the date string directly + balance: data.balance, + balance_change: data.balance_change, // The sum of balance changes for the day + })); + + return preparedData; +}; export default function BalanceHistory() { const router = useRouter(); const accountNameFromRoute = (router.query.accountName as string)?.slice(1); // Fetch account details - const { accountDetails, isAccountDetailsLoading, isAccountDetailsError ,notFound } = useAccountDetails(accountNameFromRoute, false); + const { + accountDetails, + isAccountDetailsLoading, + isAccountDetailsError, + notFound, + } = useAccountDetails(accountNameFromRoute, false); interface BalanceHistorySearchParams { accountName?: string; @@ -70,7 +124,8 @@ export default function BalanceHistory() { page, } = paramsState; - let effectiveFromBlock = paramsState.fromBlock || fromDateParam; + const defaultFromDate = React.useMemo(() => moment().subtract(1, "month").toDate(), []); + let effectiveFromBlock = paramsState.fromBlock || fromDateParam || defaultFromDate; let effectiveToBlock = paramsState.toBlock || toDateParam; if ( @@ -95,13 +150,39 @@ export default function BalanceHistory() { effectiveToBlock ); + + const defaultChartSize = 6000; + + const chartPageSize = (accountBalanceHistory?.total_operations !== undefined && accountBalanceHistory.total_operations !== 0 && accountBalanceHistory.total_operations < defaultChartSize) + ? accountBalanceHistory.total_operations + : defaultChartSize; + const chartData = useBalanceHistory( + accountNameFromRoute, + paramsState.coinType, + undefined, + chartPageSize, + "asc", + effectiveFromBlock, + effectiveToBlock + ); + + const preparedData = chartData.accountBalanceHistory + ? prepareData(chartData.accountBalanceHistory.operations_result) + : []; + // Determine the message to display based on the filters + let message = ""; + if (effectiveFromBlock === defaultFromDate && !fromBlockParam && !toBlockParam) { + message = "Showing Results for the last month."; + } else { + message = "Showing Results with applied filters."; + } + return ( <> <Head> <title>@{accountNameFromRoute} - Hive Explorer</title> </Head> - {/* Loading state for account details */} {isAccountDetailsLoading ? ( <div className="flex justify-center text-center items-center"> <Loader2 className="animate-spin mt-1 text-black h-12 w-12 ml-3" /> @@ -114,7 +195,6 @@ export default function BalanceHistory() { <Card data-testid="account-details"> <CardHeader> <div className="flex flex-wrap items-center justify-between gap-4 bg-theme dark:bg-theme"> - {/* Avatar and Name */} <div className="flex items-center gap-4"> <Image className="rounded-full border-2 border-explorer-orange" @@ -125,15 +205,8 @@ export default function BalanceHistory() { data-testid="user-avatar" /> <div> - <h2 - className="text-lg font-semibold text-gray-800 dark:text-white" - data-testid="account-name" - > - <Link - className="text-link" - href={`/@${accountNameFromRoute}`} - > - {" "} + <h2 className="text-lg font-semibold text-gray-800 dark:text-white" data-testid="account-name"> + <Link className="text-link" href={`/@${accountNameFromRoute}`}> {accountNameFromRoute} </Link> / <span className="text-text">Balance History</span> @@ -144,10 +217,8 @@ export default function BalanceHistory() { </CardHeader> </Card> - {/* Filter Options (Always visible) */} <BalanceHistorySearch /> - {/* Show Error Message if No Balance History and No Loading State */} {!isAccountBalanceHistoryLoading && !accountBalanceHistory?.total_operations ? ( <div className="w-full my-4 text-center"> No operations were found. @@ -157,15 +228,34 @@ export default function BalanceHistory() { <Loader2 className="animate-spin mt-1 h-12 w-12 ml-3" /> </div> ) : ( - // Show the table when balance history exists - <BalanceHistoryTable - operations={convertBalanceHistoryResultsToTableOperations( - accountBalanceHistory + <> + + <Card data-testid="account-details"> + {/* Display the message */} + {message && ( + <div className="p-4 bg-gray-100 dark:bg-gray-700 rounded-lg mb-4 text-center text-sm text-gray-500"> + {message}<br/> + Results are limited to {defaultChartSize} records and grouped by day.<br/> + + </div> )} - total_operations={accountBalanceHistory.total_operations} - total_pages={accountBalanceHistory.total_pages} - current_page={paramsState.page} - /> + + <BalanceHistoryChart + hiveBalanceHistoryData={(!paramsState.coinType || paramsState.coinType === "HIVE") ? preparedData : undefined} + vestsBalanceHistoryData={paramsState.coinType === "VESTS" ? preparedData : undefined} + hbdBalanceHistoryData={paramsState.coinType === "HBD" ? preparedData : undefined} + quickView={false} + className="h-[450px] mb-10 mr-0 pr-1 pb-6" + /> + </Card> + + <BalanceHistoryTable + operations={convertBalanceHistoryResultsToTableOperations(accountBalanceHistory)} + total_operations={accountBalanceHistory.total_operations} + total_pages={accountBalanceHistory.total_pages} + current_page={paramsState.page} + /> + </> )} </div> ) -- GitLab