import { addDecimal, fromNanoSeconds } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; import { useVegaWallet } from '@vegaprotocol/wallet'; import compact from 'lodash/compact'; import uniqBy from 'lodash/uniqBy'; import type { ChangeEvent } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import type { AccountHistoryQuery } from './__generated__/AccountHistory'; import { useAccountHistoryQuery } from './__generated__/AccountHistory'; import * as Schema from '@vegaprotocol/types'; import type { AssetFieldsFragment } from '@vegaprotocol/assets'; import { useAssetsDataProvider } from '@vegaprotocol/assets'; import { AsyncRenderer, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Splash, Toggle, } from '@vegaprotocol/ui-toolkit'; import { AccountTypeMapping } from '@vegaprotocol/types'; import { PriceChart } from 'pennant'; import 'pennant/dist/style.css'; import type { Account } from '@vegaprotocol/accounts'; import { accountsDataProvider } from '@vegaprotocol/accounts'; import { useThemeSwitcher } from '@vegaprotocol/react-helpers'; import { useDataProvider } from '@vegaprotocol/data-provider'; import type { Market } from '@vegaprotocol/markets'; const DateRange = { RANGE_1D: '1D', RANGE_7D: '7D', RANGE_1M: '1M', RANGE_3M: '3M', RANGE_1Y: '1Y', RANGE_YTD: 'YTD', RANGE_ALL: 'All', }; const dateRangeToggleItems = Object.entries(DateRange).map(([_, value]) => ({ label: t(value), value: value, })); const calculateStartDate = (range: string): string | undefined => { const now = new Date(); switch (range) { case DateRange.RANGE_1D: return new Date(now.setDate(now.getDate() - 1)).toISOString(); case DateRange.RANGE_7D: return new Date(now.setDate(now.getDate() - 7)).toISOString(); case DateRange.RANGE_1M: return new Date(now.setMonth(now.getMonth() - 1)).toISOString(); case DateRange.RANGE_3M: return new Date(now.setMonth(now.getMonth() - 3)).toISOString(); case DateRange.RANGE_1Y: return new Date(now.setFullYear(now.getFullYear() - 1)).toISOString(); case DateRange.RANGE_YTD: return new Date(now.setMonth(0)).toISOString(); default: return undefined; } }; export const AccountHistoryContainer = () => { const { pubKey } = useVegaWallet(); const { data: assets } = useAssetsDataProvider(); if (!pubKey) { return Connect wallet; } return ( {assets && } ); }; const AccountHistoryManager = ({ pubKey, assetData, }: { pubKey: string; assetData: AssetFieldsFragment[]; }) => { const [accountType, setAccountType] = useState( Schema.AccountType.ACCOUNT_TYPE_GENERAL ); const variablesForOneTimeQuery = useMemo( () => ({ partyId: pubKey, }), [pubKey] ); const { data: accounts } = useDataProvider({ dataProvider: accountsDataProvider, variables: variablesForOneTimeQuery, skip: !pubKey, }); const assetIds = useMemo( () => accounts?.map((e) => e?.asset?.id) || [], [accounts] ); const assets = useMemo( () => assetData .filter((a) => assetIds.includes(a.id)) .sort((a, b) => a.name.localeCompare(b.name)), [assetData, assetIds] ); const [asset, setAsset] = useState(assets[0]); const [range, setRange] = useState( DateRange.RANGE_1M ); const [market, setMarket] = useState(null); const marketFilterCb = useCallback( (item: Market) => !asset?.id || item.tradableInstrument.instrument.product.settlementAsset.id === asset?.id, [asset?.id] ); const markets = useMemo(() => { const arr = accounts ?.filter((item: Account) => Boolean(item && item.market)) .map((item) => item.market as Market) ?? null; return arr ? uniqBy(arr.filter(marketFilterCb), 'id').sort((a, b) => a.tradableInstrument.instrument.code.localeCompare( b.tradableInstrument.instrument.code ) ) : null; }, [accounts, marketFilterCb]); const resolveMarket = useCallback( (m: Market) => { setMarket(m); const newAssetId = m.tradableInstrument.instrument.product.settlementAsset.id; const newAsset = assets.find((item) => item.id === newAssetId); if ((!asset || (assets && newAssetId !== asset.id)) && newAsset) { setAsset(newAsset); } }, [asset, assets] ); const variables = useMemo( () => ({ partyId: pubKey, assetId: asset?.id || '', accountTypes: accountType ? [accountType] : undefined, dateRange: range === 'All' ? undefined : { start: calculateStartDate(range) }, marketIds: market?.id ? [market.id] : undefined, }), [pubKey, asset, accountType, range, market?.id] ); const { data } = useAccountHistoryQuery({ variables, skip: !asset || !pubKey, }); const accountTypeMenu = useMemo(() => { return ( {accountType ? `${ AccountTypeMapping[ accountType as keyof typeof Schema.AccountType ] } Account` : t('Select account type')} } > {[ Schema.AccountType.ACCOUNT_TYPE_GENERAL, Schema.AccountType.ACCOUNT_TYPE_BOND, Schema.AccountType.ACCOUNT_TYPE_MARGIN, ].map((type) => ( setAccountType(type as Schema.AccountType)} > {AccountTypeMapping[type as keyof typeof Schema.AccountType]} ))} ); }, [accountType]); const assetsMenu = useMemo(() => { return ( {asset ? asset.symbol : t('Select asset')} } > {assets.map((a) => ( setAsset(a)}> {a.symbol} ))} ); }, [assets, asset]); const marketsMenu = useMemo(() => { return accountType === Schema.AccountType.ACCOUNT_TYPE_MARGIN && markets?.length ? ( {market ? market.tradableInstrument.instrument.code : t('Select market')} } > {market && ( setMarket(null)}> {t('All markets')} )} {markets?.map((m) => ( resolveMarket(m)}> {m.tradableInstrument.instrument.code} ))} ) : null; }, [markets, market, accountType, resolveMarket]); useEffect(() => { if ( accountType !== Schema.AccountType.ACCOUNT_TYPE_MARGIN || market?.tradableInstrument.instrument.product.settlementAsset.id !== asset?.id ) { setMarket(null); } }, [accountType, asset?.id, market]); return ( <> {accountTypeMenu} {assetsMenu} {marketsMenu} > ) => setRange(e.target.value as keyof typeof DateRange) } /> {asset && ( )} ); }; export const AccountHistoryChart = ({ data, accountType, asset, }: { data: AccountHistoryQuery | undefined; accountType: Schema.AccountType; asset: AssetFieldsFragment; }) => { const { theme } = useThemeSwitcher(); const values: { cols: [string, string]; rows: [Date, number][] } | null = useMemo(() => { if (!data?.balanceChanges.edges.length) { return null; } const valuesData = compact(data.balanceChanges.edges) .reduce((acc, edge) => { if (edge.node.accountType === accountType) { acc?.push({ datetime: fromNanoSeconds(edge.node.timestamp), balance: Number(addDecimal(edge.node.balance, asset.decimals)), }); } return acc; }, [] as { datetime: Date; balance: number }[]) .reverse(); return { cols: ['Date', `${asset.symbol} account balance`], rows: compact(valuesData).map((d) => [d.datetime, d.balance]), }; }, [accountType, asset.decimals, asset.symbol, data?.balanceChanges.edges]); if (!data || !values?.rows.length) { return {t('No account history data')}; } return ; };