feat(trading): clarify collateral and breakdown tables (#3113)

This commit is contained in:
m.ray 2023-03-13 14:15:53 -04:00 committed by GitHub
parent 3edd91eadb
commit 64cf0c90a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 386 additions and 294 deletions

View File

@ -22,24 +22,25 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId('tab-accounts') cy.getByTestId('tab-accounts')
.get(tradingAccountRowId) .get(tradingAccountRowId)
.find('[col-id="breakdown"] [data-testid="breakdown"]') .find('[col-id="accounts-actions"]')
.should('have.text', 'Breakdown'); .should('have.text', 'DepositWithdraw');
cy.getByTestId('tab-accounts') cy.getByTestId('tab-accounts')
.get(tradingAccountRowId) .get(tradingAccountRowId)
.find('[col-id="breakdown"] [data-testid="deposit"]') .find('[data-testid="deposit"]')
.should('have.text', 'Deposit'); .should('have.text', 'Deposit');
cy.getByTestId('tab-accounts') cy.getByTestId('tab-accounts')
.get(tradingAccountRowId) .get(tradingAccountRowId)
.find('[col-id="breakdown"] [data-testid="withdraw"]') .find('[col-id="accounts-actions"] [data-testid="withdraw"]')
.should('have.text', 'Withdraw'); .should('have.text', 'Withdraw');
cy.getByTestId('tab-accounts') cy.getByTestId('tab-accounts')
.get(tradingAccountRowId) .get(tradingAccountRowId)
.find('[col-id="deposited"]') .find('[col-id="total"]')
.should('have.text', '100,001.01'); .should('have.text', '100,001.01');
}); });
describe('sorting by ag-grid columns should work well', () => { describe('sorting by ag-grid columns should work well', () => {
it('sorting by asset', () => { it('sorting by asset', () => {
cy.getByTestId('Collateral').click(); cy.getByTestId('Collateral').click();
@ -78,7 +79,7 @@ describe('accounts', { tags: '@smoke' }, () => {
'1,000.00', '1,000.00',
]; ];
checkSorting( checkSorting(
'deposited', 'total',
marketsSortedDefault, marketsSortedDefault,
marketsSortedAsc, marketsSortedAsc,
marketsSortedDesc marketsSortedDesc
@ -87,9 +88,27 @@ describe('accounts', { tags: '@smoke' }, () => {
it('sorting by used', () => { it('sorting by used', () => {
cy.getByTestId('Collateral').click(); cy.getByTestId('Collateral').click();
const marketsSortedDefault = ['0.00', '1.01', '0.01', '0.00', '0.00']; const marketsSortedDefault = [
const marketsSortedAsc = ['0.00', '0.00', '0.00', '0.01', '1.01']; '0.000.00%',
const marketsSortedDesc = ['1.01', '0.01', '0.00', '0.00', '0.00']; '1.010.00%',
'0.010.00%',
'0.000.00%',
'0.000.00%',
];
const marketsSortedAsc = [
'0.000.00%',
'0.000.00%',
'0.000.00%',
'0.010.00%',
'1.010.00%',
];
const marketsSortedDesc = [
'1.010.00%',
'0.010.00%',
'0.000.00%',
'0.000.00%',
'0.000.00%',
];
checkSorting( checkSorting(
'used', 'used',
marketsSortedDefault, marketsSortedDefault,
@ -98,32 +117,32 @@ describe('accounts', { tags: '@smoke' }, () => {
); );
}); });
it('sorting by available', () => { it('sorting by total', () => {
cy.getByTestId('Collateral').click(); cy.getByTestId('Collateral').click();
const marketsSortedDefault = [ const marketsSortedDefault = [
'1,000.00002', '1,000.00002',
'100,000.00', '100,001.01',
'1,000.00', '1,000.01',
'1,000.00', '1,000.00',
'1,000.00001', '1,000.00001',
]; ];
const marketsSortedAsc = [ const marketsSortedAsc = [
'1,000.00',
'1,000.00', '1,000.00',
'1,000.00001', '1,000.00001',
'1,000.00002', '1,000.00002',
'100,000.00', '1,000.01',
'100,001.01',
]; ];
const marketsSortedDesc = [ const marketsSortedDesc = [
'100,000.00', '100,001.01',
'1,000.01',
'1,000.00002', '1,000.00002',
'1,000.00001', '1,000.00001',
'1,000.00', '1,000.00',
'1,000.00',
]; ];
checkSorting( checkSorting(
'available', 'total',
marketsSortedDefault, marketsSortedDefault,
marketsSortedAsc, marketsSortedAsc,
marketsSortedDesc marketsSortedDesc

View File

@ -159,38 +159,50 @@ const accountResult = [
{ {
asset: { asset: {
__typename: 'Asset', __typename: 'Asset',
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c', id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC', symbol: 'tBTC',
decimals: 5,
}, },
available: '4000000000000001006031',
balance: '4000000000000001006031', balance: '4000000000000001006031',
breakdown: [], type: 'ACCOUNT_TYPE_GENERAL',
deposited: '4000000000000001006031', available: '4000000000000001006031',
type: AccountType.ACCOUNT_TYPE_GENERAL,
used: '0', used: '0',
total: '4000000000000001006031',
breakdown: [
{
__typename: 'AccountBalance',
type: 'ACCOUNT_TYPE_GENERAL',
balance: '4000000000000001006031',
market: null,
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
decimals: 5,
},
total: '4000000000000001006031',
available: '4000000000000001006031',
used: '4000000000000001006031',
},
],
}, },
{ {
asset: { asset: {
__typename: 'Asset', __typename: 'Asset',
decimals: 5,
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI', symbol: 'tDAI',
decimals: 5,
}, },
available: '5000593078',
balance: '5000593078', balance: '5000593078',
type: 'ACCOUNT_TYPE_GENERAL',
available: '5000593078',
used: '406922',
total: '5001000000',
breakdown: [ breakdown: [
{ {
__typename: 'AccountBalance', __typename: 'AccountBalance',
asset: { type: 'ACCOUNT_TYPE_MARGIN',
__typename: 'Asset',
decimals: 5,
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
},
available: '5000593078',
balance: '406922', balance: '406922',
deposited: '5001000000',
market: { market: {
__typename: 'Market', __typename: 'Market',
id: '9c1ee71959e566c484fcea796513137f8a02219cca2e973b7ae72dc29d099581', id: '9c1ee71959e566c484fcea796513137f8a02219cca2e973b7ae72dc29d099581',
@ -202,35 +214,50 @@ const accountResult = [
}, },
}, },
}, },
type: AccountType.ACCOUNT_TYPE_MARGIN, asset: {
__typename: 'Asset',
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
decimals: 5,
},
total: '5001000000',
available: '5000593078',
used: '406922', used: '406922',
}, },
{
__typename: 'AccountBalance',
type: 'ACCOUNT_TYPE_GENERAL',
balance: '5000593078',
market: null,
asset: {
__typename: 'Asset',
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
decimals: 5,
},
total: '5001000000',
available: '5000593078',
used: '5000593078',
},
], ],
deposited: '5001000000',
type: AccountType.ACCOUNT_TYPE_GENERAL,
used: '406922',
}, },
{ {
asset: { asset: {
__typename: 'Asset', __typename: 'Asset',
decimals: 5,
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4', id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
symbol: 'tEURO', symbol: 'tEURO',
decimals: 5,
}, },
available: '2996218603',
balance: '2996218603', balance: '2996218603',
type: 'ACCOUNT_TYPE_GENERAL',
available: '2996218603',
used: '2781397',
total: '2999000000',
breakdown: [ breakdown: [
{ {
__typename: 'AccountBalance', __typename: 'AccountBalance',
asset: { type: 'ACCOUNT_TYPE_MARGIN',
__typename: 'Asset',
decimals: 5,
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
symbol: 'tEURO',
},
available: '2996218603',
balance: '2781397', balance: '2781397',
deposited: '2999000000',
market: { market: {
__typename: 'Market', __typename: 'Market',
id: 'd90fd7c746286625504d7a3f5f420a280875acd3cd611676d9e70acc675f4540', id: 'd90fd7c746286625504d7a3f5f420a280875acd3cd611676d9e70acc675f4540',
@ -242,40 +269,91 @@ const accountResult = [
}, },
}, },
}, },
type: AccountType.ACCOUNT_TYPE_MARGIN, asset: {
__typename: 'Asset',
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
symbol: 'tEURO',
decimals: 5,
},
total: '2999000000',
available: '2996218603',
used: '2781397', used: '2781397',
}, },
{
__typename: 'AccountBalance',
type: 'ACCOUNT_TYPE_GENERAL',
balance: '2996218603',
market: null,
asset: {
__typename: 'Asset',
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
symbol: 'tEURO',
decimals: 5,
},
total: '2999000000',
available: '2996218603',
used: '2996218603',
},
], ],
deposited: '2999000000',
type: AccountType.ACCOUNT_TYPE_GENERAL,
used: '2781397',
}, },
{ {
asset: { asset: {
__typename: 'Asset', __typename: 'Asset',
decimals: 5,
id: '993ed98f4f770d91a796faab1738551193ba45c62341d20597df70fea6704ede', id: '993ed98f4f770d91a796faab1738551193ba45c62341d20597df70fea6704ede',
symbol: 'tUSDC', symbol: 'tUSDC',
decimals: 5,
}, },
available: '1990351587',
balance: '1990351587', balance: '1990351587',
breakdown: [], type: 'ACCOUNT_TYPE_GENERAL',
deposited: '1990351587', available: '1990351587',
type: AccountType.ACCOUNT_TYPE_GENERAL,
used: '0', used: '0',
total: '1990351587',
breakdown: [
{
__typename: 'AccountBalance',
type: 'ACCOUNT_TYPE_GENERAL',
balance: '1990351587',
market: null,
asset: {
__typename: 'Asset',
id: '993ed98f4f770d91a796faab1738551193ba45c62341d20597df70fea6704ede',
symbol: 'tUSDC',
decimals: 5,
},
total: '1990351587',
available: '1990351587',
used: '1990351587',
},
],
}, },
{ {
asset: { asset: {
__typename: 'Asset', __typename: 'Asset',
decimals: 5,
id: 'XYZalpha', id: 'XYZalpha',
symbol: 'XYZalpha', symbol: 'XYZalpha',
decimals: 5,
}, },
available: '10001000000',
balance: '10001000000', balance: '10001000000',
breakdown: [], type: 'ACCOUNT_TYPE_GENERAL',
deposited: '10001000000', available: '10001000000',
type: AccountType.ACCOUNT_TYPE_GENERAL,
used: '0', used: '0',
total: '10001000000',
breakdown: [
{
__typename: 'AccountBalance',
type: 'ACCOUNT_TYPE_GENERAL',
balance: '10001000000',
market: null,
asset: {
__typename: 'Asset',
id: 'XYZalpha',
symbol: 'XYZalpha',
decimals: 5,
},
total: '10001000000',
available: '10001000000',
used: '10001000000',
},
],
}, },
] as AccountFields[]; ] as AccountFields[];

View File

@ -99,7 +99,7 @@ export const accountsOnlyDataProvider = makeDataProvider<
export interface AccountFields extends Account { export interface AccountFields extends Account {
available: string; available: string;
used: string; used: string;
deposited: string; total: string;
balance: string; balance: string;
breakdown?: AccountFields[]; breakdown?: AccountFields[];
} }
@ -145,15 +145,17 @@ const getAssetAccountAggregation = (
type: AccountType.ACCOUNT_TYPE_GENERAL, type: AccountType.ACCOUNT_TYPE_GENERAL,
available: available.toString(), available: available.toString(),
used: used.toString(), used: used.toString(),
deposited: (available + used).toString(), total: (available + used).toString(),
}; };
const breakdown = accounts const breakdown = accounts
.filter((a) => USE_ACCOUNT_TYPES.includes(a.type)) .filter((a) =>
[...USE_ACCOUNT_TYPES, AccountType.ACCOUNT_TYPE_GENERAL].includes(a.type)
)
.map((a) => ({ .map((a) => ({
...a, ...a,
asset: accounts[0].asset, asset: accounts[0].asset,
deposited: balanceAccount.deposited, total: balanceAccount.total,
available: balanceAccount.available, available: balanceAccount.available,
used: a.balance, used: a.balance,
})) }))

View File

@ -27,7 +27,7 @@ const singleRow = {
}, },
available: '125600000', available: '125600000',
used: '125600000', used: '125600000',
deposited: '125600000', total: '251200000',
} as AccountFields; } as AccountFields;
const singleRowData = [singleRow]; const singleRowData = [singleRow];
@ -42,7 +42,7 @@ describe('AccountsTable', () => {
/> />
); );
}); });
const expectedHeaders = ['Asset', 'Total', 'Used', 'Available', '']; const expectedHeaders = ['Asset', 'Used', 'Available', 'Total', ''];
const headers = await screen.findAllByRole('columnheader'); const headers = await screen.findAllByRole('columnheader');
expect(headers).toHaveLength(expectedHeaders.length); expect(headers).toHaveLength(expectedHeaders.length);
expect( expect(
@ -65,8 +65,7 @@ describe('AccountsTable', () => {
'tBTC', 'tBTC',
'1,256.00', '1,256.00',
'1,256.00', '1,256.00',
'1,256.00', '2,512.00',
'Breakdown',
'Deposit', 'Deposit',
'Withdraw', 'Withdraw',
]; ];
@ -88,13 +87,8 @@ describe('AccountsTable', () => {
); );
}); });
const cells = await screen.findAllByRole('gridcell'); const cells = await screen.findAllByRole('gridcell');
const expectedValues = [ const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
'tBTC', expect(cells.length).toBe(expectedValues.length);
'1,256.00',
'1,256.00',
'1,256.00',
'Breakdown',
];
cells.forEach((cell, i) => { cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]); expect(cell).toHaveTextContent(expectedValues[i]);
}); });
@ -145,7 +139,7 @@ describe('AccountsTable', () => {
}, },
available: '0', available: '0',
balance: '125600000', balance: '125600000',
deposited: '125600000', total: '125600000',
market: { market: {
__typename: 'Market', __typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35', id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
@ -161,7 +155,7 @@ describe('AccountsTable', () => {
used: '125600000', used: '125600000',
}, },
], ],
deposited: '125600000', total: '125600000',
type: 'ACCOUNT_TYPE_GENERAL', type: 'ACCOUNT_TYPE_GENERAL',
used: '125600000', used: '125600000',
}, },

