chore(trading): ag-grid upgrade (#4187)

This commit is contained in:
Bartłomiej Głownia 2023-07-01 13:02:23 +02:00 committed by GitHub
parent 6e9e7c2a5c
commit 43aff8e359
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1107 additions and 1189 deletions

View File

@ -1,15 +1,15 @@
import { useMemo } from 'react';
import type { AssetFieldsFragment } from '@vegaprotocol/assets'; import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets'; import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid'; import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
import { useRef, useLayoutEffect } from 'react'; import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints'; import { BREAKPOINT_MD } from '../../config/breakpoints';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import type { RowClickedEvent } from 'ag-grid-community'; import type { RowClickedEvent, ColDef } from 'ag-grid-community';
type AssetsTableProps = { type AssetsTableProps = {
data: AssetFieldsFragment[] | null; data: AssetFieldsFragment[] | null;
@ -31,6 +31,58 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
}; };
}, []); }, []);
const columnDefs = useMemo<ColDef[]>(
() => [
{ headerName: t('Symbol'), field: 'symbol' },
{ headerName: t('Name'), field: 'name' },
{
flex: 2,
headerName: t('ID'),
field: 'id',
hide: window.innerWidth < BREAKPOINT_MD,
},
{
colId: 'type',
headerName: t('Type'),
field: 'source.__typename',
hide: window.innerWidth < BREAKPOINT_MD,
valueFormatter: ({ value }: { value?: string }) =>
value ? AssetTypeMapping[value].value : '',
},
{
headerName: t('Status'),
field: 'status',
hide: window.innerWidth < BREAKPOINT_MD,
valueFormatter: ({ value }: { value?: string }) =>
value ? AssetStatusMapping[value].value : '',
},
{
colId: 'actions',
headerName: '',
sortable: false,
filter: false,
resizable: false,
wrapText: true,
field: 'id',
cellRenderer: ({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<ButtonLink
onClick={(e) => {
navigate(value);
}}
>
{t('View details')}
</ButtonLink>
) : (
''
),
},
],
[navigate]
);
return ( return (
<AgGrid <AgGrid
ref={ref} ref={ref}
@ -46,60 +98,11 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
filterParams: { buttons: ['reset'] }, filterParams: { buttons: ['reset'] },
autoHeight: true, autoHeight: true,
}} }}
columnDefs={columnDefs}
suppressCellFocus={true} suppressCellFocus={true}
onRowClicked={({ data }: RowClickedEvent) => { onRowClicked={({ data }: RowClickedEvent) => {
navigate(data.id); navigate(data.id);
}} }}
>
<AgGridColumn headerName={t('Symbol')} field="symbol" />
<AgGridColumn headerName={t('Name')} field="name" />
<AgGridColumn
flex="2"
headerName={t('ID')}
field="id"
hide={window.innerWidth < BREAKPOINT_MD}
/> />
<AgGridColumn
colId="type"
headerName={t('Type')}
field="source.__typename"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) =>
value && AssetTypeMapping[value].value
}
/>
<AgGridColumn
headerName={t('Status')}
field="status"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) =>
value && AssetStatusMapping[value].value
}
/>
<AgGridColumn
colId="actions"
headerName=""
sortable={false}
filter={false}
resizable={false}
wrapText={true}
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<ButtonLink
onClick={(e) => {
navigate(value);
}}
>
{t('View details')}
</ButtonLink>
) : (
''
)
}
/>
</AgGrid>
); );
}; };

View File

@ -1,8 +1,9 @@
import { useMemo } from 'react';
import type { MarketFieldsFragment } from '@vegaprotocol/markets'; import type { MarketFieldsFragment } from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react'; import type { ColDef } from 'ag-grid-community';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
@ -39,54 +40,34 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
}; };
}, []); }, []);
return ( const columnDefs = useMemo<ColDef[]>(
<AgGrid () => [
ref={gridRef} {
rowData={data} colId: 'code',
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id} headerName: t('Code'),
overlayNoRowsTemplate={t('This chain has no markets')} field: 'tradableInstrument.instrument.code',
domLayout="autoHeight" },
defaultColDef={{ {
flex: 1, colId: 'name',
resizable: true, headerName: t('Name'),
sortable: true, field: 'tradableInstrument.instrument.name',
filter: true, },
filterParams: { buttons: ['reset'] }, {
autoHeight: true, headerName: t('Status'),
}} field: 'state',
suppressCellFocus={true} hide: window.innerWidth <= BREAKPOINT_MD,
onRowClicked={({ data, event }: RowClickedEvent) => { valueGetter: ({
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
navigate(data.id);
}
}}
>
<AgGridColumn
colId="code"
headerName={t('Code')}
field="tradableInstrument.instrument.code"
/>
<AgGridColumn
colId="name"
headerName={t('Name')}
field="tradableInstrument.instrument.name"
/>
<AgGridColumn
headerName={t('Status')}
field="state"
hide={window.innerWidth <= BREAKPOINT_MD}
valueGetter={({
data, data,
}: VegaValueGetterParams<MarketFieldsFragment>) => { }: VegaValueGetterParams<MarketFieldsFragment>) => {
return data?.state ? MarketStateMapping[data?.state] : '-'; return data?.state ? MarketStateMapping[data?.state] : '-';
}} },
/> },
<AgGridColumn {
colId="asset" colId: 'asset',
headerName={t('Settlement asset')} headerName: t('Settlement asset'),
field="tradableInstrument.instrument.product.settlementAsset.symbol" field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
hide={window.innerWidth <= BREAKPOINT_MD} hide: window.innerWidth <= BREAKPOINT_MD,
cellRenderer={({ cellRenderer: ({
data, data,
}: VegaICellRendererParams< }: VegaICellRendererParams<
MarketFieldsFragment, MarketFieldsFragment,
@ -105,19 +86,19 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
) : ( ) : (
'' ''
); );
}} },
/> },
<AgGridColumn {
flex={2} flex: 2,
headerName={t('Market ID')} headerName: t('Market ID'),
field="id" field: 'id',
hide={window.innerWidth <= BREAKPOINT_MD} hide: window.innerWidth <= BREAKPOINT_MD,
/> },
<AgGridColumn {
colId="actions" colId: 'actions',
headerName="" headerName: '',
field="id" field: 'id',
cellRenderer={({ cellRenderer: ({
value, value,
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) => }: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
value ? ( value ? (
@ -126,9 +107,34 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
</Link> </Link>
) : ( ) : (
'' ''
) ),
},
],
[openAssetDetailsDialog]
);
return (
<AgGrid
ref={gridRef}
rowData={data}
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
overlayNoRowsTemplate={t('This chain has no markets')}
domLayout="autoHeight"
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
columnDefs={columnDefs}
suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => {
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
navigate(data.id);
} }
}}
/> />
</AgGrid>
); );
}; };

View File

