fix(accounts): avoid last row re-rendering (#3950)
This commit is contained in:
parent
18c034b910
commit
d6e2432955
@ -1,11 +1,12 @@
|
|||||||
import { assetsProvider } from '@vegaprotocol/assets';
|
import { assetsMapProvider } from '@vegaprotocol/assets';
|
||||||
import { marketsProvider } from '@vegaprotocol/markets';
|
|
||||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||||
|
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||||
import {
|
import {
|
||||||
makeDataProvider,
|
makeDataProvider,
|
||||||
makeDerivedDataProvider,
|
makeDerivedDataProvider,
|
||||||
} from '@vegaprotocol/data-provider';
|
} from '@vegaprotocol/data-provider';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -20,7 +21,6 @@ import type {
|
|||||||
AccountEventsSubscription,
|
AccountEventsSubscription,
|
||||||
AccountsQueryVariables,
|
AccountsQueryVariables,
|
||||||
} from './__generated__/Accounts';
|
} from './__generated__/Accounts';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
|
||||||
import type { Asset } from '@vegaprotocol/assets';
|
import type { Asset } from '@vegaprotocol/assets';
|
||||||
|
|
||||||
const AccountType = Schema.AccountType;
|
const AccountType = Schema.AccountType;
|
||||||
@ -45,9 +45,9 @@ export const getId = (
|
|||||||
? `${account.type}-${account.asset.id}-${account.market?.id || 'null'}`
|
? `${account.type}-${account.asset.id}-${account.market?.id || 'null'}`
|
||||||
: `${account.type}-${account.assetId}-${account.marketId || 'null'}`;
|
: `${account.type}-${account.assetId}-${account.marketId || 'null'}`;
|
||||||
|
|
||||||
export type Account = Omit<AccountFieldsFragment, 'market' | 'asset'> & {
|
export type Account = Omit<AccountFieldsFragment, 'asset' | 'market'> & {
|
||||||
market?: Market | null;
|
|
||||||
asset: Asset;
|
asset: Asset;
|
||||||
|
market?: Market | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = (
|
const update = (
|
||||||
@ -170,31 +170,22 @@ export const accountsDataProvider = makeDerivedDataProvider<
|
|||||||
>(
|
>(
|
||||||
[
|
[
|
||||||
accountsOnlyDataProvider,
|
accountsOnlyDataProvider,
|
||||||
(callback, client) => marketsProvider(callback, client, undefined),
|
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||||
(callback, client) => assetsProvider(callback, client, undefined),
|
(callback, client) => assetsMapProvider(callback, client, undefined),
|
||||||
],
|
],
|
||||||
([accounts, markets, assets]): Account[] | null => {
|
([accounts, markets, assets]): Account[] | null => {
|
||||||
return accounts
|
return accounts
|
||||||
? accounts
|
? accounts
|
||||||
.map((account: AccountFieldsFragment) => {
|
.map((account: AccountFieldsFragment) => {
|
||||||
const market = markets.find(
|
const asset = (assets as Record<string, Asset>)[account.asset.id];
|
||||||
(market: Market) => market.id === account.market?.id
|
const market =
|
||||||
);
|
account.market?.id &&
|
||||||
const asset = assets.find(
|
(markets as Record<string, Asset>)[account.market?.id];
|
||||||
(asset: Asset) => asset.id === account.asset?.id
|
|
||||||
);
|
|
||||||
if (asset) {
|
if (asset) {
|
||||||
return {
|
return {
|
||||||
...account,
|
...account,
|
||||||
partyId: account.party?.id,
|
asset,
|
||||||
asset: {
|
market,
|
||||||
...asset,
|
|
||||||
},
|
|
||||||
market: market
|
|
||||||
? {
|
|
||||||
...market,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -212,3 +203,18 @@ export const aggregatedAccountsDataProvider = makeDerivedDataProvider<
|
|||||||
[accountsDataProvider],
|
[accountsDataProvider],
|
||||||
(parts) => parts[0] && getAccountData(parts[0] as Account[])
|
(parts) => parts[0] && getAccountData(parts[0] as Account[])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const aggregatedAccountDataProvider = makeDerivedDataProvider<
|
||||||
|
AccountFields,
|
||||||
|
never,
|
||||||
|
AccountsQueryVariables & { assetId: string }
|
||||||
|
>(
|
||||||
|
[
|
||||||
|
(callback, client, { partyId }) =>
|
||||||
|
aggregatedAccountsDataProvider(callback, client, { partyId }),
|
||||||
|
],
|
||||||
|
(parts, { assetId }) =>
|
||||||
|
(parts[0] as AccountFields[]).find(
|
||||||
|
(account) => account.asset.id === assetId
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
@ -1,12 +1,53 @@
|
|||||||
import { useRef, useMemo, memo } from 'react';
|
import { useRef, memo, useCallback, useState, useEffect } from 'react';
|
||||||
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { aggregatedAccountsDataProvider } from './accounts-data-provider';
|
import type { RowDataUpdatedEvent } from 'ag-grid-community';
|
||||||
|
import type { AccountFields } from './accounts-data-provider';
|
||||||
|
import {
|
||||||
|
aggregatedAccountsDataProvider,
|
||||||
|
aggregatedAccountDataProvider,
|
||||||
|
} from './accounts-data-provider';
|
||||||
import type { PinnedAsset } from './accounts-table';
|
import type { PinnedAsset } from './accounts-table';
|
||||||
import { AccountTable } from './accounts-table';
|
import { AccountTable } from './accounts-table';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import BreakdownTable from './breakdown-table';
|
||||||
|
|
||||||
|
const AccountBreakdown = ({
|
||||||
|
assetId,
|
||||||
|
partyId,
|
||||||
|
}: {
|
||||||
|
assetId: string;
|
||||||
|
partyId: string;
|
||||||
|
}) => {
|
||||||
|
const { data } = useDataProvider({
|
||||||
|
dataProvider: aggregatedAccountDataProvider,
|
||||||
|
variables: { partyId, assetId },
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="h-[35vh] w-full m-auto flex flex-col"
|
||||||
|
data-testid="usage-breakdown"
|
||||||
|
>
|
||||||
|
<h1 className="text-xl mb-4">
|
||||||
|
{data?.asset?.symbol} {t('usage breakdown')}
|
||||||
|
</h1>
|
||||||
|
{data && (
|
||||||
|
<p className="mb-2 text-sm">
|
||||||
|
{t('You have %s %s in total.', [
|
||||||
|
addDecimalsFormatNumber(data.total, data.asset.decimals),
|
||||||
|
data.asset.symbol,
|
||||||
|
])}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<BreakdownTable data={data?.breakdown || null} domLayout="autoHeight" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface AccountManagerProps {
|
interface AccountManagerProps {
|
||||||
partyId: string;
|
partyId: string;
|
||||||
@ -30,16 +71,59 @@ export const AccountManager = ({
|
|||||||
storeKey,
|
storeKey,
|
||||||
}: AccountManagerProps) => {
|
}: AccountManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
const [hasData, setHasData] = useState(Boolean(pinnedAsset));
|
||||||
|
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
|
||||||
|
const update = useCallback(
|
||||||
|
({ data }: { data: AccountFields[] | null }) => {
|
||||||
|
if (!data || !gridRef.current?.api) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const pinnedAssetRowData =
|
||||||
|
pinnedAsset && data.find((d) => d.asset.id === pinnedAsset.id);
|
||||||
|
|
||||||
|
if (pinnedAssetRowData) {
|
||||||
|
const pinnedTopRow = gridRef.current.api.getPinnedTopRow(0);
|
||||||
|
if (
|
||||||
|
pinnedTopRow?.data?.balance === '0' &&
|
||||||
|
pinnedAssetRowData.balance !== '0'
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isEqual(pinnedTopRow?.data, pinnedAssetRowData)) {
|
||||||
|
gridRef.current.api.setPinnedTopRowData([pinnedAssetRowData]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gridRef.current.api.setRowData(
|
||||||
|
pinnedAssetRowData
|
||||||
|
? data?.filter((d) => d !== pinnedAssetRowData)
|
||||||
|
: data
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[gridRef, pinnedAsset]
|
||||||
|
);
|
||||||
const { data, loading, error, reload } = useDataProvider({
|
const { data, loading, error, reload } = useDataProvider({
|
||||||
dataProvider: aggregatedAccountsDataProvider,
|
dataProvider: aggregatedAccountsDataProvider,
|
||||||
variables,
|
variables: { partyId },
|
||||||
|
update,
|
||||||
});
|
});
|
||||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||||
gridRef,
|
gridRef,
|
||||||
disabled: noBottomPlaceholder,
|
disabled: noBottomPlaceholder,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => setHasData(Boolean(pinnedAsset || data?.length)),
|
||||||
|
[data, pinnedAsset]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onRowDataUpdated = useCallback(
|
||||||
|
(event: RowDataUpdatedEvent) => {
|
||||||
|
setHasData(Boolean(pinnedAsset || event.api?.getModel().getRowCount()));
|
||||||
|
},
|
||||||
|
[pinnedAsset]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full">
|
<div className="relative h-full">
|
||||||
<AccountTable
|
<AccountTable
|
||||||
@ -48,6 +132,8 @@ export const AccountManager = ({
|
|||||||
onClickAsset={onClickAsset}
|
onClickAsset={onClickAsset}
|
||||||
onClickDeposit={onClickDeposit}
|
onClickDeposit={onClickDeposit}
|
||||||
onClickWithdraw={onClickWithdraw}
|
onClickWithdraw={onClickWithdraw}
|
||||||
|
onClickBreakdown={setBreakdownAssetId}
|
||||||
|
onRowDataUpdated={onRowDataUpdated}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
suppressLoadingOverlay
|
suppressLoadingOverlay
|
||||||
suppressNoRowsOverlay
|
suppressNoRowsOverlay
|
||||||
@ -58,13 +144,26 @@ export const AccountManager = ({
|
|||||||
<div className="pointer-events-none absolute inset-0">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
data={data}
|
data={data}
|
||||||
noDataCondition={(data) => !(data && data.length)}
|
noDataCondition={() => !hasData}
|
||||||
error={error}
|
error={error}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
noDataMessage={pinnedAsset ? ' ' : t('No accounts')}
|
noDataMessage={pinnedAsset ? ' ' : t('No accounts')}
|
||||||
reload={reload}
|
reload={reload}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Dialog
|
||||||
|
size="medium"
|
||||||
|
open={Boolean(breakdownAssetId)}
|
||||||
|
onChange={(isOpen) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setBreakdownAssetId(undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{breakdownAssetId && (
|
||||||
|
<AccountBreakdown assetId={breakdownAssetId} partyId={partyId} />
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -10,13 +10,6 @@ const singleRow = {
|
|||||||
balance: '125600000',
|
balance: '125600000',
|
||||||
market: {
|
market: {
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
tradableInstrument: {
|
|
||||||
__typename: 'TradableInstrument',
|
|
||||||
instrument: {
|
|
||||||
__typename: 'Instrument',
|
|
||||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||||
},
|
},
|
||||||
asset: {
|
asset: {
|
||||||
@ -136,13 +129,6 @@ describe('AccountsTable', () => {
|
|||||||
market: {
|
market: {
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||||
tradableInstrument: {
|
|
||||||
__typename: 'TradableInstrument',
|
|
||||||
instrument: {
|
|
||||||
__typename: 'Instrument',
|
|
||||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
type: 'ACCOUNT_TYPE_MARGIN',
|
type: 'ACCOUNT_TYPE_MARGIN',
|
||||||
used: '125600000',
|
used: '125600000',
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
import { forwardRef, useMemo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
isNumeric,
|
isNumeric,
|
||||||
@ -11,28 +11,25 @@ import type {
|
|||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
ButtonLink,
|
ButtonLink,
|
||||||
Dialog,
|
Button,
|
||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
AgGridLazy as AgGrid,
|
|
||||||
CenteredGridCellWrapper,
|
|
||||||
} from '@vegaprotocol/datagrid';
|
|
||||||
import { AgGridColumn } from 'ag-grid-react';
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
import type {
|
import type {
|
||||||
IDatasource,
|
IDatasource,
|
||||||
IGetRowsParams,
|
IGetRowsParams,
|
||||||
RowHeightParams,
|
|
||||||
RowNode,
|
RowNode,
|
||||||
|
RowHeightParams,
|
||||||
} from 'ag-grid-community';
|
} from 'ag-grid-community';
|
||||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||||
import BreakdownTable from './breakdown-table';
|
|
||||||
import type { AccountFields } from './accounts-data-provider';
|
import type { AccountFields } from './accounts-data-provider';
|
||||||
import type { Asset } from '@vegaprotocol/types';
|
import type { Asset } from '@vegaprotocol/types';
|
||||||
|
import { CenteredGridCellWrapper } from '@vegaprotocol/datagrid';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { AccountsActionsDropdown } from './accounts-actions-dropdown';
|
import { AccountsActionsDropdown } from './accounts-actions-dropdown';
|
||||||
@ -96,34 +93,42 @@ export interface AccountTableProps extends AgGridReactProps {
|
|||||||
onClickAsset: (assetId: string) => void;
|
onClickAsset: (assetId: string) => void;
|
||||||
onClickWithdraw?: (assetId: string) => void;
|
onClickWithdraw?: (assetId: string) => void;
|
||||||
onClickDeposit?: (assetId: string) => void;
|
onClickDeposit?: (assetId: string) => void;
|
||||||
|
onClickBreakdown?: (assetId: string) => void;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
storeKey?: string;
|
storeKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||||
({ onClickAsset, onClickWithdraw, onClickDeposit, ...props }, ref) => {
|
(
|
||||||
const [openBreakdown, setOpenBreakdown] = useState(false);
|
{
|
||||||
const [row, setRow] = useState<AccountFields>();
|
onClickAsset,
|
||||||
const pinnedAssetId = props.pinnedAsset?.id;
|
onClickWithdraw,
|
||||||
|
onClickDeposit,
|
||||||
|
onClickBreakdown,
|
||||||
|
rowData,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const pinnedAsset = useMemo(() => {
|
const pinnedAsset = useMemo(() => {
|
||||||
const currentPinnedAssetRow = props.rowData?.find(
|
if (!props.pinnedAsset) {
|
||||||
(row) => row.asset.id === pinnedAssetId
|
return;
|
||||||
|
}
|
||||||
|
const currentPinnedAssetRow = rowData?.find(
|
||||||
|
(row) => row.asset.id === props.pinnedAsset?.id
|
||||||
);
|
);
|
||||||
if (!currentPinnedAssetRow) {
|
if (!currentPinnedAssetRow) {
|
||||||
if (props.pinnedAsset) {
|
return {
|
||||||
return {
|
asset: props.pinnedAsset,
|
||||||
asset: props.pinnedAsset,
|
available: '0',
|
||||||
available: '0',
|
used: '0',
|
||||||
used: '0',
|
total: '0',
|
||||||
total: '0',
|
balance: '0',
|
||||||
balance: '0',
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return currentPinnedAssetRow;
|
return currentPinnedAssetRow;
|
||||||
}, [pinnedAssetId, props.pinnedAsset, props.rowData]);
|
}, [props.pinnedAsset, rowData]);
|
||||||
|
|
||||||
const { getRowHeight } = props;
|
const { getRowHeight } = props;
|
||||||
|
|
||||||
@ -131,225 +136,192 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
|||||||
(params: RowHeightParams) => {
|
(params: RowHeightParams) => {
|
||||||
if (
|
if (
|
||||||
params.node.rowPinned &&
|
params.node.rowPinned &&
|
||||||
params.data.asset.id === pinnedAssetId &&
|
params.data.asset.id === props.pinnedAsset?.id &&
|
||||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||||
) {
|
) {
|
||||||
return 32;
|
return 32;
|
||||||
}
|
}
|
||||||
return getRowHeight ? getRowHeight(params) : undefined;
|
return getRowHeight ? getRowHeight(params) : undefined;
|
||||||
},
|
},
|
||||||
[pinnedAssetId, getRowHeight]
|
[props.pinnedAsset?.id, getRowHeight]
|
||||||
);
|
);
|
||||||
|
|
||||||
const accountForPinnedAsset = props?.rowData?.find(
|
const showDepositButton = pinnedAsset?.balance === '0';
|
||||||
(a) => a.asset.id === pinnedAssetId
|
|
||||||
);
|
|
||||||
const showDepositButton = accountForPinnedAsset
|
|
||||||
? new BigNumber(accountForPinnedAsset.total).isLessThanOrEqualTo(0)
|
|
||||||
: true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AgGrid
|
||||||
<AgGrid
|
{...props}
|
||||||
{...props}
|
style={{ width: '100%', height: '100%' }}
|
||||||
style={{ width: '100%', height: '100%' }}
|
overlayNoRowsTemplate={t('No accounts')}
|
||||||
overlayNoRowsTemplate={t('No accounts')}
|
getRowId={({
|
||||||
getRowId={({
|
data,
|
||||||
data,
|
}: {
|
||||||
}: {
|
data: AccountFields & { isLastPlaceholder?: boolean; id?: string };
|
||||||
data: AccountFields & { isLastPlaceholder?: boolean; id?: string };
|
}) => (data.isLastPlaceholder && data.id ? data.id : data.asset.id)}
|
||||||
}) => (data.isLastPlaceholder && data.id ? data.id : data.asset.id)}
|
ref={ref}
|
||||||
ref={ref}
|
tooltipShowDelay={500}
|
||||||
tooltipShowDelay={500}
|
rowData={rowData?.filter(
|
||||||
rowData={props.rowData?.filter(
|
(data) => data.asset.id !== props.pinnedAsset?.id
|
||||||
(data) => data.asset.id !== pinnedAssetId
|
)}
|
||||||
|
defaultColDef={{
|
||||||
|
resizable: true,
|
||||||
|
tooltipComponent: TooltipCellComponent,
|
||||||
|
sortable: true,
|
||||||
|
comparator: accountValuesComparator,
|
||||||
|
}}
|
||||||
|
getRowHeight={getPinnedAssetRowHeight}
|
||||||
|
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined}
|
||||||
|
>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Asset')}
|
||||||
|
field="asset.symbol"
|
||||||
|
headerTooltip={t(
|
||||||
|
'Asset is the collateral that is deposited into the Vega protocol.'
|
||||||
)}
|
)}
|
||||||
defaultColDef={{
|
cellRenderer={({
|
||||||
resizable: true,
|
value,
|
||||||
tooltipComponent: TooltipCellComponent,
|
data,
|
||||||
sortable: true,
|
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
|
||||||
comparator: accountValuesComparator,
|
return (
|
||||||
|
<ButtonLink
|
||||||
|
data-testid="asset"
|
||||||
|
onClick={() => {
|
||||||
|
if (data) {
|
||||||
|
onClickAsset(data.asset.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</ButtonLink>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
getRowHeight={getPinnedAssetRowHeight}
|
/>
|
||||||
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined}
|
<AgGridColumn
|
||||||
>
|
headerName={t('Used')}
|
||||||
<AgGridColumn
|
type="rightAligned"
|
||||||
headerName={t('Asset')}
|
field="used"
|
||||||
field="asset.symbol"
|
headerTooltip={t(
|
||||||
headerTooltip={t(
|
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
||||||
'Asset is the collateral that is deposited into the Vega protocol.'
|
)}
|
||||||
)}
|
cellRenderer={({
|
||||||
cellRenderer={({
|
data,
|
||||||
value,
|
value,
|
||||||
data,
|
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||||
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
|
if (!data) return null;
|
||||||
return (
|
const percentageUsed = percentageValue(value, data.total);
|
||||||
|
const valueFormatted = formatWithAssetDecimals(data, value);
|
||||||
|
|
||||||
|
return data.breakdown ? (
|
||||||
|
<>
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
data-testid="asset"
|
data-testid="breakdown"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (data) {
|
onClickBreakdown && onClickBreakdown(data.asset.id);
|
||||||
onClickAsset(data.asset.id);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{value}
|
<span>{valueFormatted}</span>
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
);
|
<span
|
||||||
}}
|
className={classNames(
|
||||||
/>
|
colorClass(percentageUsed),
|
||||||
<AgGridColumn
|
'ml-2 inline-block w-14'
|
||||||
headerName={t('Used')}
|
)}
|
||||||
type="rightAligned"
|
>
|
||||||
field="used"
|
{percentageUsed.toFixed(2)}%
|
||||||
headerTooltip={t(
|
</span>
|
||||||
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
</>
|
||||||
)}
|
) : (
|
||||||
cellRenderer={({
|
<>
|
||||||
data,
|
<span>{valueFormatted}</span>
|
||||||
value,
|
<span className="ml-2 inline-block w-14 text-neutral-500 dark:text-neutral-400">
|
||||||
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
0.00%
|
||||||
if (!data) return null;
|
</span>
|
||||||
const percentageUsed = percentageValue(value, data.total);
|
</>
|
||||||
const valueFormatted = formatWithAssetDecimals(data, value);
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Available')}
|
||||||
|
field="available"
|
||||||
|
type="rightAligned"
|
||||||
|
headerTooltip={t(
|
||||||
|
'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
|
||||||
|
)}
|
||||||
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaICellRendererParams<AccountFields, 'available'>) => {
|
||||||
|
const percentageUsed = percentageValue(data?.used, data?.total);
|
||||||
|
|
||||||
return data.breakdown ? (
|
return (
|
||||||
<>
|
<span className={colorClass(percentageUsed, true)}>
|
||||||
<ButtonLink
|
{formatWithAssetDecimals(data, value)}
|
||||||
data-testid="breakdown"
|
</span>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Total')}
|
||||||
|
type="rightAligned"
|
||||||
|
field="total"
|
||||||
|
headerTooltip={t(
|
||||||
|
'The total amount of each asset on this key. Includes used and available collateral.'
|
||||||
|
)}
|
||||||
|
valueFormatter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<AccountFields, 'total'>) =>
|
||||||
|
formatWithAssetDecimals(data, data?.total)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
colId="accounts-actions"
|
||||||
|
field="asset.id"
|
||||||
|
{...COL_DEFS.actions}
|
||||||
|
minWidth={showDepositButton ? 130 : COL_DEFS.actions.minWidth}
|
||||||
|
maxWidth={showDepositButton ? 130 : COL_DEFS.actions.maxWidth}
|
||||||
|
cellRenderer={({
|
||||||
|
value: assetId,
|
||||||
|
node,
|
||||||
|
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
|
||||||
|
if (!assetId) return null;
|
||||||
|
if (node.rowPinned && node.data?.balance === '0') {
|
||||||
|
return (
|
||||||
|
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="primary"
|
||||||
|
data-testid="deposit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpenBreakdown(!openBreakdown);
|
onClickDeposit && onClickDeposit(assetId);
|
||||||
setRow(data);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>{valueFormatted}</span>
|
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
||||||
</ButtonLink>
|
</Button>
|
||||||
<span
|
</CenteredGridCellWrapper>
|
||||||
className={classNames(
|
|
||||||
colorClass(percentageUsed),
|
|
||||||
'ml-2 inline-block w-14'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{percentageUsed.toFixed(2)}%
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span>{valueFormatted}</span>
|
|
||||||
<span className="ml-2 inline-block w-14 text-neutral-500 dark:text-neutral-400">
|
|
||||||
0.00%
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Available')}
|
|
||||||
field="available"
|
|
||||||
type="rightAligned"
|
|
||||||
headerTooltip={t(
|
|
||||||
'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
|
|
||||||
)}
|
|
||||||
cellRenderer={({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaICellRendererParams<AccountFields, 'available'>) => {
|
|
||||||
const percentageUsed = percentageValue(data?.used, data?.total);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={colorClass(percentageUsed, true)}>
|
|
||||||
{formatWithAssetDecimals(data, value)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Total')}
|
|
||||||
type="rightAligned"
|
|
||||||
field="total"
|
|
||||||
headerTooltip={t(
|
|
||||||
'The total amount of each asset on this key. Includes used and available collateral.'
|
|
||||||
)}
|
|
||||||
valueFormatter={({
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<AccountFields, 'total'>) =>
|
|
||||||
formatWithAssetDecimals(data, data?.total)
|
|
||||||
}
|
}
|
||||||
/>
|
return props.isReadOnly ? null : (
|
||||||
<AgGridColumn
|
<AccountsActionsDropdown
|
||||||
colId="accounts-actions"
|
assetId={assetId}
|
||||||
{...COL_DEFS.actions}
|
assetContractAddress={
|
||||||
minWidth={showDepositButton ? 130 : COL_DEFS.actions.minWidth}
|
node.data?.asset.source?.__typename === 'ERC20'
|
||||||
maxWidth={showDepositButton ? 130 : COL_DEFS.actions.maxWidth}
|
? node.data.asset.source.contractAddress
|
||||||
cellRenderer={({
|
: undefined
|
||||||
data,
|
|
||||||
}: VegaICellRendererParams<AccountFields>) => {
|
|
||||||
if (!data) return null;
|
|
||||||
else {
|
|
||||||
if (showDepositButton && data.asset.id === pinnedAssetId) {
|
|
||||||
return (
|
|
||||||
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
variant="primary"
|
|
||||||
data-testid="deposit"
|
|
||||||
onClick={() => {
|
|
||||||
onClickDeposit && onClickDeposit(data.asset.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
|
||||||
</Button>
|
|
||||||
</CenteredGridCellWrapper>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return (
|
onClickDeposit={() => {
|
||||||
!props.isReadOnly && (
|
onClickDeposit && onClickDeposit(assetId);
|
||||||
<AccountsActionsDropdown
|
}}
|
||||||
assetId={data.asset.id}
|
onClickWithdraw={() => {
|
||||||
assetContractAddress={
|
onClickWithdraw && onClickWithdraw(assetId);
|
||||||
data.asset.source?.__typename === 'ERC20'
|
}}
|
||||||
? data.asset.source.contractAddress
|
onClickBreakdown={() => {
|
||||||
: undefined
|
onClickBreakdown && onClickBreakdown(assetId);
|
||||||
}
|
}}
|
||||||
onClickDeposit={() => {
|
/>
|
||||||
onClickDeposit && onClickDeposit(data.asset.id);
|
);
|
||||||
}}
|
}}
|
||||||
onClickWithdraw={() => {
|
/>
|
||||||
onClickWithdraw && onClickWithdraw(data.asset.id);
|
</AgGrid>
|
||||||
}}
|
|
||||||
onClickBreakdown={() => {
|
|
||||||
setOpenBreakdown(!openBreakdown);
|
|
||||||
setRow(data);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</AgGrid>
|
|
||||||
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
|
|
||||||
<div
|
|
||||||
className="h-[35vh] w-full m-auto flex flex-col"
|
|
||||||
data-testid="usage-breakdown"
|
|
||||||
>
|
|
||||||
<h1 className="text-xl mb-4">
|
|
||||||
{row?.asset?.symbol} {t('usage breakdown')}
|
|
||||||
</h1>
|
|
||||||
{row && (
|
|
||||||
<p className="mb-2 text-sm">
|
|
||||||
{t('You have %s %s in total.', [
|
|
||||||
addDecimalsFormatNumber(row.total, row.asset.decimals),
|
|
||||||
row.asset.symbol,
|
|
||||||
])}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<BreakdownTable
|
|
||||||
data={row?.breakdown || null}
|
|
||||||
domLayout="autoHeight"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -31,6 +31,29 @@ export const assetsProvider = makeDataProvider<
|
|||||||
getData,
|
getData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const assetsMapProvider = makeDerivedDataProvider<
|
||||||
|
Record<string, Asset>,
|
||||||
|
never,
|
||||||
|
undefined
|
||||||
|
>(
|
||||||
|
[(callback, client) => assetsProvider(callback, client, undefined)],
|
||||||
|
([assets]) => {
|
||||||
|
return ((assets as ReturnType<typeof getData>) || []).reduce(
|
||||||
|
(assets, asset) => {
|
||||||
|
assets[asset.id] = asset;
|
||||||
|
return assets;
|
||||||
|
},
|
||||||
|
{} as Record<string, Asset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useAssetsMapProvider = () =>
|
||||||
|
useDataProvider({
|
||||||
|
dataProvider: assetsMapProvider,
|
||||||
|
variables: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
export const enabledAssetsProvider = makeDerivedDataProvider<
|
export const enabledAssetsProvider = makeDerivedDataProvider<
|
||||||
ReturnType<typeof getData>,
|
ReturnType<typeof getData>,
|
||||||
never
|
never
|
||||||
|
@ -4,17 +4,18 @@ import type {
|
|||||||
ValueFormatterParams,
|
ValueFormatterParams,
|
||||||
ValueGetterParams,
|
ValueGetterParams,
|
||||||
} from 'ag-grid-community';
|
} from 'ag-grid-community';
|
||||||
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
|
import type { IDatasource, IGetRowsParams, RowNode } from 'ag-grid-community';
|
||||||
import type { AgGridReactProps } from 'ag-grid-react';
|
import type { AgGridReactProps } from 'ag-grid-react';
|
||||||
|
|
||||||
type Field = string | readonly string[];
|
type Field = string | readonly string[];
|
||||||
|
|
||||||
type RowHelper<TObj, TRow, TField extends Field> = Omit<
|
type RowHelper<TObj, TRow, TField extends Field> = Omit<
|
||||||
TObj,
|
TObj,
|
||||||
'data' | 'value'
|
'data' | 'value' | 'node'
|
||||||
> & {
|
> & {
|
||||||
data?: TRow;
|
data?: TRow;
|
||||||
value?: Get<TRow, TField>;
|
value?: Get<TRow, TField>;
|
||||||
|
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
|
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
|
||||||
@ -29,10 +30,10 @@ export type VegaValueGetterParams<TRow, TField extends Field> = RowHelper<
|
|||||||
TField
|
TField
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type VegaICellRendererParams<
|
export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit<
|
||||||
TRow,
|
RowHelper<ICellRendererParams, TRow, TField>,
|
||||||
TField extends Field = string
|
'node'
|
||||||
> = RowHelper<ICellRendererParams, TRow, TField>;
|
> & { node: NonNullable<RowHelper<ICellRendererParams, TRow, TField>['node']> };
|
||||||
|
|
||||||
export interface GetRowsParams<T> extends IGetRowsParams {
|
export interface GetRowsParams<T> extends IGetRowsParams {
|
||||||
successCallback(rowsThisBlock: T[], lastRow?: number): void;
|
successCallback(rowsThisBlock: T[], lastRow?: number): void;
|
||||||
|
@ -41,6 +41,29 @@ export const marketsProvider = makeDataProvider<
|
|||||||
fetchPolicy: 'cache-first',
|
fetchPolicy: 'cache-first',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const marketsMapProvider = makeDerivedDataProvider<
|
||||||
|
Record<string, Market>,
|
||||||
|
never,
|
||||||
|
undefined
|
||||||
|
>(
|
||||||
|
[(callback, client) => marketsProvider(callback, client, undefined)],
|
||||||
|
([markets]) => {
|
||||||
|
return ((markets as ReturnType<typeof getData>) || []).reduce(
|
||||||
|
(markets, market) => {
|
||||||
|
markets[market.id] = market;
|
||||||
|
return markets;
|
||||||
|
},
|
||||||
|
{} as Record<string, Market>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useMarketsMapProvider = () =>
|
||||||
|
useDataProvider({
|
||||||
|
dataProvider: marketsMapProvider,
|
||||||
|
variables: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
export const marketProvider = makeDerivedDataProvider<
|
export const marketProvider = makeDerivedDataProvider<
|
||||||
Market,
|
Market,
|
||||||
never,
|
never,
|
||||||
|
@ -27,7 +27,7 @@ describe('WithdrawFormContainer', () => {
|
|||||||
const account1: Account = {
|
const account1: Account = {
|
||||||
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
|
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
balance: '200099689',
|
balance: '200099689',
|
||||||
market: null,
|
market: undefined,
|
||||||
asset: {
|
asset: {
|
||||||
id: 'assetId-1',
|
id: 'assetId-1',
|
||||||
name: 'tBTC TEST',
|
name: 'tBTC TEST',
|
||||||
|
Loading…
Reference in New Issue
Block a user