chore(trading): 2825 buttons floating over table rows (#3084)
This commit is contained in:
parent
8f5a2276de
commit
197f2e8097
@ -2,10 +2,16 @@ import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
|
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
|
||||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
import {
|
||||||
|
useDataProvider,
|
||||||
|
useBottomPlaceholder,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
|
||||||
export const DepositsContainer = () => {
|
export const DepositsContainer = () => {
|
||||||
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const { pubKey, isReadOnly } = useVegaWallet();
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
const { data, loading, error, reload } = useDataProvider({
|
const { data, loading, error, reload } = useDataProvider({
|
||||||
dataProvider: depositsProvider,
|
dataProvider: depositsProvider,
|
||||||
@ -13,13 +19,15 @@ export const DepositsContainer = () => {
|
|||||||
skip: !pubKey,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
const openDepositDialog = useDepositDialog((state) => state.open);
|
const openDepositDialog = useDepositDialog((state) => state.open);
|
||||||
|
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
|
||||||
return (
|
return (
|
||||||
<div className="h-full grid grid-rows-[1fr,min-content]">
|
<div className="h-full">
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<DepositsTable
|
<DepositsTable
|
||||||
rowData={data || []}
|
rowData={data || []}
|
||||||
noRowsOverlayComponent={() => null}
|
noRowsOverlayComponent={() => null}
|
||||||
|
ref={gridRef}
|
||||||
|
{...bottomPlaceholderProps}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
@ -33,8 +41,9 @@ export const DepositsContainer = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
|
||||||
<Button
|
<Button
|
||||||
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => openDepositDialog()}
|
onClick={() => openDepositDialog()}
|
||||||
data-testid="deposit-button"
|
data-testid="deposit-button"
|
||||||
|
@ -20,36 +20,35 @@ export const WithdrawalsContainer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<VegaWalletContainer>
|
<VegaWalletContainer>
|
||||||
<div className="h-full relative grid grid-rows-[1fr,min-content]">
|
<div className="h-full relative">
|
||||||
<div className="h-full relative">
|
<WithdrawalsTable
|
||||||
<WithdrawalsTable
|
data-testid="withdrawals-history"
|
||||||
data-testid="withdrawals-history"
|
rowData={data}
|
||||||
rowData={data}
|
noRowsOverlayComponent={() => null}
|
||||||
noRowsOverlayComponent={() => null}
|
/>
|
||||||
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
<AsyncRenderer
|
||||||
|
data={data}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
|
noDataMessage={t('No withdrawals')}
|
||||||
|
reload={reload}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0">
|
|
||||||
<AsyncRenderer
|
|
||||||
data={data}
|
|
||||||
loading={loading}
|
|
||||||
error={error}
|
|
||||||
noDataCondition={(data) => !(data && data.length)}
|
|
||||||
noDataMessage={t('No withdrawals')}
|
|
||||||
reload={reload}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{!isReadOnly && (
|
|
||||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onClick={() => openWithdrawDialog()}
|
|
||||||
data-testid="withdraw-dialog-button"
|
|
||||||
>
|
|
||||||
{t('Make withdrawal')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{!isReadOnly && (
|
||||||
|
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => openWithdrawDialog()}
|
||||||
|
data-testid="withdraw-dialog-button"
|
||||||
|
>
|
||||||
|
{t('Make withdrawal')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</VegaWalletContainer>
|
</VegaWalletContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,12 +8,15 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
|||||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||||
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
|
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
|
||||||
import { useDepositDialog } from '@vegaprotocol/deposits';
|
import { useDepositDialog } from '@vegaprotocol/deposits';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
export const AccountsContainer = ({
|
export const AccountsContainer = ({
|
||||||
pinnedAsset,
|
pinnedAsset,
|
||||||
}: {
|
}: {
|
||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
}) => {
|
}) => {
|
||||||
|
const params = useParams();
|
||||||
|
const hideButtons = 'marketId' in params;
|
||||||
const { pubKey, isReadOnly } = useVegaWallet();
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||||
const openWithdrawalDialog = useWithdrawalDialog((store) => store.open);
|
const openWithdrawalDialog = useWithdrawalDialog((store) => store.open);
|
||||||
@ -36,27 +39,30 @@ export const AccountsContainer = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative grid grid-rows-[1fr,min-content]">
|
<div className="h-full relative">
|
||||||
<div>
|
<AccountManager
|
||||||
<AccountManager
|
partyId={pubKey}
|
||||||
partyId={pubKey}
|
onClickAsset={onClickAsset}
|
||||||
onClickAsset={onClickAsset}
|
onClickWithdraw={openWithdrawalDialog}
|
||||||
onClickWithdraw={openWithdrawalDialog}
|
onClickDeposit={openDepositDialog}
|
||||||
onClickDeposit={openDepositDialog}
|
isReadOnly={isReadOnly}
|
||||||
isReadOnly={isReadOnly}
|
pinnedAsset={pinnedAsset}
|
||||||
pinnedAsset={pinnedAsset}
|
/>
|
||||||
/>
|
{!isReadOnly && !hideButtons && (
|
||||||
</div>
|
<div className="flex gap-2 justify-end p-2 px-[11px] fixed bottom-0 right-2 dark:bg-black/75 bg-white/75 rounded">
|
||||||
{!isReadOnly && (
|
|
||||||
<div className="flex gap-2 justify-end p-2 px-[11px]">
|
|
||||||
<Button
|
<Button
|
||||||
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
data-testid="open-transfer-dialog"
|
data-testid="open-transfer-dialog"
|
||||||
onClick={() => openTransferDialog()}
|
onClick={() => openTransferDialog()}
|
||||||
>
|
>
|
||||||
{t('Transfer')}
|
{t('Transfer')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" onClick={() => openDepositDialog()}>
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => openDepositDialog()}
|
||||||
|
>
|
||||||
{t('Deposit')}
|
{t('Deposit')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@ export const Footer = () => {
|
|||||||
const { blockDiff, datanodeBlockHeight } = useNodeHealth();
|
const { blockDiff, datanodeBlockHeight } = useNodeHealth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300">
|
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300 fixed bottom-0 left-0 border-r bg-white dark:bg-black">
|
||||||
{/* Pull left to align with top nav, due to button padding */}
|
{/* Pull left to align with top nav, due to button padding */}
|
||||||
<div className="-ml-2">
|
<div className="-ml-2">
|
||||||
{VEGA_URL && (
|
{VEGA_URL && (
|
||||||
|
@ -72,7 +72,7 @@ function AppBody({ Component }: AppProps) {
|
|||||||
|
|
||||||
const gridClasses = classNames(
|
const gridClasses = classNames(
|
||||||
'h-full relative z-0 grid',
|
'h-full relative z-0 grid',
|
||||||
'grid-rows-[repeat(3,min-content),1fr,min-content]'
|
'grid-rows-[repeat(3,min-content),1fr]'
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
|
import { useRef, useMemo, memo, useCallback } from 'react';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
import {
|
||||||
|
useDataProvider,
|
||||||
|
useBottomPlaceholder,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { useRef, useMemo, memo } from 'react';
|
|
||||||
import type { AccountFields } from './accounts-data-provider';
|
import type { AccountFields } from './accounts-data-provider';
|
||||||
import { aggregatedAccountsDataProvider } from './accounts-data-provider';
|
import { aggregatedAccountsDataProvider } from './accounts-data-provider';
|
||||||
import type { PinnedAsset } from './accounts-table';
|
import type { PinnedAsset } from './accounts-table';
|
||||||
import { AccountTable } from './accounts-table';
|
import { AccountTable } from './accounts-table';
|
||||||
|
import type { RowHeightParams } from 'ag-grid-community';
|
||||||
|
|
||||||
interface AccountManagerProps {
|
interface AccountManagerProps {
|
||||||
partyId: string;
|
partyId: string;
|
||||||
@ -27,7 +31,6 @@ export const AccountManager = ({
|
|||||||
}: AccountManagerProps) => {
|
}: AccountManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||||
|
|
||||||
const { data, loading, error, reload } = useDataProvider<
|
const { data, loading, error, reload } = useDataProvider<
|
||||||
AccountFields[],
|
AccountFields[],
|
||||||
never
|
never
|
||||||
@ -35,6 +38,22 @@ export const AccountManager = ({
|
|||||||
dataProvider: aggregatedAccountsDataProvider,
|
dataProvider: aggregatedAccountsDataProvider,
|
||||||
variables,
|
variables,
|
||||||
});
|
});
|
||||||
|
const setId = useCallback(
|
||||||
|
(data: AccountFields) => ({
|
||||||
|
...data,
|
||||||
|
asset: { ...data.asset, id: `${data.asset.id}-1` },
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({
|
||||||
|
gridRef,
|
||||||
|
setId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getRowHeight = useCallback(
|
||||||
|
(params: RowHeightParams) => (params.node.rowPinned ? 32 : 22),
|
||||||
|
[]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full">
|
<div className="relative h-full">
|
||||||
<AccountTable
|
<AccountTable
|
||||||
@ -46,6 +65,8 @@ export const AccountManager = ({
|
|||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
noRowsOverlayComponent={() => null}
|
noRowsOverlayComponent={() => null}
|
||||||
pinnedAsset={pinnedAsset}
|
pinnedAsset={pinnedAsset}
|
||||||
|
getRowHeight={getRowHeight}
|
||||||
|
{...bottomPlaceholderProps}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
|
@ -5,9 +5,12 @@ import type {
|
|||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
VegaValueFormatterParams,
|
VegaValueFormatterParams,
|
||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
import { ButtonLink, Dialog } from '@vegaprotocol/ui-toolkit';
|
import { Button, ButtonLink, Dialog } from '@vegaprotocol/ui-toolkit';
|
||||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
import {
|
||||||
|
AgGridDynamic as AgGrid,
|
||||||
|
CenteredGridCellWrapper,
|
||||||
|
} from '@vegaprotocol/datagrid';
|
||||||
import { AgGridColumn } from 'ag-grid-react';
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
|
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
|
||||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||||
@ -86,18 +89,23 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
|||||||
cellRenderer={({
|
cellRenderer={({
|
||||||
value,
|
value,
|
||||||
data,
|
data,
|
||||||
|
node,
|
||||||
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
|
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
|
||||||
return value ? (
|
return value ? (
|
||||||
<ButtonLink
|
<CenteredGridCellWrapper
|
||||||
data-testid="asset"
|
className={node.rowPinned ? 'h-[30px]' : undefined}
|
||||||
onClick={() => {
|
|
||||||
if (data) {
|
|
||||||
onClickAsset(data.asset.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{value}
|
<ButtonLink
|
||||||
</ButtonLink>
|
data-testid="asset"
|
||||||
|
onClick={() => {
|
||||||
|
if (data) {
|
||||||
|
onClickAsset(data.asset.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</ButtonLink>
|
||||||
|
</CenteredGridCellWrapper>
|
||||||
) : null;
|
) : null;
|
||||||
}}
|
}}
|
||||||
maxWidth={300}
|
maxWidth={300}
|
||||||
@ -109,16 +117,25 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
|||||||
headerTooltip={t(
|
headerTooltip={t(
|
||||||
'This is the total amount of collateral used plus the amount available in your general account.'
|
'This is the total amount of collateral used plus the amount available in your general account.'
|
||||||
)}
|
)}
|
||||||
valueFormatter={({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<AccountFields, 'deposited'>) =>
|
|
||||||
data &&
|
|
||||||
data.asset &&
|
|
||||||
isNumeric(value) &&
|
|
||||||
addDecimalsFormatNumber(value, data.asset.decimals)
|
|
||||||
}
|
|
||||||
maxWidth={300}
|
maxWidth={300}
|
||||||
|
cellRenderer={({
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
node,
|
||||||
|
}: VegaICellRendererParams<AccountFields, 'deposited'>) => {
|
||||||
|
const valueFormatted =
|
||||||
|
data &&
|
||||||
|
data.asset &&
|
||||||
|
isNumeric(value) &&
|
||||||
|
addDecimalsFormatNumber(value, data.asset.decimals);
|
||||||
|
return node.rowPinned ? (
|
||||||
|
<CenteredGridCellWrapper className="h-[30px] justify-end">
|
||||||
|
{valueFormatted}
|
||||||
|
</CenteredGridCellWrapper>
|
||||||
|
) : (
|
||||||
|
valueFormatted
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Used')}
|
headerName={t('Used')}
|
||||||
@ -127,16 +144,25 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
|||||||
headerTooltip={t(
|
headerTooltip={t(
|
||||||
'This is the amount of collateral used from your general account.'
|
'This is the amount of collateral used from your general account.'
|
||||||
)}
|
)}
|
||||||
valueFormatter={({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<AccountFields, 'used'>) =>
|
|
||||||
data &&
|
|
||||||
data.asset &&
|
|
||||||
isNumeric(value) &&
|
|
||||||
addDecimalsFormatNumber(value, data.asset.decimals)
|
|
||||||
}
|
|
||||||
maxWidth={300}
|
maxWidth={300}
|
||||||
|
cellRenderer={({
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
node,
|
||||||
|
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||||
|
const valueFormatted =
|
||||||
|
data &&
|
||||||
|
data.asset &&
|
||||||
|
isNumeric(value) &&
|
||||||
|
addDecimalsFormatNumber(value, data.asset.decimals);
|
||||||
|
return node.rowPinned ? (
|
||||||
|
<CenteredGridCellWrapper className="h-[30px] justify-end">
|
||||||
|
{valueFormatted}
|
||||||
|
</CenteredGridCellWrapper>
|
||||||
|
) : (
|
||||||
|
valueFormatted
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Available')}
|
headerName={t('Available')}
|
||||||
@ -155,73 +181,93 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
|||||||
addDecimalsFormatNumber(value, data.asset.decimals)
|
addDecimalsFormatNumber(value, data.asset.decimals)
|
||||||
}
|
}
|
||||||
maxWidth={300}
|
maxWidth={300}
|
||||||
|
cellRenderer={({
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
node,
|
||||||
|
}: VegaICellRendererParams<AccountFields, 'available'>) => {
|
||||||
|
const valueFormatted =
|
||||||
|
data &&
|
||||||
|
data.asset &&
|
||||||
|
isNumeric(value) &&
|
||||||
|
addDecimalsFormatNumber(value, data.asset.decimals);
|
||||||
|
return node.rowPinned ? (
|
||||||
|
<CenteredGridCellWrapper className="h-[30px] justify-end">
|
||||||
|
{valueFormatted}
|
||||||
|
</CenteredGridCellWrapper>
|
||||||
|
) : (
|
||||||
|
valueFormatted
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{
|
<AgGridColumn
|
||||||
<AgGridColumn
|
colId="breakdown"
|
||||||
colId="breakdown"
|
headerName=""
|
||||||
headerName=""
|
sortable={false}
|
||||||
sortable={false}
|
minWidth={200}
|
||||||
minWidth={200}
|
type="rightAligned"
|
||||||
type="rightAligned"
|
cellRenderer={({
|
||||||
cellRenderer={({
|
data,
|
||||||
data,
|
}: VegaICellRendererParams<AccountFields>) => {
|
||||||
}: VegaICellRendererParams<AccountFields>) => {
|
if (!data) return null;
|
||||||
if (!data) return null;
|
else {
|
||||||
else {
|
if (
|
||||||
if (
|
data.asset.id === pinnedAssetId &&
|
||||||
data.asset.id === pinnedAssetId &&
|
new BigNumber(data.deposited).isLessThanOrEqualTo(0)
|
||||||
new BigNumber(data.deposited).isLessThanOrEqualTo(0)
|
) {
|
||||||
) {
|
return (
|
||||||
return (
|
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
||||||
<ButtonLink
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="primary"
|
||||||
data-testid="deposit"
|
data-testid="deposit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClickDeposit && onClickDeposit(data.asset.id);
|
onClickDeposit && onClickDeposit(data.asset.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Deposit to trade')}
|
{t('Deposit to trade')}
|
||||||
</ButtonLink>
|
</Button>
|
||||||
);
|
</CenteredGridCellWrapper>
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ButtonLink
|
|
||||||
data-testid="breakdown"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenBreakdown(!openBreakdown);
|
|
||||||
setBreakdown(data.breakdown || null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Breakdown')}
|
|
||||||
</ButtonLink>
|
|
||||||
<span className="mx-1" />
|
|
||||||
{!props.isReadOnly && (
|
|
||||||
<ButtonLink
|
|
||||||
data-testid="deposit"
|
|
||||||
onClick={() => {
|
|
||||||
onClickDeposit && onClickDeposit(data.asset.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Deposit')}
|
|
||||||
</ButtonLink>
|
|
||||||
)}
|
|
||||||
<span className="mx-1" />
|
|
||||||
{!props.isReadOnly && (
|
|
||||||
<ButtonLink
|
|
||||||
data-testid="withdraw"
|
|
||||||
onClick={() =>
|
|
||||||
onClickWithdraw && onClickWithdraw(data.asset.id)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('Withdraw')}
|
|
||||||
</ButtonLink>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
return (
|
||||||
/>
|
<>
|
||||||
}
|
<ButtonLink
|
||||||
|
data-testid="breakdown"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenBreakdown(!openBreakdown);
|
||||||
|
setBreakdown(data.breakdown || null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Breakdown')}
|
||||||
|
</ButtonLink>
|
||||||
|
<span className="mx-1" />
|
||||||
|
{!props.isReadOnly && (
|
||||||
|
<ButtonLink
|
||||||
|
data-testid="deposit"
|
||||||
|
onClick={() => {
|
||||||
|
onClickDeposit && onClickDeposit(data.asset.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Deposit')}
|
||||||
|
</ButtonLink>
|
||||||
|
)}
|
||||||
|
<span className="mx-1" />
|
||||||
|
{!props.isReadOnly && (
|
||||||
|
<ButtonLink
|
||||||
|
data-testid="withdraw"
|
||||||
|
onClick={() =>
|
||||||
|
onClickWithdraw && onClickWithdraw(data.asset.id)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('Withdraw')}
|
||||||
|
</ButtonLink>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</AgGrid>
|
</AgGrid>
|
||||||
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
|
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
|
||||||
<div className="h-[35vh] w-full m-auto flex flex-col">
|
<div className="h-[35vh] w-full m-auto flex flex-col">
|
||||||
|
@ -8,6 +8,7 @@ export * from './lib/cells/price-cell';
|
|||||||
export * from './lib/cells/price-change-cell';
|
export * from './lib/cells/price-change-cell';
|
||||||
export * from './lib/cells/price-flash-cell';
|
export * from './lib/cells/price-flash-cell';
|
||||||
export * from './lib/cells/vol-cell';
|
export * from './lib/cells/vol-cell';
|
||||||
|
export * from './lib/cells/centered-grid-cell';
|
||||||
|
|
||||||
export * from './lib/filters/date-range-filter';
|
export * from './lib/filters/date-range-filter';
|
||||||
export * from './lib/filters/set-filter';
|
export * from './lib/filters/set-filter';
|
||||||
|
@ -22,13 +22,15 @@ const agGridDarkVariables = `
|
|||||||
border-width: 1px 0;
|
border-width: 1px 0;
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
.ag-theme-balham-dark .ag-row.no-hover, .ag-theme-balham-dark .ag-row.no-hover:hover {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
.ag-theme-balham-dark .ag-react-container {
|
.ag-theme-balham-dark .ag-react-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-theme-balham-dark .ag-cell, .ag-theme-balham-dark .ag-full-width-row .ag-cell-wrapper.ag-row-group {
|
.ag-theme-balham-dark .ag-cell, .ag-theme-balham-dark .ag-full-width-row .ag-cell-wrapper.ag-row-group {
|
||||||
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -22,13 +22,15 @@ const agGridLightVariables = `
|
|||||||
border-width: 1px 0;
|
border-width: 1px 0;
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
.ag-theme-balham .ag-row.no-hover, .ag-theme-balham .ag-row.no-hover:hover {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
.ag-theme-balham .ag-react-container {
|
.ag-theme-balham .ag-react-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-theme-balham .ag-cell, .ag-theme-balham .ag-full-width-row .ag-cell-wrapper.ag-row-group {
|
.ag-theme-balham .ag-cell, .ag-theme-balham .ag-full-width-row .ag-cell-wrapper.ag-row-group {
|
||||||
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
16
libs/datagrid/src/lib/cells/centered-grid-cell.tsx
Normal file
16
libs/datagrid/src/lib/cells/centered-grid-cell.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
export const CenteredGridCellWrapper = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={classNames('flex h-[20px] p-0 justify-items-center', className)}
|
||||||
|
>
|
||||||
|
<div className="self-center">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
@ -72,7 +72,9 @@ export const DepositsTable = forwardRef<
|
|||||||
field="txHash"
|
field="txHash"
|
||||||
cellRenderer={({
|
cellRenderer={({
|
||||||
value,
|
value,
|
||||||
|
data,
|
||||||
}: VegaICellRendererParams<DepositFieldsFragment, 'txHash'>) => {
|
}: VegaICellRendererParams<DepositFieldsFragment, 'txHash'>) => {
|
||||||
|
if (!data) return null;
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
@ -23,6 +23,7 @@ import { OrdersDocument, OrdersUpdateDocument } from './__generated__/Orders';
|
|||||||
|
|
||||||
export type Order = Omit<OrderFieldsFragment, 'market'> & {
|
export type Order = Omit<OrderFieldsFragment, 'market'> & {
|
||||||
market?: Market;
|
market?: Market;
|
||||||
|
isLastPlaceholder?: boolean;
|
||||||
};
|
};
|
||||||
export type OrderEdge = Edge<Order>;
|
export type OrderEdge = Edge<Order>;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import { truncateByChars } from '@vegaprotocol/utils';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import type {
|
import type {
|
||||||
@ -8,28 +7,19 @@ import type {
|
|||||||
FilterChangedEvent,
|
FilterChangedEvent,
|
||||||
SortChangedEvent,
|
SortChangedEvent,
|
||||||
} from 'ag-grid-community';
|
} from 'ag-grid-community';
|
||||||
import { Button, Intent } from '@vegaprotocol/ui-toolkit';
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
|
||||||
import { OrderListTable } from '../order-list/order-list';
|
import { OrderListTable } from '../order-list/order-list';
|
||||||
import { useOrderListData } from './use-order-list-data';
|
import { useOrderListData } from './use-order-list-data';
|
||||||
import { useHasActiveOrder } from '../../order-hooks/use-has-active-order';
|
import { useHasActiveOrder } from '../../order-hooks/use-has-active-order';
|
||||||
import type { Filter, Sort } from './use-order-list-data';
|
import type { Filter, Sort } from './use-order-list-data';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import {
|
import {
|
||||||
normalizeOrderAmendment,
|
normalizeOrderAmendment,
|
||||||
useVegaTransactionStore,
|
useVegaTransactionStore,
|
||||||
} from '@vegaprotocol/wallet';
|
} from '@vegaprotocol/wallet';
|
||||||
import type {
|
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||||
VegaTxState,
|
|
||||||
TransactionResult,
|
|
||||||
OrderTxUpdateFieldsFragment,
|
|
||||||
} from '@vegaprotocol/wallet';
|
|
||||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||||
import type { OrderSubFieldsFragment } from '../../order-hooks';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
import type { Order } from '../order-data-provider';
|
import type { Order } from '../order-data-provider';
|
||||||
|
|
||||||
export interface OrderListManagerProps {
|
export interface OrderListManagerProps {
|
||||||
@ -39,41 +29,6 @@ export interface OrderListManagerProps {
|
|||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TransactionComplete = ({
|
|
||||||
transaction,
|
|
||||||
transactionResult,
|
|
||||||
}: {
|
|
||||||
transaction: VegaTxState;
|
|
||||||
transactionResult?: TransactionResult;
|
|
||||||
}) => {
|
|
||||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
|
||||||
if (!transactionResult) return null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{transactionResult.status ? (
|
|
||||||
<p>{t('Transaction successful')}</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-vega-pink">
|
|
||||||
{t('Transaction failed')}: {transactionResult.error}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{transaction.txHash && (
|
|
||||||
<>
|
|
||||||
<p className="font-semibold mt-4">{t('Transaction')}</p>
|
|
||||||
<p>
|
|
||||||
<Link
|
|
||||||
href={`${VEGA_EXPLORER_URL}/txs/${transaction.txHash}`}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{truncateByChars(transaction.txHash)}
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CancelAllOrdersButton = ({
|
const CancelAllOrdersButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
marketId,
|
marketId,
|
||||||
@ -83,8 +38,9 @@ const CancelAllOrdersButton = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const hasActiveOrder = useHasActiveOrder(marketId);
|
const hasActiveOrder = useHasActiveOrder(marketId);
|
||||||
return hasActiveOrder ? (
|
return hasActiveOrder ? (
|
||||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end px-[11px] py-2 absolute bottom-0 right-3 rounded">
|
||||||
<Button
|
<Button
|
||||||
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onClick(marketId)}
|
onClick={() => onClick(marketId)}
|
||||||
data-testid="cancelAll"
|
data-testid="cancelAll"
|
||||||
@ -107,24 +63,48 @@ export const OrderListManager = ({
|
|||||||
const [filter, setFilter] = useState<Filter | undefined>();
|
const [filter, setFilter] = useState<Filter | undefined>();
|
||||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||||
const create = useVegaTransactionStore((state) => state.create);
|
const create = useVegaTransactionStore((state) => state.create);
|
||||||
|
const hasActiveOrder = useHasActiveOrder(marketId);
|
||||||
|
|
||||||
const { data, error, loading, addNewRows, getRows, reload } =
|
const {
|
||||||
useOrderListData({
|
data,
|
||||||
partyId,
|
error,
|
||||||
marketId,
|
loading,
|
||||||
sort,
|
addNewRows,
|
||||||
filter,
|
getRows,
|
||||||
gridRef,
|
reload,
|
||||||
scrolledToTop,
|
makeBottomPlaceholders,
|
||||||
});
|
} = useOrderListData({
|
||||||
|
partyId,
|
||||||
|
marketId,
|
||||||
|
sort,
|
||||||
|
filter,
|
||||||
|
gridRef,
|
||||||
|
scrolledToTop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkBottomPlaceholder = useCallback(() => {
|
||||||
|
if (!isReadOnly && hasActiveOrder) {
|
||||||
|
const rowCont = gridRef.current?.api?.getModel().getRowCount() ?? 0;
|
||||||
|
const lastRowIndex = gridRef.current?.api?.getLastDisplayedRow();
|
||||||
|
if (lastRowIndex && rowCont - 1 === lastRowIndex) {
|
||||||
|
const lastrow =
|
||||||
|
gridRef.current?.api.getDisplayedRowAtIndex(lastRowIndex);
|
||||||
|
lastrow?.setRowHeight(50);
|
||||||
|
makeBottomPlaceholders(lastrow?.data);
|
||||||
|
gridRef.current?.api.onRowHeightChanged();
|
||||||
|
gridRef.current?.api.refreshInfiniteCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isReadOnly, hasActiveOrder, makeBottomPlaceholders]);
|
||||||
|
|
||||||
const onBodyScrollEnd = useCallback(
|
const onBodyScrollEnd = useCallback(
|
||||||
(event: BodyScrollEndEvent) => {
|
(event: BodyScrollEndEvent) => {
|
||||||
if (event.top === 0) {
|
if (event.top === 0) {
|
||||||
addNewRows();
|
addNewRows();
|
||||||
}
|
}
|
||||||
|
checkBottomPlaceholder();
|
||||||
},
|
},
|
||||||
[addNewRows]
|
[addNewRows, checkBottomPlaceholder]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onBodyScroll = useCallback((event: BodyScrollEvent) => {
|
const onBodyScroll = useCallback((event: BodyScrollEvent) => {
|
||||||
@ -133,18 +113,21 @@ export const OrderListManager = ({
|
|||||||
|
|
||||||
const onFilterChanged = useCallback(
|
const onFilterChanged = useCallback(
|
||||||
(event: FilterChangedEvent) => {
|
(event: FilterChangedEvent) => {
|
||||||
|
makeBottomPlaceholders();
|
||||||
const updatedFilter = event.api.getFilterModel();
|
const updatedFilter = event.api.getFilterModel();
|
||||||
if (Object.keys(updatedFilter).length) {
|
if (Object.keys(updatedFilter).length) {
|
||||||
setFilter(updatedFilter);
|
setFilter(updatedFilter);
|
||||||
} else {
|
} else {
|
||||||
setFilter(undefined);
|
setFilter(undefined);
|
||||||
}
|
}
|
||||||
|
checkBottomPlaceholder();
|
||||||
},
|
},
|
||||||
[setFilter]
|
[setFilter, makeBottomPlaceholders, checkBottomPlaceholder]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSortChange = useCallback(
|
const onSortChange = useCallback(
|
||||||
(event: SortChangedEvent) => {
|
(event: SortChangedEvent) => {
|
||||||
|
makeBottomPlaceholders();
|
||||||
const sort = event.columnApi
|
const sort = event.columnApi
|
||||||
.getColumnState()
|
.getColumnState()
|
||||||
.sort((a, b) => (a.sortIndex || 0) - (b.sortIndex || 0))
|
.sort((a, b) => (a.sortIndex || 0) - (b.sortIndex || 0))
|
||||||
@ -156,8 +139,9 @@ export const OrderListManager = ({
|
|||||||
return acc;
|
return acc;
|
||||||
}, [] as { colId: string; sort: string }[]);
|
}, [] as { colId: string; sort: string }[]);
|
||||||
setSort(sort.length > 0 ? sort : undefined);
|
setSort(sort.length > 0 ? sort : undefined);
|
||||||
|
checkBottomPlaceholder();
|
||||||
},
|
},
|
||||||
[setSort]
|
[setSort, makeBottomPlaceholders, checkBottomPlaceholder]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cancel = useCallback(
|
const cancel = useCallback(
|
||||||
@ -172,7 +156,6 @@ export const OrderListManager = ({
|
|||||||
},
|
},
|
||||||
[create]
|
[create]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cancelAll = useCallback(
|
const cancelAll = useCallback(
|
||||||
(marketId?: string) => {
|
(marketId?: string) => {
|
||||||
create({
|
create({
|
||||||
@ -183,43 +166,47 @@ export const OrderListManager = ({
|
|||||||
},
|
},
|
||||||
[create]
|
[create]
|
||||||
);
|
);
|
||||||
|
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules } =
|
||||||
|
useBottomPlaceholder<Order>({
|
||||||
|
gridRef,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="h-full relative grid grid-rows-[1fr,min-content]">
|
<div className="h-full relative">
|
||||||
<div className="relative">
|
<OrderListTable
|
||||||
<OrderListTable
|
ref={gridRef}
|
||||||
ref={gridRef}
|
rowModelType="infinite"
|
||||||
rowModelType="infinite"
|
datasource={{ getRows }}
|
||||||
datasource={{ getRows }}
|
onBodyScrollEnd={onBodyScrollEnd}
|
||||||
onBodyScrollEnd={onBodyScrollEnd}
|
onBodyScroll={onBodyScroll}
|
||||||
onBodyScroll={onBodyScroll}
|
onFilterChanged={onFilterChanged}
|
||||||
onFilterChanged={onFilterChanged}
|
onSortChanged={onSortChange}
|
||||||
onSortChanged={onSortChange}
|
cancel={cancel}
|
||||||
cancel={cancel}
|
setEditOrder={setEditOrder}
|
||||||
setEditOrder={setEditOrder}
|
onMarketClick={onMarketClick}
|
||||||
onMarketClick={onMarketClick}
|
isReadOnly={isReadOnly}
|
||||||
isReadOnly={isReadOnly}
|
blockLoadDebounceMillis={100}
|
||||||
blockLoadDebounceMillis={100}
|
suppressLoadingOverlay
|
||||||
suppressLoadingOverlay
|
suppressNoRowsOverlay
|
||||||
suppressNoRowsOverlay
|
isFullWidthRow={isFullWidthRow}
|
||||||
|
fullWidthCellRenderer={fullWidthCellRenderer}
|
||||||
|
rowClassRules={rowClassRules}
|
||||||
|
/>
|
||||||
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
<AsyncRenderer
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
data={data}
|
||||||
|
noDataMessage={t('No orders')}
|
||||||
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
|
reload={reload}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0">
|
|
||||||
<AsyncRenderer
|
|
||||||
loading={loading}
|
|
||||||
error={error}
|
|
||||||
data={data}
|
|
||||||
noDataMessage={t('No orders')}
|
|
||||||
noDataCondition={(data) => !(data && data.length)}
|
|
||||||
reload={reload}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{!isReadOnly && (
|
|
||||||
<CancelAllOrdersButton onClick={cancelAll} marketId={marketId} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{!isReadOnly && (
|
||||||
|
<CancelAllOrdersButton onClick={cancelAll} marketId={marketId} />
|
||||||
|
)}
|
||||||
{editOrder && (
|
{editOrder && (
|
||||||
<OrderEditDialog
|
<OrderEditDialog
|
||||||
isOpen={Boolean(editOrder)}
|
isOpen={Boolean(editOrder)}
|
||||||
@ -257,76 +244,3 @@ export const OrderListManager = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEditDialogTitle = (
|
|
||||||
status?: Schema.OrderStatus
|
|
||||||
): string | undefined => {
|
|
||||||
if (!status) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case Schema.OrderStatus.STATUS_ACTIVE:
|
|
||||||
return t('Order updated');
|
|
||||||
case Schema.OrderStatus.STATUS_FILLED:
|
|
||||||
return t('Order filled');
|
|
||||||
case Schema.OrderStatus.STATUS_PARTIALLY_FILLED:
|
|
||||||
return t('Order partially filled');
|
|
||||||
case Schema.OrderStatus.STATUS_PARKED:
|
|
||||||
return t('Order parked');
|
|
||||||
case Schema.OrderStatus.STATUS_STOPPED:
|
|
||||||
return t('Order stopped');
|
|
||||||
case Schema.OrderStatus.STATUS_EXPIRED:
|
|
||||||
return t('Order expired');
|
|
||||||
case Schema.OrderStatus.STATUS_CANCELLED:
|
|
||||||
return t('Order cancelled');
|
|
||||||
case Schema.OrderStatus.STATUS_REJECTED:
|
|
||||||
return t('Order rejected');
|
|
||||||
default:
|
|
||||||
return t('Order amendment failed');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCancelDialogIntent = ({
|
|
||||||
cancelledOrder,
|
|
||||||
transactionResult,
|
|
||||||
}: {
|
|
||||||
cancelledOrder: OrderSubFieldsFragment | null;
|
|
||||||
transactionResult?: TransactionResult;
|
|
||||||
}): Intent | undefined => {
|
|
||||||
if (cancelledOrder) {
|
|
||||||
if (cancelledOrder.status === Schema.OrderStatus.STATUS_CANCELLED) {
|
|
||||||
return Intent.Success;
|
|
||||||
}
|
|
||||||
return Intent.Danger;
|
|
||||||
}
|
|
||||||
if (transactionResult) {
|
|
||||||
if ('error' in transactionResult && transactionResult.error) {
|
|
||||||
return Intent.Danger;
|
|
||||||
}
|
|
||||||
return Intent.Success;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCancelDialogTitle = ({
|
|
||||||
cancelledOrder,
|
|
||||||
transactionResult,
|
|
||||||
}: {
|
|
||||||
cancelledOrder: OrderSubFieldsFragment | null;
|
|
||||||
transactionResult?: TransactionResult;
|
|
||||||
}): string | undefined => {
|
|
||||||
if (cancelledOrder) {
|
|
||||||
if (cancelledOrder.status === Schema.OrderStatus.STATUS_CANCELLED) {
|
|
||||||
return t('Order cancelled');
|
|
||||||
}
|
|
||||||
return t('Order cancellation failed');
|
|
||||||
}
|
|
||||||
if (transactionResult) {
|
|
||||||
if (transactionResult.status) {
|
|
||||||
return t('Orders cancelled');
|
|
||||||
}
|
|
||||||
return t('Orders not cancelled');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
@ -51,6 +51,21 @@ export const useOrderListData = ({
|
|||||||
const dataRef = useRef<(OrderEdge | null)[] | null>(null);
|
const dataRef = useRef<(OrderEdge | null)[] | null>(null);
|
||||||
const totalCountRef = useRef<number | undefined>(undefined);
|
const totalCountRef = useRef<number | undefined>(undefined);
|
||||||
const newRows = useRef(0);
|
const newRows = useRef(0);
|
||||||
|
const placeholderAdded = useRef(-1);
|
||||||
|
|
||||||
|
const makeBottomPlaceholders = useCallback((order?: Order) => {
|
||||||
|
if (!order) {
|
||||||
|
if (placeholderAdded.current >= 0) {
|
||||||
|
dataRef.current?.splice(placeholderAdded.current, 1);
|
||||||
|
}
|
||||||
|
placeholderAdded.current = -1;
|
||||||
|
} else if (placeholderAdded.current === -1) {
|
||||||
|
dataRef.current?.push({
|
||||||
|
node: { ...order, id: `${order?.id}-1`, isLastPlaceholder: true },
|
||||||
|
});
|
||||||
|
placeholderAdded.current = (dataRef.current?.length || 0) - 1;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const variables = useMemo<
|
const variables = useMemo<
|
||||||
OrdersQueryVariables & OrdersUpdateSubscriptionVariables
|
OrdersQueryVariables & OrdersUpdateSubscriptionVariables
|
||||||
@ -130,5 +145,13 @@ export const useOrderListData = ({
|
|||||||
load,
|
load,
|
||||||
newRows
|
newRows
|
||||||
);
|
);
|
||||||
return { loading, error, data, addNewRows, getRows, reload };
|
return {
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
data,
|
||||||
|
addNewRows,
|
||||||
|
getRows,
|
||||||
|
reload,
|
||||||
|
makeBottomPlaceholders,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ import * as Schema from '@vegaprotocol/types';
|
|||||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridColumn } from 'ag-grid-react';
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { forwardRef } from 'react';
|
import { memo, forwardRef } from 'react';
|
||||||
import {
|
import {
|
||||||
AgGridDynamic as AgGrid,
|
AgGridDynamic as AgGrid,
|
||||||
SetFilter,
|
SetFilter,
|
||||||
@ -33,257 +33,262 @@ export type OrderListTableProps = OrderListProps & {
|
|||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
export const OrderListTable = memo(
|
||||||
({ cancel, setEditOrder, onMarketClick, ...props }, ref) => {
|
forwardRef<AgGridReact, OrderListTableProps>(
|
||||||
return (
|
({ cancel, setEditOrder, onMarketClick, ...props }, ref) => {
|
||||||
<AgGrid
|
return (
|
||||||
ref={ref}
|
<AgGrid
|
||||||
overlayNoRowsTemplate={t('No orders')}
|
ref={ref}
|
||||||
defaultColDef={{
|
overlayNoRowsTemplate={t('No orders')}
|
||||||
flex: 1,
|
defaultColDef={{
|
||||||
resizable: true,
|
flex: 1,
|
||||||
filterParams: { buttons: ['reset'] },
|
resizable: true,
|
||||||
}}
|
filterParams: { buttons: ['reset'] },
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
}}
|
|
||||||
getRowId={({ data }) => data.id}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Market')}
|
|
||||||
field="market.tradableInstrument.instrument.code"
|
|
||||||
cellRenderer={({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaICellRendererParams<
|
|
||||||
Order,
|
|
||||||
'market.tradableInstrument.instrument.code'
|
|
||||||
>) =>
|
|
||||||
onMarketClick ? (
|
|
||||||
<Link
|
|
||||||
onClick={() =>
|
|
||||||
data?.market?.id && onMarketClick(data?.market?.id)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Size')}
|
|
||||||
field="size"
|
|
||||||
cellClass="font-mono text-right"
|
|
||||||
type="rightAligned"
|
|
||||||
cellClassRules={{
|
|
||||||
[positiveClassNames]: ({ data }: { data: Order }) =>
|
|
||||||
data?.side === Schema.Side.SIDE_BUY,
|
|
||||||
[negativeClassNames]: ({ data }: { data: Order }) =>
|
|
||||||
data?.side === Schema.Side.SIDE_SELL,
|
|
||||||
}}
|
}}
|
||||||
valueFormatter={({
|
style={{
|
||||||
value,
|
width: '100%',
|
||||||
data,
|
height: '100%',
|
||||||
node,
|
|
||||||
}: VegaValueFormatterParams<Order, 'size'>) => {
|
|
||||||
if (!data) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (!data?.market || !isNumeric(value)) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
const prefix = data
|
|
||||||
? data.side === Schema.Side.SIDE_BUY
|
|
||||||
? '+'
|
|
||||||
: '-'
|
|
||||||
: '';
|
|
||||||
return (
|
|
||||||
prefix +
|
|
||||||
addDecimalsFormatNumber(value, data.market.positionDecimalPlaces)
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
getRowId={({ data }) => data.id}
|
||||||
<AgGridColumn
|
{...props}
|
||||||
field="type"
|
>
|
||||||
filter={SetFilter}
|
<AgGridColumn
|
||||||
filterParams={{
|
headerName={t('Market')}
|
||||||
set: Schema.OrderTypeMapping,
|
field="market.tradableInstrument.instrument.code"
|
||||||
}}
|
cellRenderer={({
|
||||||
valueFormatter={({
|
value,
|
||||||
data: order,
|
data,
|
||||||
value,
|
}: VegaICellRendererParams<
|
||||||
node,
|
Order,
|
||||||
}: VegaValueFormatterParams<Order, 'type'>) => {
|
'market.tradableInstrument.instrument.code'
|
||||||
if (!order) {
|
>) =>
|
||||||
return undefined;
|
onMarketClick ? (
|
||||||
}
|
<Link
|
||||||
if (!value) return '-';
|
onClick={() =>
|
||||||
if (order?.peggedOrder) return t('Pegged');
|
data?.market?.id && onMarketClick(data?.market?.id)
|
||||||
if (order?.liquidityProvision) return t('Liquidity provision');
|
}
|
||||||
return Schema.OrderTypeMapping[value];
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
field="status"
|
|
||||||
filter={SetFilter}
|
|
||||||
filterParams={{
|
|
||||||
set: Schema.OrderStatusMapping,
|
|
||||||
}}
|
|
||||||
valueFormatter={({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<Order, 'status'>) => {
|
|
||||||
if (data?.rejectionReason && value) {
|
|
||||||
return `${Schema.OrderStatusMapping[value]}: ${
|
|
||||||
data?.rejectionReason &&
|
|
||||||
Schema.OrderRejectionReasonMapping[data.rejectionReason]
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
return value ? Schema.OrderStatusMapping[value] : '';
|
|
||||||
}}
|
|
||||||
cellRenderer={({
|
|
||||||
valueFormatted,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
valueFormatted: string;
|
|
||||||
data: Order;
|
|
||||||
}) => (
|
|
||||||
<span data-testid={`order-status-${data?.id}`}>
|
|
||||||
{valueFormatted}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Filled')}
|
|
||||||
field="remaining"
|
|
||||||
cellClass="font-mono text-right"
|
|
||||||
type="rightAligned"
|
|
||||||
valueFormatter={({
|
|
||||||
data,
|
|
||||||
value,
|
|
||||||
node,
|
|
||||||
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
|
||||||
if (!data) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
const dps = data.market.positionDecimalPlaces;
|
|
||||||
const size = new BigNumber(data.size);
|
|
||||||
const remaining = new BigNumber(value);
|
|
||||||
const fills = size.minus(remaining);
|
|
||||||
return `${addDecimalsFormatNumber(
|
|
||||||
fills.toString(),
|
|
||||||
dps
|
|
||||||
)}/${addDecimalsFormatNumber(size.toString(), dps)}`;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
field="price"
|
|
||||||
type="rightAligned"
|
|
||||||
cellClass="font-mono text-right"
|
|
||||||
valueFormatter={({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
node,
|
|
||||||
}: VegaValueFormatterParams<Order, 'price'>) => {
|
|
||||||
if (!data) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!data?.market ||
|
|
||||||
data.type === Schema.OrderType.TYPE_MARKET ||
|
|
||||||
!isNumeric(value)
|
|
||||||
) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
field="timeInForce"
|
|
||||||
filter={SetFilter}
|
|
||||||
filterParams={{
|
|
||||||
set: Schema.OrderTimeInForceMapping,
|
|
||||||
}}
|
|
||||||
valueFormatter={({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<Order, 'timeInForce'>) => {
|
|
||||||
if (
|
|
||||||
value === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
|
||||||
data?.expiresAt
|
|
||||||
) {
|
|
||||||
const expiry = getDateTimeFormat().format(
|
|
||||||
new Date(data.expiresAt)
|
|
||||||
);
|
|
||||||
return `${Schema.OrderTimeInForceMapping[value]}: ${expiry}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value ? Schema.OrderTimeInForceMapping[value] : '';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
field="createdAt"
|
|
||||||
cellRenderer={({
|
|
||||||
data,
|
|
||||||
value,
|
|
||||||
}: VegaICellRendererParams<Order, 'createdAt'>) => {
|
|
||||||
return (
|
|
||||||
<span data-value={value}>
|
|
||||||
{value ? getDateTimeFormat().format(new Date(value)) : value}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
field="updatedAt"
|
|
||||||
filter={DateRangeFilter}
|
|
||||||
cellRenderer={({
|
|
||||||
data,
|
|
||||||
value,
|
|
||||||
}: VegaICellRendererParams<Order, 'updatedAt'>) => {
|
|
||||||
if (!data) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span data-value={value}>
|
|
||||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
colId="amend"
|
|
||||||
headerName=""
|
|
||||||
field="status"
|
|
||||||
minWidth={100}
|
|
||||||
type="rightAligned"
|
|
||||||
cellRenderer={({ data, node }: VegaICellRendererParams<Order>) => {
|
|
||||||
return data && isOrderAmendable(data) && !props.isReadOnly ? (
|
|
||||||
<>
|
|
||||||
<ButtonLink
|
|
||||||
data-testid="edit"
|
|
||||||
onClick={() => setEditOrder(data)}
|
|
||||||
>
|
>
|
||||||
{t('Edit')}
|
{value}
|
||||||
</ButtonLink>
|
</Link>
|
||||||
<span className="mx-1" />
|
) : (
|
||||||
<ButtonLink data-testid="cancel" onClick={() => cancel(data)}>
|
value
|
||||||
{t('Cancel')}
|
)
|
||||||
</ButtonLink>
|
}
|
||||||
</>
|
/>
|
||||||
) : null;
|
<AgGridColumn
|
||||||
}}
|
headerName={t('Size')}
|
||||||
/>
|
field="size"
|
||||||
</AgGrid>
|
cellClass="font-mono text-right"
|
||||||
);
|
type="rightAligned"
|
||||||
}
|
cellClassRules={{
|
||||||
|
[positiveClassNames]: ({ data }: { data: Order }) =>
|
||||||
|
data?.side === Schema.Side.SIDE_BUY,
|
||||||
|
[negativeClassNames]: ({ data }: { data: Order }) =>
|
||||||
|
data?.side === Schema.Side.SIDE_SELL,
|
||||||
|
}}
|
||||||
|
valueFormatter={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
node,
|
||||||
|
}: VegaValueFormatterParams<Order, 'size'>) => {
|
||||||
|
if (!data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!data?.market || !isNumeric(value)) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const prefix = data
|
||||||
|
? data.side === Schema.Side.SIDE_BUY
|
||||||
|
? '+'
|
||||||
|
: '-'
|
||||||
|
: '';
|
||||||
|
return (
|
||||||
|
prefix +
|
||||||
|
addDecimalsFormatNumber(
|
||||||
|
value,
|
||||||
|
data.market.positionDecimalPlaces
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
field="type"
|
||||||
|
filter={SetFilter}
|
||||||
|
filterParams={{
|
||||||
|
set: Schema.OrderTypeMapping,
|
||||||
|
}}
|
||||||
|
valueFormatter={({
|
||||||
|
data: order,
|
||||||
|
value,
|
||||||
|
node,
|
||||||
|
}: VegaValueFormatterParams<Order, 'type'>) => {
|
||||||
|
if (!order) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!value) return '-';
|
||||||
|
if (order?.peggedOrder) return t('Pegged');
|
||||||
|
if (order?.liquidityProvision) return t('Liquidity provision');
|
||||||
|
return Schema.OrderTypeMapping[value];
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
field="status"
|
||||||
|
filter={SetFilter}
|
||||||
|
filterParams={{
|
||||||
|
set: Schema.OrderStatusMapping,
|
||||||
|
}}
|
||||||
|
valueFormatter={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<Order, 'status'>) => {
|
||||||
|
if (data?.rejectionReason && value) {
|
||||||
|
return `${Schema.OrderStatusMapping[value]}: ${
|
||||||
|
data?.rejectionReason &&
|
||||||
|
Schema.OrderRejectionReasonMapping[data.rejectionReason]
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
return value ? Schema.OrderStatusMapping[value] : '';
|
||||||
|
}}
|
||||||
|
cellRenderer={({
|
||||||
|
valueFormatted,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
valueFormatted: string;
|
||||||
|
data: Order;
|
||||||
|
}) => (
|
||||||
|
<span data-testid={`order-status-${data?.id}`}>
|
||||||
|
{valueFormatted}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Filled')}
|
||||||
|
field="remaining"
|
||||||
|
cellClass="font-mono text-right"
|
||||||
|
type="rightAligned"
|
||||||
|
valueFormatter={({
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
node,
|
||||||
|
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
||||||
|
if (!data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const dps = data.market.positionDecimalPlaces;
|
||||||
|
const size = new BigNumber(data.size);
|
||||||
|
const remaining = new BigNumber(value);
|
||||||
|
const fills = size.minus(remaining);
|
||||||
|
return `${addDecimalsFormatNumber(
|
||||||
|
fills.toString(),
|
||||||
|
dps
|
||||||
|
)}/${addDecimalsFormatNumber(size.toString(), dps)}`;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
field="price"
|
||||||
|
type="rightAligned"
|
||||||
|
cellClass="font-mono text-right"
|
||||||
|
valueFormatter={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
node,
|
||||||
|
}: VegaValueFormatterParams<Order, 'price'>) => {
|
||||||
|
if (!data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!data?.market ||
|
||||||
|
data.type === Schema.OrderType.TYPE_MARKET ||
|
||||||
|
!isNumeric(value)
|
||||||
|
) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
field="timeInForce"
|
||||||
|
filter={SetFilter}
|
||||||
|
filterParams={{
|
||||||
|
set: Schema.OrderTimeInForceMapping,
|
||||||
|
}}
|
||||||
|
valueFormatter={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<Order, 'timeInForce'>) => {
|
||||||
|
if (
|
||||||
|
value === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
||||||
|
data?.expiresAt
|
||||||
|
) {
|
||||||
|
const expiry = getDateTimeFormat().format(
|
||||||
|
new Date(data.expiresAt)
|
||||||
|
);
|
||||||
|
return `${Schema.OrderTimeInForceMapping[value]}: ${expiry}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value ? Schema.OrderTimeInForceMapping[value] : '';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
field="createdAt"
|
||||||
|
cellRenderer={({
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
}: VegaICellRendererParams<Order, 'createdAt'>) => {
|
||||||
|
return (
|
||||||
|
<span data-value={value}>
|
||||||
|
{value ? getDateTimeFormat().format(new Date(value)) : value}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
field="updatedAt"
|
||||||
|
filter={DateRangeFilter}
|
||||||
|
cellRenderer={({
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
}: VegaICellRendererParams<Order, 'updatedAt'>) => {
|
||||||
|
if (!data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span data-value={value}>
|
||||||
|
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
colId="amend"
|
||||||
|
headerName=""
|
||||||
|
field="status"
|
||||||
|
minWidth={100}
|
||||||
|
type="rightAligned"
|
||||||
|
cellRenderer={({ data, node }: VegaICellRendererParams<Order>) => {
|
||||||
|
return data && isOrderAmendable(data) && !props.isReadOnly ? (
|
||||||
|
<>
|
||||||
|
<ButtonLink
|
||||||
|
data-testid="edit"
|
||||||
|
onClick={() => setEditOrder(data)}
|
||||||
|
>
|
||||||
|
{t('Edit')}
|
||||||
|
</ButtonLink>
|
||||||
|
<span className="mx-1" />
|
||||||
|
<ButtonLink data-testid="cancel" onClick={() => cancel(data)}>
|
||||||
|
{t('Cancel')}
|
||||||
|
</ButtonLink>
|
||||||
|
</>
|
||||||
|
) : null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AgGrid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,8 +7,7 @@ export const useHasActiveOrder = (marketId?: string) => {
|
|||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const [hasActiveOrder, setHasActiveOrder] = useState(false);
|
const [hasActiveOrder, setHasActiveOrder] = useState(false);
|
||||||
const update = useCallback(({ data }: { data: boolean | null }) => {
|
const update = useCallback(({ data }: { data: boolean | null }) => {
|
||||||
console.log({ data });
|
setHasActiveOrder(Boolean(data));
|
||||||
setHasActiveOrder(!!data);
|
|
||||||
return true;
|
return true;
|
||||||
}, []);
|
}, []);
|
||||||
useDataProvider({
|
useDataProvider({
|
||||||
|
@ -16,3 +16,4 @@ export * from './use-storybook-theme-observer';
|
|||||||
export * from './use-yesterday';
|
export * from './use-yesterday';
|
||||||
export * from './use-previous';
|
export * from './use-previous';
|
||||||
export * from './use-logger';
|
export * from './use-logger';
|
||||||
|
export * from './use-bottom-placeholder';
|
||||||
|
71
libs/react-helpers/src/hooks/use-bottom-placeholder.tsx
Normal file
71
libs/react-helpers/src/hooks/use-bottom-placeholder.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import type { RefObject } from 'react';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
import type { IsFullWidthRowParams } from 'ag-grid-community';
|
||||||
|
|
||||||
|
const NO_HOVER_CSS_RULE = { 'no-hover': 'data?.isLastPlaceholder' };
|
||||||
|
const fullWidthCellRenderer = () => null;
|
||||||
|
const isFullWidthRow = (params: IsFullWidthRowParams) =>
|
||||||
|
params.rowNode.data?.isLastPlaceholder;
|
||||||
|
|
||||||
|
interface Props<T> {
|
||||||
|
gridRef: RefObject<AgGridReact>;
|
||||||
|
setId?: (data: T) => T;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export const useBottomPlaceholder = <T extends {}>({
|
||||||
|
gridRef,
|
||||||
|
setId,
|
||||||
|
}: Props<T>) => {
|
||||||
|
const onBodyScrollEnd = useCallback(() => {
|
||||||
|
const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0;
|
||||||
|
const lastRowIndex = gridRef.current?.api.getLastDisplayedRow() ?? 0;
|
||||||
|
if (lastRowIndex && rowCont - 1 === lastRowIndex) {
|
||||||
|
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(lastRowIndex);
|
||||||
|
if (lastRow?.data && !lastRow?.data.isLastPlaceholder) {
|
||||||
|
const newData = setId
|
||||||
|
? setId({ ...lastRow.data, isLastPlaceholder: true })
|
||||||
|
: {
|
||||||
|
...lastRow.data,
|
||||||
|
isLastPlaceholder: true,
|
||||||
|
id: `${lastRow.data?.id || '-'}-1`,
|
||||||
|
};
|
||||||
|
const add = [newData];
|
||||||
|
const newIndex = lastRowIndex + 1;
|
||||||
|
gridRef.current?.api.applyTransaction({
|
||||||
|
add,
|
||||||
|
addIndex: newIndex,
|
||||||
|
});
|
||||||
|
const newLastRow =
|
||||||
|
gridRef.current?.api.getDisplayedRowAtIndex(newIndex);
|
||||||
|
newLastRow?.setRowHeight(50);
|
||||||
|
gridRef.current?.api.onRowHeightChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [gridRef, setId]);
|
||||||
|
|
||||||
|
const onRowsChanged = useCallback(() => {
|
||||||
|
const remove: T[] = [];
|
||||||
|
gridRef.current?.api.forEachNodeAfterFilterAndSort((rowNode) => {
|
||||||
|
if (rowNode.data.isLastPlaceholder) {
|
||||||
|
remove.push(rowNode.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridRef.current?.api.applyTransaction({
|
||||||
|
remove,
|
||||||
|
});
|
||||||
|
onBodyScrollEnd();
|
||||||
|
}, [gridRef, onBodyScrollEnd]);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
onBodyScrollEnd,
|
||||||
|
rowClassRules: NO_HOVER_CSS_RULE,
|
||||||
|
isFullWidthRow,
|
||||||
|
fullWidthCellRenderer,
|
||||||
|
onSortChanged: onRowsChanged,
|
||||||
|
onFilterChange: onRowsChanged,
|
||||||
|
}),
|
||||||
|
[onBodyScrollEnd, onRowsChanged]
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { useThemeSwitcher } from '@vegaprotocol/utils';
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import '../src/styles.css';
|
import '../src/styles.css';
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { AgGridColumn } from 'ag-grid-react';
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
import {
|
import {
|
||||||
getDateTimeFormat,
|
getDateTimeFormat,
|
||||||
@ -5,14 +7,9 @@ import {
|
|||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
isNumeric,
|
isNumeric,
|
||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
|
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
import { Link, ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||||
Link,
|
|
||||||
ButtonLink,
|
|
||||||
Intent,
|
|
||||||
Icon,
|
|
||||||
Loader,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
import type {
|
import type {
|
||||||
TypedDataAgGrid,
|
TypedDataAgGrid,
|
||||||
@ -29,11 +26,13 @@ import { ApprovalStatus } from './use-verify-withdrawal';
|
|||||||
export const WithdrawalsTable = (
|
export const WithdrawalsTable = (
|
||||||
props: TypedDataAgGrid<WithdrawalFieldsFragment>
|
props: TypedDataAgGrid<WithdrawalFieldsFragment>
|
||||||
) => {
|
) => {
|
||||||
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
const { ETHERSCAN_URL } = useEnvironment();
|
||||||
const createWithdrawApproval = useEthWithdrawApprovalsStore(
|
const createWithdrawApproval = useEthWithdrawApprovalsStore(
|
||||||
(store) => store.create
|
(store) => store.create
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
overlayNoRowsTemplate={t('No withdrawals')}
|
overlayNoRowsTemplate={t('No withdrawals')}
|
||||||
@ -45,8 +44,9 @@ export const WithdrawalsTable = (
|
|||||||
EtherscanLinkCell,
|
EtherscanLinkCell,
|
||||||
CompleteCell,
|
CompleteCell,
|
||||||
}}
|
}}
|
||||||
suppressCellFocus={true}
|
suppressCellFocus
|
||||||
domLayout="autoHeight"
|
ref={gridRef}
|
||||||
|
{...bottomPlaceholderProps}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
||||||
@ -69,10 +69,12 @@ export const WithdrawalsTable = (
|
|||||||
cellRendererParams={{ ethUrl: ETHERSCAN_URL }}
|
cellRendererParams={{ ethUrl: ETHERSCAN_URL }}
|
||||||
valueFormatter={({
|
valueFormatter={({
|
||||||
value,
|
value,
|
||||||
|
data,
|
||||||
}: VegaValueFormatterParams<
|
}: VegaValueFormatterParams<
|
||||||
WithdrawalFieldsFragment,
|
WithdrawalFieldsFragment,
|
||||||
'details.receiverAddress'
|
'details.receiverAddress'
|
||||||
>) => {
|
>) => {
|
||||||
|
if (!data) return null;
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
return truncateByChars(value);
|
return truncateByChars(value);
|
||||||
}}
|
}}
|
||||||
@ -82,20 +84,34 @@ export const WithdrawalsTable = (
|
|||||||
field="createdTimestamp"
|
field="createdTimestamp"
|
||||||
valueFormatter={({
|
valueFormatter={({
|
||||||
value,
|
value,
|
||||||
|
data,
|
||||||
}: VegaValueFormatterParams<
|
}: VegaValueFormatterParams<
|
||||||
WithdrawalFieldsFragment,
|
WithdrawalFieldsFragment,
|
||||||
'createdTimestamp'
|
'createdTimestamp'
|
||||||
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-')}
|
>) =>
|
||||||
|
data
|
||||||
|
? value
|
||||||
|
? getDateTimeFormat().format(new Date(value))
|
||||||
|
: '-'
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Completed')}
|
headerName={t('Completed')}
|
||||||
field="withdrawnTimestamp"
|
field="withdrawnTimestamp"
|
||||||
valueFormatter={({
|
valueFormatter={({
|
||||||
value,
|
value,
|
||||||
|
data,
|
||||||
}: VegaValueFormatterParams<
|
}: VegaValueFormatterParams<
|
||||||
WithdrawalFieldsFragment,
|
WithdrawalFieldsFragment,
|
||||||
'withdrawnTimestamp'
|
'withdrawnTimestamp'
|
||||||
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-')}
|
>) =>
|
||||||
|
data
|
||||||
|
? value
|
||||||
|
? getDateTimeFormat().format(new Date(value))
|
||||||
|
: '-'
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Status')}
|
headerName={t('Status')}
|
||||||
@ -126,8 +142,11 @@ export type CompleteCellProps = {
|
|||||||
data: WithdrawalFieldsFragment;
|
data: WithdrawalFieldsFragment;
|
||||||
complete: (withdrawal: WithdrawalFieldsFragment) => void;
|
complete: (withdrawal: WithdrawalFieldsFragment) => void;
|
||||||
};
|
};
|
||||||
export const CompleteCell = ({ data, complete }: CompleteCellProps) =>
|
export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
|
||||||
data.pendingOnForeignChain ? (
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return data.pendingOnForeignChain ? (
|
||||||
'-'
|
'-'
|
||||||
) : (
|
) : (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
@ -137,6 +156,7 @@ export const CompleteCell = ({ data, complete }: CompleteCellProps) =>
|
|||||||
{t('Complete withdrawal')}
|
{t('Complete withdrawal')}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const EtherscanLinkCell = ({
|
export const EtherscanLinkCell = ({
|
||||||
value,
|
value,
|
||||||
@ -158,6 +178,9 @@ export const EtherscanLinkCell = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const StatusCell = ({ data }: { data: WithdrawalFieldsFragment }) => {
|
export const StatusCell = ({ data }: { data: WithdrawalFieldsFragment }) => {
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (data.pendingOnForeignChain || !data.txHash) {
|
if (data.pendingOnForeignChain || !data.txHash) {
|
||||||
return <span>{t('Pending')}</span>;
|
return <span>{t('Pending')}</span>;
|
||||||
}
|
}
|
||||||
@ -195,25 +218,6 @@ const RecipientCell = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getVerifyDialogProps = (status: ApprovalStatus) => {
|
|
||||||
if (status === ApprovalStatus.Error) {
|
|
||||||
return {
|
|
||||||
intent: Intent.Danger,
|
|
||||||
icon: <Icon name="warning-sign" />,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === ApprovalStatus.Pending) {
|
|
||||||
return { intent: Intent.None, icon: <Loader size="small" /> };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === ApprovalStatus.Delayed) {
|
|
||||||
return { intent: Intent.Warning, icon: <Icon name="time" /> };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { intent: Intent.None };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const VerificationStatus = ({ state }: { state: VerifyState }) => {
|
export const VerificationStatus = ({ state }: { state: VerifyState }) => {
|
||||||
if (state.status === ApprovalStatus.Error) {
|
if (state.status === ApprovalStatus.Error) {
|
||||||
return <p>{state.message || t('Something went wrong')}</p>;
|
return <p>{state.message || t('Something went wrong')}</p>;
|
||||||
|
Loading…
Reference in New Issue
Block a user