feat(#2273): orders and fills on trading page to be market specific (#2395)

* feat(#2273): make orders and fills tabs market specific in trade grid

* feat(#2273): fix order navigation and show orders for this market only checkbox

* fix(#2273): fills container should not require market

* feat(#2273): add marketId as hook dependency

* fix: use data-testid in trading orders

* fix(#2273): default to false
This commit is contained in:
m.ray 2022-12-14 07:59:59 -05:00 committed by GitHub
parent b508d7b252
commit e49ad9da6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 182 additions and 147 deletions

View File

@ -141,10 +141,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
id: orderId, id: orderId,
status: Schema.OrderStatus.STATUS_ACTIVE, status: Schema.OrderStatus.STATUS_ACTIVE,
}); });
cy.get(`[data-testid=order-status-${orderId}]`).should( cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Active');
'have.text',
'Active'
);
}); });
it('must see an expired order', () => { it('must see an expired order', () => {
// 7002-SORD-042 // 7002-SORD-042
@ -152,10 +149,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
id: orderId, id: orderId,
status: Schema.OrderStatus.STATUS_EXPIRED, status: Schema.OrderStatus.STATUS_EXPIRED,
}); });
cy.get(`[data-testid=order-status-${orderId}]`).should( cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Expired');
'have.text',
'Expired'
);
}); });
it('must see a cancelled order', () => { it('must see a cancelled order', () => {
@ -165,10 +159,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
id: orderId, id: orderId,
status: Schema.OrderStatus.STATUS_CANCELLED, status: Schema.OrderStatus.STATUS_CANCELLED,
}); });
cy.get(`[data-testid=order-status-${orderId}]`).should( cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Cancelled');
'have.text',
'Cancelled'
);
}); });
it('must see a stopped order', () => { it('must see a stopped order', () => {
@ -178,10 +169,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
id: orderId, id: orderId,
status: Schema.OrderStatus.STATUS_STOPPED, status: Schema.OrderStatus.STATUS_STOPPED,
}); });
cy.get(`[data-testid=order-status-${orderId}]`).should( cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Stopped');
'have.text',
'Stopped'
);
}); });
it('must see a partially filled order', () => { it('must see a partially filled order', () => {
@ -192,11 +180,11 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
size: '5', size: '5',
remaining: '1', remaining: '1',
}); });
cy.get(`[data-testid=order-status-${orderId}]`).should( cy.getByTestId(`order-status-${orderId}`).should(
'have.text', 'have.text',
'PartiallyFilled' 'PartiallyFilled'
); );
cy.get(`[data-testid=order-status-${orderId}]`) cy.getByTestId(`order-status-${orderId}`)
.parent() .parent()
.siblings(`[col-id=${orderRemaining}]`) .siblings(`[col-id=${orderRemaining}]`)
.should('have.text', '4/5'); .should('have.text', '4/5');
@ -209,10 +197,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
id: orderId, id: orderId,
status: Schema.OrderStatus.STATUS_FILLED, status: Schema.OrderStatus.STATUS_FILLED,
}); });
cy.get(`[data-testid=order-status-${orderId}]`).should( cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Filled');
'have.text',
'Filled'
);
}); });
it('must see a rejected order', () => { it('must see a rejected order', () => {
@ -222,7 +207,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
status: Schema.OrderStatus.STATUS_REJECTED, status: Schema.OrderStatus.STATUS_REJECTED,
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_INTERNAL_ERROR, rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_INTERNAL_ERROR,
}); });
cy.get(`[data-testid=order-status-${orderId}]`).should( cy.getByTestId(`order-status-${orderId}`).should(
'have.text', 'have.text',
'Rejected: Internal error' 'Rejected: Internal error'
); );
@ -235,9 +220,6 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
id: orderId, id: orderId,
status: Schema.OrderStatus.STATUS_PARKED, status: Schema.OrderStatus.STATUS_PARKED,
}); });
cy.get(`[data-testid=order-status-${orderId}]`).should( cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Parked');
'have.text',
'Parked'
);
}); });
}); });

View File