View File

@ -8,7 +8,6 @@ import { t } from '@vegaprotocol/i18n';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
VegaValueFormatterParams, VegaValueFormatterParams,
VegaValueGetterParams,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import { Button, ButtonLink, Dialog } from '@vegaprotocol/ui-toolkit'; import { Button, ButtonLink, Dialog } from '@vegaprotocol/ui-toolkit';
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit'; import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
@ -17,12 +16,56 @@ import {
CenteredGridCellWrapper, CenteredGridCellWrapper,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import { AgGridColumn } from 'ag-grid-react'; import { AgGridColumn } from 'ag-grid-react';
import type { IDatasource, IGetRowsParams } from 'ag-grid-community'; import type { IDatasource, IGetRowsParams, RowNode } 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 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 BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import classNames from 'classnames';
const colorClass = (percentageUsed: number, neutral = false) => {
return classNames({
'text-neutral-500 dark:text-neutral-400': percentageUsed < 75 && !neutral,
'text-vega-orange': percentageUsed >= 75 && percentageUsed < 90,
'text-vega-pink': percentageUsed >= 90,
});
};
export const percentageValue = (part?: string, total?: string) =>
new BigNumber(part || 0)
.dividedBy(total || 1)
.multipliedBy(100)
.toNumber();
const formatWithAssetDecimals = (
data: AccountFields | undefined,
value: string | undefined
) => {
return (
data &&
data.asset &&
isNumeric(value) &&
addDecimalsFormatNumber(value, data.asset.decimals)
);
};
export const accountValuesComparator = (
valueA: string,
valueB: string,
nodeA: RowNode,
nodeB: RowNode
) => {
if (isNumeric(valueA) && isNumeric(valueB)) {
const a = toBigNum(valueA, nodeA.data.asset?.decimals);
const b = toBigNum(valueB, nodeB.data.asset?.decimals);
if (a.isEqualTo(b)) return 0;
return a.isGreaterThan(b) ? 1 : -1;
}
if (valueA === valueB) return 0;
return valueA > valueB ? 1 : -1;
};
export interface GetRowsParams extends Omit<IGetRowsParams, 'successCallback'> { export interface GetRowsParams extends Omit<IGetRowsParams, 'successCallback'> {
successCallback(rowsThisBlock: AccountFields[], lastRow?: number): void; successCallback(rowsThisBlock: AccountFields[], lastRow?: number): void;
@ -47,7 +90,7 @@ export interface AccountTableProps extends AgGridReactProps {
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>( export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
({ onClickAsset, onClickWithdraw, onClickDeposit, ...props }, ref) => { ({ onClickAsset, onClickWithdraw, onClickDeposit, ...props }, ref) => {
const [openBreakdown, setOpenBreakdown] = useState(false); const [openBreakdown, setOpenBreakdown] = useState(false);
const [breakdown, setBreakdown] = useState<AccountFields[] | null>(null); const [row, setRow] = useState<AccountFields>();
const pinnedAssetId = props.pinnedAsset?.id; const pinnedAssetId = props.pinnedAsset?.id;
const pinnedAssetRow = useMemo(() => { const pinnedAssetRow = useMemo(() => {
@ -60,7 +103,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
asset: props.pinnedAsset, asset: props.pinnedAsset,
available: '0', available: '0',
used: '0', used: '0',
deposited: '0', total: '0',
balance: '0', balance: '0',
}; };
} }
@ -81,6 +124,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
resizable: true, resizable: true,
tooltipComponent: TooltipCellComponent, tooltipComponent: TooltipCellComponent,
sortable: true, sortable: true,
comparator: accountValuesComparator,
}} }}
{...props} {...props}
pinnedTopRowData={pinnedAssetRow ? [pinnedAssetRow] : undefined} pinnedTopRowData={pinnedAssetRow ? [pinnedAssetRow] : undefined}
@ -94,90 +138,64 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
cellRenderer={({ cellRenderer={({
value, value,
data, data,
node,
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => { }: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
return value ? ( return (
<CenteredGridCellWrapper <ButtonLink
className={node.rowPinned ? 'h-[30px]' : undefined} data-testid="asset"
onClick={() => {
if (data) {
onClickAsset(data.asset.id);
}
}}
> >
<ButtonLink {value}
data-testid="asset" </ButtonLink>
onClick={() => {
if (data) {
onClickAsset(data.asset.id);
}
}}
>
{value}
</ButtonLink>
</CenteredGridCellWrapper>
) : null;
}}
maxWidth={300}
/>
<AgGridColumn
headerName={t('Total')}
type="rightAligned"
field="deposited"
headerTooltip={t(
'This is the total amount of collateral used plus the amount available in your general account.'
)}
valueGetter={({
data,
}: VegaValueGetterParams<AccountFields, 'deposited'>) => {
return !data?.deposited
? undefined
: toBigNum(data.deposited, data.asset.decimals).toNumber();
}}
maxWidth={300}
cellRenderer={({
data,
node,
}: VegaICellRendererParams<AccountFields, 'deposited'>) => {
const valueFormatted =
data &&
data.asset &&
isNumeric(data.deposited) &&
addDecimalsFormatNumber(data.deposited, data.asset.decimals);
return node.rowPinned ? (
<CenteredGridCellWrapper className="h-[30px] justify-end">
{valueFormatted}
</CenteredGridCellWrapper>
) : (
valueFormatted
); );
}} }}
maxWidth={300}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Used')} headerName={t('Used')}
type="rightAligned" type="rightAligned"
field="used" field="used"
headerTooltip={t( headerTooltip={t(
'This is the amount of collateral used from your general account.' 'Currently allocated to a market as margin or bond. Check the breakdown for details.'
)} )}
valueGetter={({
data,
}: VegaValueGetterParams<AccountFields, 'used'>) => {
return !data?.used
? undefined
: toBigNum(data.used, data.asset.decimals).toNumber();
}}
maxWidth={300}
cellRenderer={({ cellRenderer={({
data, data,
node, value,
}: VegaICellRendererParams<AccountFields, 'used'>) => { }: VegaICellRendererParams<AccountFields, 'used'>) => {
const valueFormatted = if (!data) return null;
data && const percentageUsed = percentageValue(value, data.total);
data.asset && const valueFormatted = formatWithAssetDecimals(data, value);
isNumeric(data.used) &&
addDecimalsFormatNumber(data.used, data.asset.decimals); return data.breakdown ? (
return node.rowPinned ? ( <>
<CenteredGridCellWrapper className="h-[30px] justify-end"> <ButtonLink
{valueFormatted} data-testid="breakdown"
</CenteredGridCellWrapper> onClick={() => {
setOpenBreakdown(!openBreakdown);
setRow(data);
}}
>
<span>{valueFormatted}</span>
</ButtonLink>
<span
className={classNames(
colorClass(percentageUsed),
'ml-2 inline-block w-14'
)}
>
{percentageUsed.toFixed(2)}%
</span>
</>
) : ( ) : (
valueFormatted <>
<span>{valueFormatted}</span>
<span className="ml-2 inline-block w-14 text-neutral-500 dark:text-neutral-400">
0.00%
</span>
</>
); );
}} }}
/> />
@ -186,115 +204,113 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
field="available" field="available"
type="rightAligned" type="rightAligned"
headerTooltip={t( headerTooltip={t(
'This is the amount of collateral available in your general account.' 'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
)} )}
valueGetter={({
data,
}: VegaValueGetterParams<AccountFields, 'available'>) => {
return !data?.available
? undefined
: toBigNum(data.available, data.asset.decimals).toNumber();
}}
valueFormatter={({
data,
}: VegaValueFormatterParams<AccountFields, 'available'>) =>
data &&
data.asset &&
isNumeric(data.available) &&
addDecimalsFormatNumber(data.available, data.asset.decimals)
}
maxWidth={300}
cellRenderer={({ cellRenderer={({
value,
data, data,
node,
}: VegaICellRendererParams<AccountFields, 'available'>) => { }: VegaICellRendererParams<AccountFields, 'available'>) => {
const valueFormatted = const percentageUsed = percentageValue(data?.used, data?.total);
data &&
data.asset && return (
isNumeric(data.available) && <span className={colorClass(percentageUsed, true)}>
addDecimalsFormatNumber(data.available, data.asset.decimals); {formatWithAssetDecimals(data, value)}
return node.rowPinned ? ( </span>
<CenteredGridCellWrapper className="h-[30px] justify-end">
{valueFormatted}
</CenteredGridCellWrapper>
) : (
valueFormatted
); );
}} }}
/> />
<AgGridColumn <AgGridColumn
colId="breakdown" headerName={t('Total')}
headerName=""
sortable={false}
minWidth={200}
type="rightAligned" type="rightAligned"
cellRenderer={({ field="total"
headerTooltip={t(
'The total amount of each asset on this key. Includes used and available collateral.'
)}
valueFormatter={({
data, data,
}: VegaICellRendererParams<AccountFields>) => { }: VegaValueFormatterParams<AccountFields, 'total'>) =>
if (!data) return null; formatWithAssetDecimals(data, data?.total)
else { }
if ( />
data.asset.id === pinnedAssetId && {
new BigNumber(data.deposited).isLessThanOrEqualTo(0) <AgGridColumn
) { colId="accounts-actions"
headerName=""
sortable={false}
minWidth={200}
type="rightAligned"
cellRenderer={({
data,
}: VegaICellRendererParams<AccountFields>) => {
if (!data) return null;
else {
if (
data.asset.id === pinnedAssetId &&
new BigNumber(data.total).isLessThanOrEqualTo(0)
) {
return (
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
<Button
size="xs"
variant="primary"
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit to trade')}
</Button>
</CenteredGridCellWrapper>
);
}
return ( return (
<CenteredGridCellWrapper className="h-[30px] justify-end py-1"> <>
<Button <span className="mx-1" />
size="xs" {!props.isReadOnly && (
variant="primary" <ButtonLink
data-testid="deposit" data-testid="deposit"
onClick={() => { onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id); onClickDeposit && onClickDeposit(data.asset.id);
}} }}
> >
{t('Deposit to trade')} {t('Deposit')}
</Button> </ButtonLink>
</CenteredGridCellWrapper> )}
<span className="mx-1" />
{!props.isReadOnly && (
<ButtonLink
data-testid="withdraw"
onClick={() =>
onClickWithdraw && onClickWithdraw(data.asset.id)
}
>
{t('Withdraw')}
</ButtonLink>
)}
</>
); );
} }
return ( }}
<> />
<ButtonLink }
data-testid="breakdown"
onClick={() => {
setOpenBreakdown(!openBreakdown);
setBreakdown(data.breakdown || null);
}}
>
{t('Breakdown')}
</ButtonLink>
<span className="mx-1" />
{!props.isReadOnly && (
<ButtonLink
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit')}
</ButtonLink>
)}
<span className="mx-1" />
{!props.isReadOnly && (
<ButtonLink
data-testid="withdraw"
onClick={() =>
onClickWithdraw && onClickWithdraw(data.asset.id)
}
>
{t('Withdraw')}
</ButtonLink>
)}
</>
);
}
}}
/>
</AgGrid> </AgGrid>
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}> <Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
<div className="h-[35vh] w-full m-auto flex flex-col"> <div className="h-[35vh] w-full m-auto flex flex-col">
<h1 className="text-xl mb-4">{t('Collateral breakdown')}</h1> <h1 className="text-xl mb-4">
<BreakdownTable data={breakdown} domLayout="autoHeight" /> {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> </div>
</Dialog> </Dialog>
</> </>

View File

@ -27,7 +27,7 @@ const singleRow = {
}, },
available: '125600000', available: '125600000',
used: '125600000', used: '125600000',
deposited: '125600000', total: '251200000',
} as AccountFields; } as AccountFields;
const singleRowData = [singleRow]; const singleRowData = [singleRow];
@ -37,10 +37,10 @@ describe('BreakdownTable', () => {
render(<BreakdownTable data={singleRowData} />); render(<BreakdownTable data={singleRowData} />);
}); });
const headers = await screen.findAllByRole('columnheader'); const headers = await screen.findAllByRole('columnheader');
expect(headers).toHaveLength(4); expect(headers).toHaveLength(3);
expect( expect(
headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim()) headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim())
).toEqual(['Account type', 'Market', 'Used', 'Balance']); ).toEqual(['Market', 'Account type', 'Balance']);
}); });
it('should apply correct formatting', async () => { it('should apply correct formatting', async () => {
@ -49,9 +49,9 @@ describe('BreakdownTable', () => {
}); });
const cells = await screen.findAllByRole('gridcell'); const cells = await screen.findAllByRole('gridcell');
const expectedValues = [ const expectedValues = [
'Margin',
'BTCUSD Monthly (30 Jun 2022)', 'BTCUSD Monthly (30 Jun 2022)',
'1,256.001,256.00', 'Margin',
'1,256.00 (50%)',
'1,256.00', '1,256.00',
'1,256.00', '1,256.00',
]; ];
@ -83,7 +83,7 @@ describe('BreakdownTable', () => {
}, },
available: '0', available: '0',
balance: '125600000', balance: '125600000',
deposited: '125600000', total: '125600000',
market: { market: {
__typename: 'Market', __typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35', id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
@ -99,7 +99,7 @@ describe('BreakdownTable', () => {
used: '125600000', used: '125600000',
}, },
], ],
deposited: '125600000', total: '125600000',
type: 'ACCOUNT_TYPE_GENERAL', type: 'ACCOUNT_TYPE_GENERAL',
used: '125600000', used: '125600000',
}, },