@ -1,7 +1,6 @@
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals'; import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals'; import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
@ -9,7 +8,7 @@ import type {
VegaValueFormatterParams, VegaValueFormatterParams,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import { useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { RowClickedEvent } from 'ag-grid-community'; import type { RowClickedEvent, ColDef } from 'ag-grid-community';
import { getDateTimeFormat } from '@vegaprotocol/utils'; import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import {
@ -64,67 +63,38 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
title: '', title: '',
content: null, content: null,
}); });
const columnDefs = useMemo<ColDef[]>(
return ( () => [
<> {
<AgGrid colId: 'title',
ref={gridRef} headerName: t('Title'),
rowData={data} field: 'rationale.title',
getRowId={({ data }: { data: ProposalListFieldsFragment }) => flex: 2,
data.id || data.rationale.title wrapText: true,
} },
overlayNoRowsTemplate={t('This chain has no markets')} {
domLayout="autoHeight" colId: 'type',
defaultColDef={{ maxWidth: 180,
flex: 1, hide: window.innerWidth <= BREAKPOINT_MD,
resizable: true, headerName: t('Type'),
sortable: true, field: 'terms.change.__typename',
filter: true, },
filterParams: { buttons: ['reset'] }, {
autoHeight: true, maxWidth: 100,
}} headerName: t('State'),
suppressCellFocus={true} field: 'state',
onRowClicked={({ data, event }: RowClickedEvent) => { valueFormatter: ({
if (
(event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON'
) {
const proposalPage = tokenLink(
TOKEN_PROPOSAL.replace(':id', data.id)
);
window.open(proposalPage, '_blank');
}
}}
>
<AgGridColumn
colId="title"
headerName={t('Title')}
field="rationale.title"
flex={2}
wrapText={true}
/>
<AgGridColumn
colId="type"
maxWidth={180}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Type')}
field="terms.change.__typename"
/>
<AgGridColumn
maxWidth={100}
headerName={t('State')}
field="state"
valueFormatter={({
value, value,
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => { }: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
return value ? ProposalStateMapping[value] : '-'; return value ? ProposalStateMapping[value] : '-';
}} },
/> },
<AgGridColumn {
colId="voting" colId: 'voting',
maxWidth={100} maxWidth: 100,
hide={window.innerWidth <= BREAKPOINT_MD} hide: window.innerWidth <= BREAKPOINT_MD,
headerName={t('Voting')} headerName: t('Voting'),
cellRenderer={({ cellRenderer: ({
data, data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => { }: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (data) { if (data) {
@ -144,46 +114,46 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
); );
} }
return '-'; return '-';
}} },
/> },
<AgGridColumn {
colId="cDate" colId: 'cDate',
maxWidth={150} maxWidth: 150,
hide={window.innerWidth <= BREAKPOINT_MD} hide: window.innerWidth <= BREAKPOINT_MD,
headerName={t('Closing date')} headerName: t('Closing date'),
field="terms.closingDatetime" field: 'terms.closingDatetime',
valueFormatter={({ valueFormatter: ({
value, value,
}: VegaValueFormatterParams< }: VegaValueFormatterParams<
ProposalListFieldsFragment, ProposalListFieldsFragment,
'terms.closingDatetime' 'terms.closingDatetime'
>) => { >) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-'; return value ? getDateTimeFormat().format(new Date(value)) : '-';
}} },
/> },
<AgGridColumn {
colId="eDate" colId: 'eDate',
maxWidth={150} maxWidth: 150,
hide={window.innerWidth <= BREAKPOINT_MD} hide: window.innerWidth <= BREAKPOINT_MD,
headerName={t('Enactment date')} headerName: t('Enactment date'),
field="terms.enactmentDatetime" field: 'terms.enactmentDatetime',
valueFormatter={({ valueFormatte: ({
value, value,
}: VegaValueFormatterParams< }: VegaValueFormatterParams<
ProposalListFieldsFragment, ProposalListFieldsFragment,
'terms.enactmentDatetime' 'terms.enactmentDatetime'
>) => { >) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-'; return value ? getDateTimeFormat().format(new Date(value)) : '-';
}} },
/> },
<AgGridColumn {
colId="actions" colId: 'actions',
minWidth={window.innerWidth > BREAKPOINT_MD ? 221 : 80} minWidth: window.innerWidth > BREAKPOINT_MD ? 221 : 80,
maxWidth={221} maxWidth: 221,
sortable={false} sortable: false,
filter={false} filter: false,
resizable={false} resizable: false,
cellRenderer={({ cellRenderer: ({
data, data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => { }: VegaICellRendererParams<ProposalListFieldsFragment>) => {
const proposalPage = tokenLink( const proposalPage = tokenLink(
@ -199,10 +169,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
}; };
return ( return (
<div className="pb-1"> <div className="pb-1">
<button <button className="underline max-md:hidden" onClick={openDialog}>
className="underline max-md:hidden"
onClick={openDialog}
>
{t('View terms')} {t('View terms')}
</button>{' '} </button>{' '}
<ExternalLink className="max-md:hidden" href={proposalPage}> <ExternalLink className="max-md:hidden" href={proposalPage}>
@ -213,9 +180,42 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
</ExternalLink> </ExternalLink>
</div> </div>
); );
},
},
],
[requiredMajorityPercentage, tokenLink]
);
return (
<>
<AgGrid
ref={gridRef}
rowData={data}
getRowId={({ data }: { data: ProposalListFieldsFragment }) =>
data.id || data.rationale.title
}
overlayNoRowsTemplate={t('This chain has no markets')}
domLayout="autoHeight"
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
columnDefs={columnDefs}
suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => {
if (
(event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON'
) {
const proposalPage = tokenLink(
TOKEN_PROPOSAL.replace(':id', data.id)
);
window.open(proposalPage, '_blank');
}
}} }}
/> />
</AgGrid>
<JsonViewerDialog <JsonViewerDialog
open={dialog.open} open={dialog.open}
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })} onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}

View File

@ -1,6 +1,5 @@
@import 'ag-grid-community/dist/styles/ag-grid.css'; @import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/dist/styles/ag-theme-balham.css'; @import 'ag-grid-community/styles/ag-theme-balham.css';
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
@tailwind base; @tailwind base;

View File

@ -1,6 +0,0 @@
function ReactMarkdown({ children }) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}
export default ReactMarkdown;

View File

@ -1,6 +1,5 @@
@import 'ag-grid-community/dist/styles/ag-grid.css'; @import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/dist/styles/ag-theme-balham.css'; @import 'ag-grid-community/styles/ag-theme-balham.css';
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;

View File

