feat(accounts): accounts table quantum formatting (#4076)
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
This commit is contained in:
parent
c8d55c6293
commit
98d248e02e
@ -1,5 +1,5 @@
|
||||
// #region consts
|
||||
const asset = 'asset';
|
||||
const assetColId = '[col-id="asset.symbol"]';
|
||||
const assetDetailsDialog = 'dialog-content';
|
||||
const assetRow = 'key-value-table-row';
|
||||
const contractAddress = '7_value';
|
||||
@ -108,7 +108,7 @@ beforeEach(() => {
|
||||
|
||||
const visitPortfolioAndClickAsset = (assetName: string) => {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId(asset).contains(assetName).click();
|
||||
cy.get(assetColId).contains(assetName).click();
|
||||
};
|
||||
|
||||
const testTooltip = (index: number, testId: string, tooltip: string) => {
|
||||
|
@ -20,6 +20,7 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
// 7001-COLL-006
|
||||
// 7001-COLL-007
|
||||
// 1003-TRAN-001
|
||||
// 7001-COLL-012
|
||||
|
||||
const tradingAccountRowId = '[row-id="t-0"]';
|
||||
cy.getByTestId('Collateral').click();
|
||||
@ -34,28 +35,23 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId('tab-accounts')
|
||||
.get(tradingAccountRowId)
|
||||
.find('[col-id="used"]')
|
||||
.should('have.text', '1.010.00%');
|
||||
.should('have.text', '1.01' + '1.00%');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.get(tradingAccountRowId)
|
||||
.find('[col-id="available"]')
|
||||
.should('have.text', '100,000.00');
|
||||
.should('have.text', '100.00');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.get(tradingAccountRowId)
|
||||
.find('[col-id="total"]')
|
||||
.should('have.text', '100,001.01');
|
||||
.should('have.text', '101.01');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.get(tradingAccountRowId)
|
||||
.find('[col-id="accounts-actions"]')
|
||||
.should('have.text', '');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.get(tradingAccountRowId)
|
||||
.find('[col-id="total"]')
|
||||
.should('have.text', '100,001.01');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.get('[col-id="accounts-actions"]')
|
||||
.find('[data-testid="dropdown-menu"]')
|
||||
@ -101,7 +97,7 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
'Liquidity provision fee reward account balance',
|
||||
'Market proposer reward account balance',
|
||||
];
|
||||
cy.getByTestId('asset').contains('tEURO').click();
|
||||
cy.get('[col-id="asset.symbol"]').contains('tEURO').click();
|
||||
cy.get('[data-testid$="_label"]').should('have.length', 16);
|
||||
cy.get('[data-testid$="_label"]').each((element, index) => {
|
||||
cy.wrap(element).should('have.text', titles[index]);
|
||||
@ -112,7 +108,7 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
|
||||
it('should open usage breakdown dialog when clicked on used', () => {
|
||||
// 7001-COLL-009
|
||||
cy.getByTestId('breakdown').contains('1.01').click();
|
||||
cy.get('[col-id="used"]').contains('1.01').click();
|
||||
const headers = ['Market', 'Account type', 'Balance'];
|
||||
cy.getByTestId('usage-breakdown').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
@ -137,7 +133,7 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = ['tBTC', 'tEURO', 'tDAI', 'tBTC'];
|
||||
const marketsSortedAsc = ['tBTC', 'tBTC', 'tDAI', 'tEURO'];
|
||||
const marketsSortedDesc = ['tEURO', 'tDAI', 'tBTC', 'tBTC'];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
checkSorting(
|
||||
'asset.symbol',
|
||||
marketsSortedDefault,
|
||||
@ -149,23 +145,14 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
it('sorting by total', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = [
|
||||
'1,000.00002',
|
||||
'1,000.00',
|
||||
'1,000.01',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
'1,000.00002',
|
||||
'1,000.01',
|
||||
];
|
||||
const marketsSortedDesc = [
|
||||
'1,000.01',
|
||||
'1,000.00002',
|
||||
'1,000.00001',
|
||||
'1,000.00',
|
||||
];
|
||||
const marketsSortedAsc = ['1,000.00', '1,000.00', '1,000.00', '1,000.01'];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
|
||||
checkSorting(
|
||||
'total',
|
||||
marketsSortedDefault,
|
||||
@ -176,24 +163,22 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
|
||||
it('sorting by used', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
// concat actual value with percentage value
|
||||
// as cypress will pick up the entire cell contes
|
||||
// textContent
|
||||
const marketsSortedDefault = [
|
||||
'0.000.00%',
|
||||
'0.010.00%',
|
||||
'0.000.00%',
|
||||
'0.000.00%',
|
||||
'0.00' + '0.00%',
|
||||
'0.01' + '0.00%',
|
||||
'0.00' + '0.00%',
|
||||
'0.00' + '0.00%',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'0.000.00%',
|
||||
'0.000.00%',
|
||||
'0.000.00%',
|
||||
'0.010.00%',
|
||||
];
|
||||
const marketsSortedDesc = [
|
||||
'0.010.00%',
|
||||
'0.000.00%',
|
||||
'0.000.00%',
|
||||
'0.000.00%',
|
||||
'0.00' + '0.00%',
|
||||
'0.00' + '0.00%',
|
||||
'0.00' + '0.00%',
|
||||
'0.01' + '0.00%',
|
||||
];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
checkSorting(
|
||||
'used',
|
||||
marketsSortedDefault,
|
||||
@ -205,23 +190,13 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
it('sorting by total', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = [
|
||||
'1,000.00002',
|
||||
'1,000.00',
|
||||
'1,000.01',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
'1,000.00002',
|
||||
'1,000.01',
|
||||
];
|
||||
const marketsSortedDesc = [
|
||||
'1,000.01',
|
||||
'1,000.00002',
|
||||
'1,000.00001',
|
||||
'1,000.00',
|
||||
];
|
||||
const marketsSortedAsc = ['1,000.00', '1,000.00', '1,000.00', '1,000.01'];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
|
||||
checkSorting(
|
||||
'total',
|
||||
|
@ -102,7 +102,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
.within(() => {
|
||||
cy.get('[data-state="closed"]').should(
|
||||
'have.text',
|
||||
'Total margin available100,000.01 tDAI'
|
||||
'Total margin available' + '100.01 tDAI'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -293,43 +293,36 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
});
|
||||
function validatePositionsDisplayed(multiKey = false) {
|
||||
cy.getByTestId('tab-positions').should('be.visible');
|
||||
cy.getByTestId('tab-positions').within(() => {
|
||||
cy.get('[col-id="marketName"]')
|
||||
.should('be.visible')
|
||||
.each(($marketSymbol) => {
|
||||
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
cy.getByTestId('tab-positions')
|
||||
.get('.ag-center-cols-container .ag-row')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('[col-id="marketName"]')
|
||||
.should('be.visible')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
|
||||
cy.get('.ag-center-cols-container [col-id="openVolume"]').each(
|
||||
($openVolume) => {
|
||||
cy.wrap($openVolume).invoke('text').should('not.be.empty');
|
||||
cy.get('[col-id="openVolume"]').should('not.be.empty');
|
||||
|
||||
// includes average entry price, mark price, realised PNL & leverage
|
||||
cy.getByTestId('flash-cell').should('not.be.empty');
|
||||
|
||||
if (!multiKey) {
|
||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2,767.3');
|
||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||
.should('contain.text', '0.01');
|
||||
}
|
||||
);
|
||||
|
||||
// includes average entry price, mark price, realised PNL & leverage
|
||||
cy.getByTestId('flash-cell').each(($prices) => {
|
||||
cy.wrap($prices).invoke('text').should('not.be.empty');
|
||||
cy.get('[col-id="unrealisedPNL"]').should('not.be.empty');
|
||||
cy.get('[col-id="notional"]').should('contain.text', '276,761.40348'); // Total tDAI position
|
||||
cy.get('[col-id="realisedPNL"]').should('contain.text', '2.30'); // Total Realised PNL
|
||||
cy.get('[col-id="unrealisedPNL"]').should('contain.text', '8.95'); // Total Unrealised PNL
|
||||
});
|
||||
|
||||
if (!multiKey) {
|
||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||
.should('contain.text', '0.01');
|
||||
}
|
||||
|
||||
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
|
||||
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('[col-id="notional"]').should('contain.text', '276,761.40348'); // Total tDAI position
|
||||
cy.get('[col-id="realisedPNL"]').should('contain.text', '2.30'); // Total Realised PNL
|
||||
cy.get('[col-id="unrealisedPNL"]').should('contain.text', '8.95'); // Total Unrealised PNL
|
||||
|
||||
cy.get('.ag-header-row [col-id="notional"]')
|
||||
.should('contain.text', 'Notional')
|
||||
.realHover();
|
||||
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
||||
});
|
||||
cy.get('.ag-header-row [col-id="notional"]')
|
||||
.should('contain.text', 'Notional')
|
||||
.realHover();
|
||||
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
||||
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||
}
|
||||
|
@ -143,7 +143,6 @@ const MarketBottomPanel = memo(
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
noBottomPlaceholder
|
||||
hideButtons
|
||||
storeKey="marketCollateral"
|
||||
/>
|
||||
|
@ -12,12 +12,10 @@ import { useDepositDialog } from '@vegaprotocol/deposits';
|
||||
export const AccountsContainer = ({
|
||||
pinnedAsset,
|
||||
hideButtons,
|
||||
noBottomPlaceholder,
|
||||
storeKey,
|
||||
}: {
|
||||
pinnedAsset?: PinnedAsset;
|
||||
hideButtons?: boolean;
|
||||
noBottomPlaceholder?: boolean;
|
||||
storeKey?: string;
|
||||
}) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
@ -50,7 +48,6 @@ export const AccountsContainer = ({
|
||||
onClickDeposit={openDepositDialog}
|
||||
isReadOnly={isReadOnly}
|
||||
pinnedAsset={pinnedAsset}
|
||||
noBottomPlaceholder={noBottomPlaceholder}
|
||||
storeKey={storeKey}
|
||||
/>
|
||||
{!isReadOnly && !hideButtons && (
|
||||
|
@ -1,17 +1,14 @@
|
||||
import { useRef, memo, useCallback, useState } from 'react';
|
||||
import { useRef, memo, useState } from 'react';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
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';
|
||||
|
||||
@ -54,7 +51,6 @@ interface AccountManagerProps {
|
||||
onClickDeposit?: (assetId?: string) => void;
|
||||
isReadOnly: boolean;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
noBottomPlaceholder?: boolean;
|
||||
storeKey?: string;
|
||||
}
|
||||
|
||||
@ -65,48 +61,14 @@ export const AccountManager = ({
|
||||
partyId,
|
||||
isReadOnly,
|
||||
pinnedAsset,
|
||||
noBottomPlaceholder,
|
||||
storeKey,
|
||||
}: AccountManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
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, error } = useDataProvider({
|
||||
dataProvider: aggregatedAccountsDataProvider,
|
||||
variables: { partyId },
|
||||
update,
|
||||
});
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
disabled: noBottomPlaceholder,
|
||||
});
|
||||
|
||||
return (
|
||||
@ -121,7 +83,6 @@ export const AccountManager = ({
|
||||
isReadOnly={isReadOnly}
|
||||
pinnedAsset={pinnedAsset}
|
||||
storeKey={storeKey}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No accounts')}
|
||||
/>
|
||||
<Dialog
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { forwardRef, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
isNumeric,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
@ -10,21 +11,15 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
ButtonLink,
|
||||
Button,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type {
|
||||
IDatasource,
|
||||
IGetRowsParams,
|
||||
RowNode,
|
||||
RowHeightParams,
|
||||
ColDef,
|
||||
} from 'ag-grid-community';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
@ -35,29 +30,16 @@ import classNames from 'classnames';
|
||||
import { AccountsActionsDropdown } from './accounts-actions-dropdown';
|
||||
|
||||
const colorClass = (percentageUsed: number, neutral = false) => {
|
||||
return classNames({
|
||||
return classNames('text-right', {
|
||||
'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 percentageValue = (part: string, total: string) => {
|
||||
total = !total || total === '0' ? '1' : total;
|
||||
return new BigNumber(part).dividedBy(total).multipliedBy(100).toNumber();
|
||||
};
|
||||
|
||||
export const accountValuesComparator = (
|
||||
@ -81,15 +63,10 @@ export interface GetRowsParams extends Omit<IGetRowsParams, 'successCallback'> {
|
||||
successCallback(rowsThisBlock: AccountFields[], lastRow?: number): void;
|
||||
}
|
||||
|
||||
export interface Datasource extends IDatasource {
|
||||
getRows(params: GetRowsParams): void;
|
||||
}
|
||||
|
||||
export type PinnedAsset = Pick<Asset, 'symbol' | 'name' | 'id' | 'decimals'>;
|
||||
|
||||
export interface AccountTableProps extends AgGridReactProps {
|
||||
rowData?: AccountFields[] | null;
|
||||
datasource?: Datasource;
|
||||
onClickAsset: (assetId: string) => void;
|
||||
onClickWithdraw?: (assetId: string) => void;
|
||||
onClickDeposit?: (assetId: string) => void;
|
||||
@ -148,83 +125,55 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
|
||||
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={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(
|
||||
const colDefs = useMemo(() => {
|
||||
const defs: ColDef[] = [
|
||||
{
|
||||
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 (
|
||||
<ButtonLink
|
||||
data-testid="asset"
|
||||
onClick={() => {
|
||||
if (data) {
|
||||
onClickAsset(data.asset.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</ButtonLink>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Used')}
|
||||
type="rightAligned"
|
||||
field="used"
|
||||
headerTooltip={t(
|
||||
),
|
||||
cellClass: 'underline',
|
||||
onCellClicked: ({ data }) => {
|
||||
if (data) {
|
||||
onClickAsset(data.asset.id);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Used'),
|
||||
type: 'rightAligned',
|
||||
field: 'used',
|
||||
headerTooltip: t(
|
||||
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
||||
)}
|
||||
cellRenderer={({
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
onCellClicked: ({ data }) => {
|
||||
if (!data || !onClickBreakdown) return;
|
||||
onClickBreakdown(data.asset.id);
|
||||
},
|
||||
cellRenderer: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||
if (!data) return null;
|
||||
if (!value || !data) return '-';
|
||||
const percentageUsed = percentageValue(value, data.total);
|
||||
const valueFormatted = formatWithAssetDecimals(data, value);
|
||||
const valueFormatted = addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
|
||||
return data.breakdown ? (
|
||||
<>
|
||||
<ButtonLink
|
||||
data-testid="breakdown"
|
||||
onClick={() => {
|
||||
onClickBreakdown && onClickBreakdown(data.asset.id);
|
||||
}}
|
||||
>
|
||||
<span>{valueFormatted}</span>
|
||||
</ButtonLink>
|
||||
<span className="underline">{valueFormatted}</span>
|
||||
<span
|
||||
className={classNames(
|
||||
colorClass(percentageUsed),
|
||||
'ml-2 inline-block w-14'
|
||||
'ml-1 inline-block w-14'
|
||||
)}
|
||||
>
|
||||
{percentageUsed.toFixed(2)}%
|
||||
@ -232,59 +181,77 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>{valueFormatted}</span>
|
||||
<span className="ml-2 inline-block w-14 text-neutral-500 dark:text-neutral-400">
|
||||
0.00%
|
||||
<span className="underline">{valueFormatted}</span>
|
||||
<span className="ml-2 inline-block w-14 text-vega-light-200 dark:text-vega-dark-200">
|
||||
{t('0.00%')}'
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Available')}
|
||||
field="available"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
},
|
||||
},
|
||||
{
|
||||
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={({
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
cellClass: ({ data }) => {
|
||||
const percentageUsed = percentageValue(data?.used, data?.total);
|
||||
return colorClass(percentageUsed, true);
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<AccountFields, 'available'>) => {
|
||||
const percentageUsed = percentageValue(data?.used, data?.total);
|
||||
|
||||
return (
|
||||
<span className={colorClass(percentageUsed, true)}>
|
||||
{formatWithAssetDecimals(data, value)}
|
||||
</span>
|
||||
}: VegaValueFormatterParams<AccountFields, 'available'>) => {
|
||||
if (!value || !data) return '-';
|
||||
return addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Total')}
|
||||
type="rightAligned"
|
||||
field="total"
|
||||
headerTooltip={t(
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Total'),
|
||||
type: 'rightAligned',
|
||||
field: 'total',
|
||||
headerTooltip: t(
|
||||
'The total amount of each asset on this key. Includes used and available collateral.'
|
||||
)}
|
||||
valueFormatter={({
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
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,
|
||||
}: VegaValueFormatterParams<AccountFields, 'total'>) => {
|
||||
if (!data || !value) return '-';
|
||||
return addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
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') {
|
||||
if (node.rowPinned && node.data?.total === '0') {
|
||||
return (
|
||||
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
||||
<Button
|
||||
@ -319,9 +286,42 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
},
|
||||
},
|
||||
];
|
||||
return defs;
|
||||
}, [
|
||||
onClickAsset,
|
||||
onClickBreakdown,
|
||||
onClickDeposit,
|
||||
onClickWithdraw,
|
||||
props.isReadOnly,
|
||||
showDepositButton,
|
||||
]);
|
||||
|
||||
const data = rowData?.filter(
|
||||
(data) => data.asset.id !== props.pinnedAsset?.id
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No accounts')}
|
||||
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
rowData={data}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
sortable: true,
|
||||
comparator: accountValuesComparator,
|
||||
}}
|
||||
columnDefs={colDefs}
|
||||
getRowHeight={getPinnedAssetRowHeight}
|
||||
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -35,6 +35,7 @@ export const accountFields: AccountFieldsFragment[] = [
|
||||
balance: '100000000',
|
||||
market: null,
|
||||
asset: {
|
||||
// tEURO
|
||||
__typename: 'Asset',
|
||||
id: 'asset-id',
|
||||
},
|
||||
@ -44,6 +45,7 @@ export const accountFields: AccountFieldsFragment[] = [
|
||||
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
balance: '100000000',
|
||||
asset: {
|
||||
// tDAI
|
||||
__typename: 'Asset',
|
||||
id: 'asset-id-2',
|
||||
},
|
||||
@ -57,6 +59,7 @@ export const accountFields: AccountFieldsFragment[] = [
|
||||
id: 'market-2',
|
||||
},
|
||||
asset: {
|
||||
// tEURO
|
||||
__typename: 'Asset',
|
||||
id: 'asset-id',
|
||||
},
|
||||
@ -70,6 +73,7 @@ export const accountFields: AccountFieldsFragment[] = [
|
||||
id: 'market-0',
|
||||
},
|
||||
asset: {
|
||||
// AST0
|
||||
__typename: 'Asset',
|
||||
id: 'asset-0',
|
||||
},
|
||||
@ -83,6 +87,7 @@ export const accountFields: AccountFieldsFragment[] = [
|
||||
id: 'market-3',
|
||||
},
|
||||
asset: {
|
||||
// AST0
|
||||
__typename: 'Asset',
|
||||
id: 'asset-0',
|
||||
},
|
||||
@ -90,9 +95,10 @@ export const accountFields: AccountFieldsFragment[] = [
|
||||
{
|
||||
__typename: 'AccountBalance',
|
||||
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
balance: '10000000000',
|
||||
balance: '10000000',
|
||||
market: null,
|
||||
asset: {
|
||||
// AST0
|
||||
__typename: 'Asset',
|
||||
id: 'asset-0',
|
||||
},
|
||||
@ -104,6 +110,7 @@ export const accountFields: AccountFieldsFragment[] = [
|
||||
balance: '100000001',
|
||||
market: null,
|
||||
asset: {
|
||||
// tBTC (sepolia)
|
||||
__typename: 'Asset',
|
||||
id: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
|
||||
},
|
||||
@ -114,6 +121,7 @@ export const accountFields: AccountFieldsFragment[] = [
|
||||
balance: '100000002',
|
||||
market: null,
|
||||
asset: {
|
||||
// tBTC (test)
|
||||
__typename: 'Asset',
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
},
|
||||
|
@ -1,52 +1,106 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { Intent, TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import type {
|
||||
ValueProps,
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { progressBarCellRendererSelector } from '@vegaprotocol/datagrid';
|
||||
import { ProgressBarCell } from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid, PriceCell } from '@vegaprotocol/datagrid';
|
||||
import type { ValueFormatterParams } from 'ag-grid-community';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import { accountValuesComparator } from './accounts-table';
|
||||
|
||||
export const progressBarValueFormatter = ({
|
||||
data,
|
||||
node,
|
||||
}: ValueFormatterParams): ValueProps['valueFormatted'] | undefined => {
|
||||
if (!data || node?.rowPinned) {
|
||||
return undefined;
|
||||
}
|
||||
const min = BigInt(data.used);
|
||||
const mid = BigInt(data.available);
|
||||
const max = BigInt(data.total);
|
||||
const range = max > min ? max : min;
|
||||
return {
|
||||
low: addDecimalsFormatNumber(min.toString(), data.asset.decimals),
|
||||
high: addDecimalsFormatNumber(mid.toString(), data.asset.decimals),
|
||||
value: range ? Number((min * BigInt(100)) / range) : 0,
|
||||
intent: Intent.Warning,
|
||||
};
|
||||
};
|
||||
|
||||
interface BreakdownTableProps extends AgGridReactProps {
|
||||
data: AccountFields[] | null;
|
||||
}
|
||||
|
||||
const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
|
||||
({ data }, ref) => {
|
||||
const coldefs = useMemo(() => {
|
||||
const defs: ColDef[] = [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.name',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
AccountFields,
|
||||
'market.tradableInstrument.instrument.name'
|
||||
>) => {
|
||||
if (!value) return 'None';
|
||||
return value;
|
||||
},
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
field: 'type',
|
||||
maxWidth: 300,
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<AccountFields, 'type'>) => {
|
||||
return value
|
||||
? AccountTypeMapping[value as keyof typeof AccountTypeMapping]
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Balance'),
|
||||
field: 'used',
|
||||
flex: 2,
|
||||
maxWidth: 500,
|
||||
type: 'rightAligned',
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
cellRenderer: ({
|
||||
data,
|
||||
node,
|
||||
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||
if (!data || node?.rowPinned) {
|
||||
return undefined;
|
||||
}
|
||||
const min = BigInt(data.used);
|
||||
const mid = BigInt(data.available);
|
||||
const max = BigInt(data.total);
|
||||
const range = max > min ? max : min;
|
||||
const formattedData = {
|
||||
low: addDecimalsFormatNumberQuantum(
|
||||
min.toString(),
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
),
|
||||
high: addDecimalsFormatNumberQuantum(
|
||||
mid.toString(),
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
),
|
||||
value: range ? Number((min * BigInt(100)) / range) : 0,
|
||||
intent: Intent.Warning,
|
||||
};
|
||||
return <ProgressBarCell valueFormatted={formattedData} />;
|
||||
},
|
||||
comparator: accountValuesComparator,
|
||||
},
|
||||
];
|
||||
return defs;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('Collateral not used')}
|
||||
rowData={data}
|
||||
getRowId={({ data }: { data: AccountFields }) =>
|
||||
`${data.asset.id}-${data.type}-${data.market?.id}`
|
||||
`${data.asset.id}:${data.type}:${data.market?.id}`
|
||||
}
|
||||
ref={ref}
|
||||
rowHeight={34}
|
||||
@ -57,44 +111,8 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
|
||||
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
|
||||
headerName={t('Account type')}
|
||||
field="type"
|
||||
maxWidth={300}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<AccountFields, 'type'>) =>
|
||||
value
|
||||
? AccountTypeMapping[value as keyof typeof AccountTypeMapping]
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Balance')}
|
||||
field="used"
|
||||
flex={2}
|
||||
maxWidth={500}
|
||||
cellRendererSelector={progressBarCellRendererSelector}
|
||||
valueFormatter={progressBarValueFormatter}
|
||||
comparator={accountValuesComparator}
|
||||
/>
|
||||
</AgGrid>
|
||||
columnDefs={coldefs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -26,6 +26,7 @@ export const AgGridThemed = ({
|
||||
enableCellTextSelection: true,
|
||||
overlayLoadingTemplate: t('Loading...'),
|
||||
overlayNoRowsTemplate: t('No data'),
|
||||
suppressCellFocus: true,
|
||||
};
|
||||
|
||||
const wrapperClasses = classNames('vega-ag-grid', {
|
||||
|
@ -1,9 +1,5 @@
|
||||
import type { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { ProgressBar } from '@vegaprotocol/ui-toolkit';
|
||||
import type {
|
||||
CellRendererSelectorResult,
|
||||
ICellRendererParams,
|
||||
} from 'ag-grid-community';
|
||||
|
||||
export interface ValueProps {
|
||||
valueFormatted?: {
|
||||
@ -19,7 +15,7 @@ export const EmptyCell = () => '';
|
||||
export const ProgressBarCell = ({ valueFormatted }: ValueProps) => {
|
||||
return valueFormatted ? (
|
||||
<>
|
||||
<div className="flex justify-between leading-tight font-mono">
|
||||
<div className="text-right leading-tight font-mono">
|
||||
<div>
|
||||
{valueFormatted.low} ({valueFormatted.value}%)
|
||||
</div>
|
||||
@ -32,11 +28,3 @@ export const ProgressBarCell = ({ valueFormatted }: ValueProps) => {
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const progressBarCellRendererSelector = (
|
||||
params: ICellRendererParams
|
||||
): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: ProgressBarCell,
|
||||
};
|
||||
};
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
} from '@radix-ui/react-tooltip';
|
||||
import type { ITooltipParams } from 'ag-grid-community';
|
||||
|
||||
const tooltipContentClasses =
|
||||
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs break-word';
|
||||
export interface TooltipProps {
|
||||
children: React.ReactElement;
|
||||
description?: string | ReactNode;
|
||||
@ -40,7 +42,7 @@ export const Tooltip = ({
|
||||
align={align}
|
||||
side={side}
|
||||
alignOffset={8}
|
||||
className="max-w-sm border border-neutral-600 bg-neutral-100 dark:bg-neutral-800 px-4 py-2 z-20 rounded text-sm text-black dark:text-white break-word"
|
||||
className={tooltipContentClasses}
|
||||
>
|
||||
<div className="relative z-0" data-testid="tooltip-content">
|
||||
{description}
|
||||
@ -55,9 +57,5 @@ export const Tooltip = ({
|
||||
);
|
||||
|
||||
export const TooltipCellComponent = (props: ITooltipParams) => {
|
||||
return (
|
||||
<p className="max-w-sm border border-neutral-600 bg-neutral-100 dark:bg-neutral-800 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white">
|
||||
{props.value}
|
||||
</p>
|
||||
);
|
||||
return <p className={tooltipContentClasses}>{props.value}</p>;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user