import classNames from 'classnames'; import { forwardRef } from 'react'; import type { CSSProperties } from 'react'; import type { ICellRendererParams, CellRendererSelectorResult, } from 'ag-grid-community'; import type { ValueProps as PriceCellProps, VegaValueFormatterParams, VegaValueGetterParams, TypedDataAgGrid, } from '@vegaprotocol/ui-toolkit'; import { EmptyCell, ProgressBarCell } from '@vegaprotocol/ui-toolkit'; import { PriceFlashCell, addDecimalsFormatNumber, volumePrefix, t, toBigNum, formatNumber, getDateTimeFormat, signedNumberCssClass, signedNumberCssClassRules, DateRangeFilter, } from '@vegaprotocol/react-helpers'; import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; import { AgGridColumn } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react'; import type { Position } from './positions-data-providers'; import { Schema } from '@vegaprotocol/types'; import { Intent, Button, TooltipCellComponent } from '@vegaprotocol/ui-toolkit'; import { getRowId } from './use-positions-data'; interface Props extends TypedDataAgGrid { onClose?: (data: Position) => void; style?: CSSProperties; } export interface MarketNameCellProps { valueFormatted?: [string, string]; } export const MarketNameCell = ({ valueFormatted }: MarketNameCellProps) => { if (valueFormatted && valueFormatted[1]) { return (
{valueFormatted[0]}
{valueFormatted[1]}
); } return (valueFormatted && valueFormatted[0]) || undefined; }; export interface AmountCellProps { valueFormatted?: Pick< Position, 'openVolume' | 'marketDecimalPlaces' | 'positionDecimalPlaces' | 'notional' >; } export const AmountCell = ({ valueFormatted }: AmountCellProps) => { if (!valueFormatted) { return null; } const { openVolume, positionDecimalPlaces, marketDecimalPlaces, notional } = valueFormatted; return valueFormatted ? (
{volumePrefix( addDecimalsFormatNumber(openVolume, positionDecimalPlaces) )}
{addDecimalsFormatNumber(notional, marketDecimalPlaces)}
) : null; }; AmountCell.displayName = 'AmountCell'; const ButtonCell = ({ onClick, data, }: { onClick: (position: Position) => void; data: Position; }) => { return ( ); }; const progressBarValueFormatter = ({ data, node, }: VegaValueFormatterParams): | 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, }; }; export const PositionsTable = forwardRef( ({ onClose, ...props }, ref) => { return ( ) => { if (!value) { return undefined; } // split market name into two parts, 'Part1 (Part2)' or 'Part1 - Part2' const matches = value.match(/^(.*)(\((.*)\)| - (.*))\s*$/); if (matches && matches[1] && matches[3]) { return [matches[1].trim(), matches[3].trim()]; } return [value]; }} /> ) => { return data?.notional === undefined ? undefined : toBigNum(data?.notional, data.decimals).toNumber(); }} valueFormatter={({ data, }: VegaValueFormatterParams) => { return !data ? undefined : addDecimalsFormatNumber(data.notional, data.decimals); }} /> ) => { return data?.openVolume === undefined ? undefined : toBigNum(data?.openVolume, data.decimals).toNumber(); }} valueFormatter={({ data, }: VegaValueFormatterParams): | string | undefined => { return data?.openVolume === undefined ? undefined : volumePrefix( addDecimalsFormatNumber( data.openVolume, data.positionDecimalPlaces ) ); }} /> { return { component: params.node.rowPinned ? EmptyCell : PriceFlashCell, }; }} filter="agNumberColumnFilter" valueGetter={({ data, }: VegaValueGetterParams) => { return !data || data.marketTradingMode === Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION ? undefined : toBigNum(data.markPrice, data.marketDecimalPlaces).toNumber(); }} valueFormatter={({ data, node, }: VegaValueFormatterParams) => { if (!data || node?.rowPinned) { return undefined; } if ( data.marketTradingMode === Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION ) { return '-'; } return addDecimalsFormatNumber( data.markPrice, data.marketDecimalPlaces ); }} /> { return { component: params.node.rowPinned ? EmptyCell : PriceFlashCell, }; }} filter="agNumberColumnFilter" valueGetter={({ data, }: VegaValueGetterParams) => { return data?.markPrice === undefined || !data ? undefined : toBigNum( data.averageEntryPrice, data.marketDecimalPlaces ).toNumber(); }} valueFormatter={({ data, node, }: VegaValueFormatterParams): | string | undefined => { if (!data || node?.rowPinned) { return undefined; } return addDecimalsFormatNumber( data.averageEntryPrice, data.marketDecimalPlaces ); }} /> ) => { return !data ? undefined : toBigNum( data?.liquidationPrice, data.marketDecimalPlaces ).toNumber(); }} cellRendererSelector={( params: ICellRendererParams ): CellRendererSelectorResult => { return { component: params.node.rowPinned ? EmptyCell : ProgressBarCell, }; }} valueFormatter={progressBarValueFormatter} /> { return { component: params.node.rowPinned ? EmptyCell : PriceFlashCell, }; }} valueFormatter={({ value, }: VegaValueFormatterParams) => value === undefined ? undefined : formatNumber(value.toString(), 1) } /> { return { component: params.node.rowPinned ? EmptyCell : PriceFlashCell, }; }} valueGetter={({ data, }: VegaValueGetterParams) => { return !data ? undefined : toBigNum(data.marginAccountBalance, data.decimals).toNumber(); }} valueFormatter={({ data, node, }: VegaValueFormatterParams): | string | undefined => { if (!data || node?.rowPinned) { return undefined; } return addDecimalsFormatNumber( data.marginAccountBalance, data.decimals ); }} /> ) => { return !data ? undefined : toBigNum(data.realisedPNL, data.decimals).toNumber(); }} valueFormatter={({ data, }: VegaValueFormatterParams) => { return !data ? undefined : addDecimalsFormatNumber(data.realisedPNL, data.decimals); }} cellRenderer="PriceFlashCell" 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.' )} /> ) => { return !data ? undefined : toBigNum(data.unrealisedPNL, data.decimals).toNumber(); }} valueFormatter={({ data, }: VegaValueFormatterParams) => !data ? undefined : addDecimalsFormatNumber(data.unrealisedPNL, data.decimals) } cellRenderer="PriceFlashCell" headerTooltip={t( 'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.' )} /> ) => { if (!value) { return value; } return getDateTimeFormat().format(new Date(value)); }} /> {onClose ? ( { return { component: params.node.rowPinned ? EmptyCell : ButtonCell, }; }} cellRendererParams={{ onClick: onClose }} /> ) : null} ); } ); export default PositionsTable;