@ -21,11 +21,14 @@ import {
HealthBar, HealthBar,
TooltipCellComponent, TooltipCellComponent,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import type { GetRowIdParams, RowClickedEvent } from 'ag-grid-community'; import type {
import 'ag-grid-community/dist/styles/ag-grid.css'; GetRowIdParams,
import 'ag-grid-community/dist/styles/ag-theme-alpine.css'; RowClickedEvent,
import { AgGridColumn } from 'ag-grid-react'; ColDef,
import { useCallback, useState } from 'react'; } from 'ag-grid-community';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import { useCallback, useState, useMemo } from 'react';
import { Grid } from '../../grid'; import { Grid } from '../../grid';
import { HealthDialog } from '../../health-dialog'; import { HealthDialog } from '../../health-dialog';
@ -39,6 +42,234 @@ export const MarketList = () => {
const consoleLink = useLinks(DApp.Console); const consoleLink = useLinks(DApp.Console);
const getRowId = useCallback(({ data }: GetRowIdParams) => data.id, []); const getRowId = useCallback(({ data }: GetRowIdParams) => data.id, []);
const columnDefs = useMemo<ColDef[]>(
() => [
{
headerName: t('Market (futures)'),
field: 'tradableInstrument.instrument.name',
cellRenderer: ({ value, data }: { value: string; data: Market }) => {
return (
<>
<span className="leading-3">{value}</span>
<span className="leading-3">
{
data?.tradableInstrument?.instrument?.product?.settlementAsset
?.symbol
}
</span>
</>
);
},
minWidth: 100,
flex: 1,
headerTooltip: t('The market name and settlement asset'),
},
{
headerName: t('Market Code'),
headerTooltip: t(
'The market code is a unique identifier for this market'
),
field: 'tradableInstrument.instrument.code',
},
{
headerName: t('Type'),
headerTooltip: t('Type'),
field: 'tradableInstrument.instrument.product.__typename',
},
{
headerName: t('Last Price'),
headerTooltip: t('Latest price for this market'),
field: 'data.markPrice',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Market, 'data.markPrice'>) =>
value && data
? formatWithAsset(
value,
data.tradableInstrument.instrument.product.settlementAsset
)
: '-',
},
{
headerName: t('Change (24h)'),
headerTooltip: t('Change in price over the last 24h'),
cellRenderer: ({
data,
}: VegaValueFormatterParams<Market, 'data.candles'>) => {
if (data && data.candles) {
const prices = data.candles.map((candle) => candle.close);
return (
<PriceChangeCell
candles={prices}
decimalPlaces={data?.decimalPlaces}
/>
);
} else return <div>{t('-')}</div>;
},
},
{
headerName: t('Volume (24h)'),
field: 'dayVolume',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Market, 'dayVolume'>) =>
value && data
? `${addDecimalsFormatNumber(
value,
data.tradableInstrument.instrument.product.settlementAsset
.decimals
)} (${displayChange(data.volumeChange)})`
: '-',
headerTooltip: t('The trade volume over the last 24h'),
},
{
headerName: t('Total staked by LPs'),
field: 'liquidityCommitted',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Market, 'liquidityCommitted'>) =>
data && value
? formatWithAsset(
value.toString(),
data.tradableInstrument.instrument.product.settlementAsset
)
: '-',
headerTooltip: t('The amount of funds allocated to provide liquidity'),
},
{
headerName: t('Target stake'),
field: 'target',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Market, 'target'>) =>
data && value
? formatWithAsset(
value,
data.tradableInstrument.instrument.product.settlementAsset
)
: '-',
headerTooltip: t(
'The ideal committed liquidity to operate the market. If total commitment currently below this level then LPs can set the fee level with new commitment.'
),
},
{
headerName: t('% Target stake met'),
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
if (data) {
const roundedPercentage =
parseInt(
(data.liquidityCommitted / parseFloat(data.target)).toFixed(0)
) * 100;
const display = Number.isNaN(roundedPercentage)
? 'N/A'
: formatNumberPercentage(toBigNum(roundedPercentage, 0), 0);
return display;
} else return '-';
},
headerTooltip: t('% Target stake met'),
},
{
headerName: t('Fee levels'),
field: 'fees',
valueFormatter: ({ value }: VegaValueFormatterParams<Market, 'fees'>) =>
value ? `${value.factors.liquidityFee}%` : '-',
headerTooltip: t('Fee level for this market'),
},
{
headerName: t('Status'),
field: 'tradingMode',
cellRenderer: ({
value,
data,
}: {
value: Schema.MarketTradingMode;
data: Market;
}) => {
return <Status trigger={data.data?.trigger} tradingMode={value} />;
},
headerTooltip: t(
'The current market status - those below the target stake mark are most in need of liquidity'
),
},
{
headerComponent: () => {
return (
<div>
<span>{t('Health')}</span>{' '}
<button
onClick={() => setIsHealthDialogOpen(true)}
aria-label={t('open tooltip')}
>
<Icon name="info-sign" />
</button>
</div>
);
},
field: 'tradingMode',
cellRenderer: ({
value,
data,
}: {
value: Schema.MarketTradingMode;
data: Market;
}) => (
<HealthBar
target={data.target}
decimals={
data.tradableInstrument.instrument.product.settlementAsset
.decimals
}
levels={data.feeLevels}
intent={intentForStatus(value)}
/>
),
sortable: false,
cellStyle: { overflow: 'unset' },
},
{
headerName: t('Age'),
field: 'marketTimestamps.open',
headerTooltip: t('Age of the market'),
valueFormatter: ({
value,
}: VegaValueFormatterParams<Market, 'marketTimestamps.open'>) => {
return value ? formatDistanceToNow(new Date(value)) : '-';
},
},
{
headerName: t('Closing Time'),
field: 'tradableInstrument.instrument.metadata.tags',
headerTooltip: t('Closing time of the market'),
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
let expiry;
if (data?.tradableInstrument.instrument.metadata.tags) {
expiry = getExpiryDate(
data?.tradableInstrument.instrument.metadata.tags,
data?.marketTimestamps.close,
data?.state
);
}
return expiry ? expiry : '-';
},
},
],
[]
);
return ( return (
<AsyncRenderer loading={loading} error={error} data={data}> <AsyncRenderer loading={loading} error={error} data={data}>
@ -64,258 +295,11 @@ export const MarketList = () => {
cellClass: ['flex', 'flex-col', 'justify-center'], cellClass: ['flex', 'flex-col', 'justify-center'],
tooltipComponent: TooltipCellComponent, tooltipComponent: TooltipCellComponent,
}} }}
columnDefs={columnDefs}
getRowId={getRowId} getRowId={getRowId}
isRowClickable isRowClickable
tooltipShowDelay={500} tooltipShowDelay={500}
>
<AgGridColumn
headerName={t('Market (futures)')}
field="tradableInstrument.instrument.name"
cellRenderer={({
value,
data,
}: {
value: string;
data: Market;
}) => {
return (
<>
<span className="leading-3">{value}</span>
<span className="leading-3">
{
data?.tradableInstrument?.instrument?.product
?.settlementAsset?.symbol
}
</span>
</>
);
}}
minWidth={100}
flex="1"
headerTooltip={t('The market name and settlement asset')}
/> />
<AgGridColumn
headerName={t('Market Code')}
headerTooltip={t(
'The market code is a unique identifier for this market'
)}
field="tradableInstrument.instrument.code"
/>
<AgGridColumn
headerName={t('Type')}
headerTooltip={t('Type')}
field="tradableInstrument.instrument.product.__typename"
/>
<AgGridColumn
headerName={t('Last Price')}
headerTooltip={t('Latest price for this market')}
field="data.markPrice"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Market, 'data.markPrice'>) =>
value && data
? formatWithAsset(
value,
data.tradableInstrument.instrument.product.settlementAsset
)
: '-'
}
/>
<AgGridColumn
headerName={t('Change (24h)')}
headerTooltip={t('Change in price over the last 24h')}
cellRenderer={({
data,
}: VegaValueFormatterParams<Market, 'data.candles'>) => {
if (data && data.candles) {
const prices = data.candles.map((candle) => candle.close);
return (
<PriceChangeCell
candles={prices}
decimalPlaces={data?.decimalPlaces}
/>
);
} else return <div>{t('-')}</div>;
}}
/>
<AgGridColumn
headerName={t('Volume (24h)')}
field="dayVolume"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Market, 'dayVolume'>) =>
value && data
? `${addDecimalsFormatNumber(
value,
data.tradableInstrument.instrument.product.settlementAsset
.decimals
)} (${displayChange(data.volumeChange)})`
: '-'
}
headerTooltip={t('The trade volume over the last 24h')}
/>
<AgGridColumn
headerName={t('Total staked by LPs')}
field="liquidityCommitted"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Market, 'liquidityCommitted'>) =>
data && value
? formatWithAsset(
value.toString(),
data.tradableInstrument.instrument.product.settlementAsset
)
: '-'
}
headerTooltip={t(
'The amount of funds allocated to provide liquidity'
)}
/>
<AgGridColumn
headerName={t('Target stake')}
field="target"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Market, 'target'>) =>
data && value
? formatWithAsset(
value,
data.tradableInstrument.instrument.product.settlementAsset
)
: '-'
}
headerTooltip={t(
'The ideal committed liquidity to operate the market. If total commitment currently below this level then LPs can set the fee level with new commitment.'
)}
/>
<AgGridColumn
headerName={t('% Target stake met')}
valueFormatter={({
data,
}: VegaValueFormatterParams<Market, ''>) => {
if (data) {
const roundedPercentage =
parseInt(
(data.liquidityCommitted / parseFloat(data.target)).toFixed(
0
)
) * 100;
const display = Number.isNaN(roundedPercentage)
? 'N/A'
: formatNumberPercentage(toBigNum(roundedPercentage, 0), 0);
return display;
} else return '-';
}}
headerTooltip={t('% Target stake met')}
/>
<AgGridColumn
headerName={t('Fee levels')}
field="fees"
valueFormatter={({
value,
}: VegaValueFormatterParams<Market, 'fees'>) =>
value ? `${value.factors.liquidityFee}%` : '-'
}
headerTooltip={t('Fee level for this market')}
/>
<AgGridColumn
headerName={t('Status')}
field="tradingMode"
cellRenderer={({
value,
data,
}: {
value: Schema.MarketTradingMode;
data: Market;
}) => {
return (
<Status trigger={data.data?.trigger} tradingMode={value} />
);
}}
headerTooltip={t(
'The current market status - those below the target stake mark are most in need of liquidity'
)}
/>
<AgGridColumn
headerComponent={() => {
return (
<div>
<span>{t('Health')}</span>{' '}
<button
onClick={() => setIsHealthDialogOpen(true)}
aria-label={t('open tooltip')}
>
<Icon name="info-sign" />
</button>
</div>
);
}}
field="tradingMode"
cellRenderer={({
value,
data,
}: {
value: Schema.MarketTradingMode;
data: Market;
}) => (
<HealthBar
target={data.target}
decimals={
data.tradableInstrument.instrument.product.settlementAsset
.decimals
}
levels={data.feeLevels}
intent={intentForStatus(value)}
/>
)}
sortable={false}
cellStyle={{ overflow: 'unset' }}
/>
<AgGridColumn
headerName={t('Age')}
field="marketTimestamps.open"
headerTooltip={t('Age of the market')}
valueFormatter={({
value,
}: VegaValueFormatterParams<Market, 'marketTimestamps.open'>) => {
return value ? formatDistanceToNow(new Date(value)) : '-';
}}
/>
<AgGridColumn
headerName={t('Closing Time')}
field="tradableInstrument.instrument.metadata.tags"
headerTooltip={t('Closing time of the market')}
valueFormatter={({
data,
}: VegaValueFormatterParams<Market, ''>) => {
let expiry;
if (data?.tradableInstrument.instrument.metadata.tags) {
expiry = getExpiryDate(
data?.tradableInstrument.instrument.metadata.tags,
data?.marketTimestamps.close,
data?.state
);
}
return expiry ? expiry : '-';
}}
/>
</Grid>
<HealthDialog <HealthDialog
isOpen={isHealthDialogOpen} isOpen={isHealthDialogOpen}
onChange={() => { onChange={() => {

View File

@ -1,7 +1,6 @@
import { useCallback } from 'react'; import { useCallback, useMemo } from 'react';
import { AgGridColumn } from 'ag-grid-react';
import type { GetRowIdParams } from 'ag-grid-community'; import type { GetRowIdParams, ColDef } from 'ag-grid-community';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { import type {
@ -36,6 +35,75 @@ export const LPProvidersGrid = ({
}; };
}) => { }) => {
const getRowId = useCallback(({ data }: GetRowIdParams) => data.party.id, []); const getRowId = useCallback(({ data }: GetRowIdParams) => data.party.id, []);
const columnDefs = useMemo<ColDef[]>(
() => [
{
headerName: t('LPs'),
field: 'party.id',
flex: 1,
minWidth: 100,
headerTooltip: t('Liquidity providers'),
},
{
headerName: t('Duration'),
valueFormatter: formatToHours,
field: 'createdAt',
headerTooltip: t('Time in market'),
},
{
headerName: t('Equity-like share'),
field: 'equityLikeShare',
valueFormatter: ({ value }: { value?: string | null }) => {
return value
? `${parseFloat(parseFloat(value).toFixed(2)) * 100}%`
: '';
},
headerTooltip: t(
'The share of the markets liquidity held - the earlier you commit liquidity the greater % fees you earn'
),
minWidth: 140,
},
{
headerName: t('committed bond'),
field: 'commitmentAmount',
valueFormatter: ({ value }: { value?: string | null }) =>
value ? formatWithAsset(value, settlementAsset) : '0',
headerTooltip: t('The amount of funds allocated to provide liquidity'),
minWidth: 140,
},
{
headerName: t('Margin Req.'),
field: 'margin',
headerTooltip: t(
'Margin required for arising positions based on liquidity commitment'
),
},
{
headerName: t('24h Fees'),
field: 'fees',
headerTooltip: t(
'Total fees earned by the liquidity provider in the last 24 hours'
),
},
{
headerName: t('Fee level'),
valueFormatter: ({ value }: { value?: string | null }) => `${value}%`,
field: 'fee',
headerTooltip: t(
"The market's liquidity fee, or the percentage of a trade's value which is collected from the price taker for every trade"
),
},
{
headerName: t('APY'),
field: 'apy',
headerTooltip: t(
'An annualised estimate based on the total liquidity provision fees and maker fees collected by liquidity providers, the maximum margin needed and maximum commitment (bond) over the course of 7 epochs'
),
},
],
[settlementAsset]
);
return ( return (
<Grid <Grid
@ -49,74 +117,9 @@ export const LPProvidersGrid = ({
tooltipComponent: TooltipCellComponent, tooltipComponent: TooltipCellComponent,
minWidth: 100, minWidth: 100,
}} }}
columnDefs={columnDefs}
getRowId={getRowId} getRowId={getRowId}
rowHeight={92} rowHeight={92}
>
<AgGridColumn
headerName={t('LPs')}
field="party.id"
flex="1"
minWidth={100}
headerTooltip={t('Liquidity providers')}
/> />
<AgGridColumn
headerName={t('Duration')}
valueFormatter={formatToHours}
field="createdAt"
headerTooltip={t('Time in market')}
/>
<AgGridColumn
headerName={t('Equity-like share')}
field="equityLikeShare"
valueFormatter={({ value }: { value?: string | null }) => {
return value
? `${parseFloat(parseFloat(value).toFixed(2)) * 100}%`
: '';
}}
headerTooltip={t(
'The share of the markets liquidity held - the earlier you commit liquidity the greater % fees you earn'
)}
minWidth={140}
/>
<AgGridColumn
headerName={t('committed bond')}
field="commitmentAmount"
valueFormatter={({ value }: { value?: string | null }) =>
value ? formatWithAsset(value, settlementAsset) : '0'
}
headerTooltip={t('The amount of funds allocated to provide liquidity')}
minWidth={140}
/>
<AgGridColumn
headerName={t('Margin Req.')}
field="margin"
headerTooltip={t(
'Margin required for arising positions based on liquidity commitment'
)}
/>
<AgGridColumn
headerName={t('24h Fees')}
field="fees"
headerTooltip={t(
'Total fees earned by the liquidity provider in the last 24 hours'
)}
/>
<AgGridColumn
headerName={t('Fee level')}
valueFormatter={({ value }: { value?: string | null }) => `${value}%`}
field="fee"
headerTooltip={t(
"The market's liquidity fee, or the percentage of a trade's value which is collected from the price taker for every trade"
)}
/>
<AgGridColumn
headerName={t('APY')}
field="apy"
headerTooltip={t(
'An annualised estimate based on the total liquidity provision fees and maker fees collected by liquidity providers, the maximum margin needed and maximum commitment (bond) over the course of 7 epochs'
)}
/>
</Grid>
); );
}; };