View File

@ -1,5 +1,5 @@
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import {
Intent, Intent,
@ -13,6 +13,7 @@ import type { ValueProps } from '@vegaprotocol/ui-toolkit';
import type { VegaValueFormatterParams } from '@vegaprotocol/datagrid'; import type { VegaValueFormatterParams } from '@vegaprotocol/datagrid';
import { AgGridDynamic as AgGrid, PriceCell } from '@vegaprotocol/datagrid'; import { AgGridDynamic as AgGrid, PriceCell } from '@vegaprotocol/datagrid';
import type { ValueFormatterParams } from 'ag-grid-community'; import type { ValueFormatterParams } from 'ag-grid-community';
import { accountValuesComparator } from './accounts-table';
export const progressBarValueFormatter = ({ export const progressBarValueFormatter = ({
data, data,
@ -23,24 +24,16 @@ export const progressBarValueFormatter = ({
} }
const min = BigInt(data.used); const min = BigInt(data.used);
const mid = BigInt(data.available); const mid = BigInt(data.available);
const max = BigInt(data.deposited); const max = BigInt(data.total);
const range = max > min ? max : min; const range = max > min ? max : min;
return { return {
low: addDecimalsFormatNumber(min.toString(), data.asset.decimals, 4), low: addDecimalsFormatNumber(min.toString(), data.asset.decimals),
high: addDecimalsFormatNumber(mid.toString(), data.asset.decimals, 4), high: addDecimalsFormatNumber(mid.toString(), data.asset.decimals),
value: range ? Number((min * BigInt(100)) / range) : 0, value: range ? Number((min * BigInt(100)) / range) : 0,
intent: Intent.Warning, intent: Intent.Warning,
}; };
}; };
export const progressBarHeaderComponentParams = {
template:
'<div class="ag-cell-label-container" role="presentation">' +
` <span>${t('Available')}</span>` +
' <span ref="eText" class="ag-header-cell-text"></span>' +
'</div>',
};
interface BreakdownTableProps extends AgGridReactProps { interface BreakdownTableProps extends AgGridReactProps {
data: AccountFields[] | null; data: AccountFields[] | null;
} }
@ -62,8 +55,23 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
defaultColDef={{ defaultColDef={{
flex: 1, flex: 1,
resizable: true, resizable: true,
sortable: true,
}} }}
> >
<AgGridColumn
headerName={t('Market')}
field="market.tradableInstrument.instrument.name"
valueFormatter={({
value,
}: VegaValueFormatterParams<
AccountFields,
'market.tradableInstrument.instrument.name'
>) => {
if (!value) return 'None';
return value;
}}
minWidth={200}
/>
<AgGridColumn <AgGridColumn
headerName={t('Account type')} headerName={t('Account type')}
field="type" field="type"
@ -76,42 +84,15 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
: '' : ''
} }
/> />
<AgGridColumn <AgGridColumn
headerName={t('Market')} headerName={t('Balance')}
field="market.tradableInstrument.instrument.name"
valueFormatter={({
value,
}: VegaValueFormatterParams<
AccountFields,
'market.tradableInstrument.instrument.name'
>) => {
if (!value) return '-';
return value;
}}
minWidth={200}
/>
<AgGridColumn
headerName={t('Used')}
field="used" field="used"
flex={2} flex={2}
maxWidth={500} maxWidth={500}
headerComponentParams={progressBarHeaderComponentParams}
cellRendererSelector={progressBarCellRendererSelector} cellRendererSelector={progressBarCellRendererSelector}
valueFormatter={progressBarValueFormatter} valueFormatter={progressBarValueFormatter}
/> comparator={accountValuesComparator}
<AgGridColumn
headerName={t('Balance')}
field="balance"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<AccountFields, 'balance'>) => {
if (data && data.asset && isNumeric(value)) {
return addDecimalsFormatNumber(value, data.asset.decimals);
}
return '-';
}}
maxWidth={300}
/> />
</AgGrid> </AgGrid>
); );

View File

@ -53,6 +53,7 @@ export const checkSorting = (
}); });
checkSortChange(orderTabDesc, column); checkSortChange(orderTabDesc, column);
}; };
const checkSortChange = (tabsArr: string[], column: string) => { const checkSortChange = (tabsArr: string[], column: string) => {
cy.get('.ag-center-cols-container').within(() => { cy.get('.ag-center-cols-container').within(() => {
tabsArr.forEach((entry, i) => { tabsArr.forEach((entry, i) => {

View File

@ -20,8 +20,9 @@ export const ProgressBarCell = ({ valueFormatted }: ValueProps) => {
return valueFormatted ? ( return valueFormatted ? (
<> <>
<div className="flex justify-between leading-tight font-mono"> <div className="flex justify-between leading-tight font-mono">
<div>{valueFormatted.low}</div> <div>
<div>{valueFormatted.high}</div> {valueFormatted.low} ({valueFormatted.value}%)
</div>
</div> </div>
<ProgressBar <ProgressBar
value={valueFormatted.value} value={valueFormatted.value}