329 lines
9.7 KiB
TypeScript
329 lines
9.7 KiB
TypeScript
import { useMemo, useCallback } from 'react';
|
|
import {
|
|
addDecimalsFormatNumber,
|
|
addDecimalsFormatNumberQuantum,
|
|
isNumeric,
|
|
toBigNum,
|
|
} from '@vegaprotocol/utils';
|
|
import { t } from '@vegaprotocol/i18n';
|
|
import type {
|
|
VegaICellRendererParams,
|
|
VegaValueFormatterParams,
|
|
} from '@vegaprotocol/datagrid';
|
|
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
|
import {
|
|
Intent,
|
|
TradingButton,
|
|
VegaIcon,
|
|
VegaIconNames,
|
|
TooltipCellComponent,
|
|
} from '@vegaprotocol/ui-toolkit';
|
|
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
|
import type {
|
|
IGetRowsParams,
|
|
IRowNode,
|
|
RowHeightParams,
|
|
ColDef,
|
|
} from 'ag-grid-community';
|
|
import type { AgGridReactProps } from 'ag-grid-react';
|
|
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';
|
|
|
|
const colorClass = (percentageUsed: number, neutral = false) => {
|
|
return classNames('text-right', {
|
|
'text-vega-orange': percentageUsed >= 75 && percentageUsed < 90,
|
|
'text-vega-red': percentageUsed >= 90,
|
|
});
|
|
};
|
|
|
|
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 = (
|
|
valueA: string,
|
|
valueB: string,
|
|
nodeA: IRowNode,
|
|
nodeB: IRowNode
|
|
) => {
|
|
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;
|
|
};
|
|
|
|
const defaultColDef = {
|
|
resizable: true,
|
|
sortable: true,
|
|
tooltipComponent: TooltipCellComponent,
|
|
comparator: accountValuesComparator,
|
|
};
|
|
export interface GetRowsParams extends Omit<IGetRowsParams, 'successCallback'> {
|
|
successCallback(rowsThisBlock: AccountFields[], lastRow?: number): void;
|
|
}
|
|
|
|
export type PinnedAsset = Pick<Asset, 'symbol' | 'name' | 'id' | 'decimals'>;
|
|
|
|
export interface AccountTableProps extends AgGridReactProps {
|
|
rowData?: AccountFields[] | null;
|
|
onClickAsset: (assetId: string) => void;
|
|
onClickWithdraw?: (assetId: string) => void;
|
|
onClickDeposit?: (assetId: string) => void;
|
|
onClickBreakdown?: (assetId: string) => void;
|
|
onClickTransfer?: (assetId: string) => void;
|
|
isReadOnly: boolean;
|
|
pinnedAsset?: PinnedAsset;
|
|
}
|
|
|
|
export const AccountTable = ({
|
|
onClickAsset,
|
|
onClickWithdraw,
|
|
onClickDeposit,
|
|
onClickBreakdown,
|
|
onClickTransfer,
|
|
rowData,
|
|
isReadOnly,
|
|
pinnedAsset,
|
|
...props
|
|
}: AccountTableProps) => {
|
|
const pinnedRow = useMemo(() => {
|
|
if (!pinnedAsset) {
|
|
return;
|
|
}
|
|
const currentPinnedAssetRow = rowData?.find(
|
|
(row) => row.asset.id === pinnedAsset?.id
|
|
);
|
|
if (!currentPinnedAssetRow) {
|
|
return {
|
|
asset: pinnedAsset,
|
|
available: '0',
|
|
used: '0',
|
|
total: '0',
|
|
balance: '0',
|
|
};
|
|
}
|
|
return currentPinnedAssetRow;
|
|
}, [pinnedAsset, rowData]);
|
|
|
|
const { getRowHeight } = props;
|
|
|
|
const getPinnedAssetRowHeight = useCallback(
|
|
(params: RowHeightParams) => {
|
|
if (
|
|
params.node.rowPinned &&
|
|
params.data.asset.id === pinnedAsset?.id &&
|
|
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
|
) {
|
|
return 32;
|
|
}
|
|
return getRowHeight ? getRowHeight(params) : undefined;
|
|
},
|
|
[pinnedAsset?.id, getRowHeight]
|
|
);
|
|
|
|
const showDepositButton = pinnedRow?.balance === '0';
|
|
|
|
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.'
|
|
),
|
|
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.'
|
|
),
|
|
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 (!value || !data) return '-';
|
|
const percentageUsed = percentageValue(value, data.total);
|
|
const valueFormatted = addDecimalsFormatNumberQuantum(
|
|
value,
|
|
data.asset.decimals,
|
|
data.asset.quantum
|
|
);
|
|
|
|
return data.breakdown ? (
|
|
<>
|
|
<span className="underline">{valueFormatted}</span>
|
|
<span
|
|
className={classNames(
|
|
colorClass(percentageUsed),
|
|
'ml-1 inline-block w-14'
|
|
)}
|
|
>
|
|
{percentageUsed.toFixed(2)}%
|
|
</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<span className="underline">{valueFormatted}</span>
|
|
<span className="inline-block ml-2 w-14 text-muted">
|
|
{t('0.00%')}
|
|
</span>
|
|
</>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
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.'
|
|
),
|
|
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,
|
|
}: VegaValueFormatterParams<AccountFields, 'available'>) => {
|
|
if (!value || !data) return '-';
|
|
return addDecimalsFormatNumberQuantum(
|
|
value,
|
|
data.asset.decimals,
|
|
data.asset.quantum
|
|
);
|
|
},
|
|
},
|
|
|
|
{
|
|
headerName: t('Total'),
|
|
type: 'rightAligned',
|
|
field: 'total',
|
|
headerTooltip: t(
|
|
'The total amount of each asset on this key. Includes used and available collateral.'
|
|
),
|
|
tooltipValueGetter: ({ value, data }) => {
|
|
if (!value || !data) return null;
|
|
return addDecimalsFormatNumber(value, data.asset.decimals);
|
|
},
|
|
valueFormatter: ({
|
|
data,
|
|
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 ? 105 : COL_DEFS.actions.minWidth,
|
|
maxWidth: showDepositButton ? 105 : 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">
|
|
<TradingButton
|
|
size="extra-small"
|
|
intent={Intent.Primary}
|
|
data-testid="deposit"
|
|
onClick={() => {
|
|
onClickDeposit && onClickDeposit(assetId);
|
|
}}
|
|
>
|
|
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
|
</TradingButton>
|
|
</CenteredGridCellWrapper>
|
|
);
|
|
}
|
|
return isReadOnly ? null : (
|
|
<AccountsActionsDropdown
|
|
assetId={assetId}
|
|
assetContractAddress={
|
|
node.data?.asset.source?.__typename === 'ERC20'
|
|
? node.data.asset.source.contractAddress
|
|
: undefined
|
|
}
|
|
onClickDeposit={() => {
|
|
onClickDeposit && onClickDeposit(assetId);
|
|
}}
|
|
onClickWithdraw={() => {
|
|
onClickWithdraw && onClickWithdraw(assetId);
|
|
}}
|
|
onClickBreakdown={() => {
|
|
onClickBreakdown && onClickBreakdown(assetId);
|
|
}}
|
|
onClickTransfer={() => {
|
|
onClickTransfer && onClickTransfer(assetId);
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
return defs;
|
|
}, [
|
|
onClickAsset,
|
|
onClickBreakdown,
|
|
onClickDeposit,
|
|
onClickWithdraw,
|
|
onClickTransfer,
|
|
isReadOnly,
|
|
showDepositButton,
|
|
]);
|
|
|
|
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
|
|
|
|
return (
|
|
<AgGrid
|
|
{...props}
|
|
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
|
tooltipShowDelay={500}
|
|
rowData={data}
|
|
defaultColDef={defaultColDef}
|
|
columnDefs={colDefs}
|
|
getRowHeight={getPinnedAssetRowHeight}
|
|
pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
|
|
/>
|
|
);
|
|
};
|