View File

@ -1,5 +1,4 @@
import { useRef, useCallback, useEffect } from 'react'; import { useRef, useCallback, useEffect } from 'react';
import type { ReactNode } from 'react';
import { AgGridReact } from 'ag-grid-react'; import { AgGridReact } from 'ag-grid-react';
import type { import type {
AgGridReactProps, AgGridReactProps,
@ -7,18 +6,17 @@ import type {
AgGridReact as AgGridReactType, AgGridReact as AgGridReactType,
} from 'ag-grid-react'; } from 'ag-grid-react';
import classNames from 'classnames'; import classNames from 'classnames';
import 'ag-grid-community/dist/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css'; import 'ag-grid-community/styles/ag-theme-alpine.css';
import './grid.scss'; import './grid.scss';
type Props = (AgGridReactProps | AgReactUiProps) & { type Props = (AgGridReactProps | AgReactUiProps) & {
isRowClickable?: boolean; isRowClickable?: boolean;
style?: React.CSSProperties; style?: React.CSSProperties;
children: ReactNode;
}; };
export const Grid = ({ isRowClickable, children, ...props }: Props) => { export const Grid = ({ isRowClickable, ...props }: Props) => {
const gridRef = useRef<AgGridReactType | null>(null); const gridRef = useRef<AgGridReactType | null>(null);
const resizeGrid = useCallback(() => { const resizeGrid = useCallback(() => {
@ -44,8 +42,6 @@ export const Grid = ({ isRowClickable, children, ...props }: Props) => {
onGridReady={handleOnGridReady} onGridReady={handleOnGridReady}
suppressRowClickSelection suppressRowClickSelection
{...props} {...props}
> />
{children}
</AgGridReact>
); );
}; };

View File

@ -90,7 +90,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
cy.getByTestId('All').click(); cy.getByTestId('All').click();
cy.get(`[row-id="${partiallyFilledId}"]`) cy.get(`[row-id="${partiallyFilledId}"]`)
.eq(1) .eq(0)
.within(() => { .within(() => {
cy.get(`[col-id='${orderStatus}']`).should( cy.get(`[col-id='${orderStatus}']`).should(
'have.text', 'have.text',

View File

@ -108,7 +108,7 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
cy.get( cy.get(
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]' '[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
) )
.eq(1) .eq(0)
.within(() => { .within(() => {
emptyCells.forEach((cell) => { emptyCells.forEach((cell) => {
cy.get(`[col-id="${cell}"]`).should('contain.text', '-'); cy.get(`[col-id="${cell}"]`).should('contain.text', '-');

View File

@ -1,6 +1,5 @@
@import 'ag-grid-community/dist/styles/ag-grid.css'; @import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/dist/styles/ag-theme-balham.css'; @import 'ag-grid-community/styles/ag-theme-balham.css';
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;

View File

@ -44,47 +44,44 @@ describe('AccountsTable', () => {
}); });
it('should apply correct formatting', async () => { it('should apply correct formatting', async () => {
await act(async () => { const { container } = render(
render(
<AccountTable <AccountTable
rowData={singleRowData} rowData={singleRowData}
onClickAsset={() => null} onClickAsset={() => null}
isReadOnly={false} isReadOnly={false}
/> />
); );
});
const cells = await screen.findAllByRole('gridcell'); const cells = await screen.findAllByRole('gridcell');
const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', '']; const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
cells.forEach((cell, i) => { cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]); expect(cell).toHaveTextContent(expectedValues[i]);
}); });
const rows = await screen.findAllByRole('row'); const rows = container.querySelector('.ag-center-cols-container');
expect(rows.length).toBe(6); expect(rows?.childElementCount).toBe(1);
}); });
it('should apply correct formatting in view as user mode', async () => { it('should apply correct formatting in view as user mode', async () => {
await act(async () => { const { container } = render(
render(
<AccountTable <AccountTable
rowData={singleRowData} rowData={singleRowData}
onClickAsset={() => null} onClickAsset={() => null}
isReadOnly={true} isReadOnly={true}
/> />
); );
});
const cells = await screen.findAllByRole('gridcell'); const cells = await screen.findAllByRole('gridcell');
const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', '']; const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
expect(cells.length).toBe(expectedValues.length); expect(cells.length).toBe(expectedValues.length);
cells.forEach((cell, i) => { cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]); expect(cell).toHaveTextContent(expectedValues[i]);
}); });
const rows = await screen.findAllByRole('row'); const rows = container.querySelector('.ag-center-cols-container');
expect(rows.length).toBe(6); expect(rows?.childElementCount).toBe(1);
}); });
it('should not add first asset as pinned', async () => { it('should add asset as pinned', async () => {
await act(async () => { const { container, rerender } = render(
render(
<AccountTable <AccountTable
rowData={singleRowData} rowData={singleRowData}
onClickAsset={() => null} onClickAsset={() => null}
@ -97,9 +94,29 @@ describe('AccountsTable', () => {
}} }}
/> />
); );
}); await screen.findAllByRole('rowgroup');
const rows = await screen.findAllByRole('row'); let rows = container.querySelector('.ag-center-cols-container');
expect(rows.length).toBe(6); expect(rows?.childElementCount).toBe(0);
let pinnedRows = container.querySelector('.ag-floating-top-container');
expect(pinnedRows?.childElementCount ?? 0).toBe(1);
rerender(
<AccountTable
rowData={singleRowData}
onClickAsset={() => null}
isReadOnly={false}
pinnedAsset={{
decimals: 5,
id: '',
symbol: 'tBTC',
name: 'tBTC',
}}
/>
);
rows = container.querySelector('.ag-center-cols-container');
expect(rows?.childElementCount ?? 0).toBe(1);
pinnedRows = container.querySelector('.ag-floating-top-container');
expect(pinnedRows?.childElementCount ?? 0).toBe(1);
}); });
it('should get correct account data', () => { it('should get correct account data', () => {

View File

@ -17,7 +17,7 @@ import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
IGetRowsParams, IGetRowsParams,
RowNode, IRowNode,
RowHeightParams, RowHeightParams,
ColDef, ColDef,
} from 'ag-grid-community'; } from 'ag-grid-community';
@ -45,8 +45,8 @@ export const percentageValue = (part: string, total: string) => {
export const accountValuesComparator = ( export const accountValuesComparator = (
valueA: string, valueA: string,
valueB: string, valueB: string,
nodeA: RowNode, nodeA: IRowNode,
nodeB: RowNode nodeB: IRowNode
) => { ) => {
if (isNumeric(valueA) && isNumeric(valueB)) { if (isNumeric(valueA) && isNumeric(valueB)) {
const a = toBigNum(valueA, nodeA.data.asset?.decimals); const a = toBigNum(valueA, nodeA.data.asset?.decimals);
@ -83,20 +83,22 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
onClickDeposit, onClickDeposit,
onClickBreakdown, onClickBreakdown,
rowData, rowData,
isReadOnly,
pinnedAsset,
...props ...props
}, },
ref ref
) => { ) => {
const pinnedAsset = useMemo(() => { const pinnedRow = useMemo(() => {
if (!props.pinnedAsset) { if (!pinnedAsset) {
return; return;
} }
const currentPinnedAssetRow = rowData?.find( const currentPinnedAssetRow = rowData?.find(
(row) => row.asset.id === props.pinnedAsset?.id (row) => row.asset.id === pinnedAsset?.id
); );
if (!currentPinnedAssetRow) { if (!currentPinnedAssetRow) {
return { return {
asset: props.pinnedAsset, asset: pinnedAsset,
available: '0', available: '0',
used: '0', used: '0',
total: '0', total: '0',
@ -104,7 +106,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
}; };
} }
return currentPinnedAssetRow; return currentPinnedAssetRow;
}, [props.pinnedAsset, rowData]); }, [pinnedAsset, rowData]);
const { getRowHeight } = props; const { getRowHeight } = props;
@ -112,17 +114,17 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
(params: RowHeightParams) => { (params: RowHeightParams) => {
if ( if (
params.node.rowPinned && params.node.rowPinned &&
params.data.asset.id === props.pinnedAsset?.id && params.data.asset.id === pinnedAsset?.id &&
new BigNumber(params.data.total).isLessThanOrEqualTo(0) new BigNumber(params.data.total).isLessThanOrEqualTo(0)
) { ) {
return 32; return 32;
} }
return getRowHeight ? getRowHeight(params) : undefined; return getRowHeight ? getRowHeight(params) : undefined;
}, },
[props.pinnedAsset?.id, getRowHeight] [pinnedAsset?.id, getRowHeight]
); );
const showDepositButton = pinnedAsset?.balance === '0'; const showDepositButton = pinnedRow?.balance === '0';
const colDefs = useMemo(() => { const colDefs = useMemo(() => {
const defs: ColDef[] = [ const defs: ColDef[] = [
@ -266,7 +268,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
</CenteredGridCellWrapper> </CenteredGridCellWrapper>
); );
} }
return props.isReadOnly ? null : ( return isReadOnly ? null : (
<AccountsActionsDropdown <AccountsActionsDropdown
assetId={assetId} assetId={assetId}
assetContractAddress={ assetContractAddress={
@ -294,13 +296,11 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
onClickBreakdown, onClickBreakdown,
onClickDeposit, onClickDeposit,
onClickWithdraw, onClickWithdraw,
props.isReadOnly, isReadOnly,
showDepositButton, showDepositButton,
]); ]);
const data = rowData?.filter( const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
(data) => data.asset.id !== props.pinnedAsset?.id
);
return ( return (
<AgGrid <AgGrid
@ -318,7 +318,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
}} }}
columnDefs={colDefs} columnDefs={colDefs}
getRowHeight={getPinnedAssetRowHeight} getRowHeight={getPinnedAssetRowHeight}
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined} pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
/> />
); );
} }

View File

@ -8,7 +8,6 @@ export * from './lib/cells/numeric-cell';
export * from './lib/cells/price-cell'; 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/centered-grid-cell'; export * from './lib/cells/centered-grid-cell';
export * from './lib/cells/market-name-cell'; export * from './lib/cells/market-name-cell';
export * from './lib/cells/order-type-cell'; export * from './lib/cells/order-type-cell';

View File

@ -1,8 +1,10 @@
import { memo } from 'react'; import { memo } from 'react';
import { BID_COLOR, ASK_COLOR } from './vol-cell';
import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils'; import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils';
import { NumericCell } from './numeric-cell'; import { NumericCell } from './numeric-cell';
import { theme } from '@vegaprotocol/tailwindcss-config';
const BID_COLOR = theme.colors.vega.green.DEFAULT;
const ASK_COLOR = theme.colors.vega.pink.DEFAULT;
export interface CumulativeVolProps { export interface CumulativeVolProps {
ask?: number; ask?: number;
bid?: number; bid?: number;

View File

@ -1,49 +0,0 @@
import { render, screen } from '@testing-library/react';
import { VolCell } from './vol-cell';
import * as tailwind from '@vegaprotocol/tailwindcss-config';
describe('VolCell', () => {
const significantPart = '12,345';
const decimalPart = '67';
const props = {
value: 1234567,
valueFormatted: `${significantPart}.${decimalPart}`,
type: 'ask' as const,
testId: 'cell',
};
it('Displays formatted value', () => {
render(<VolCell {...props} />);
expect(screen.getByTestId(props.testId)).toHaveTextContent(
props.valueFormatted
);
expect(screen.getByText(decimalPart)).toBeInTheDocument();
expect(screen.getByText(decimalPart)).toHaveClass('opacity-60');
});
it('Displays 0', () => {
render(<VolCell {...props} value={0} valueFormatted="0.00" />);
expect(screen.getByTestId(props.testId)).toHaveTextContent('0.00');
});
it('Displays - if value is not a number', () => {
render(<VolCell {...props} value={null} valueFormatted="" />);
expect(screen.getByTestId(props.testId)).toHaveTextContent('-');
});
it('renders bid volume bar', () => {
render(<VolCell {...props} type="bid" />);
expect(screen.getByTestId('vol-bar')).toHaveClass('left-0'); // renders bid bars from the left
expect(screen.getByTestId('vol-bar')).toHaveStyle({
backgroundColor: tailwind.theme.colors.vega.green.DEFAULT,
});
});
it('renders ask volume bar', () => {
render(<VolCell {...props} type="ask" />);
expect(screen.getByTestId('vol-bar')).toHaveClass('right-0'); // renders ask bars from the right
expect(screen.getByTestId('vol-bar')).toHaveStyle({
backgroundColor: tailwind.theme.colors.vega.pink.DEFAULT,
});
});
});

View File

@ -1,50 +0,0 @@
import { memo } from 'react';
import type { ICellRendererParams } from 'ag-grid-community';
import classNames from 'classnames';
import { theme } from '@vegaprotocol/tailwindcss-config';
import { NumericCell } from './numeric-cell';
export interface VolCellProps {
value: number | bigint | null | undefined;
valueFormatted: string;
relativeValue?: number;
type: 'ask' | 'bid';
testId?: string;
}
export interface IVolCellProps extends ICellRendererParams {
value: number | bigint | null | undefined;
valueFormatted: Omit<VolCellProps, 'value'>;
}
export const BID_COLOR = theme.colors.vega.green.DEFAULT;
export const ASK_COLOR = theme.colors.vega.pink.DEFAULT;
export const VolCell = memo(
({ value, valueFormatted, relativeValue, type, testId }: VolCellProps) => {
if ((!value && value !== 0) || isNaN(Number(value))) {
return <div data-testid={testId || 'vol'}>-</div>;
}
return (
<div className="relative" data-testid={testId || 'vol'}>
<div
data-testid="vol-bar"
className={classNames(
'h-full absolute top-0 opacity-40 dark:opacity-100',
{
'left-0': type === 'bid',
'right-0': type === 'ask',
}
)}
style={{
width: relativeValue ? `${relativeValue}%` : '0%',
backgroundColor: type === 'bid' ? BID_COLOR : ASK_COLOR,
opacity: 0.6,
}}
/>
<NumericCell value={value} valueFormatted={valueFormatted} />
</div>
);
}
);
VolCell.displayName = 'VolCell';

View File

@ -7,6 +7,6 @@ export const COL_DEFS = {
minWidth: 45, minWidth: 45,
maxWidth: 45, maxWidth: 45,
type: 'rightAligned', type: 'rightAligned',
pinned: 'right', pinned: 'right' as const,
}, },
}; };

View File

@ -4,18 +4,17 @@ import type {
ValueFormatterParams, ValueFormatterParams,
ValueGetterParams, ValueGetterParams,
} from 'ag-grid-community'; } from 'ag-grid-community';
import type { IDatasource, IGetRowsParams, RowNode } from 'ag-grid-community'; import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
import type { AgGridReactProps } from 'ag-grid-react'; import type { AgGridReactProps } from 'ag-grid-react';
type Field = string | readonly string[]; type Field = string | readonly string[];
type RowHelper<TObj, TRow, TField extends Field> = Omit< type RowHelper<TObj, TRow, TField extends Field> = Omit<
TObj, TObj,
'data' | 'value' | 'node' 'data' | 'value'
> & { > & {
data?: TRow; data?: TRow;
value?: Get<TRow, TField>; value?: Get<TRow, TField>;
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
}; };
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper< export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
@ -24,12 +23,8 @@ export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
TField TField
>; >;
export type VegaValueGetterParams<TRow> = Omit< export type VegaValueGetterParams<TRow> = Omit<ValueGetterParams, 'data'> & {
ValueGetterParams,
'data' | 'node'
> & {
data?: TRow; data?: TRow;
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
}; };
export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit< export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit<

View File

@ -1,11 +1,11 @@
import { forwardRef } from 'react'; import { forwardRef, useMemo } from 'react';
import { AgGridColumn } from 'ag-grid-react';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
getDateTimeFormat, getDateTimeFormat,
truncateByChars, truncateByChars,
isNumeric, isNumeric,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import type { ColDef } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
@ -21,51 +21,46 @@ export const DepositsTable = forwardRef<
AgGridReact, AgGridReact,
TypedDataAgGrid<DepositFieldsFragment> TypedDataAgGrid<DepositFieldsFragment>
>((props, ref) => { >((props, ref) => {
return ( const columnDefs = useMemo<ColDef[]>(
<AgGrid () => [
ref={ref} { headerName: 'Asset', field: 'asset.symbol' },
defaultColDef={{ flex: 1 }} {
style={{ width: '100%', height: '100%' }} headerName: 'Amount',
{...props} field: 'amount',
> valueFormatter: ({
<AgGridColumn headerName="Asset" field="asset.symbol" />
<AgGridColumn
headerName="Amount"
field="amount"
valueFormatter={({
value, value,
data, data,
}: VegaValueFormatterParams<DepositFieldsFragment, 'amount'>) => { }: VegaValueFormatterParams<DepositFieldsFragment, 'amount'>) => {
return isNumeric(value) && data return isNumeric(value) && data
? addDecimalsFormatNumber(value, data.asset.decimals) ? addDecimalsFormatNumber(value, data.asset.decimals)
: null; : '';
}} },
/> },
<AgGridColumn {
headerName="Created at" headerName: 'Created at',
field="createdTimestamp" field: 'createdTimestamp',
valueFormatter={({ valueFormatter: ({
value, value,
}: VegaValueFormatterParams< }: VegaValueFormatterParams<
DepositFieldsFragment, DepositFieldsFragment,
'createdTimestamp' 'createdTimestamp'
>) => { >) => {
return value ? getDateTimeFormat().format(new Date(value)) : ''; return value ? getDateTimeFormat().format(new Date(value)) : '';
}} },
/> },
<AgGridColumn {
headerName="Status" headerName: 'Status',
field="status" field: 'status',
valueFormatter={({ valueFormatter: ({
value, value,
}: VegaValueFormatterParams<DepositFieldsFragment, 'status'>) => { }: VegaValueFormatterParams<DepositFieldsFragment, 'status'>) => {
return value ? DepositStatusMapping[value] : ''; return value ? DepositStatusMapping[value] : '';
}} },
/> },
<AgGridColumn {
headerName="Tx hash" headerName: 'Tx hash',
field="txHash" field: 'txHash',
cellRenderer={({ cellRenderer: ({
value, value,
data, data,
}: VegaICellRendererParams<DepositFieldsFragment, 'txHash'>) => { }: VegaICellRendererParams<DepositFieldsFragment, 'txHash'>) => {
@ -76,9 +71,19 @@ export const DepositsTable = forwardRef<
{truncateByChars(value)} {truncateByChars(value)}
</EtherscanLink> </EtherscanLink>
); );
}} },
flex={1} flex: 1,
},
],
[]
);
return (
<AgGrid
ref={ref}
defaultColDef={{ flex: 1 }}
columnDefs={columnDefs}
style={{ width: '100%', height: '100%' }}
{...props}
/> />
</AgGrid>
); );
}); });