@ -71,100 +71,123 @@ const MainGrid = ({
}: { }: {
marketId: string; marketId: string;
onSelect?: (marketId: string) => void; onSelect?: (marketId: string) => void;
}) => ( }) => {
<ResizableGrid vertical> const [showMarketOnly, setShowMarketOnly] = useState(false);
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}> return (
<ResizableGrid proportionalLayout={false} minSize={200}> <ResizableGrid vertical>
<ResizableGridPanel <ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
priority={LayoutPriority.High} <ResizableGrid proportionalLayout={false} minSize={200}>
minSize={200} <ResizableGridPanel
preferredSize="50%" priority={LayoutPriority.High}
> minSize={200}
<TradeGridChild> preferredSize="50%"
<Tabs> >
<Tab id="candles" name={t('Candles')}> <TradeGridChild>
<TradingViews.Candles marketId={marketId} /> <Tabs>
</Tab> <Tab id="candles" name={t('Candles')}>
<Tab id="depth" name={t('Depth')}> <TradingViews.Candles marketId={marketId} />
<TradingViews.Depth marketId={marketId} /> </Tab>
</Tab> <Tab id="depth" name={t('Depth')}>
<Tab id="liquidity" name={t('Liquidity')}> <TradingViews.Depth marketId={marketId} />
<TradingViews.Liquidity marketId={marketId} /> </Tab>
</Tab> <Tab id="liquidity" name={t('Liquidity')}>
</Tabs> <TradingViews.Liquidity marketId={marketId} />
</TradeGridChild> </Tab>
</ResizableGridPanel> </Tabs>
<ResizableGridPanel </TradeGridChild>
priority={LayoutPriority.Low} </ResizableGridPanel>
preferredSize={330} <ResizableGridPanel
minSize={300} priority={LayoutPriority.Low}
> preferredSize={330}
<TradeGridChild> minSize={300}
<Tabs> >
<Tab id="ticket" name={t('Ticket')}> <TradeGridChild>
<TradingViews.Ticket marketId={marketId} /> <Tabs>
</Tab> <Tab id="ticket" name={t('Ticket')}>
<Tab id="info" name={t('Info')}> <TradingViews.Ticket marketId={marketId} />
<TradingViews.Info </Tab>
marketId={marketId} <Tab id="info" name={t('Info')}>
onSelect={(id: string) => { <TradingViews.Info
onSelect?.(id); marketId={marketId}
}} onSelect={(id: string) => {
onSelect?.(id);
}}
/>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={430}
minSize={200}
>
<TradeGridChild>
<Tabs>
<Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={marketId} />
</Tab>
<Tab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="25%"
minSize={50}
>
<TradeGridChild>
<Tabs>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.Positions />
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<label className="flex align-right whitespace-nowrap overflow-hidden text-ellipsis m-1 text-xs">
<input
className="mr-1"
type="checkbox"
checked={showMarketOnly}
onChange={() => setShowMarketOnly(!showMarketOnly)}
/>
{t('Show orders for this market only')}
</label>
<TradingViews.Orders
marketId={showMarketOnly ? marketId : ''}
/> />
</Tab> </VegaWalletContainer>
</Tabs> </Tab>
</TradeGridChild> <Tab id="fills" name={t('Fills')}>
</ResizableGridPanel> <VegaWalletContainer>
<ResizableGridPanel <label className="flex align-right whitespace-nowrap overflow-hidden text-ellipsis m-1 text-xs">
priority={LayoutPriority.Low} <input
preferredSize={430} className="mr-1"
minSize={200} type="checkbox"
> checked={showMarketOnly}
<TradeGridChild> onChange={() => setShowMarketOnly(!showMarketOnly)}
<Tabs> />
<Tab id="orderbook" name={t('Orderbook')}> {t('Show fills for this market only')}
<TradingViews.Orderbook marketId={marketId} /> </label>
</Tab> <TradingViews.Fills marketId={showMarketOnly ? marketId : ''} />
<Tab id="trades" name={t('Trades')}> </VegaWalletContainer>
<TradingViews.Trades marketId={marketId} /> </Tab>
</Tab> <Tab id="accounts" name={t('Collateral')}>
</Tabs> <VegaWalletContainer>
</TradeGridChild> <TradingViews.Collateral />
</ResizableGridPanel> </VegaWalletContainer>
</ResizableGrid> </Tab>
</ResizableGridPanel> </Tabs>
<ResizableGridPanel </TradeGridChild>
priority={LayoutPriority.Low} </ResizableGridPanel>
preferredSize="25%" </ResizableGrid>
minSize={50} );
> };
<TradeGridChild>
<Tabs>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.Positions />
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<TradingViews.Orders />
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<TradingViews.Fills />
</VegaWalletContainer>
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral />
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
);
const MainGridWrapped = memo(MainGrid); const MainGridWrapped = memo(MainGrid);
export const TradeGrid = ({ market, onSelect }: TradeGridProps) => { export const TradeGrid = ({ market, onSelect }: TradeGridProps) => {

View File

@ -61,7 +61,9 @@ export const CandlesChartContainer = ({
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="px-4 py-2 flex flex-row flex-wrap gap-4"> <div className="px-4 py-2 flex flex-row flex-wrap gap-4">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger>{t('Interval')}</DropdownMenuTrigger> <DropdownMenuTrigger>
{t(`Interval: ${intervalLabels[interval]}`)}
</DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
value={interval} value={interval}

View File

@ -3,7 +3,7 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { FillsManager } from './fills-manager'; import { FillsManager } from './fills-manager';
export const FillsContainer = () => { export const FillsContainer = ({ marketId }: { marketId?: string }) => {
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
if (!pubKey) { if (!pubKey) {
@ -14,5 +14,5 @@ export const FillsContainer = () => {
); );
} }
return <FillsManager partyId={pubKey} />; return <FillsManager partyId={pubKey} marketId={marketId} />;
}; };

View File

@ -7,13 +7,15 @@ import { useFillsList } from './use-fills-list';
interface FillsManagerProps { interface FillsManagerProps {
partyId: string; partyId: string;
marketId?: string;
} }
export const FillsManager = ({ partyId }: FillsManagerProps) => { export const FillsManager = ({ partyId, marketId }: FillsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const scrolledToTop = useRef(true); const scrolledToTop = useRef(true);
const { data, error, loading, addNewRows, getRows } = useFillsList({ const { data, error, loading, addNewRows, getRows } = useFillsList({
partyId, partyId,
marketId,
gridRef, gridRef,
scrolledToTop, scrolledToTop,
}); });

View File

@ -10,11 +10,17 @@ import { fillsWithMarketProvider } from './fills-data-provider';
interface Props { interface Props {
partyId: string; partyId: string;
marketId?: string;
gridRef: RefObject<AgGridReact>; gridRef: RefObject<AgGridReact>;
scrolledToTop: RefObject<boolean>; scrolledToTop: RefObject<boolean>;
} }
export const useFillsList = ({ partyId, gridRef, scrolledToTop }: Props) => { export const useFillsList = ({
partyId,
marketId,
gridRef,
scrolledToTop,
}: Props) => {
const dataRef = useRef<(TradeEdge | null)[] | null>(null); const dataRef = useRef<(TradeEdge | null)[] | null>(null);
const totalCountRef = useRef<number | undefined>(undefined); const totalCountRef = useRef<number | undefined>(undefined);
const newRows = useRef(0); const newRows = useRef(0);
@ -72,7 +78,7 @@ export const useFillsList = ({ partyId, gridRef, scrolledToTop }: Props) => {
[] []
); );
const variables = useMemo(() => ({ partyId }), [partyId]); const variables = useMemo(() => ({ partyId, marketId }), [partyId, marketId]);
const { data, error, loading, load, totalCount } = useDataProvider< const { data, error, loading, load, totalCount } = useDataProvider<
(TradeEdge | null)[], (TradeEdge | null)[],

View File

@ -67,6 +67,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
tooltipComponent: TooltipCellComponent, tooltipComponent: TooltipCellComponent,
sortable: true, sortable: true,
}} }}
enableCellTextSelection={true}
rowData={data} rowData={data}
> >
<AgGridColumn <AgGridColumn

View File

@ -62,8 +62,8 @@ fragment OrderUpdateFields on OrderUpdate {
} }
} }
subscription OrdersUpdate($partyId: ID!) { subscription OrdersUpdate($partyId: ID!, $marketId: ID) {
orders(partyId: $partyId) { orders(partyId: $partyId, marketId: $marketId) {
...OrderUpdateFields ...OrderUpdateFields
} }
} }

