fix: container moved, progress bar in helpers
This commit is contained in:
@@ -11,7 +11,6 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Market_market } from './__generated__/Market';
|
||||
import { AccountsContainer } from '@vegaprotocol/accounts';
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||
import {
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
import { TradingModeTooltip } from '../../components/trading-mode-tooltip';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Header, HeaderStat } from '../../components/header';
|
||||
import { AccountsContainer } from '../portfolio/accounts-container';
|
||||
|
||||
const TradingViews = {
|
||||
Candles: CandlesChartContainer,
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, Dialog } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { WithdrawalDialogs } from '@vegaprotocol/withdraws';
|
||||
import { Web3Container } from '@vegaprotocol/web3';
|
||||
import { DepositContainer } from '@vegaprotocol/deposits';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { AccountsTable } from '@vegaprotocol/accounts';
|
||||
|
||||
export const AccountsContainer = () => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const [depositDialog, setDepositDialog] = useState(false);
|
||||
|
||||
if (!keypair) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Please connect Vega wallet')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Web3Container>
|
||||
<div className="h-full">
|
||||
<AssetAccountTable partyId={keypair.pub} />
|
||||
<div className="m-auto ml-4">
|
||||
<Button size="sm" onClick={() => setDepositDialog(true)}>
|
||||
{t('Deposit new asset')}
|
||||
</Button>
|
||||
</div>
|
||||
<DepositDialog
|
||||
depositDialog={depositDialog}
|
||||
setDepositDialog={setDepositDialog}
|
||||
/>
|
||||
</div>
|
||||
</Web3Container>
|
||||
);
|
||||
};
|
||||
|
||||
export const AssetAccountTable = ({ partyId }: { partyId: string }) => {
|
||||
const [withdrawDialog, setWithdrawDialog] = useState(false);
|
||||
const [depositDialog, setDepositDialog] = useState(false);
|
||||
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
||||
useAssetDetailsDialogStore();
|
||||
return (
|
||||
<>
|
||||
<AccountsTable
|
||||
partyId={partyId}
|
||||
onClickAsset={(value) => {
|
||||
if (value) {
|
||||
setAssetDetailsDialogOpen(true);
|
||||
setAssetDetailsDialogSymbol(value);
|
||||
}
|
||||
}}
|
||||
onClickWithdraw={() => setWithdrawDialog(true)}
|
||||
onClickDeposit={() => setDepositDialog(true)}
|
||||
/>
|
||||
<WithdrawalDialogs
|
||||
withdrawDialog={withdrawDialog}
|
||||
setWithdrawDialog={setWithdrawDialog}
|
||||
/>
|
||||
<DepositDialog
|
||||
depositDialog={depositDialog}
|
||||
setDepositDialog={setDepositDialog}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface DepositDialogProps {
|
||||
assetId?: string;
|
||||
depositDialog: boolean;
|
||||
setDepositDialog: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const DepositDialog = ({
|
||||
assetId,
|
||||
depositDialog,
|
||||
setDepositDialog,
|
||||
}: DepositDialogProps) => {
|
||||
return (
|
||||
<Dialog open={depositDialog} onChange={setDepositDialog}>
|
||||
<h1 className="text-2xl mb-4">{t('Deposit')}</h1>
|
||||
<DepositContainer assetId={assetId} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { AccountsContainer } from '@vegaprotocol/accounts';
|
||||
import { ResizableGridPanel, Tab, Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { WithdrawalsContainer } from './withdrawals-container';
|
||||
import { FillsContainer } from '@vegaprotocol/fills';
|
||||
@@ -10,6 +9,7 @@ import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { DepositsContainer } from './deposits-container';
|
||||
import { ResizableGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { LayoutPriority } from 'allotment';
|
||||
import { AccountsContainer } from './accounts-container';
|
||||
|
||||
const Portfolio = () => {
|
||||
const wrapperClasses = 'h-full max-h-full flex flex-col';
|
||||
|
||||
@@ -13,7 +13,6 @@ export const WithdrawalsContainer = () => {
|
||||
const { withdrawals, loading, error } = useWithdrawals();
|
||||
const [withdrawDialog, setWithdrawDialog] = useState(false);
|
||||
|
||||
console.log('render');
|
||||
return (
|
||||
<Web3Container>
|
||||
<VegaWalletContainer>
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import { Schema as Types } from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type AccountFieldsFragment = { __typename?: 'Account', type: Types.AccountType, balance: string, market?: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number } };
|
||||
|
||||
export type AccountsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type AccountsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, accounts?: Array<{ __typename?: 'Account', type: Types.AccountType, balance: string, market?: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number } }> | null } | null };
|
||||
|
||||
export type AccountEventsSubscriptionVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type AccountEventsSubscription = { __typename?: 'Subscription', accounts: Array<{ __typename?: 'AccountUpdate', type: Types.AccountType, balance: string, assetId: string, marketId?: string | null }> };
|
||||
|
||||
export const AccountFieldsFragmentDoc = gql`
|
||||
fragment AccountFields on Account {
|
||||
type
|
||||
balance
|
||||
market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
asset {
|
||||
id
|
||||
symbol
|
||||
decimals
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const AccountsDocument = gql`
|
||||
query Accounts($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
accounts {
|
||||
...AccountFields
|
||||
}
|
||||
}
|
||||
}
|
||||
${AccountFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useAccountsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useAccountsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useAccountsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useAccountsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useAccountsQuery(baseOptions: Apollo.QueryHookOptions<AccountsQuery, AccountsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<AccountsQuery, AccountsQueryVariables>(AccountsDocument, options);
|
||||
}
|
||||
export function useAccountsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AccountsQuery, AccountsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<AccountsQuery, AccountsQueryVariables>(AccountsDocument, options);
|
||||
}
|
||||
export type AccountsQueryHookResult = ReturnType<typeof useAccountsQuery>;
|
||||
export type AccountsLazyQueryHookResult = ReturnType<typeof useAccountsLazyQuery>;
|
||||
export type AccountsQueryResult = Apollo.QueryResult<AccountsQuery, AccountsQueryVariables>;
|
||||
export const AccountEventsDocument = gql`
|
||||
subscription AccountEvents($partyId: ID!) {
|
||||
accounts(partyId: $partyId) {
|
||||
type
|
||||
balance
|
||||
assetId
|
||||
marketId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useAccountEventsSubscription__
|
||||
*
|
||||
* To run a query within a React component, call `useAccountEventsSubscription` and pass it any options that fit your needs.
|
||||
* When your component renders, `useAccountEventsSubscription` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useAccountEventsSubscription({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useAccountEventsSubscription(baseOptions: Apollo.SubscriptionHookOptions<AccountEventsSubscription, AccountEventsSubscriptionVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useSubscription<AccountEventsSubscription, AccountEventsSubscriptionVariables>(AccountEventsDocument, options);
|
||||
}
|
||||
export type AccountEventsSubscriptionHookResult = ReturnType<typeof useAccountEventsSubscription>;
|
||||
export type AccountEventsSubscriptionResult = Apollo.SubscriptionResult<AccountEventsSubscription>;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from './Accounts';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Accounts';
|
||||
@@ -1,142 +0,0 @@
|
||||
import { forwardRef } from 'react';
|
||||
import type {
|
||||
GroupCellRendererParams,
|
||||
ValueFormatterParams,
|
||||
} from 'ag-grid-community';
|
||||
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
|
||||
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import type { AccountFieldsFragment } from './__generated__/Accounts';
|
||||
import { getId } from './accounts-data-provider';
|
||||
import type { AccountFields } from './accounts-manager';
|
||||
import {
|
||||
progressBarCellRendererSelector,
|
||||
progressBarHeaderComponentParams,
|
||||
progressBarValueFormatter,
|
||||
} from './accounts-table';
|
||||
|
||||
interface AccountsTableProps extends AgGridReactProps {
|
||||
data: AccountFields[] | null;
|
||||
onClickAsset: () => void;
|
||||
onClickWithdraw: () => void;
|
||||
onClickDeposit: () => void;
|
||||
expanded: boolean;
|
||||
showRows: boolean;
|
||||
hideHeader?: boolean;
|
||||
}
|
||||
|
||||
interface AccountsTableValueFormatterParams extends ValueFormatterParams {
|
||||
data: AccountFieldsFragment;
|
||||
}
|
||||
|
||||
export const assetDecimalsFormatter = ({
|
||||
value,
|
||||
data,
|
||||
}: AccountsTableValueFormatterParams) =>
|
||||
addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
|
||||
export const AccountDeposit = forwardRef<AgGridReact, AccountsTableProps>(
|
||||
(
|
||||
{
|
||||
data,
|
||||
onClickAsset,
|
||||
expanded,
|
||||
showRows,
|
||||
hideHeader,
|
||||
onClickWithdraw,
|
||||
onClickDeposit,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const openAssetAccountCellRenderer = ({ value }: GroupCellRendererParams) =>
|
||||
showRows ? (
|
||||
<button onClick={onClickAsset}>
|
||||
<span className="p-2">
|
||||
<Icon
|
||||
name="chevron-down"
|
||||
className={expanded ? 'rotate-180' : ''}
|
||||
size={3}
|
||||
/>
|
||||
</span>
|
||||
{value}
|
||||
</button>
|
||||
) : (
|
||||
<button className="pl-4">{value}</button>
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
rowData={data}
|
||||
getRowId={({ data }) => getId(data)}
|
||||
ref={ref}
|
||||
rowHeight={34}
|
||||
tooltipShowDelay={500}
|
||||
headerHeight={hideHeader ? 0 : undefined}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
}}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Asset')}
|
||||
colId="asset.symbol"
|
||||
field="asset.symbol"
|
||||
headerTooltip={t(
|
||||
'Asset is the collateral that is deposited into the Vega protocol.'
|
||||
)}
|
||||
cellRenderer={openAssetAccountCellRenderer}
|
||||
maxWidth={300}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Used')}
|
||||
field="used"
|
||||
flex={2}
|
||||
maxWidth={500}
|
||||
headerComponentParams={progressBarHeaderComponentParams}
|
||||
cellRendererSelector={progressBarCellRendererSelector}
|
||||
valueFormatter={progressBarValueFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Deposited')}
|
||||
field="deposited"
|
||||
valueFormatter={assetDecimalsFormatter}
|
||||
maxWidth={300}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName=""
|
||||
field="deposit"
|
||||
maxWidth={300}
|
||||
cellRenderer={() => {
|
||||
return (
|
||||
<Button size="xs" data-testid="deposit" onClick={onClickDeposit}>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName=""
|
||||
field="withdraw"
|
||||
maxWidth={300}
|
||||
cellRenderer={() => {
|
||||
return (
|
||||
<Button
|
||||
size="xs"
|
||||
data-testid="withdraw"
|
||||
onClick={onClickWithdraw}
|
||||
>
|
||||
{t('Withdraw')}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default AccountDeposit;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { AccountsManager } from './accounts-manager';
|
||||
|
||||
export const AccountsContainer = () => {
|
||||
const { keypair } = useVegaWallet();
|
||||
|
||||
if (!keypair) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Please connect Vega wallet')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return <AccountsManager partyId={keypair.pub} />;
|
||||
};
|
||||
@@ -9,11 +9,16 @@ import type {
|
||||
AccountsQuery,
|
||||
AccountEventsSubscription,
|
||||
} from './__generated___/Accounts';
|
||||
import type { SummaryRow } from '@vegaprotocol/react-helpers';
|
||||
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import type { ColumnApi } from 'ag-grid-community';
|
||||
import type { AccountFields } from './accounts-manager';
|
||||
|
||||
export interface AccountFields extends AccountFieldsFragment {
|
||||
available: string;
|
||||
used: string;
|
||||
deposited: string;
|
||||
balance: string;
|
||||
breakdown?: AccountFields[];
|
||||
}
|
||||
|
||||
function isAccount(
|
||||
account:
|
||||
@@ -32,49 +37,6 @@ export const getId = (
|
||||
? `${account.type}-${account.asset.id}-${account.market?.id ?? 'null'}`
|
||||
: `${account.type}-${account.assetId}-${account.marketId}`;
|
||||
|
||||
export const getGroupId = (
|
||||
data: AccountFields & SummaryRow,
|
||||
columnApi: ColumnApi
|
||||
) => {
|
||||
if (data.__summaryRow) {
|
||||
return null;
|
||||
}
|
||||
const sortColumnId = columnApi.getColumnState().find((c) => c.sort)?.colId;
|
||||
switch (sortColumnId) {
|
||||
case 'asset.symbol':
|
||||
return data.asset.id;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getGroupSummaryRow = (
|
||||
data: AccountFields[],
|
||||
columnApi: ColumnApi
|
||||
): Partial<AccountFields & SummaryRow> | null => {
|
||||
if (!data.length) {
|
||||
return null;
|
||||
}
|
||||
// TODO to be updated for sorting or summary
|
||||
return null;
|
||||
};
|
||||
|
||||
const update = (
|
||||
data: AccountFieldsFragment[],
|
||||
deltas: AccountEventsSubscription['accounts']
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
deltas.forEach((delta) => {
|
||||
const id = getId(delta);
|
||||
const index = draft.findIndex((a) => getId(a) === id);
|
||||
if (index !== -1) {
|
||||
draft[index].balance = delta.balance;
|
||||
} else {
|
||||
// #TODO handle new account
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const INCOMING_ACCOUNT_TYPES = [
|
||||
AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
@@ -102,6 +64,24 @@ const getDelta = (
|
||||
subscriptionData: AccountEventsSubscription
|
||||
): AccountEventsSubscription['accounts'] => subscriptionData.accounts;
|
||||
|
||||
const update = (
|
||||
data: AccountFieldsFragment[],
|
||||
deltas: AccountEventsSubscription['accounts']
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
deltas.forEach((delta) => {
|
||||
const id = getId(delta);
|
||||
const index = draft.findIndex((a) => getId(a) === id);
|
||||
if (index !== -1) {
|
||||
draft[index].balance = delta.balance;
|
||||
} else {
|
||||
// #TODO handle new account
|
||||
// draft.push(delta);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const accountsDataProvider = makeDataProvider<
|
||||
AccountsQuery,
|
||||
AccountFieldsFragment[],
|
||||
@@ -115,41 +95,46 @@ export const accountsDataProvider = makeDataProvider<
|
||||
getDelta,
|
||||
});
|
||||
|
||||
const getSymbols = (data: AccountFieldsFragment[]) =>
|
||||
Array.from(new Set(data.map((a) => a.asset.symbol))).sort();
|
||||
|
||||
// TODO add test for this
|
||||
export const getAccountData = (
|
||||
data: AccountFieldsFragment[],
|
||||
assetSymbol: string
|
||||
) => {
|
||||
const assetData = data.filter((a) => a.asset.symbol === assetSymbol);
|
||||
data: AccountFieldsFragment[]
|
||||
): AccountFields[] => {
|
||||
const collateralData = getSymbols(data).map((assetSymbol) => {
|
||||
const assetData = data.filter((a) => a.asset.symbol === assetSymbol);
|
||||
|
||||
const deposited = assetData
|
||||
.filter((a) => [AccountType.ACCOUNT_TYPE_GENERAL].includes(a.type))
|
||||
.reduce((acc, a) => acc + BigInt(a.balance), BigInt(0));
|
||||
const deposited = assetData
|
||||
.filter((a) => [AccountType.ACCOUNT_TYPE_GENERAL].includes(a.type))
|
||||
.reduce((acc, a) => acc + BigInt(a.balance), BigInt(0));
|
||||
|
||||
const incoming = assetData
|
||||
.filter((a) => INCOMING_ACCOUNT_TYPES.includes(a.type))
|
||||
.reduce((acc, a) => acc + BigInt(a.balance), BigInt(0));
|
||||
const incoming = assetData
|
||||
.filter((a) => INCOMING_ACCOUNT_TYPES.includes(a.type))
|
||||
.reduce((acc, a) => acc + BigInt(a.balance), BigInt(0));
|
||||
|
||||
const used = assetData
|
||||
.filter((a) => OUTCOMING_ACCOUNT_TYPES.includes(a.type))
|
||||
.reduce((acc, a) => acc + BigInt(a.balance), BigInt(0));
|
||||
const used = assetData
|
||||
.filter((a) => OUTCOMING_ACCOUNT_TYPES.includes(a.type))
|
||||
.reduce((acc, a) => acc + BigInt(a.balance), BigInt(0));
|
||||
|
||||
const depositRow = {
|
||||
...assetData[0],
|
||||
available: (incoming - used).toString(),
|
||||
deposited: deposited.toString(),
|
||||
used: used.toString(),
|
||||
};
|
||||
const accountRows = assetData
|
||||
.filter((a) => !INCOMING_ACCOUNT_TYPES.includes(a.type))
|
||||
.map((a) => ({
|
||||
...a,
|
||||
available: (incoming - BigInt(a.balance)).toString(),
|
||||
const depositRow: AccountFields = {
|
||||
...assetData[0],
|
||||
available: (incoming - used).toString(),
|
||||
deposited: deposited.toString(),
|
||||
used: a.balance.toString(),
|
||||
}));
|
||||
used: used.toString(),
|
||||
};
|
||||
|
||||
return {
|
||||
accountRows,
|
||||
depositRow,
|
||||
};
|
||||
const accountRows = assetData
|
||||
.filter((a) => !INCOMING_ACCOUNT_TYPES.includes(a.type))
|
||||
.map((a) => ({
|
||||
...a,
|
||||
available: (incoming - BigInt(a.balance)).toString(),
|
||||
deposited: deposited.toString(),
|
||||
used: a.balance.toString(),
|
||||
}));
|
||||
|
||||
return { ...depositRow, breakdown: accountRows };
|
||||
});
|
||||
|
||||
return collateralData;
|
||||
};
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { AsyncRenderer, Button, Dialog } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
addSummaryRows,
|
||||
t,
|
||||
useDataProvider,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
AccountFieldsFragment,
|
||||
AccountEventsSubscription,
|
||||
} from './__generated___/Accounts';
|
||||
import {
|
||||
accountsDataProvider,
|
||||
getAccountData,
|
||||
getGroupId,
|
||||
getGroupSummaryRow,
|
||||
getId,
|
||||
} from './accounts-data-provider';
|
||||
import AccountsTable from './accounts-table';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AccountDeposit } from './account-deposit';
|
||||
import { WithdrawalDialogs } from '@vegaprotocol/withdraws';
|
||||
import { Web3Container } from '@vegaprotocol/web3';
|
||||
import { DepositContainer } from '@vegaprotocol/deposits';
|
||||
import produce from 'immer';
|
||||
import merge from 'lodash/merge';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export interface AccountFields extends AccountFieldsFragment {
|
||||
available: string;
|
||||
used: string;
|
||||
deposited: string;
|
||||
balance: string;
|
||||
}
|
||||
|
||||
const getSymbols = (account: AccountFieldsFragment[]) =>
|
||||
Array.from(new Set(account.map((a) => a.asset.symbol))).sort();
|
||||
|
||||
interface AccountsManagerProps {
|
||||
partyId: string;
|
||||
}
|
||||
|
||||
export const AccountsManager = ({ partyId }: AccountsManagerProps) => {
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
const assetSymbols = useRef<string[] | undefined>();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const update = useCallback(
|
||||
({ delta: deltas }: { delta: AccountEventsSubscription['accounts'] }) => {
|
||||
const update: AccountFieldsFragment[] = [];
|
||||
const add: AccountFieldsFragment[] = [];
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
const api = gridRef.current.api;
|
||||
deltas.forEach((delta) => {
|
||||
const rowNode = api.getRowNode(getId(delta));
|
||||
if (rowNode) {
|
||||
const updatedData = produce<AccountFieldsFragment>(
|
||||
rowNode.data,
|
||||
(draft: AccountFieldsFragment) => {
|
||||
merge(draft, delta);
|
||||
}
|
||||
);
|
||||
if (updatedData !== rowNode.data) {
|
||||
update.push(updatedData);
|
||||
}
|
||||
} else {
|
||||
// #TODO handle new account (or leave it to data provider to handle it)
|
||||
}
|
||||
});
|
||||
if (update.length || add.length) {
|
||||
gridRef.current.api.applyTransactionAsync({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
if (add.length) {
|
||||
addSummaryRows(
|
||||
gridRef.current.api,
|
||||
gridRef.current.columnApi,
|
||||
getGroupId,
|
||||
getGroupSummaryRow
|
||||
);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
const { data, error, loading } = useDataProvider<
|
||||
AccountFieldsFragment[],
|
||||
AccountEventsSubscription['accounts']
|
||||
>({ dataProvider: accountsDataProvider, update, variables });
|
||||
const [depositDialog, setDepositDialog] = useState(false);
|
||||
|
||||
const symbols = data && getSymbols(data);
|
||||
return (
|
||||
<Web3Container>
|
||||
<AsyncRenderer loading={loading} error={error} data={assetSymbols}>
|
||||
{symbols &&
|
||||
symbols.map((assetSymbol, i) => (
|
||||
<AssetAccountTable
|
||||
ref={gridRef}
|
||||
key={assetSymbol}
|
||||
assetSymbol={assetSymbol}
|
||||
data={data}
|
||||
hideHeader={i !== 0}
|
||||
/>
|
||||
))}
|
||||
</AsyncRenderer>
|
||||
<div className="m-auto ml-4">
|
||||
<Button size="sm" onClick={() => setDepositDialog(true)}>
|
||||
{t('Deposit new asset')}
|
||||
</Button>
|
||||
</div>
|
||||
<DepositDialog
|
||||
depositDialog={depositDialog}
|
||||
setDepositDialog={setDepositDialog}
|
||||
/>
|
||||
</Web3Container>
|
||||
);
|
||||
};
|
||||
|
||||
export interface AssetAccountTableProps {
|
||||
assetSymbol: string;
|
||||
data: AccountFieldsFragment[];
|
||||
hideHeader?: boolean;
|
||||
}
|
||||
|
||||
export const AssetAccountTable = forwardRef<
|
||||
AgGridReact,
|
||||
AssetAccountTableProps
|
||||
>(({ data, assetSymbol, hideHeader }, ref) => {
|
||||
const { accountRows, depositRow } = getAccountData(data, assetSymbol);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [withdrawDialog, setWithdrawDialog] = useState(false);
|
||||
const [depositDialog, setDepositDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={classNames({
|
||||
'h-[50px]': hideHeader,
|
||||
'h-[85px]': !hideHeader,
|
||||
})}
|
||||
>
|
||||
<AccountDeposit
|
||||
ref={ref}
|
||||
data={[depositRow]}
|
||||
expanded={open}
|
||||
showRows={accountRows?.length > 0}
|
||||
hideHeader={hideHeader}
|
||||
onClickAsset={() => setOpen(!open)}
|
||||
onClickWithdraw={() => setWithdrawDialog(true)}
|
||||
onClickDeposit={() => setDepositDialog(true)}
|
||||
/>
|
||||
</div>
|
||||
{open && accountRows.length > 0 && (
|
||||
<div className="h-[15vh]">
|
||||
<AccountsTable ref={ref} data={accountRows} domLayout="autoHeight" />
|
||||
</div>
|
||||
)}
|
||||
<WithdrawalDialogs
|
||||
assetId={depositRow.asset.id}
|
||||
withdrawDialog={withdrawDialog}
|
||||
setWithdrawDialog={setWithdrawDialog}
|
||||
/>
|
||||
<DepositDialog
|
||||
assetId={depositRow.asset.id}
|
||||
depositDialog={depositDialog}
|
||||
setDepositDialog={setDepositDialog}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export interface DepositDialogProps {
|
||||
assetId?: string;
|
||||
depositDialog: boolean;
|
||||
setDepositDialog: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const DepositDialog = ({
|
||||
assetId,
|
||||
depositDialog,
|
||||
setDepositDialog,
|
||||
}: DepositDialogProps) => {
|
||||
return (
|
||||
<Dialog open={depositDialog} onChange={setDepositDialog}>
|
||||
<h1 className="text-2xl mb-4">{t('Deposit')}</h1>
|
||||
<DepositContainer assetId={assetId} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
import AccountsTable from './accounts-table';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import type { AccountFields } from './accounts-manager';
|
||||
import { Schema as Types } from '@vegaprotocol/types';
|
||||
|
||||
const singleRow: AccountFields = {
|
||||
__typename: 'Account',
|
||||
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
balance: '125600000',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
||||
},
|
||||
},
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
},
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
decimals: 5,
|
||||
},
|
||||
available: '125600000',
|
||||
used: '125600000',
|
||||
deposited: '125600000',
|
||||
};
|
||||
const singleRowData = [singleRow];
|
||||
|
||||
describe('AccountsTable', () => {
|
||||
it('should render successfully', async () => {
|
||||
await act(async () => {
|
||||
const { baseElement } = render(<AccountsTable data={[]} />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render correct columns', async () => {
|
||||
act(async () => {
|
||||
render(<AccountsTable data={singleRowData} />);
|
||||
await waitFor(async () => {
|
||||
const headers = await screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(4);
|
||||
expect(
|
||||
headers.map((h) =>
|
||||
h.querySelector('[ref="eText"]')?.textContent?.trim()
|
||||
)
|
||||
).toEqual(['Asset', 'Type', 'Market', 'Balance']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply correct formatting', async () => {
|
||||
act(async () => {
|
||||
render(<AccountsTable data={singleRowData} />);
|
||||
await waitFor(async () => {
|
||||
const cells = await screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
'tBTC',
|
||||
singleRow.type,
|
||||
'BTCUSD Monthly (30 Jun 2022)',
|
||||
'1,256.00000',
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,59 +1,41 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type {
|
||||
CellRendererSelectorResult,
|
||||
GroupCellRendererParams,
|
||||
ICellRendererParams,
|
||||
ValueFormatterParams,
|
||||
} from 'ag-grid-community';
|
||||
import type { Asset, ValueProps } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
PriceCell,
|
||||
addDecimalsFormatNumber,
|
||||
t,
|
||||
formatNumberPercentage,
|
||||
calculateLowHighRange,
|
||||
progressBarCellRendererSelector,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { Button, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridDynamic as AgGrid, ProgressBar } from '@vegaprotocol/ui-toolkit';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
|
||||
import { Button, ButtonLink, Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import type { AccountFieldsFragment } from './__generated___/Accounts';
|
||||
import { getId } from './accounts-data-provider';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { AccountFields } from './accounts-manager';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import type { AccountType } from '@vegaprotocol/types';
|
||||
import {
|
||||
accountsDataProvider,
|
||||
getAccountData,
|
||||
getId,
|
||||
} from './accounts-data-provider';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import type {
|
||||
AccountEventsSubscription,
|
||||
AccountFieldsFragment,
|
||||
} from './__generated___/Accounts';
|
||||
import BreakdownTable from './breakdown-table';
|
||||
import produce from 'immer';
|
||||
import merge from 'lodash/merge';
|
||||
|
||||
export interface ValueProps {
|
||||
valueFormatted?: {
|
||||
low: string;
|
||||
high: string;
|
||||
percentage: string;
|
||||
value: number;
|
||||
intent?: Intent;
|
||||
};
|
||||
interface AccountsTableProps extends AgGridReactProps {
|
||||
partyId: string;
|
||||
onClickAsset: (value?: string | Asset) => void;
|
||||
onClickWithdraw?: () => void;
|
||||
onClickDeposit?: () => void;
|
||||
}
|
||||
|
||||
export const EmptyCell = () => '';
|
||||
|
||||
export const ProgressBarCell = ({ valueFormatted }: ValueProps) => {
|
||||
return valueFormatted ? (
|
||||
<>
|
||||
<div className="flex justify-between leading-tight font-mono">
|
||||
<div>{valueFormatted.low}</div>
|
||||
<div>
|
||||
{valueFormatted.high} ({valueFormatted.percentage})
|
||||
</div>
|
||||
</div>
|
||||
<ProgressBar
|
||||
value={valueFormatted.value}
|
||||
intent={valueFormatted.intent}
|
||||
className="mt-2"
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const progressBarValueFormatter = ({
|
||||
data,
|
||||
node,
|
||||
@@ -63,17 +45,10 @@ export const progressBarValueFormatter = ({
|
||||
}
|
||||
const min = BigInt(data.used);
|
||||
const max = BigInt(data.deposited);
|
||||
const range = max;
|
||||
const value = range ? Number((min * BigInt(100)) / range) : 0;
|
||||
return {
|
||||
low: addDecimalsFormatNumber(min.toString(), data.asset.decimals),
|
||||
high: addDecimalsFormatNumber((max - min).toString(), data.asset.decimals),
|
||||
value: value,
|
||||
intent: Intent.None,
|
||||
percentage: value
|
||||
? formatNumberPercentage(new BigNumber(value), 2)
|
||||
: '0.00%',
|
||||
};
|
||||
const mid = max > min ? max - min : max;
|
||||
const intent = Intent.None;
|
||||
const decimals = data.asset.decimals;
|
||||
return calculateLowHighRange(max, min, decimals, mid, intent);
|
||||
};
|
||||
|
||||
export const progressBarHeaderComponentParams = {
|
||||
@@ -84,19 +59,6 @@ export const progressBarHeaderComponentParams = {
|
||||
'</div>',
|
||||
};
|
||||
|
||||
export const progressBarCellRendererSelector = (
|
||||
params: ICellRendererParams
|
||||
): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: params.node.rowPinned ? EmptyCell : ProgressBarCell,
|
||||
};
|
||||
};
|
||||
|
||||
interface AccountsTableProps extends AgGridReactProps {
|
||||
data: AccountFields[] | null;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
interface AccountsTableValueFormatterParams extends ValueFormatterParams {
|
||||
data: AccountFieldsFragment;
|
||||
}
|
||||
@@ -107,46 +69,90 @@ export const assetDecimalsFormatter = ({
|
||||
}: AccountsTableValueFormatterParams) =>
|
||||
addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
|
||||
export const AccountsTable = forwardRef<AgGridReact, AccountsTableProps>(
|
||||
({ data }, ref) => {
|
||||
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
||||
useAssetDetailsDialogStore();
|
||||
const assetDialogCellRenderer = ({ value }: GroupCellRendererParams) => {
|
||||
if (!value || value.length <= 0) return '-';
|
||||
return (
|
||||
<Button
|
||||
size="xs"
|
||||
className="hover:underline"
|
||||
onClick={() => {
|
||||
setAssetDetailsDialogOpen(true);
|
||||
setAssetDetailsDialogSymbol(value);
|
||||
}}
|
||||
>
|
||||
{t('Asset details')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
export const AccountsTable = ({
|
||||
onClickAsset,
|
||||
onClickWithdraw,
|
||||
onClickDeposit,
|
||||
partyId,
|
||||
}: AccountsTableProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const update = useCallback(
|
||||
({ delta: deltas }: { delta: AccountEventsSubscription['accounts'] }) => {
|
||||
const update: AccountFieldsFragment[] = [];
|
||||
const add: AccountFieldsFragment[] = [];
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
const api = gridRef.current.api;
|
||||
deltas.forEach((delta) => {
|
||||
const rowNode = api.getRowNode(getId(delta));
|
||||
if (rowNode) {
|
||||
const updatedData = produce<AccountFieldsFragment>(
|
||||
rowNode.data,
|
||||
(draft: AccountFieldsFragment) => {
|
||||
merge(draft, delta);
|
||||
}
|
||||
);
|
||||
if (updatedData !== rowNode.data) {
|
||||
update.push(updatedData);
|
||||
}
|
||||
} else {
|
||||
// #TODO handle new account (or leave it to data provider to handle it)
|
||||
}
|
||||
});
|
||||
if (update.length || add.length) {
|
||||
gridRef.current.api.applyTransactionAsync({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
return (
|
||||
const { data: collateralData } = useDataProvider<
|
||||
AccountFieldsFragment[],
|
||||
AccountEventsSubscription['accounts']
|
||||
>({ dataProvider: accountsDataProvider, update, variables: { partyId } });
|
||||
const data = collateralData && getAccountData(collateralData);
|
||||
const [openBreakdown, setOpenBreakdown] = useState(false);
|
||||
const [breakdown, setBreakdown] = useState<AccountFields[] | null>(null);
|
||||
return (
|
||||
<>
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No accounts')}
|
||||
rowData={data}
|
||||
getRowId={({ data }) => getId(data)}
|
||||
ref={ref}
|
||||
ref={gridRef}
|
||||
rowHeight={34}
|
||||
headerHeight={0}
|
||||
components={{ PriceCell }}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
sortable: true,
|
||||
}}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.name"
|
||||
valueFormatter="value || '—'"
|
||||
headerName={t('Asset')}
|
||||
field="asset.symbol"
|
||||
headerTooltip={t(
|
||||
'Asset is the collateral that is deposited into the Vega protocol.'
|
||||
)}
|
||||
cellRenderer={({ value }: ValueFormatterParams) => {
|
||||
return (
|
||||
<ButtonLink
|
||||
data-testid="deposit"
|
||||
onClick={() => {
|
||||
onClickAsset(value);
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</ButtonLink>
|
||||
);
|
||||
}}
|
||||
maxWidth={300}
|
||||
/>
|
||||
<AgGridColumn
|
||||
@@ -159,26 +165,67 @@ export const AccountsTable = forwardRef<AgGridReact, AccountsTableProps>(
|
||||
valueFormatter={progressBarValueFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Type')}
|
||||
field="type"
|
||||
headerName={t('Deposited')}
|
||||
field="deposited"
|
||||
valueFormatter={assetDecimalsFormatter}
|
||||
maxWidth={300}
|
||||
valueFormatter={({ value }: ValueFormatterParams) =>
|
||||
AccountTypeMapping[value as AccountType]
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Asset')}
|
||||
colId="asset.symbol"
|
||||
field="asset.symbol"
|
||||
maxWidth={300}
|
||||
headerTooltip={t(
|
||||
'Asset is the collateral that is deposited into the Vega protocol.'
|
||||
)}
|
||||
cellRenderer={assetDialogCellRenderer}
|
||||
headerName=""
|
||||
field="breakdown"
|
||||
minWidth={250}
|
||||
cellRenderer={({ value }: GroupCellRendererParams) => {
|
||||
return (
|
||||
<Button
|
||||
size="xs"
|
||||
data-testid="breakdown"
|
||||
onClick={() => {
|
||||
setOpenBreakdown(!openBreakdown);
|
||||
setBreakdown(value);
|
||||
}}
|
||||
>
|
||||
{t('Collateral breakdown')}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName=""
|
||||
field="deposit"
|
||||
maxWidth={200}
|
||||
cellRenderer={() => {
|
||||
return (
|
||||
<Button size="xs" data-testid="deposit" onClick={onClickDeposit}>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName=""
|
||||
field="withdraw"
|
||||
maxWidth={200}
|
||||
cellRenderer={() => {
|
||||
return (
|
||||
<Button
|
||||
size="xs"
|
||||
data-testid="withdraw"
|
||||
onClick={onClickWithdraw}
|
||||
>
|
||||
{t('Withdraw')}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
}
|
||||
);
|
||||
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
|
||||
<div className="h-[35vh] w-full m-auto flex flex-col">
|
||||
<h1 className="text-xl mb-4">{'Breakdown'}</h1>
|
||||
<BreakdownTable data={breakdown} domLayout="autoHeight" />
|
||||
</div>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountsTable;
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import BreakdownTable from './breakdown-table';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import { Schema as Types } from '@vegaprotocol/types';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import { getAccountData } from './accounts-data-provider';
|
||||
|
||||
const singleRow: AccountFields = {
|
||||
__typename: 'Account',
|
||||
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
balance: '125600000',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
||||
},
|
||||
},
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
},
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
decimals: 5,
|
||||
},
|
||||
available: '125600000',
|
||||
used: '125600000',
|
||||
deposited: '125600000',
|
||||
};
|
||||
const singleRowData = [singleRow];
|
||||
|
||||
describe('AccountsTable', () => {
|
||||
it('should render successfully', async () => {
|
||||
await act(async () => {
|
||||
const { baseElement } = render(<BreakdownTable data={[]} />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render correct columns', async () => {
|
||||
act(async () => {
|
||||
render(<BreakdownTable data={singleRowData} />);
|
||||
await waitFor(async () => {
|
||||
const headers = await screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(4);
|
||||
expect(
|
||||
headers.map((h) =>
|
||||
h.querySelector('[ref="eText"]')?.textContent?.trim()
|
||||
)
|
||||
).toEqual(['Asset', 'Type', 'Market', 'Balance']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply correct formatting', async () => {
|
||||
act(async () => {
|
||||
render(<BreakdownTable data={singleRowData} />);
|
||||
await waitFor(async () => {
|
||||
const cells = await screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
'tBTC',
|
||||
singleRow.type,
|
||||
'BTCUSD Monthly (30 Jun 2022)',
|
||||
'1,256.00000',
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get correct account data', () => {
|
||||
const result = getAccountData([singleRow]);
|
||||
const expected = [
|
||||
{
|
||||
__typename: 'Account',
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
},
|
||||
available: '-125600000',
|
||||
balance: '125600000',
|
||||
breakdown: [
|
||||
{
|
||||
__typename: 'Account',
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
},
|
||||
available: '-125600000',
|
||||
balance: '125600000',
|
||||
deposited: '0',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'ACCOUNT_TYPE_MARGIN',
|
||||
used: '125600000',
|
||||
},
|
||||
],
|
||||
deposited: '0',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'ACCOUNT_TYPE_MARGIN',
|
||||
used: '125600000',
|
||||
},
|
||||
];
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import type { ValueFormatterParams } from 'ag-grid-community';
|
||||
import {
|
||||
PriceCell,
|
||||
progressBarCellRendererSelector,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import { getId } from './accounts-data-provider';
|
||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import type { AccountType } from '@vegaprotocol/types';
|
||||
import {
|
||||
progressBarHeaderComponentParams,
|
||||
progressBarValueFormatter,
|
||||
} from './accounts-table';
|
||||
|
||||
interface BreakdownTableProps extends AgGridReactProps {
|
||||
data: AccountFields[] | null;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
|
||||
({ data }, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No collateral breakdown')}
|
||||
rowData={data}
|
||||
getRowId={({ data }) => getId(data)}
|
||||
ref={ref}
|
||||
rowHeight={34}
|
||||
components={{ PriceCell }}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
}}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.name"
|
||||
valueFormatter="value || '—'"
|
||||
maxWidth={300}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Used')}
|
||||
field="used"
|
||||
flex={2}
|
||||
maxWidth={500}
|
||||
headerComponentParams={progressBarHeaderComponentParams}
|
||||
cellRendererSelector={progressBarCellRendererSelector}
|
||||
valueFormatter={progressBarValueFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Type')}
|
||||
field="type"
|
||||
maxWidth={300}
|
||||
valueFormatter={({ value }: ValueFormatterParams) =>
|
||||
AccountTypeMapping[value as AccountType]
|
||||
}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default BreakdownTable;
|
||||
@@ -1,6 +1,6 @@
|
||||
export * from './__generated___/Accounts';
|
||||
export * from './accounts-container';
|
||||
export * from './__generated__';
|
||||
// export * from './__generated___';
|
||||
export * from './accounts-data-provider';
|
||||
export * from './accounts-manager';
|
||||
export * from './accounts-table';
|
||||
export * from './asset-balance';
|
||||
export * from './breakdown-table';
|
||||
|
||||
@@ -153,7 +153,7 @@ export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
>(
|
||||
[ordersProvider, marketsProvider],
|
||||
(partsData): OrderWithMarketEdge[] =>
|
||||
(partsData[0] as Parameters<typeof update>['0']).map((edge) => ({
|
||||
((partsData[0] as Parameters<typeof update>['0']) || []).map((edge) => ({
|
||||
cursor: edge.cursor,
|
||||
node: {
|
||||
...edge.node,
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
ICellRendererParams,
|
||||
CellRendererSelectorResult,
|
||||
} from 'ag-grid-community';
|
||||
import type { ValueProps as PriceCellProps } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
PriceFlashCell,
|
||||
addDecimalsFormatNumber,
|
||||
@@ -16,8 +17,11 @@ import {
|
||||
getDateTimeFormat,
|
||||
signedNumberCssClass,
|
||||
signedNumberCssClassRules,
|
||||
calculateLowHighRange,
|
||||
EmptyCell,
|
||||
ProgressBarCell,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { AgGridDynamic as AgGrid, ProgressBar } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
|
||||
@@ -64,33 +68,6 @@ export const MarketNameCell = ({ valueFormatted }: MarketNameCellProps) => {
|
||||
return (valueFormatted && valueFormatted[0]) || undefined;
|
||||
};
|
||||
|
||||
export interface PriceCellProps {
|
||||
valueFormatted?: {
|
||||
low: string;
|
||||
high: string;
|
||||
value: number;
|
||||
intent?: Intent;
|
||||
};
|
||||
}
|
||||
|
||||
export const ProgressBarCell = ({ valueFormatted }: PriceCellProps) => {
|
||||
return valueFormatted ? (
|
||||
<>
|
||||
<div className="flex justify-between leading-tight font-mono">
|
||||
<div>{valueFormatted.low}</div>
|
||||
<div>{valueFormatted.high}</div>
|
||||
</div>
|
||||
<ProgressBar
|
||||
value={valueFormatted.value}
|
||||
intent={valueFormatted.intent}
|
||||
className="mt-2"
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
ProgressBarCell.displayName = 'PriceFlashCell';
|
||||
|
||||
export interface AmountCellProps {
|
||||
valueFormatted?: Pick<
|
||||
Position,
|
||||
@@ -140,7 +117,22 @@ const ButtonCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
const EmptyCell = () => '';
|
||||
const progressBarValueFormatter = ({
|
||||
data,
|
||||
node,
|
||||
}: PositionsTableValueFormatterParams):
|
||||
| PriceCellProps['valueFormatted']
|
||||
| undefined => {
|
||||
if (!data || node?.rowPinned) {
|
||||
return undefined;
|
||||
}
|
||||
const min = BigInt(data.averageEntryPrice);
|
||||
const max = BigInt(data.liquidationPrice);
|
||||
const mid = BigInt(data.markPrice);
|
||||
const decimals = data.marketDecimalPlaces;
|
||||
const intent = data.lowMarginLevel ? Intent.Warning : Intent.None;
|
||||
return calculateLowHighRange(max, min, decimals, mid, intent);
|
||||
};
|
||||
|
||||
export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
({ onClose, ...props }, ref) => {
|
||||
@@ -264,32 +256,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
component: params.node.rowPinned ? EmptyCell : ProgressBarCell,
|
||||
};
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
node,
|
||||
}: PositionsTableValueFormatterParams):
|
||||
| PriceCellProps['valueFormatted']
|
||||
| undefined => {
|
||||
if (!data || node?.rowPinned) {
|
||||
return undefined;
|
||||
}
|
||||
const min = BigInt(data.averageEntryPrice);
|
||||
const max = BigInt(data.liquidationPrice);
|
||||
const mid = BigInt(data.markPrice);
|
||||
const range = max - min;
|
||||
return {
|
||||
low: addDecimalsFormatNumber(
|
||||
min.toString(),
|
||||
data.marketDecimalPlaces
|
||||
),
|
||||
high: addDecimalsFormatNumber(
|
||||
max.toString(),
|
||||
data.marketDecimalPlaces
|
||||
),
|
||||
value: range ? Number(((mid - min) * BigInt(100)) / range) : 0,
|
||||
intent: data.lowMarginLevel ? Intent.Warning : undefined,
|
||||
};
|
||||
}}
|
||||
valueFormatter={progressBarValueFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Leverage')}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import type {
|
||||
CellRendererSelectorResult,
|
||||
ICellRendererParams,
|
||||
} from 'ag-grid-community';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||
import type { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { ProgressBar } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export interface ValueProps {
|
||||
valueFormatted?: {
|
||||
low: string;
|
||||
high: string;
|
||||
value: number;
|
||||
intent?: Intent;
|
||||
};
|
||||
}
|
||||
|
||||
export const EmptyCell = () => '';
|
||||
|
||||
export const ProgressBarCell = ({ valueFormatted }: ValueProps) => {
|
||||
return valueFormatted ? (
|
||||
<>
|
||||
<div className="flex justify-between leading-tight font-mono">
|
||||
<div>{valueFormatted.low}</div>
|
||||
<div>{valueFormatted.high}</div>
|
||||
</div>
|
||||
<ProgressBar
|
||||
value={valueFormatted.value}
|
||||
intent={valueFormatted.intent}
|
||||
className="mt-2"
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const progressBarCellRendererSelector = (
|
||||
params: ICellRendererParams
|
||||
): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: params.node.rowPinned ? EmptyCell : ProgressBarCell,
|
||||
};
|
||||
};
|
||||
|
||||
export const calculateLowHighRange = (
|
||||
max: bigint,
|
||||
min: bigint,
|
||||
decimals: number,
|
||||
mid: bigint,
|
||||
intent: Intent
|
||||
) => {
|
||||
const range = max > min ? max - min : max; // coloured range should stay between min max bounds.
|
||||
return {
|
||||
low: addDecimalsFormatNumber(min.toString(), decimals),
|
||||
high: addDecimalsFormatNumber(max.toString(), decimals),
|
||||
value: range ? Number(((mid - min) * BigInt(100)) / range) : 0,
|
||||
intent,
|
||||
};
|
||||
};
|
||||
@@ -6,3 +6,4 @@ export * from './price-flash-cell';
|
||||
export * from './size';
|
||||
export * from './summary-rows';
|
||||
export * from './vol-cell';
|
||||
export * from './grid-progress-bar';
|
||||
|
||||
Reference in New Issue
Block a user