View File

@ -1,9 +1,10 @@
import { useMemo } from 'react';
import type { import type {
AgGridReact, AgGridReact,
AgGridReactProps, AgGridReactProps,
AgReactUiProps, AgReactUiProps,
} from 'ag-grid-react'; } from 'ag-grid-react';
import type { ITooltipParams } from 'ag-grid-community'; import type { ITooltipParams, ColDef } from 'ag-grid-community';
import { import {
addDecimal, addDecimal,
addDecimalsFormatNumber, addDecimalsFormatNumber,
@ -13,7 +14,6 @@ import {
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { AgGridColumn } from 'ag-grid-react';
import { import {
AgGridLazy as AgGrid, AgGridLazy as AgGrid,
positiveClassNames, positiveClassNames,
@ -43,29 +43,19 @@ export type Props = (AgGridReactProps | AgReactUiProps) & {
export const FillsTable = forwardRef<AgGridReact, Props>( export const FillsTable = forwardRef<AgGridReact, Props>(
({ partyId, onMarketClick, ...props }, ref) => { ({ partyId, onMarketClick, ...props }, ref) => {
return ( const columnDefs = useMemo<ColDef[]>(
<AgGrid () => [
ref={ref} {
overlayNoRowsTemplate={t('No fills')} headerName: t('Market'),
defaultColDef={{ resizable: true }} field: 'market.tradableInstrument.instrument.name',
style={{ width: '100%', height: '100%' }} cellRenderer: 'MarketNameCell',
getRowId={({ data }) => data?.id} cellRendererParams: { idPath: 'market.id', onMarketClick },
tooltipShowDelay={0} },
tooltipHideDelay={2000} {
components={{ MarketNameCell }} headerName: t('Size'),
{...props} type: 'rightAligned',
> field: 'size',
<AgGridColumn cellClassRules: {
headerName={t('Market')}
field="market.tradableInstrument.instrument.name"
cellRenderer="MarketNameCell"
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
/>
<AgGridColumn
headerName={t('Size')}
type="rightAligned"
field="size"
cellClassRules={{
[positiveClassNames]: ({ data }: { data: Trade }) => { [positiveClassNames]: ({ data }: { data: Trade }) => {
const partySide = getPartySide(data, partyId); const partySide = getPartySide(data, partyId);
return partySide === 'buyer'; return partySide === 'buyer';
@ -74,48 +64,47 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
const partySide = getPartySide(data, partyId); const partySide = getPartySide(data, partyId);
return partySide === 'seller'; return partySide === 'seller';
}, },
}} },
valueFormatter={formatSize(partyId)} valueFormatter: formatSize(partyId),
/> },
<AgGridColumn {
headerName={t('Price')} headerName: t('Price'),
field="price" field: 'price',
valueFormatter={formatPrice} valueFormatter: formatPrice,
type="rightAligned" type: 'rightAligned',
/> },
<AgGridColumn {
headerName={t('Notional')} headerName: t('Notional'),
field="price" field: 'price',
valueFormatter={formatTotal} valueFormatter: formatTotal,
type="rightAligned" type: 'rightAligned',
/> },
<AgGridColumn {
headerName={t('Role')} headerName: t('Role'),
field="aggressor" field: 'aggressor',
valueFormatter={formatRole(partyId)} valueFormatter: formatRole(partyId),
/> },
<AgGridColumn {
headerName={t('Fee')} headerName: t('Fee'),
field="market.tradableInstrument.instrument.product" field: 'market.tradableInstrument.instrument.product',
valueFormatter={formatFee(partyId)} valueFormatter: formatFee(partyId),
type="rightAligned" type: 'rightAligned',
tooltipField="market.tradableInstrument.instrument.product" tooltipField: 'market.tradableInstrument.instrument.product',
tooltipComponent={FeesBreakdownTooltip} tooltipComponent: FeesBreakdownTooltip,
tooltipComponentParams={{ partyId }} tooltipComponentParams: { partyId },
/> },
<AgGridColumn {
headerName={t('Date')} headerName: t('Date'),
field="createdAt" field: 'createdAt',
valueFormatter={({ valueFormatter: ({
value, value,
}: VegaValueFormatterParams<Trade, 'createdAt'>) => { }: VegaValueFormatterParams<Trade, 'createdAt'>) => {
return value ? getDateTimeFormat().format(new Date(value)) : ''; return value ? getDateTimeFormat().format(new Date(value)) : '';
}} },
/> },
<AgGridColumn {
colId="fill-actions" colId: 'fill-actions',
{...COL_DEFS.actions} cellRenderer: ({ data }: VegaICellRendererParams<Trade, 'id'>) => {
cellRenderer={({ data }: VegaICellRendererParams<Trade, 'id'>) => {
if (!data) return null; if (!data) return null;
return ( return (
<FillActionsDropdown <FillActionsDropdown
@ -124,9 +113,25 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
tradeId={data.id} tradeId={data.id}
/> />
); );
}} },
...COL_DEFS.actions,
},
],
[onMarketClick, partyId]
);
return (
<AgGrid
ref={ref}
columnDefs={columnDefs}
overlayNoRowsTemplate={t('No fills')}
defaultColDef={{ resizable: true }}
style={{ width: '100%', height: '100%' }}
getRowId={({ data }) => data?.id}
tooltipShowDelay={0}
tooltipHideDelay={2000}
components={{ MarketNameCell }}
{...props}
/> />
</AgGrid>
); );
} }
); );

View File

@ -15,15 +15,15 @@ import {
SetFilter, SetFilter,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import type * as Types from '@vegaprotocol/types'; import type * as Types from '@vegaprotocol/types';
import type { ColDef } from 'ag-grid-community';
import { import {
AccountTypeMapping, AccountTypeMapping,
DescriptionTransferTypeMapping, DescriptionTransferTypeMapping,
TransferTypeMapping, TransferTypeMapping,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import type { LedgerEntry } from './ledger-entries-data-provider'; import type { LedgerEntry } from './ledger-entries-data-provider';
import { forwardRef } from 'react'; import { forwardRef, useMemo } from 'react';
import { formatRFC3339, subDays } from 'date-fns'; import { formatRFC3339, subDays } from 'date-fns';
export const TransferTooltipCellComponent = ({ export const TransferTooltipCellComponent = ({
@ -47,6 +47,143 @@ type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>( export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
(props, ref) => { (props, ref) => {
const columnDefs = useMemo<ColDef[]>(
() => [
{
headerName: t('Sender'),
field: 'fromAccountPartyId',
cellRenderer: ({
value,
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
truncateByChars(value || ''),
},
{
headerName: t('Account type'),
filter: SetFilter,
filterParams: {
set: AccountTypeMapping,
},
field: 'fromAccountType',
cellRenderer: ({
value,
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
value ? AccountTypeMapping[value] : '-',
},
{
headerName: t('Market'),
field: 'marketSender.tradableInstrument.instrument.code',
cellRenderer: ({
value,
}: VegaValueFormatterParams<
LedgerEntry,
'marketSender.tradableInstrument.instrument.code'
>) => value || '-',
},
{
headerName: t('Receiver'),
field: 'toAccountPartyId',
cellRenderer: ({
value,
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
truncateByChars(value || ''),
},
{
headerName: t('Account type'),
filter: SetFilter,
filterParams: {
set: AccountTypeMapping,
},
field: 'toAccountType',
cellRenderer: ({
value,
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
value ? AccountTypeMapping[value] : '-',
},
{
headerName: t('Market'),
field: 'marketReceiver.tradableInstrument.instrument.code',
cellRenderer: ({
value,
}: VegaValueFormatterParams<
LedgerEntry,
'marketReceiver.tradableInstrument.instrument.code'
>) => value || '-',
},
{
headerName: t('Transfer type'),
field: 'transferType',
tooltipField: 'transferType',
filter: SetFilter,
filterParams: {
set: TransferTypeMapping,
},
valueFormatter: ({
value,
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
value ? TransferTypeMapping[value] : '',
},
{
headerName: t('Quantity'),
field: 'quantity',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
const assetDecimalPlaces = data?.asset?.decimals || 0;
return value
? addDecimalsFormatNumber(value, assetDecimalPlaces)
: '';
},
},
{
headerName: t('Asset'),
field: 'assetId',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
data?.asset?.symbol || '',
},
{
headerName: t('Sender account balance'),
field: 'fromAccountBalance',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
const assetDecimalPlaces = data?.asset?.decimals || 0;
return value
? addDecimalsFormatNumber(value, assetDecimalPlaces)
: '';
},
},
{
headerName: t('Receiver account balance'),
field: 'toAccountBalance',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
const assetDecimalPlaces = data?.asset?.decimals || 0;
return value
? addDecimalsFormatNumber(value, assetDecimalPlaces)
: '';
},
},
{
headerName: t('Vega time'),
field: 'vegaTime',
valueFormatter: ({
value,
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-',
filterParams: dateRangeFilterParams,
filter: DateRangeFilter,
flex: 1,
},
],
[]
);
return ( return (
<AgGrid <AgGrid
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
@ -61,148 +198,9 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
buttons: ['reset'], buttons: ['reset'],
}, },
}} }}
columnDefs={columnDefs}
{...props} {...props}
>
<AgGridColumn
headerName={t('Sender')}
field="fromAccountPartyId"
cellRenderer={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
truncateByChars(value || '')
}
/> />
<AgGridColumn
headerName={t('Account type')}
filter={SetFilter}
filterParams={{
set: AccountTypeMapping,
}}
field="fromAccountType"
cellRenderer={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
value ? AccountTypeMapping[value] : '-'
}
/>
<AgGridColumn
headerName={t('Market')}
field="marketSender.tradableInstrument.instrument.code"
cellRenderer={({
value,
}: VegaValueFormatterParams<
LedgerEntry,
'marketSender.tradableInstrument.instrument.code'
>) => value || '-'}
/>
<AgGridColumn
headerName={t('Receiver')}
field="toAccountPartyId"
cellRenderer={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
truncateByChars(value || '')
}
/>
<AgGridColumn
headerName={t('Account type')}
filter={SetFilter}
filterParams={{
set: AccountTypeMapping,
}}
field="toAccountType"
cellRenderer={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
value ? AccountTypeMapping[value] : '-'
}
/>
<AgGridColumn
headerName={t('Market')}
field="marketReceiver.tradableInstrument.instrument.code"
cellRenderer={({
value,
}: VegaValueFormatterParams<
LedgerEntry,
'marketReceiver.tradableInstrument.instrument.code'
>) => value || '-'}
/>
<AgGridColumn
headerName={t('Transfer type')}
field="transferType"
tooltipField="transferType"
filter={SetFilter}
filterParams={{
set: TransferTypeMapping,
}}
valueFormatter={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
value ? TransferTypeMapping[value] : ''
}
/>
<AgGridColumn
headerName={t('Quantity')}
field="quantity"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
const assetDecimalPlaces = data?.asset?.decimals || 0;
return value
? addDecimalsFormatNumber(value, assetDecimalPlaces)
: value;
}}
/>
<AgGridColumn
headerName={t('Asset')}
field="assetId"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
data?.asset?.symbol || value
}
/>
<AgGridColumn
headerName={t('Sender account balance')}
field="fromAccountBalance"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
const assetDecimalPlaces = data?.asset?.decimals || 0;
return value
? addDecimalsFormatNumber(value, assetDecimalPlaces)
: value;
}}
/>
<AgGridColumn
headerName={t('Receiver account balance')}
field="toAccountBalance"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
const assetDecimalPlaces = data?.asset?.decimals || 0;
return value
? addDecimalsFormatNumber(value, assetDecimalPlaces)
: value;
}}
/>
<AgGridColumn
headerName={t('Vega time')}
field="vegaTime"
valueFormatter={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-'
}
filterParams={dateRangeFilterParams}
filter={DateRangeFilter}
flex={1}
/>
</AgGrid>
); );
} }
); );

View File

@ -188,7 +188,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
<AgGrid <AgGrid
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No liquidity provisions')} overlayNoRowsTemplate={t('No liquidity provisions')}
getRowId={({ data }) => data.id} getRowId={({ data }: { data: LiquidityProvisionData }) => data.id || ''}
ref={ref} ref={ref}
tooltipShowDelay={500} tooltipShowDelay={500}
defaultColDef={{ defaultColDef={{

View File

@ -67,6 +67,7 @@ export const liquidityProviderFeeShareQuery = (
export const liquidityFields: LiquidityProvisionFieldsFragment[] = [ export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
{ {
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
party: { party: {
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f', id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
accountsConnection: { accountsConnection: {
@ -92,6 +93,7 @@ export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
__typename: 'LiquidityProvision', __typename: 'LiquidityProvision',
}, },
{ {
id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
party: { party: {
id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f', id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
accountsConnection: { accountsConnection: {

View File

@ -1,5 +1,6 @@
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react'; import { useMemo } from 'react';
import type { ColDef } from 'ag-grid-community';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
@ -47,7 +48,79 @@ interface Props extends AgGridReactProps {
onClick?: (price?: string) => void; onClick?: (price?: string) => void;
} }
export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => { export const TradesTable = forwardRef<AgGridReact, Props>(
({ onClick, ...props }, ref) => {
const columnDefs = useMemo<ColDef[]>(
() => [
{
headerName: t('Price'),
field: 'price',
type: 'rightAligned',
width: 130,
cellClass: changeCellClass,
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Trade, 'price'>) => {
if (!value || !data?.market) {
return '';
}
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
},
cellRenderer: ({
value,
data,
}: VegaICellRendererParams<Trade, 'price'>) => {
if (!data?.market || !value) {
return '';
}
return (
<button
onClick={() =>
onClick &&
onClick(addDecimal(value, data.market?.decimalPlaces || 0))
}
className="hover:dark:bg-neutral-800 hover:bg-neutral-200"
>
{addDecimalsFormatNumber(value, data.market.decimalPlaces)}
</button>
);
},
},
{
headerName: t('Size'),
field: 'size',
width: 125,
type: 'rightAligned',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Trade, 'size'>) => {
if (!value || !data?.market) {
return '';
}
return addDecimalsFormatNumber(
value,
data.market.positionDecimalPlaces
);
},
cellRenderer: NumericCell,
},
{
headerName: t('Created at'),
field: 'createdAt',
type: 'rightAligned',
width: 170,
cellClass: 'text-right',
valueFormatter: ({
value,
}: VegaValueFormatterParams<Trade, 'createdAt'>) => {
return value && getDateTimeFormat().format(new Date(value));
},
},
],
[onClick]
);
return ( return (
<AgGrid <AgGrid
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
@ -56,76 +129,9 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
defaultColDef={{ defaultColDef={{
flex: 1, flex: 1,
}} }}
columnDefs={columnDefs}
{...props} {...props}
>
<AgGridColumn
headerName={t('Price')}
field="price"
type="rightAligned"
width={130}
cellClass={changeCellClass}
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Trade, 'price'>) => {
if (!value || !data?.market) {
return null;
}
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
}}
cellRenderer={({
value,
data,
}: VegaICellRendererParams<Trade, 'price'>) => {
if (!data?.market || !value) {
return null;
}
return (
<button
onClick={() =>
props.onClick &&
props.onClick(
addDecimal(value, data.market?.decimalPlaces || 0)
)
}
className="hover:dark:bg-neutral-800 hover:bg-neutral-200"
>
{addDecimalsFormatNumber(value, data.market.decimalPlaces)}
</button>
);
}}
/> />
<AgGridColumn );
headerName={t('Size')}
field="size"
width={125}
type="rightAligned"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Trade, 'size'>) => {
if (!value || !data?.market) {
return null;
} }
return addDecimalsFormatNumber( );
value,
data.market.positionDecimalPlaces
);
}}
cellRenderer={NumericCell}
/>
<AgGridColumn
headerName={t('Created at')}
field="createdAt"
type="rightAligned"
width={170}
cellClass="text-right"
valueFormatter={({
value,
}: VegaValueFormatterParams<Trade, 'createdAt'>) => {
return value && getDateTimeFormat().format(new Date(value));
}}
/>
</AgGrid>
);
});

View File

@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState, useMemo } from 'react';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react'; import type { ColDef } from 'ag-grid-community';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
convertToCountdownString, convertToCountdownString,
@ -34,20 +34,111 @@ import * as Schema from '@vegaprotocol/types';
import type { TimestampedWithdrawals } from './use-ready-to-complete-withdrawals-toast'; import type { TimestampedWithdrawals } from './use-ready-to-complete-withdrawals-toast';
import classNames from 'classnames'; import classNames from 'classnames';
export const WithdrawalsTable = ( export const WithdrawalsTable = ({
props: TypedDataAgGrid<WithdrawalFieldsFragment> & { delayed,
ready,
...props
}: TypedDataAgGrid<WithdrawalFieldsFragment> & {
ready?: TimestampedWithdrawals; ready?: TimestampedWithdrawals;
delayed?: TimestampedWithdrawals; delayed?: TimestampedWithdrawals;
} }) => {
) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const createWithdrawApproval = useEthWithdrawApprovalsStore( const createWithdrawApproval = useEthWithdrawApprovalsStore(
(store) => store.create (store) => store.create
); );
const columnDefs = useMemo<ColDef[]>(
() => [
{ headerName: 'Asset', field: 'asset.symbol' },
{
headerName: t('Amount'),
field: 'amount',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'amount'>) => {
return isNumeric(value) && data?.asset
? addDecimalsFormatNumber(value, data.asset.decimals)
: '';
},
},
{
headerName: t('Recipient'),
field: 'details.receiverAddress',
cellRenderer: 'RecipientCell',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<
WithdrawalFieldsFragment,
'details.receiverAddress'
>) => {
if (!data) return '';
if (!value) return '-';
return truncateByChars(value);
},
},
{
headerName: t('Created'),
field: 'createdTimestamp',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<
WithdrawalFieldsFragment,
'createdTimestamp'
>) =>
data
? value
? getDateTimeFormat().format(new Date(value))
: '-'
: '',
},
{
headerName: t('Completed'),
field: 'withdrawnTimestamp',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<
WithdrawalFieldsFragment,
'withdrawnTimestamp'
>) =>
data
? value
? getDateTimeFormat().format(new Date(value))
: '-'
: '',
},
{
headerName: t('Status'),
field: 'status',
cellRenderer: 'StatusCell',
cellRendererParams: { ready, delayed },
},
{
headerName: t('Transaction'),
field: 'txHash',
flex: 2,
type: 'rightAligned',
cellRendererParams: {
complete: (withdrawal: WithdrawalFieldsFragment) => {
createWithdrawApproval(withdrawal);
},
},
cellRendererSelector: ({
data,
}: VegaICellRendererParams<WithdrawalFieldsFragment>) => ({
component: data?.txHash ? 'EtherscanLinkCell' : 'CompleteCell',
}),
},
],
[createWithdrawApproval, delayed, ready]
);
return ( return (
<AgGrid <AgGrid
overlayNoRowsTemplate={t('No withdrawals')} overlayNoRowsTemplate={t('No withdrawals')}
columnDefs={columnDefs}
defaultColDef={{ flex: 1 }} defaultColDef={{ flex: 1 }}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
components={{ components={{
@ -59,93 +150,7 @@ export const WithdrawalsTable = (
suppressCellFocus suppressCellFocus
ref={gridRef} ref={gridRef}
{...props} {...props}
>
<AgGridColumn headerName="Asset" field="asset.symbol" />
<AgGridColumn
headerName={t('Amount')}
field="amount"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'amount'>) => {
return isNumeric(value) && data?.asset
? addDecimalsFormatNumber(value, data.asset.decimals)
: '';
}}
/> />
<AgGridColumn
headerName={t('Recipient')}
field="details.receiverAddress"
cellRenderer="RecipientCell"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<
WithdrawalFieldsFragment,
'details.receiverAddress'
>) => {
if (!data) return null;
if (!value) return '-';
return truncateByChars(value);
}}
/>
<AgGridColumn
headerName={t('Created')}
field="createdTimestamp"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<
WithdrawalFieldsFragment,
'createdTimestamp'
>) =>
data
? value
? getDateTimeFormat().format(new Date(value))
: '-'
: null
}
/>
<AgGridColumn
headerName={t('Completed')}
field="withdrawnTimestamp"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<
WithdrawalFieldsFragment,
'withdrawnTimestamp'
>) =>
data
? value
? getDateTimeFormat().format(new Date(value))
: '-'
: null
}
/>
<AgGridColumn
headerName={t('Status')}
field="status"
cellRendererParams={{ ready: props.ready, delayed: props.delayed }}
cellRenderer="StatusCell"
/>
<AgGridColumn
headerName={t('Transaction')}
field="txHash"
flex={2}
type="rightAligned"
cellRendererParams={{
complete: (withdrawal: WithdrawalFieldsFragment) => {
createWithdrawApproval(withdrawal);
},
}}
cellRendererSelector={({
data,
}: VegaICellRendererParams<WithdrawalFieldsFragment>) => ({
component: data?.txHash ? 'EtherscanLinkCell' : 'CompleteCell',
})}
/>
</AgGrid>
); );
}; };

View File

@ -46,8 +46,8 @@
"@web3-react/metamask": "^8.1.2-beta.0", "@web3-react/metamask": "^8.1.2-beta.0",
"@web3-react/walletconnect": "8.1.3-beta.0", "@web3-react/walletconnect": "8.1.3-beta.0",
"@web3-react/walletconnect-v2": "^8.1.3-beta.0", "@web3-react/walletconnect-v2": "^8.1.3-beta.0",
"ag-grid-community": "^27.0.1", "ag-grid-community": "^29.3.5",
"ag-grid-react": "^27.0.1", "ag-grid-react": "^29.3.5",
"allotment": "1.18.1", "allotment": "1.18.1",
"alpha-lyrae": "vegaprotocol/alpha-lyrae", "alpha-lyrae": "vegaprotocol/alpha-lyrae",
"apollo": "^2.33.9", "apollo": "^2.33.9",

View File

@ -8809,15 +8809,15 @@ aes-js@^3.1.2:
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
ag-grid-community@^27.0.1: ag-grid-community@^29.3.5:
version "27.3.0" version "29.3.5"
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-27.3.0.tgz#b1e94a58026aaf2f0cd7920e35833325b5e762c7" resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-29.3.5.tgz#16897896d10fa3ecac79279aad50d3aaa17c5f33"
integrity sha512-R5oZMXEHXnOLrmhn91J8lR0bv6IAnRcU6maO+wKLMJxffRWaAYFAuw1jt7bdmcKCv8c65F6LEBx4ykSOALa9vA== integrity sha512-LxUo21f2/CH31ACEs1C7Q/ggGGI1fQPSTB4aY5OThmM+lBkygZ7QszBE8jpfgWOIjvjdtcdIeQbmbjkHeMsA7A==
ag-grid-react@^27.0.1: ag-grid-react@^29.3.5:
version "27.3.0" version "29.3.5"
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-27.3.0.tgz#fe06647653f8b0b349b8e613aab8ea2e07915562" resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-29.3.5.tgz#0eae8934d372c7751e98789542fc663aee0ad6ad"
integrity sha512-2bs9YfJ/shvBZQLLjny4NFvht+ic6VtpTPO0r3bHHOhlL3Fjx2rGvS6AHSwfvu+kJacHCta30PjaEbX8T3UDyw== integrity sha512-Eg0GJ8hEBuxdVaN5g+qITOzhw0MGL9avL0Oaajr+p7QRtq2pIFHLZSknWsCBzUTjidiu75WZMKwlZjtGEuafdQ==
dependencies: dependencies:
prop-types "^15.8.1" prop-types "^15.8.1"