View File

@ -18,6 +18,7 @@ export type OrderUpdateFieldsFragment = { __typename?: 'OrderUpdate', id: string
export type OrdersUpdateSubscriptionVariables = Types.Exact<{ export type OrdersUpdateSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID']; partyId: Types.Scalars['ID'];
marketId?: Types.InputMaybe<Types.Scalars['ID']>;
}>; }>;
@ -121,8 +122,8 @@ export type OrdersQueryHookResult = ReturnType<typeof useOrdersQuery>;
export type OrdersLazyQueryHookResult = ReturnType<typeof useOrdersLazyQuery>; export type OrdersLazyQueryHookResult = ReturnType<typeof useOrdersLazyQuery>;
export type OrdersQueryResult = Apollo.QueryResult<OrdersQuery, OrdersQueryVariables>; export type OrdersQueryResult = Apollo.QueryResult<OrdersQuery, OrdersQueryVariables>;
export const OrdersUpdateDocument = gql` export const OrdersUpdateDocument = gql`
subscription OrdersUpdate($partyId: ID!) { subscription OrdersUpdate($partyId: ID!, $marketId: ID) {
orders(partyId: $partyId) { orders(partyId: $partyId, marketId: $marketId) {
...OrderUpdateFields ...OrderUpdateFields
} }
} }
@ -141,6 +142,7 @@ export const OrdersUpdateDocument = gql`
* const { data, loading, error } = useOrdersUpdateSubscription({ * const { data, loading, error } = useOrdersUpdateSubscription({
* variables: { * variables: {
* partyId: // value for 'partyId' * partyId: // value for 'partyId'
* marketId: // value for 'marketId'
* }, * },
* }); * });
*/ */
@ -149,4 +151,4 @@ export function useOrdersUpdateSubscription(baseOptions: Apollo.SubscriptionHook
return Apollo.useSubscription<OrdersUpdateSubscription, OrdersUpdateSubscriptionVariables>(OrdersUpdateDocument, options); return Apollo.useSubscription<OrdersUpdateSubscription, OrdersUpdateSubscriptionVariables>(OrdersUpdateDocument, options);
} }
export type OrdersUpdateSubscriptionHookResult = ReturnType<typeof useOrdersUpdateSubscription>; export type OrdersUpdateSubscriptionHookResult = ReturnType<typeof useOrdersUpdateSubscription>;
export type OrdersUpdateSubscriptionResult = Apollo.SubscriptionResult<OrdersUpdateSubscription>; export type OrdersUpdateSubscriptionResult = Apollo.SubscriptionResult<OrdersUpdateSubscription>;

View File

@ -3,12 +3,12 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { OrderListManager } from './order-list-manager'; import { OrderListManager } from './order-list-manager';
export const OrderListContainer = () => { export const OrderListContainer = ({ marketId }: { marketId?: string }) => {
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
if (!pubKey) { if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>; return <Splash>{t('Please connect Vega wallet')}</Splash>;
} }
return <OrderListManager partyId={pubKey} />; return <OrderListManager partyId={pubKey} marketId={marketId} />;
}; };

View File

@ -15,9 +15,13 @@ import type { Filter, Sort } from './use-order-list-data';
export interface OrderListManagerProps { export interface OrderListManagerProps {
partyId: string; partyId: string;
marketId?: string;
} }
export const OrderListManager = ({ partyId }: OrderListManagerProps) => { export const OrderListManager = ({
partyId,
marketId,
}: OrderListManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const scrolledToTop = useRef(true); const scrolledToTop = useRef(true);
const [sort, setSort] = useState<Sort[] | undefined>(); const [sort, setSort] = useState<Sort[] | undefined>();
@ -25,6 +29,7 @@ export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
const { data, error, loading, addNewRows, getRows } = useOrderListData({ const { data, error, loading, addNewRows, getRows } = useOrderListData({
partyId, partyId,
marketId,
sort, sort,
filter, filter,
gridRef, gridRef,
@ -74,6 +79,7 @@ export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
onBodyScroll={onBodyScroll} onBodyScroll={onBodyScroll}
onFilterChanged={onFilterChanged} onFilterChanged={onFilterChanged}
onSortChanged={onSortChange} onSortChanged={onSortChange}
marketId={marketId}
/> />
<div className="pointer-events-none absolute inset-0 top-5"> <div className="pointer-events-none absolute inset-0 top-5">
<AsyncRenderer <AsyncRenderer

View File

@ -10,7 +10,10 @@ import type {
OrderEdge, OrderEdge,
Order, Order,
} from '../order-data-provider/order-data-provider'; } from '../order-data-provider/order-data-provider';
import type { OrdersQueryVariables } from '../order-data-provider/__generated__/Orders'; import type {
OrdersQueryVariables,
OrdersUpdateSubscriptionVariables,
} from '../order-data-provider/__generated__/Orders';
import type * as Types from '@vegaprotocol/types'; import type * as Types from '@vegaprotocol/types';
export interface Sort { export interface Sort {
colId: string; colId: string;
@ -32,6 +35,7 @@ export interface Filter {
} }
interface Props { interface Props {
partyId: string; partyId: string;
marketId?: string;
filter?: Filter; filter?: Filter;
sort?: Sort[]; sort?: Sort[];
gridRef: RefObject<AgGridReact>; gridRef: RefObject<AgGridReact>;
@ -40,6 +44,7 @@ interface Props {
export const useOrderListData = ({ export const useOrderListData = ({
partyId, partyId,
marketId,
sort, sort,
filter, filter,
gridRef, gridRef,
@ -49,9 +54,11 @@ export const useOrderListData = ({
const totalCountRef = useRef<number | undefined>(undefined); const totalCountRef = useRef<number | undefined>(undefined);
const newRows = useRef(0); const newRows = useRef(0);
const variables = useMemo<OrdersQueryVariables>( const variables = useMemo<
() => ({ partyId, dateRange: filter?.updatedAt?.value }), OrdersQueryVariables & OrdersUpdateSubscriptionVariables
[partyId, filter] >(
() => ({ partyId, dateRange: filter?.updatedAt?.value, marketId }),
[partyId, marketId, filter]
); );
const addNewRows = useCallback(() => { const addNewRows = useCallback(() => {
@ -85,11 +92,14 @@ export const useOrderListData = ({
(dataRef.current?.length && data?.length) || (dataRef.current?.length && data?.length) ||
(!dataRef.current?.length && !data?.length) (!dataRef.current?.length && !data?.length)
); );
dataRef.current = data; dataRef.current =
!!marketId && !!data
? data.filter((d) => d?.node.market?.id === marketId)
: data;
gridRef.current?.api?.refreshInfiniteCache(); gridRef.current?.api?.refreshInfiniteCache();
return avoidRerender; return avoidRerender;
}, },
[gridRef, scrolledToTop] [gridRef, scrolledToTop, marketId]
); );
const insert = useCallback( const insert = useCallback(
@ -100,11 +110,14 @@ export const useOrderListData = ({
data: (OrderEdge | null)[] | null; data: (OrderEdge | null)[] | null;
totalCount?: number; totalCount?: number;
}) => { }) => {
dataRef.current = data; dataRef.current =
!!marketId && !!data
? data.filter((d) => d?.node.market?.id === marketId)
: data;
totalCountRef.current = totalCount; totalCountRef.current = totalCount;
return true; return true;
}, },
[] [marketId]
); );
const { data, error, loading, load, totalCount } = useDataProvider({ const { data, error, loading, load, totalCount } = useDataProvider({

View File

@ -36,7 +36,7 @@ import type { AgGridReact } from 'ag-grid-react';
import type { Order } from '../order-data-provider'; import type { Order } from '../order-data-provider';
import type { OrderEventFieldsFragment } from '../../order-hooks'; import type { OrderEventFieldsFragment } from '../../order-hooks';
type OrderListProps = TypedDataAgGrid<Order>; type OrderListProps = TypedDataAgGrid<Order> & { marketId?: string };
export const TransactionComplete = ({ export const TransactionComplete = ({
transaction, transaction,
@ -84,7 +84,7 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
<OrderListTable <OrderListTable
{...props} {...props}
cancelAll={() => { cancelAll={() => {
orderCancel.cancel({}); orderCancel.cancel({ marketId: props.marketId });
}} }}
cancel={(order: Order) => { cancel={(order: Order) => {
if (!order.market) return; if (!order.market) return;
@ -174,9 +174,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
'market.tradableInstrument.instrument.code' 'market.tradableInstrument.instrument.code'
>) => >) =>
data?.market?.id ? ( data?.market?.id ? (
<Link href={`/markets/${data?.market?.id}`} target="_blank"> <Link href={`/#/markets/${data?.market?.id}`}>{value}</Link>
{value}
</Link>
) : ( ) : (
value value
) )