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 { marketsProvider } from '@vegaprotocol/markets';
|
||||
import { assetsMapProvider } from '@vegaprotocol/assets';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||
import {
|
||||
makeDataProvider,
|
||||
makeDerivedDataProvider,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import produce from 'immer';
|
||||
|
||||
import {
|
||||
@ -20,7 +21,6 @@ import type {
|
||||
AccountEventsSubscription,
|
||||
AccountsQueryVariables,
|
||||
} from './__generated__/Accounts';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
|
||||
const AccountType = Schema.AccountType;
|
||||
@ -45,9 +45,9 @@ export const getId = (
|
||||
? `${account.type}-${account.asset.id}-${account.market?.id || 'null'}`
|
||||
: `${account.type}-${account.assetId}-${account.marketId || 'null'}`;
|
||||
|
||||
export type Account = Omit<AccountFieldsFragment, 'market' | 'asset'> & {
|
||||
market?: Market | null;
|
||||
export type Account = Omit<AccountFieldsFragment, 'asset' | 'market'> & {
|
||||
asset: Asset;
|
||||
market?: Market | null;
|
||||
};
|
||||
|
||||
const update = (
|
||||
@ -170,31 +170,22 @@ export const accountsDataProvider = makeDerivedDataProvider<
|
||||
>(
|
||||
[
|
||||
accountsOnlyDataProvider,
|
||||
(callback, client) => marketsProvider(callback, client, undefined),
|
||||
(callback, client) => assetsProvider(callback, client, undefined),
|
||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||
(callback, client) => assetsMapProvider(callback, client, undefined),
|
||||
],
|
||||
([accounts, markets, assets]): Account[] | null => {
|
||||
return accounts
|
||||
? accounts
|
||||
.map((account: AccountFieldsFragment) => {
|
||||
const market = markets.find(
|
||||
(market: Market) => market.id === account.market?.id
|
||||
);
|
||||
const asset = assets.find(
|
||||
(asset: Asset) => asset.id === account.asset?.id
|
||||
);
|
||||
const asset = (assets as Record<string, Asset>)[account.asset.id];
|
||||
const market =
|
||||
account.market?.id &&
|
||||
(markets as Record<string, Asset>)[account.market?.id];
|
||||
if (asset) {
|
||||
return {
|
||||
...account,
|
||||
partyId: account.party?.id,
|
||||
asset: {
|
||||
...asset,
|
||||
},
|
||||
market: market
|
||||
? {
|
||||
...market,
|
||||
}
|
||||
: null,
|
||||
asset,
|
||||
market,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
@ -212,3 +203,18 @@ export const aggregatedAccountsDataProvider = makeDerivedDataProvider<
|
||||
[accountsDataProvider],
|
||||
(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 { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
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 { 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 {
|
||||
partyId: string;
|
||||
@ -30,16 +71,59 @@ export const AccountManager = ({
|
||||
storeKey,
|
||||
}: AccountManagerProps) => {
|
||||
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({
|
||||
dataProvider: aggregatedAccountsDataProvider,
|
||||
variables,
|
||||
variables: { partyId },
|
||||
update,
|
||||
});
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
disabled: noBottomPlaceholder,
|
||||
});
|
||||
|
||||
useEffect(
|
||||
() => setHasData(Boolean(pinnedAsset || data?.length)),
|
||||
[data, pinnedAsset]
|
||||
);
|
||||
|
||||
const onRowDataUpdated = useCallback(
|
||||
(event: RowDataUpdatedEvent) => {
|
||||
setHasData(Boolean(pinnedAsset || event.api?.getModel().getRowCount()));
|
||||
},
|
||||
[pinnedAsset]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative h-full">
|
||||
<AccountTable
|
||||
@ -48,6 +132,8 @@ export const AccountManager = ({
|
||||
onClickAsset={onClickAsset}
|
||||
onClickDeposit={onClickDeposit}
|
||||
onClickWithdraw={onClickWithdraw}
|
||||
onClickBreakdown={setBreakdownAssetId}
|
||||
onRowDataUpdated={onRowDataUpdated}
|
||||
isReadOnly={isReadOnly}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
@ -58,13 +144,26 @@ export const AccountManager = ({
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
data={data}
|
||||
noDataCondition={(data) => !(data && data.length)}
|
||||
noDataCondition={() => !hasData}
|
||||
error={error}
|
||||
loading={loading}
|
||||
noDataMessage={pinnedAsset ? ' ' : t('No accounts')}
|
||||
reload={reload}
|
||||
/>
|
||||
</div>
|
||||
<Dialog
|
||||
size="medium"
|
||||
open={Boolean(breakdownAssetId)}
|
||||
onChange={(isOpen) => {
|
||||
if (!isOpen) {
|
||||
setBreakdownAssetId(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{breakdownAssetId && (
|
||||
<AccountBreakdown assetId={breakdownAssetId} partyId={partyId} />
|
||||
)}
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -10,13 +10,6 @@ const singleRow = {
|
||||
balance: '125600000',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
||||
},
|
||||
},
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
},
|
||||
asset: {
|
||||
@ -136,13 +129,6 @@ describe('AccountsTable', () => {
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'ACCOUNT_TYPE_MARGIN',
|
||||
used: '125600000',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
||||
import { forwardRef, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
isNumeric,
|
||||
@ -11,28 +11,25 @@ import type {
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
Button,
|
||||
ButtonLink,
|
||||
Dialog,
|
||||
Button,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
CenteredGridCellWrapper,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type {
|
||||
IDatasource,
|
||||
IGetRowsParams,
|
||||
RowHeightParams,
|
||||
RowNode,
|
||||
RowHeightParams,
|
||||
} from 'ag-grid-community';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import BreakdownTable from './breakdown-table';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import type { Asset } from '@vegaprotocol/types';
|
||||
import { CenteredGridCellWrapper } from '@vegaprotocol/datagrid';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import classNames from 'classnames';
|
||||
import { AccountsActionsDropdown } from './accounts-actions-dropdown';
|
||||
@ -96,34 +93,42 @@ export interface AccountTableProps extends AgGridReactProps {
|
||||
onClickAsset: (assetId: string) => void;
|
||||
onClickWithdraw?: (assetId: string) => void;
|
||||
onClickDeposit?: (assetId: string) => void;
|
||||
onClickBreakdown?: (assetId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
storeKey?: string;
|
||||
}
|
||||
|
||||
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
({ onClickAsset, onClickWithdraw, onClickDeposit, ...props }, ref) => {
|
||||
const [openBreakdown, setOpenBreakdown] = useState(false);
|
||||
const [row, setRow] = useState<AccountFields>();
|
||||
const pinnedAssetId = props.pinnedAsset?.id;
|
||||
|
||||
(
|
||||
{
|
||||
onClickAsset,
|
||||
onClickWithdraw,
|
||||
onClickDeposit,
|
||||
onClickBreakdown,
|
||||
rowData,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const pinnedAsset = useMemo(() => {
|
||||
const currentPinnedAssetRow = props.rowData?.find(
|
||||
(row) => row.asset.id === pinnedAssetId
|
||||
if (!props.pinnedAsset) {
|
||||
return;
|
||||
}
|
||||
const currentPinnedAssetRow = rowData?.find(
|
||||
(row) => row.asset.id === props.pinnedAsset?.id
|
||||
);
|
||||
if (!currentPinnedAssetRow) {
|
||||
if (props.pinnedAsset) {
|
||||
return {
|
||||
asset: props.pinnedAsset,
|
||||
available: '0',
|
||||
used: '0',
|
||||
total: '0',
|
||||
balance: '0',
|
||||
};
|
||||
}
|
||||
return {
|
||||
asset: props.pinnedAsset,
|
||||
available: '0',
|
||||
used: '0',
|
||||
total: '0',
|
||||
balance: '0',
|
||||
};
|
||||
}
|
||||
return currentPinnedAssetRow;
|
||||
}, [pinnedAssetId, props.pinnedAsset, props.rowData]);
|
||||
}, [props.pinnedAsset, rowData]);
|
||||
|
||||
const { getRowHeight } = props;
|
||||
|
||||
@ -131,225 +136,192 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
(params: RowHeightParams) => {
|
||||
if (
|
||||
params.node.rowPinned &&
|
||||
params.data.asset.id === pinnedAssetId &&
|
||||
params.data.asset.id === props.pinnedAsset?.id &&
|
||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||
) {
|
||||
return 32;
|
||||
}
|
||||
return getRowHeight ? getRowHeight(params) : undefined;
|
||||
},
|
||||
[pinnedAssetId, getRowHeight]
|
||||
[props.pinnedAsset?.id, getRowHeight]
|
||||
);
|
||||
|
||||
const accountForPinnedAsset = props?.rowData?.find(
|
||||
(a) => a.asset.id === pinnedAssetId
|
||||
);
|
||||
const showDepositButton = accountForPinnedAsset
|
||||
? new BigNumber(accountForPinnedAsset.total).isLessThanOrEqualTo(0)
|
||||
: true;
|
||||
const showDepositButton = pinnedAsset?.balance === '0';
|
||||
|
||||
return (
|
||||
<>
|
||||
<AgGrid
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No accounts')}
|
||||
getRowId={({
|
||||
data,
|
||||
}: {
|
||||
data: AccountFields & { isLastPlaceholder?: boolean; id?: string };
|
||||
}) => (data.isLastPlaceholder && data.id ? data.id : data.asset.id)}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
rowData={props.rowData?.filter(
|
||||
(data) => data.asset.id !== pinnedAssetId
|
||||
<AgGrid
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No accounts')}
|
||||
getRowId={({
|
||||
data,
|
||||
}: {
|
||||
data: AccountFields & { isLastPlaceholder?: boolean; id?: string };
|
||||
}) => (data.isLastPlaceholder && data.id ? data.id : data.asset.id)}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
rowData={rowData?.filter(
|
||||
(data) => data.asset.id !== props.pinnedAsset?.id
|
||||
)}
|
||||
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={{
|
||||
resizable: true,
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
sortable: true,
|
||||
comparator: accountValuesComparator,
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
|
||||
return (
|
||||
<ButtonLink
|
||||
data-testid="asset"
|
||||
onClick={() => {
|
||||
if (data) {
|
||||
onClickAsset(data.asset.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</ButtonLink>
|
||||
);
|
||||
}}
|
||||
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.'
|
||||
)}
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
|
||||
return (
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Used')}
|
||||
type="rightAligned"
|
||||
field="used"
|
||||
headerTooltip={t(
|
||||
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
||||
)}
|
||||
cellRenderer={({
|
||||
data,
|
||||
value,
|
||||
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||
if (!data) return null;
|
||||
const percentageUsed = percentageValue(value, data.total);
|
||||
const valueFormatted = formatWithAssetDecimals(data, value);
|
||||
|
||||
return data.breakdown ? (
|
||||
<>
|
||||
<ButtonLink
|
||||
data-testid="asset"
|
||||
data-testid="breakdown"
|
||||
onClick={() => {
|
||||
if (data) {
|
||||
onClickAsset(data.asset.id);
|
||||
}
|
||||
onClickBreakdown && onClickBreakdown(data.asset.id);
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
<span>{valueFormatted}</span>
|
||||
</ButtonLink>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Used')}
|
||||
type="rightAligned"
|
||||
field="used"
|
||||
headerTooltip={t(
|
||||
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
||||
)}
|
||||
cellRenderer={({
|
||||
data,
|
||||
value,
|
||||
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||
if (!data) return null;
|
||||
const percentageUsed = percentageValue(value, data.total);
|
||||
const valueFormatted = formatWithAssetDecimals(data, value);
|
||||
<span
|
||||
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 data.breakdown ? (
|
||||
<>
|
||||
<ButtonLink
|
||||
data-testid="breakdown"
|
||||
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)
|
||||
}
|
||||
/>
|
||||
<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={() => {
|
||||
setOpenBreakdown(!openBreakdown);
|
||||
setRow(data);
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
>
|
||||
<span>{valueFormatted}</span>
|
||||
</ButtonLink>
|
||||
<span
|
||||
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>
|
||||
</>
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
||||
</Button>
|
||||
</CenteredGridCellWrapper>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<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)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="accounts-actions"
|
||||
{...COL_DEFS.actions}
|
||||
minWidth={showDepositButton ? 130 : COL_DEFS.actions.minWidth}
|
||||
maxWidth={showDepositButton ? 130 : COL_DEFS.actions.maxWidth}
|
||||
cellRenderer={({
|
||||
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 props.isReadOnly ? null : (
|
||||
<AccountsActionsDropdown
|
||||
assetId={assetId}
|
||||
assetContractAddress={
|
||||
node.data?.asset.source?.__typename === 'ERC20'
|
||||
? node.data.asset.source.contractAddress
|
||||
: undefined
|
||||
}
|
||||
return (
|
||||
!props.isReadOnly && (
|
||||
<AccountsActionsDropdown
|
||||
assetId={data.asset.id}
|
||||
assetContractAddress={
|
||||
data.asset.source?.__typename === 'ERC20'
|
||||
? data.asset.source.contractAddress
|
||||
: undefined
|
||||
}
|
||||
onClickDeposit={() => {
|
||||
onClickDeposit && onClickDeposit(data.asset.id);
|
||||
}}
|
||||
onClickWithdraw={() => {
|
||||
onClickWithdraw && onClickWithdraw(data.asset.id);
|
||||
}}
|
||||
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>
|
||||
</>
|
||||
onClickDeposit={() => {
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
onClickWithdraw={() => {
|
||||
onClickWithdraw && onClickWithdraw(assetId);
|
||||
}}
|
||||
onClickBreakdown={() => {
|
||||
onClickBreakdown && onClickBreakdown(assetId);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -31,6 +31,29 @@ export const assetsProvider = makeDataProvider<
|
||||
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<
|
||||
ReturnType<typeof getData>,
|
||||
never
|
||||
|
@ -4,17 +4,18 @@ import type {
|
||||
ValueFormatterParams,
|
||||
ValueGetterParams,
|
||||
} 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';
|
||||
|
||||
type Field = string | readonly string[];
|
||||
|
||||
type RowHelper<TObj, TRow, TField extends Field> = Omit<
|
||||
TObj,
|
||||
'data' | 'value'
|
||||
'data' | 'value' | 'node'
|
||||
> & {
|
||||
data?: TRow;
|
||||
value?: Get<TRow, TField>;
|
||||
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
|
||||
};
|
||||
|
||||
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
|
||||
@ -29,10 +30,10 @@ export type VegaValueGetterParams<TRow, TField extends Field> = RowHelper<
|
||||
TField
|
||||
>;
|
||||
|
||||
export type VegaICellRendererParams<
|
||||
TRow,
|
||||
TField extends Field = string
|
||||
> = RowHelper<ICellRendererParams, TRow, TField>;
|
||||
export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit<
|
||||
RowHelper<ICellRendererParams, TRow, TField>,
|
||||
'node'
|
||||
> & { node: NonNullable<RowHelper<ICellRendererParams, TRow, TField>['node']> };
|
||||
|
||||
export interface GetRowsParams<T> extends IGetRowsParams {
|
||||
successCallback(rowsThisBlock: T[], lastRow?: number): void;
|
||||
|
@ -41,6 +41,29 @@ export const marketsProvider = makeDataProvider<
|
||||
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<
|
||||
Market,
|
||||
never,
|
||||
|
@ -27,7 +27,7 @@ describe('WithdrawFormContainer', () => {
|
||||
const account1: Account = {
|
||||
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
balance: '200099689',
|
||||
market: null,
|
||||
market: undefined,
|
||||
asset: {
|
||||
id: 'assetId-1',
|
||||
name: 'tBTC TEST',
|
||||
|
Loading…
Reference in New Issue
Block a user