feat(positions): improve tooltips (#5481)
This commit is contained in:
parent
08c57b6759
commit
8b94a75ba4
@ -8,11 +8,13 @@ export const LiquidationPrice = ({
|
|||||||
openVolume,
|
openVolume,
|
||||||
collateralAvailable,
|
collateralAvailable,
|
||||||
decimalPlaces,
|
decimalPlaces,
|
||||||
|
className,
|
||||||
}: {
|
}: {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
openVolume: string;
|
openVolume: string;
|
||||||
collateralAvailable: string;
|
collateralAvailable: string;
|
||||||
decimalPlaces: number;
|
decimalPlaces: number;
|
||||||
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const { data: currentData, previousData } = useEstimatePositionQuery({
|
const { data: currentData, previousData } = useEstimatePositionQuery({
|
||||||
@ -39,22 +41,19 @@ export const LiquidationPrice = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
align="end"
|
||||||
description={
|
description={
|
||||||
<table>
|
<dl className="grid grid-cols-2">
|
||||||
<tbody>
|
<dt>{t('Worst case')}</dt>
|
||||||
<tr>
|
<dd className="pl-2 text-right font-mono">{worstCase}</dd>
|
||||||
<th>{t('Worst case')}</th>
|
<dt className="font-normal">{t('Best case')}</dt>
|
||||||
<td className="pl-2 text-right font-mono">{worstCase}</td>
|
<dd className="pl-2 text-right font-mono">{bestCase}</dd>
|
||||||
</tr>
|
</dl>
|
||||||
<tr>
|
|
||||||
<th>{t('Best case')}</th>
|
|
||||||
<td className="pl-2 text-right font-mono">{bestCase}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span data-testid="liquidation-price">{worstCase}</span>
|
<span data-testid="liquidation-price" className={className}>
|
||||||
|
{worstCase}
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -314,7 +314,9 @@ describe('Positions', () => {
|
|||||||
});
|
});
|
||||||
const cells = screen.getAllByRole('gridcell');
|
const cells = screen.getAllByRole('gridcell');
|
||||||
const cell = cells[1];
|
const cell = cells[1];
|
||||||
await userEvent.hover(cell);
|
const tooltipTrigger = cell.querySelector('[data-state="closed"]');
|
||||||
|
expect(tooltipTrigger).not.toBeNull();
|
||||||
|
await userEvent.hover(tooltipTrigger as Element);
|
||||||
const tooltip = within(await screen.findByRole('tooltip'));
|
const tooltip = within(await screen.findByRole('tooltip'));
|
||||||
expect(tooltip.getByText(data.text)).toBeInTheDocument();
|
expect(tooltip.getByText(data.text)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@ -329,8 +331,9 @@ describe('Positions', () => {
|
|||||||
});
|
});
|
||||||
const cells = screen.getAllByRole('gridcell');
|
const cells = screen.getAllByRole('gridcell');
|
||||||
const cell = cells[5];
|
const cell = cells[5];
|
||||||
|
const tooltipTrigger = cell.querySelector('[data-state="closed"]');
|
||||||
await userEvent.hover(cell);
|
expect(tooltipTrigger).not.toBeNull();
|
||||||
|
await userEvent.hover(tooltipTrigger as Element);
|
||||||
const tooltip = within(await screen.findByRole('tooltip'));
|
const tooltip = within(await screen.findByRole('tooltip'));
|
||||||
expect(tooltip.getByText('Realised PNL: 1.23')).toBeInTheDocument();
|
expect(tooltip.getByText('Realised PNL: 1.23')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo, type CSSProperties, type ReactNode } from 'react';
|
import { useMemo, type CSSProperties, type ReactNode } from 'react';
|
||||||
import { type ColDef, type ITooltipParams } from 'ag-grid-community';
|
import { type ColDef } from 'ag-grid-community';
|
||||||
import {
|
import {
|
||||||
AgGrid,
|
AgGrid,
|
||||||
COL_DEFS,
|
COL_DEFS,
|
||||||
@ -20,6 +20,7 @@ import {
|
|||||||
ExternalLink,
|
ExternalLink,
|
||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
|
Tooltip,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
import {
|
||||||
volumePrefix,
|
volumePrefix,
|
||||||
@ -148,15 +149,6 @@ export const PositionsTable = ({
|
|||||||
data.positionDecimalPlaces
|
data.positionDecimalPlaces
|
||||||
).toNumber();
|
).toNumber();
|
||||||
},
|
},
|
||||||
tooltipValueGetter: ({ data }: ITooltipParams<Position>) => {
|
|
||||||
if (
|
|
||||||
!data ||
|
|
||||||
data.status === PositionStatus.POSITION_STATUS_UNSPECIFIED
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return data.status;
|
|
||||||
},
|
|
||||||
valueFormatter: ({
|
valueFormatter: ({
|
||||||
data,
|
data,
|
||||||
}: VegaValueFormatterParams<Position, 'openVolume'>): string => {
|
}: VegaValueFormatterParams<Position, 'openVolume'>): string => {
|
||||||
@ -171,68 +163,6 @@ export const PositionsTable = ({
|
|||||||
|
|
||||||
return vol;
|
return vol;
|
||||||
},
|
},
|
||||||
tooltipComponent: (args: ITooltipParams<Position>) => {
|
|
||||||
if (!args.data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const POSITION_RESOLUTION_LINK =
|
|
||||||
DocsLinks?.POSITION_RESOLUTION ?? '';
|
|
||||||
let primaryTooltip;
|
|
||||||
switch (args.data.status) {
|
|
||||||
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
|
||||||
primaryTooltip = t('Your position was closed.');
|
|
||||||
break;
|
|
||||||
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
|
||||||
primaryTooltip = t('Your open orders were cancelled.');
|
|
||||||
break;
|
|
||||||
case PositionStatus.POSITION_STATUS_DISTRESSED:
|
|
||||||
primaryTooltip = t('Your position is distressed.');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let secondaryTooltip;
|
|
||||||
switch (args.data.status) {
|
|
||||||
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
|
||||||
secondaryTooltip = t(
|
|
||||||
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
|
|
||||||
{ assetSymbol: args.data.assetSymbol }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
|
||||||
secondaryTooltip = t(
|
|
||||||
'The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.'
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case PositionStatus.POSITION_STATUS_DISTRESSED:
|
|
||||||
secondaryTooltip = t(
|
|
||||||
'The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.'
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
secondaryTooltip = t('Maintained by network');
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TooltipCellComponent
|
|
||||||
{...args}
|
|
||||||
value={
|
|
||||||
<>
|
|
||||||
<p className="mb-2">{primaryTooltip}</p>
|
|
||||||
<p className="mb-2">{secondaryTooltip}</p>
|
|
||||||
<p className="mb-2">
|
|
||||||
{t('Status: {{status}}', {
|
|
||||||
status: PositionStatusMapping[args.data.status],
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
{POSITION_RESOLUTION_LINK && (
|
|
||||||
<ExternalLink href={POSITION_RESOLUTION_LINK}>
|
|
||||||
{t('Read more about position resolution')}
|
|
||||||
</ExternalLink>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cellRenderer: OpenVolumeCell,
|
cellRenderer: OpenVolumeCell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -337,12 +267,15 @@ export const PositionsTable = ({
|
|||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<LiquidationPrice
|
<div className="flex h-[45px] items-center">
|
||||||
marketId={data.marketId}
|
<LiquidationPrice
|
||||||
openVolume={data.openVolume}
|
className="block text-right grow"
|
||||||
collateralAvailable={data.totalBalance}
|
marketId={data.marketId}
|
||||||
decimalPlaces={data.marketDecimalPlaces}
|
openVolume={data.openVolume}
|
||||||
/>
|
collateralAvailable={data.totalBalance}
|
||||||
|
decimalPlaces={data.marketDecimalPlaces}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -354,13 +287,13 @@ export const PositionsTable = ({
|
|||||||
cellClass: 'font-mono text-right',
|
cellClass: 'font-mono text-right',
|
||||||
filter: 'agNumberColumnFilter',
|
filter: 'agNumberColumnFilter',
|
||||||
valueGetter: realisedPNLValueGetter,
|
valueGetter: realisedPNLValueGetter,
|
||||||
// @ts-ignore no type overlap, but the functions are identical
|
cellRenderer: (
|
||||||
tooltipValueGetter: realisedPNLValueGetter,
|
args: VegaICellRendererParams<Position, 'realisedPNL'>
|
||||||
tooltipComponent: (args: ITooltipParams) => {
|
) => {
|
||||||
const LOSS_SOCIALIZATION_LINK =
|
const LOSS_SOCIALIZATION_LINK =
|
||||||
DocsLinks?.LOSS_SOCIALIZATION ?? '';
|
DocsLinks?.LOSS_SOCIALIZATION ?? '';
|
||||||
|
|
||||||
if (!args.data) {
|
if (!args.data || args.value === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,7 +304,11 @@ export const PositionsTable = ({
|
|||||||
if (losses <= 0) {
|
if (losses <= 0) {
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
return (
|
return (
|
||||||
<TooltipCellComponent {...args} value={args.valueFormatted} />
|
<Tooltip description={args.valueFormatted} align="end">
|
||||||
|
<div>
|
||||||
|
<PNLCell {...args} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,20 +318,24 @@ export const PositionsTable = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipCellComponent
|
<Tooltip
|
||||||
{...args}
|
align="end"
|
||||||
value={
|
description={
|
||||||
<>
|
<>
|
||||||
<p className="mb-2">
|
<p className="mb-2">
|
||||||
{t('Realised PNL: {{value}}', {
|
{t('Realised PNL: {{value}}', {
|
||||||
value: args.value,
|
nsSeparator: '*',
|
||||||
|
replace: { value: args.value },
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
<p className="mb-2">
|
<p className="mb-2">
|
||||||
{t(
|
{t(
|
||||||
'Lifetime loss socialisation deductions: {{losses}}',
|
'Lifetime loss socialisation deductions: {{losses}}',
|
||||||
{
|
{
|
||||||
losses: lossesFormatted,
|
nsSeparator: '*',
|
||||||
|
replace: {
|
||||||
|
losses: lossesFormatted,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
@ -411,7 +352,11 @@ export const PositionsTable = ({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
<div>
|
||||||
|
<PNLCell {...args} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
valueFormatter: ({
|
valueFormatter: ({
|
||||||
@ -428,7 +373,6 @@ export const PositionsTable = ({
|
|||||||
headerTooltip: t(
|
headerTooltip: t(
|
||||||
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
|
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
|
||||||
),
|
),
|
||||||
cellRenderer: PNLCell,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: t('Unrealised PNL'),
|
headerName: t('Unrealised PNL'),
|
||||||
@ -520,10 +464,66 @@ export const OpenVolumeCell = ({
|
|||||||
valueFormatted,
|
valueFormatted,
|
||||||
data,
|
data,
|
||||||
}: VegaICellRendererParams<Position, 'openVolume'>) => {
|
}: VegaICellRendererParams<Position, 'openVolume'>) => {
|
||||||
|
const t = useT();
|
||||||
if (!valueFormatted || !data || !data.notional) {
|
if (!valueFormatted || !data || !data.notional) {
|
||||||
return <>-</>;
|
return <>-</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const POSITION_RESOLUTION_LINK = DocsLinks?.POSITION_RESOLUTION ?? '';
|
||||||
|
let primaryTooltip;
|
||||||
|
switch (data.status) {
|
||||||
|
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
||||||
|
primaryTooltip = t('Your position was closed.');
|
||||||
|
break;
|
||||||
|
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
||||||
|
primaryTooltip = t('Your open orders were cancelled.');
|
||||||
|
break;
|
||||||
|
case PositionStatus.POSITION_STATUS_DISTRESSED:
|
||||||
|
primaryTooltip = t('Your position is distressed.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let secondaryTooltip;
|
||||||
|
switch (data.status) {
|
||||||
|
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
||||||
|
secondaryTooltip = t(
|
||||||
|
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
|
||||||
|
{ assetSymbol: data.assetSymbol }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
||||||
|
secondaryTooltip = t(
|
||||||
|
'The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case PositionStatus.POSITION_STATUS_DISTRESSED:
|
||||||
|
secondaryTooltip = t(
|
||||||
|
'The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
secondaryTooltip = t('Maintained by network');
|
||||||
|
}
|
||||||
|
const description = (
|
||||||
|
<>
|
||||||
|
<p className="mb-2">{primaryTooltip}</p>
|
||||||
|
<p className="mb-2">{secondaryTooltip}</p>
|
||||||
|
<p className="mb-2">
|
||||||
|
{t('Status: {{status}}', {
|
||||||
|
nsSeparator: '*',
|
||||||
|
replace: {
|
||||||
|
status: PositionStatusMapping[data.status],
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{POSITION_RESOLUTION_LINK && (
|
||||||
|
<ExternalLink href={POSITION_RESOLUTION_LINK}>
|
||||||
|
{t('Read more about position resolution')}
|
||||||
|
</ExternalLink>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const notional = addDecimalsFormatNumber(
|
const notional = addDecimalsFormatNumber(
|
||||||
data.notional,
|
data.notional,
|
||||||
data.marketDecimalPlaces
|
data.marketDecimalPlaces
|
||||||
@ -539,16 +539,11 @@ export const OpenVolumeCell = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WarningCell
|
<Tooltip description={description}>
|
||||||
showIcon={
|
<div>
|
||||||
// not sure why but data.status has become a union of all the enum values
|
<WarningCell showIcon>{cellContent}</WarningCell>
|
||||||
// rather than just being the enum itself
|
</div>
|
||||||
(data.status as PositionStatus) !==
|
</Tooltip>
|
||||||
PositionStatus.POSITION_STATUS_UNSPECIFIED
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{cellContent}
|
|
||||||
</WarningCell>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user