* feat(#1643): add grid set filter, amend filters in orders table * feat(#1643): strictly type variables in orders data provider * feat(#1643): add date range param to orders query * feat(#1643): add date range filter * feat(#1643): handle data provider updates after variables change in ag-grid infinite row model * feat(#1643): fix unit tests * feat(#1643): use DateRangeFilter in positions table instead of agDateColumnFilter * feat(#1643): add date range filter support to orders data provider * feat(#1643): fix update functions * feat(#1643): remove sortable from orders list columns * chore: remove console.log
This commit is contained in:
parent
7ba72e3c8c
commit
25699b6283
@ -58,8 +58,10 @@ export const Last24hVolume = ({
|
||||
).current;
|
||||
|
||||
const update = useCallback(
|
||||
({ data }: { data: Candle[] }) => {
|
||||
throttledSetCandles(data);
|
||||
({ data }: { data: Candle[] | null }) => {
|
||||
if (data) {
|
||||
throttledSetCandles(data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[throttledSetCandles]
|
||||
@ -80,8 +82,10 @@ export const Last24hVolume = ({
|
||||
).current;
|
||||
|
||||
const updateCandle24hAgo = useCallback(
|
||||
({ data }: { data: Candle[] }) => {
|
||||
throttledSetVolumeChange(data);
|
||||
({ data }: { data: Candle[] | null }) => {
|
||||
if (data) {
|
||||
throttledSetVolumeChange(data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[throttledSetVolumeChange]
|
||||
|
@ -48,7 +48,7 @@ export const Liquidity = () => {
|
||||
});
|
||||
|
||||
const update = useCallback(
|
||||
({ data }: { data: LiquidityProvisionData[] }) => {
|
||||
({ data }: { data: LiquidityProvisionData[] | null }) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
|
@ -85,9 +85,9 @@ export const Market = ({
|
||||
|
||||
const marketName = data?.tradableInstrument.instrument.name;
|
||||
const updateProvider = useCallback(
|
||||
({ data: marketData }: { data: MarketData }) => {
|
||||
({ data: marketData }: { data: MarketData | null }) => {
|
||||
const marketPrice = calculatePrice(
|
||||
marketData.markPrice,
|
||||
marketData?.markPrice,
|
||||
data?.decimalPlaces
|
||||
);
|
||||
if (marketName) {
|
||||
|
@ -55,8 +55,10 @@ export const Last24hPriceChange = ({ marketId }: { marketId: string }) => {
|
||||
}, constants.DEBOUNCE_UPDATE_TIME)
|
||||
).current;
|
||||
const update = useCallback(
|
||||
({ data }: { data: Candle[] }) => {
|
||||
throttledSetCandles(data);
|
||||
({ data }: { data: Candle[] | null }) => {
|
||||
if (data) {
|
||||
throttledSetCandles(data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[throttledSetCandles]
|
||||
|
@ -57,8 +57,10 @@ export const Last24hVolume = ({ marketId }: { marketId: string }) => {
|
||||
}, constants.DEBOUNCE_UPDATE_TIME)
|
||||
).current;
|
||||
const update = useCallback(
|
||||
({ data }: { data: Candle[] }) => {
|
||||
throttledSetCandles(data);
|
||||
({ data }: { data: Candle[] | null }) => {
|
||||
if (data) {
|
||||
throttledSetCandles(data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[throttledSetCandles]
|
||||
|
@ -33,9 +33,9 @@ export const MarketMarkPrice = ({ marketId }: { marketId: string }) => {
|
||||
}, constants.DEBOUNCE_UPDATE_TIME)
|
||||
).current;
|
||||
const update = useCallback(
|
||||
({ data: marketData }: { data: MarketData }) => {
|
||||
({ data: marketData }: { data: MarketData | null }) => {
|
||||
throttledSetMarketPrice(
|
||||
marketData.markPrice && data?.decimalPlaces
|
||||
marketData?.markPrice && data?.decimalPlaces
|
||||
? addDecimalsFormatNumber(marketData.markPrice, data.decimalPlaces)
|
||||
: '-'
|
||||
);
|
||||
|
@ -43,13 +43,15 @@ export const MarketTradingModeComponent = ({ marketId, onSelect }: Props) => {
|
||||
});
|
||||
|
||||
const update = useCallback(
|
||||
({ data: marketData }: { data: MarketData }) => {
|
||||
setTradingMode(marketData.marketTradingMode);
|
||||
setTrigger(marketData.trigger);
|
||||
setMarket({
|
||||
...data,
|
||||
data: marketData,
|
||||
} as TradingModeMarket);
|
||||
({ data: marketData }: { data: MarketData | null }) => {
|
||||
if (marketData) {
|
||||
setTradingMode(marketData.marketTradingMode);
|
||||
setTrigger(marketData.trigger);
|
||||
setMarket({
|
||||
...data,
|
||||
data: marketData,
|
||||
} as TradingModeMarket);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[data]
|
||||
|
@ -34,9 +34,10 @@ export const MarketVolume = ({ marketId }: { marketId: string }) => {
|
||||
}, constants.DEBOUNCE_UPDATE_TIME)
|
||||
).current;
|
||||
const update = useCallback(
|
||||
({ data: marketData }: { data: MarketData }) => {
|
||||
({ data: marketData }: { data: MarketData | null }) => {
|
||||
throttledSetMarketVolume(
|
||||
marketData.indicativeVolume && data?.positionDecimalPlaces !== undefined
|
||||
marketData?.indicativeVolume &&
|
||||
data?.positionDecimalPlaces !== undefined
|
||||
? addDecimalsFormatNumber(
|
||||
marketData.indicativeVolume,
|
||||
data.positionDecimalPlaces
|
||||
|
@ -17,7 +17,6 @@ export const PageQueryContainer = <TData, TVariables = OperationVariables>({
|
||||
}: PageQueryContainerProps<TData, TVariables>) => {
|
||||
const { data, loading, error } = useQuery<TData, TVariables>(query, {
|
||||
...options,
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -130,10 +130,13 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
||||
variables,
|
||||
});
|
||||
|
||||
const marketDataUpdate = useCallback(({ data }: { data: MarketData }) => {
|
||||
marketDataRef.current = data;
|
||||
return true;
|
||||
}, []);
|
||||
const marketDataUpdate = useCallback(
|
||||
({ data }: { data: MarketData | null }) => {
|
||||
marketDataRef.current = data;
|
||||
return true;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
data: marketData,
|
||||
|
@ -100,11 +100,14 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
|
||||
variables,
|
||||
});
|
||||
|
||||
const marketDataUpdate = useCallback(({ data }: { data: MarketData }) => {
|
||||
marketDataRef.current = data;
|
||||
updateOrderbookData.current();
|
||||
return true;
|
||||
}, []);
|
||||
const marketDataUpdate = useCallback(
|
||||
({ data }: { data: MarketData | null }) => {
|
||||
marketDataRef.current = data;
|
||||
updateOrderbookData.current();
|
||||
return true;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
data: marketData,
|
||||
|
@ -22,10 +22,10 @@ fragment OrderFields on Order {
|
||||
}
|
||||
}
|
||||
|
||||
query Orders($partyId: ID!, $pagination: Pagination) {
|
||||
query Orders($partyId: ID!, $pagination: Pagination, $dateRange: DateRange) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
ordersConnection(pagination: $pagination) {
|
||||
ordersConnection(pagination: $pagination, dateRange: $dateRange) {
|
||||
edges {
|
||||
node {
|
||||
...OrderFields
|
||||
|
@ -8,6 +8,7 @@ export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Typ
|
||||
export type OrdersQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
pagination?: Types.InputMaybe<Types.Pagination>;
|
||||
dateRange?: Types.InputMaybe<Types.DateRange>;
|
||||
}>;
|
||||
|
||||
|
||||
@ -69,10 +70,10 @@ export const OrderUpdateFieldsFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const OrdersDocument = gql`
|
||||
query Orders($partyId: ID!, $pagination: Pagination) {
|
||||
query Orders($partyId: ID!, $pagination: Pagination, $dateRange: DateRange) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
ordersConnection(pagination: $pagination) {
|
||||
ordersConnection(pagination: $pagination, dateRange: $dateRange) {
|
||||
edges {
|
||||
node {
|
||||
...OrderFields
|
||||
@ -104,6 +105,7 @@ export const OrdersDocument = gql`
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* pagination: // value for 'pagination'
|
||||
* dateRange: // value for 'dateRange'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
@ -25,7 +25,7 @@ describe('order data provider', () => {
|
||||
id: '0',
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
},
|
||||
// this one should be dropped because new newer below
|
||||
// this one should be dropped because newer below
|
||||
{
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-02-01').toISOString(),
|
||||
@ -49,7 +49,7 @@ describe('order data provider', () => {
|
||||
},
|
||||
] as OrderUpdateFieldsFragment[];
|
||||
|
||||
const updatedData = update(data, delta);
|
||||
const updatedData = update(data, delta, () => null, { partyId: '0x123' });
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||
).toEqual(-1);
|
||||
@ -62,5 +62,69 @@ describe('order data provider', () => {
|
||||
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
|
||||
delta[4].updatedAt
|
||||
);
|
||||
expect(update([], delta, () => null, { partyId: '0x123' }).length).toEqual(
|
||||
4
|
||||
);
|
||||
});
|
||||
it('add only data matching date range filter', () => {
|
||||
const data = [
|
||||
{
|
||||
node: {
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-01-31').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
},
|
||||
},
|
||||
] as Edge<OrderFieldsFragment>[];
|
||||
|
||||
const delta = [
|
||||
// this one should be ignored because it does not match date range
|
||||
{
|
||||
id: '0',
|
||||
createdAt: new Date('2022-02-02').toISOString(),
|
||||
},
|
||||
// this one should be removed because it does not match date range
|
||||
{
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-02-02').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
// this one should be updated
|
||||
{
|
||||
id: '2',
|
||||
updatedAt: new Date('2022-01-31').toISOString(),
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
},
|
||||
// this should be added
|
||||
{
|
||||
id: '4',
|
||||
createdAt: new Date('2022-01-31').toISOString(),
|
||||
},
|
||||
] as OrderUpdateFieldsFragment[];
|
||||
|
||||
const updatedData = update(data, delta, () => null, {
|
||||
partyId: '0x123',
|
||||
dateRange: { end: new Date('2022-02-01').toISOString() },
|
||||
});
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||
).toEqual(-1);
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[1].id)
|
||||
).toEqual(-1);
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[0].node.updatedAt).toEqual(
|
||||
delta[2].updatedAt
|
||||
);
|
||||
expect(updatedData && updatedData[1].node.id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
|
||||
delta[3].updatedAt
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -15,6 +15,7 @@ import type {
|
||||
OrderFieldsFragment,
|
||||
OrdersQuery,
|
||||
OrdersUpdateSubscription,
|
||||
OrdersQueryVariables,
|
||||
} from './__generated__/Orders';
|
||||
import { OrdersDocument, OrdersUpdateDocument } from './__generated__/Orders';
|
||||
|
||||
@ -24,7 +25,7 @@ export type Order = Omit<OrderFieldsFragment, 'market'> & {
|
||||
export type OrderEdge = Edge<Order>;
|
||||
|
||||
const getData = (responseData: OrdersQuery) =>
|
||||
responseData?.party?.ordersConnection?.edges || null;
|
||||
responseData?.party?.ordersConnection?.edges || [];
|
||||
|
||||
const getDelta = (subscriptionData: OrdersUpdateSubscription) =>
|
||||
subscriptionData.orders || [];
|
||||
@ -34,7 +35,9 @@ const getPageInfo = (responseData: OrdersQuery): PageInfo | null =>
|
||||
|
||||
export const update = (
|
||||
data: ReturnType<typeof getData>,
|
||||
delta: ReturnType<typeof getDelta>
|
||||
delta: ReturnType<typeof getDelta>,
|
||||
reload: () => void,
|
||||
variables?: OrdersQueryVariables
|
||||
) => {
|
||||
if (!data) {
|
||||
return data;
|
||||
@ -51,14 +54,36 @@ export const update = (
|
||||
incoming.reverse().forEach((node) => {
|
||||
const index = draft.findIndex((edge) => edge.node.id === node.id);
|
||||
const newer =
|
||||
draft.length === 0 ||
|
||||
(node.updatedAt || node.createdAt) >=
|
||||
(draft[0].node.updatedAt || draft[0].node.createdAt);
|
||||
(draft[0].node.updatedAt || draft[0].node.createdAt);
|
||||
let doesFilterPass = true;
|
||||
if (
|
||||
doesFilterPass &&
|
||||
variables?.dateRange?.start &&
|
||||
new Date(node.updatedAt || node.createdAt) <=
|
||||
new Date(variables?.dateRange?.start)
|
||||
) {
|
||||
doesFilterPass = false;
|
||||
}
|
||||
if (
|
||||
doesFilterPass &&
|
||||
variables?.dateRange?.end &&
|
||||
new Date(node.updatedAt || node.createdAt) >=
|
||||
new Date(variables?.dateRange?.end)
|
||||
) {
|
||||
doesFilterPass = false;
|
||||
}
|
||||
if (index !== -1) {
|
||||
Object.assign(draft[index].node, node);
|
||||
if (newer) {
|
||||
draft.unshift(...draft.splice(index, 1));
|
||||
if (doesFilterPass) {
|
||||
Object.assign(draft[index].node, node);
|
||||
if (newer) {
|
||||
draft.unshift(...draft.splice(index, 1));
|
||||
}
|
||||
} else {
|
||||
draft.splice(index, 1);
|
||||
}
|
||||
} else if (newer) {
|
||||
} else if (newer && doesFilterPass) {
|
||||
const { marketId, liquidityProvisionId, ...order } = node;
|
||||
|
||||
// If there is a liquidity provision id add the object to the resulting order
|
||||
@ -103,7 +128,8 @@ export const ordersProvider = makeDataProvider({
|
||||
|
||||
export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
(OrderEdge | null)[],
|
||||
Order[]
|
||||
Order[],
|
||||
OrdersQueryVariables
|
||||
>(
|
||||
[ordersProvider, marketsProvider],
|
||||
(partsData): OrderEdge[] =>
|
||||
|
@ -4,6 +4,20 @@ import * as useDataProviderHook from '@vegaprotocol/react-helpers';
|
||||
import type { OrderFieldsFragment } from '../';
|
||||
import * as orderListMock from '../order-list/order-list';
|
||||
import { forwardRef } from 'react';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
const generateJsx = () => {
|
||||
const pubKey = '0x123';
|
||||
return (
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={{ pubKey } as VegaWalletContextShape}>
|
||||
<OrderListManager partyId={pubKey} />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
it('Renders a loading state while awaiting orders', () => {
|
||||
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
|
||||
@ -15,7 +29,7 @@ it('Renders a loading state while awaiting orders', () => {
|
||||
load: jest.fn(),
|
||||
totalCount: 0,
|
||||
});
|
||||
render(<OrderListManager partyId="0x123" />);
|
||||
render(generateJsx());
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@ -30,7 +44,7 @@ it('Renders an error state', () => {
|
||||
load: jest.fn(),
|
||||
totalCount: undefined,
|
||||
});
|
||||
render(<OrderListManager partyId="0x123" />);
|
||||
render(generateJsx());
|
||||
expect(
|
||||
screen.getByText(`Something went wrong: ${errorMsg}`)
|
||||
).toBeInTheDocument();
|
||||
@ -49,6 +63,6 @@ it('Renders the order list if orders provided', async () => {
|
||||
load: jest.fn(),
|
||||
totalCount: undefined,
|
||||
});
|
||||
render(<OrderListManager partyId="0x123" />);
|
||||
render(generateJsx());
|
||||
expect(await screen.findByText('OrderList')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -1,21 +1,32 @@
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { useRef } from 'react';
|
||||
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useRef, useState } from 'react';
|
||||
import type {
|
||||
BodyScrollEvent,
|
||||
BodyScrollEndEvent,
|
||||
FilterChangedEvent,
|
||||
SortChangedEvent,
|
||||
} from 'ag-grid-community';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
import { OrderList } from '../order-list/order-list';
|
||||
import { useOrderListData } from './use-order-list-data';
|
||||
import type { Filter, Sort } from './use-order-list-data';
|
||||
|
||||
interface OrderListManagerProps {
|
||||
export interface OrderListManagerProps {
|
||||
partyId: string;
|
||||
}
|
||||
|
||||
export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const scrolledToTop = useRef(true);
|
||||
const [sort, setSort] = useState<Sort[] | undefined>();
|
||||
const [filter, setFilter] = useState<Filter | undefined>();
|
||||
|
||||
const { data, error, loading, addNewRows, getRows } = useOrderListData({
|
||||
partyId,
|
||||
sort,
|
||||
filter,
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
});
|
||||
@ -30,16 +41,49 @@ export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
|
||||
scrolledToTop.current = event.top <= 0;
|
||||
};
|
||||
|
||||
const onFilterChanged = (event: FilterChangedEvent) => {
|
||||
const updatedFilter = event.api.getFilterModel();
|
||||
if (Object.keys(updatedFilter).length) {
|
||||
setFilter(updatedFilter);
|
||||
} else if (filter) {
|
||||
setFilter(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const onSortChange = (event: SortChangedEvent) => {
|
||||
const sort = event.columnApi
|
||||
.getColumnState()
|
||||
.sort((a, b) => (a.sortIndex || 0) - (b.sortIndex || 0))
|
||||
.reduce((acc, col) => {
|
||||
if (col.sort) {
|
||||
const { colId, sort } = col;
|
||||
acc.push({ colId, sort });
|
||||
}
|
||||
return acc;
|
||||
}, [] as { colId: string; sort: string }[]);
|
||||
setSort(sort.length > 0 ? sort : undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<>
|
||||
<OrderList
|
||||
ref={gridRef}
|
||||
rowModelType={data?.length ? 'infinite' : 'clientSide'}
|
||||
rowData={data?.length ? undefined : []}
|
||||
rowModelType="infinite"
|
||||
datasource={{ getRows }}
|
||||
onBodyScrollEnd={onBodyScrollEnd}
|
||||
onBodyScroll={onBodyScroll}
|
||||
onFilterChanged={onFilterChanged}
|
||||
onSortChanged={onSortChange}
|
||||
/>
|
||||
</AsyncRenderer>
|
||||
<div className="pointer-events-none absolute inset-0 top-5">
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={data}
|
||||
noDataMessage={t('No orders')}
|
||||
noDataCondition={(data) => !(data && data.length)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -95,8 +95,6 @@ describe('useOrderListData Hook', () => {
|
||||
getRows: expect.any(Function),
|
||||
});
|
||||
updateMock({ data: mockData, delta: [] });
|
||||
expect(mockRefreshAgGridApi).not.toHaveBeenCalled();
|
||||
updateMock({ data: mockData, delta: [] });
|
||||
expect(mockRefreshAgGridApi).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -5,17 +5,43 @@ import {
|
||||
makeInfiniteScrollGetRows,
|
||||
useDataProvider,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { ordersWithMarketProvider } from '../';
|
||||
import type { OrderEdge, Order } from '../';
|
||||
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
import type {
|
||||
OrderEdge,
|
||||
Order,
|
||||
} from '../order-data-provider/order-data-provider';
|
||||
import type { OrdersQueryVariables } from '../order-data-provider/__generated__/Orders';
|
||||
import type { Schema as Types } from '@vegaprotocol/types';
|
||||
export interface Sort {
|
||||
colId: string;
|
||||
sort: string;
|
||||
}
|
||||
export interface Filter {
|
||||
updatedAt?: {
|
||||
value: Types.DateRange;
|
||||
};
|
||||
type?: {
|
||||
value: Types.OrderType[];
|
||||
};
|
||||
status?: {
|
||||
value: Types.OrderStatus[];
|
||||
};
|
||||
timeInForce?: {
|
||||
value: Types.OrderTimeInForce[];
|
||||
};
|
||||
}
|
||||
interface Props {
|
||||
partyId: string;
|
||||
filter?: Filter;
|
||||
sort?: Sort[];
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
scrolledToTop: RefObject<boolean>;
|
||||
}
|
||||
|
||||
export const useOrderListData = ({
|
||||
partyId,
|
||||
sort,
|
||||
filter,
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
}: Props) => {
|
||||
@ -23,7 +49,10 @@ export const useOrderListData = ({
|
||||
const totalCountRef = useRef<number | undefined>(undefined);
|
||||
const newRows = useRef(0);
|
||||
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
const variables = useMemo<OrdersQueryVariables>(
|
||||
() => ({ partyId, dateRange: filter?.updatedAt?.value }),
|
||||
[partyId, filter]
|
||||
);
|
||||
|
||||
const addNewRows = useCallback(() => {
|
||||
if (newRows.current === 0) {
|
||||
@ -37,22 +66,28 @@ export const useOrderListData = ({
|
||||
}, [gridRef]);
|
||||
|
||||
const update = useCallback(
|
||||
({ data, delta }: { data: (OrderEdge | null)[]; delta?: Order[] }) => {
|
||||
if (dataRef.current?.length) {
|
||||
if (!scrolledToTop.current) {
|
||||
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||
if (createdAt) {
|
||||
newRows.current += (delta || []).filter(
|
||||
(trade) => trade.createdAt > createdAt
|
||||
).length;
|
||||
}
|
||||
({
|
||||
data,
|
||||
delta,
|
||||
}: {
|
||||
data: (OrderEdge | null)[] | null;
|
||||
delta?: Order[];
|
||||
}) => {
|
||||
if (dataRef.current?.length && delta?.length && !scrolledToTop.current) {
|
||||
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||
if (createdAt) {
|
||||
newRows.current += (delta || []).filter(
|
||||
(trade) => trade.createdAt > createdAt
|
||||
).length;
|
||||
}
|
||||
dataRef.current = data;
|
||||
gridRef.current?.api?.refreshInfiniteCache();
|
||||
return true;
|
||||
}
|
||||
const avoidRerender = !!(
|
||||
(dataRef.current?.length && data?.length) ||
|
||||
(!dataRef.current?.length && !data?.length)
|
||||
);
|
||||
dataRef.current = data;
|
||||
return false;
|
||||
gridRef.current?.api?.refreshInfiniteCache();
|
||||
return avoidRerender;
|
||||
},
|
||||
[gridRef, scrolledToTop]
|
||||
);
|
||||
@ -62,7 +97,7 @@ export const useOrderListData = ({
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
data: (OrderEdge | null)[];
|
||||
data: (OrderEdge | null)[] | null;
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
dataRef.current = data;
|
||||
|
@ -7,6 +7,8 @@ import {
|
||||
positiveClassNames,
|
||||
t,
|
||||
truncateByChars,
|
||||
SetFilter,
|
||||
DateRangeFilter,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
OrderRejectionReasonMapping,
|
||||
@ -155,7 +157,11 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
overlayNoRowsTemplate="No orders"
|
||||
defaultColDef={{ flex: 1, resizable: true }}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data.id}
|
||||
rowHeight={34}
|
||||
@ -165,6 +171,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.code"
|
||||
filter
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
@ -216,6 +223,10 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="type"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: OrderTypeMapping,
|
||||
}}
|
||||
valueFormatter={({
|
||||
data: order,
|
||||
value,
|
||||
@ -232,6 +243,10 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="status"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: OrderStatusMapping,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
@ -295,6 +310,10 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="timeInForce"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: OrderTimeInForceMapping,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
@ -322,6 +341,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="updatedAt"
|
||||
filter={DateRangeFilter}
|
||||
valueFormatter={({
|
||||
value,
|
||||
node,
|
||||
|
@ -1,6 +1,11 @@
|
||||
import { useRef } from 'react';
|
||||
import { AsyncRenderer, Icon, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useClosePosition, usePositionsData, PositionsTable } from '../';
|
||||
import {
|
||||
useClosePosition,
|
||||
usePositionsData,
|
||||
PositionsTable,
|
||||
getSummaryRowData,
|
||||
} from '../';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { Requested } from './close-position-dialog/requested';
|
||||
import { Complete } from './close-position-dialog/complete';
|
||||
@ -29,6 +34,7 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
||||
<PositionsTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
pinnedBottomRowData={data ? [getSummaryRowData(data)] : []}
|
||||
onClose={(position) => submit(position)}
|
||||
/>
|
||||
</AsyncRenderer>
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
getDateTimeFormat,
|
||||
signedNumberCssClass,
|
||||
signedNumberCssClassRules,
|
||||
DateRangeFilter,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
@ -137,6 +138,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
}}
|
||||
components={{ AmountCell, PriceFlashCell, ProgressBarCell }}
|
||||
@ -421,7 +423,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
headerName={t('Updated')}
|
||||
field="updatedAt"
|
||||
type="rightAligned"
|
||||
filter="agDateColumnFilter"
|
||||
filter={DateRangeFilter}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Position, 'updatedAt'>) => {
|
||||
|
@ -9,7 +9,7 @@ import { t, toBigNum, useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const getRowId = ({ data }: { data: Position }) => data.marketId;
|
||||
|
||||
const getSummaryRowData = (positions: Position[]) => {
|
||||
export const getSummaryRowData = (positions: Position[]) => {
|
||||
const summaryRow = {
|
||||
notional: new BigNumber(0),
|
||||
realisedPNL: BigInt(0),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
|
||||
import { isOrderActive, ordersWithMarketProvider } from '@vegaprotocol/orders';
|
||||
import type { OrdersQueryVariables } from '@vegaprotocol/orders';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@ -8,7 +9,10 @@ export const useRequestClosePositionData = (
|
||||
partyId?: string
|
||||
) => {
|
||||
const marketVariables = useMemo(() => ({ marketId }), [marketId]);
|
||||
const orderVariables = useMemo(() => ({ partyId }), [partyId]);
|
||||
const orderVariables = useMemo<OrdersQueryVariables>(
|
||||
() => ({ partyId: partyId || '' }),
|
||||
[partyId]
|
||||
);
|
||||
const { data: market, loading: marketLoading } = useDataProvider({
|
||||
dataProvider: marketProvider,
|
||||
variables: marketVariables,
|
||||
@ -21,6 +25,7 @@ export const useRequestClosePositionData = (
|
||||
const { data: orderData, loading: orderDataLoading } = useDataProvider({
|
||||
dataProvider: ordersWithMarketProvider,
|
||||
variables: orderVariables,
|
||||
skip: !partyId,
|
||||
});
|
||||
|
||||
const orders = useMemo(() => {
|
||||
|
@ -21,14 +21,22 @@ interface useDataProviderParams<
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> {
|
||||
dataProvider: Subscribe<Data, Delta, Variables>;
|
||||
update?: ({ delta, data }: { delta?: Delta; data: Data }) => boolean;
|
||||
update?: ({
|
||||
delta,
|
||||
data,
|
||||
variables,
|
||||
}: {
|
||||
delta?: Delta;
|
||||
data: Data | null;
|
||||
variables?: Variables;
|
||||
}) => boolean;
|
||||
insert?: ({
|
||||
insertionData,
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
insertionData: Data;
|
||||
data: Data;
|
||||
data: Data | null;
|
||||
totalCount?: number;
|
||||
}) => boolean;
|
||||
variables?: Variables;
|
||||
@ -95,41 +103,45 @@ export const useDataProvider = <
|
||||
} = arg;
|
||||
setError(error);
|
||||
setLoading(loading);
|
||||
if (!error && !loading) {
|
||||
// if update or insert function returns true it means that component handles updates
|
||||
// component can use flush() which will call callback without delta and cause data state update
|
||||
if (initialized.current && data) {
|
||||
if (
|
||||
isUpdate &&
|
||||
!noUpdate &&
|
||||
update &&
|
||||
hasDelta<Delta>(arg) &&
|
||||
update({ delta: arg.delta, data })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isInsert &&
|
||||
insert &&
|
||||
(!insertionData || insert({ insertionData, data, totalCount }))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// if update or insert function returns true it means that component handles updates
|
||||
// component can use flush() which will call callback without delta and cause data state update
|
||||
if (initialized.current) {
|
||||
if (
|
||||
isUpdate &&
|
||||
!noUpdate &&
|
||||
update &&
|
||||
hasDelta<Delta>(arg) &&
|
||||
update({ delta: arg.delta, data, variables })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setTotalCount(totalCount);
|
||||
setData(data);
|
||||
if (updateOnInit && !initialized.current && update && data) {
|
||||
update({ data });
|
||||
if (
|
||||
isInsert &&
|
||||
insert &&
|
||||
(!insertionData || insert({ insertionData, data, totalCount }))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
initialized.current = true;
|
||||
}
|
||||
setTotalCount(totalCount);
|
||||
setData(data);
|
||||
if (updateOnInit && !initialized.current && update) {
|
||||
update({ data });
|
||||
}
|
||||
initialized.current = true;
|
||||
},
|
||||
[update, insert, noUpdate, updateOnInit]
|
||||
[update, insert, noUpdate, updateOnInit, variables]
|
||||
);
|
||||
useEffect(() => {
|
||||
setData(null);
|
||||
setError(undefined);
|
||||
setTotalCount(undefined);
|
||||
if (skip) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
initialized.current = false;
|
||||
const { unsubscribe, flush, reload, load } = dataProvider(
|
||||
callback,
|
||||
client,
|
||||
@ -138,7 +150,6 @@ export const useDataProvider = <
|
||||
flushRef.current = flush;
|
||||
reloadRef.current = reload;
|
||||
loadRef.current = load;
|
||||
initialized.current = false;
|
||||
return unsubscribe;
|
||||
}, [client, initialized, dataProvider, callback, variables, skip]);
|
||||
return { data, loading, error, flush, reload, load, totalCount };
|
||||
|
@ -2,8 +2,8 @@ import type {
|
||||
ApolloClient,
|
||||
DocumentNode,
|
||||
FetchPolicy,
|
||||
TypedDocumentNode,
|
||||
OperationVariables,
|
||||
TypedDocumentNode,
|
||||
} from '@apollo/client';
|
||||
import type { Subscription } from 'zen-observable-ts';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
@ -35,6 +35,10 @@ export interface Load<Data> {
|
||||
(start?: number, end?: number): Promise<Data | null>;
|
||||
}
|
||||
|
||||
export interface Reload {
|
||||
(forceReset?: boolean): void;
|
||||
}
|
||||
|
||||
type Pagination = Schema.Pagination & {
|
||||
skip?: number;
|
||||
};
|
||||
@ -56,7 +60,7 @@ export interface Subscribe<
|
||||
variables?: Variables
|
||||
): {
|
||||
unsubscribe: () => void;
|
||||
reload: (forceReset?: boolean) => void;
|
||||
reload: Reload;
|
||||
flush: () => void;
|
||||
load?: Load<Data>;
|
||||
};
|
||||
@ -65,8 +69,12 @@ export interface Subscribe<
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
|
||||
|
||||
export interface Update<Data, Delta> {
|
||||
(data: Data, delta: Delta, reload: (forceReset?: boolean) => void): Data;
|
||||
export interface Update<
|
||||
Data,
|
||||
Delta,
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> {
|
||||
(data: Data, delta: Delta, reload: Reload, variables?: Variables): Data;
|
||||
}
|
||||
|
||||
export interface Append<Data> {
|
||||
@ -82,8 +90,8 @@ export interface Append<Data> {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetData<QueryData, Data> {
|
||||
(queryData: QueryData, variables?: OperationVariables): Data | null;
|
||||
interface GetData<QueryData, Data, Variables> {
|
||||
(queryData: QueryData, variables?: Variables): Data | null;
|
||||
}
|
||||
|
||||
interface GetPageInfo<QueryData> {
|
||||
@ -94,8 +102,8 @@ interface GetTotalCount<QueryData> {
|
||||
(queryData: QueryData): number | undefined;
|
||||
}
|
||||
|
||||
interface GetDelta<SubscriptionData, Delta> {
|
||||
(subscriptionData: SubscriptionData, variables?: OperationVariables): Delta;
|
||||
interface GetDelta<SubscriptionData, Delta, Variables> {
|
||||
(subscriptionData: SubscriptionData, variables?: Variables): Delta;
|
||||
}
|
||||
|
||||
export type Node = { id: string };
|
||||
@ -146,12 +154,18 @@ export function defaultAppend<Data>(
|
||||
return { data, totalCount };
|
||||
}
|
||||
|
||||
interface DataProviderParams<QueryData, Data, SubscriptionData, Delta> {
|
||||
interface DataProviderParams<
|
||||
QueryData,
|
||||
Data,
|
||||
SubscriptionData,
|
||||
Delta,
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> {
|
||||
query: Query<QueryData>;
|
||||
subscriptionQuery?: Query<SubscriptionData>;
|
||||
update?: Update<Data, Delta>;
|
||||
getData: GetData<QueryData, Data>;
|
||||
getDelta?: GetDelta<SubscriptionData, Delta>;
|
||||
update?: Update<Data, Delta, Variables>;
|
||||
getData: GetData<QueryData, Data, Variables>;
|
||||
getDelta?: GetDelta<SubscriptionData, Delta, Variables>;
|
||||
pagination?: {
|
||||
getPageInfo: GetPageInfo<QueryData>;
|
||||
getTotalCount?: GetTotalCount<QueryData>;
|
||||
@ -185,18 +199,20 @@ function makeDataProviderInternal<
|
||||
pagination,
|
||||
fetchPolicy,
|
||||
resetDelay,
|
||||
}: DataProviderParams<QueryData, Data, SubscriptionData, Delta>): Subscribe<
|
||||
}: DataProviderParams<
|
||||
QueryData,
|
||||
Data,
|
||||
SubscriptionData,
|
||||
Delta,
|
||||
Variables
|
||||
> {
|
||||
>): Subscribe<Data, Delta, Variables> {
|
||||
// list of callbacks passed through subscribe call
|
||||
const callbacks: UpdateCallback<Data, Delta>[] = [];
|
||||
// subscription is started before initial query, all deltas that will arrive before initial query response are put on queue
|
||||
const updateQueue: Delta[] = [];
|
||||
|
||||
let resetTimer: ReturnType<typeof setTimeout>;
|
||||
let variables: OperationVariables | undefined;
|
||||
let variables: Variables | undefined;
|
||||
let data: Data | null = null;
|
||||
let error: Error | undefined;
|
||||
let loading = true;
|
||||
@ -228,14 +244,14 @@ function makeDataProviderInternal<
|
||||
};
|
||||
|
||||
const load = async (start?: number, end?: number) => {
|
||||
if (!pagination || !pageInfo || !(data instanceof Array)) {
|
||||
if (!pagination) {
|
||||
return Promise.reject();
|
||||
}
|
||||
const paginationVariables: Pagination = {
|
||||
first: pagination.first,
|
||||
after: pageInfo.endCursor,
|
||||
after: pageInfo?.endCursor,
|
||||
};
|
||||
if (start !== undefined) {
|
||||
if (start !== undefined && data instanceof Array) {
|
||||
if (!start) {
|
||||
paginationVariables.after = undefined;
|
||||
} else if (data && data[start - 1]) {
|
||||
@ -252,7 +268,7 @@ function makeDataProviderInternal<
|
||||
paginationVariables.after = (data[start - 1 - skip] as Cursor).cursor;
|
||||
}
|
||||
}
|
||||
} else if (!pageInfo.hasNextPage) {
|
||||
} else if (!pageInfo?.hasNextPage) {
|
||||
return null;
|
||||
}
|
||||
const res = await client.query<QueryData>({
|
||||
@ -291,7 +307,6 @@ function makeDataProviderInternal<
|
||||
? { ...variables, pagination: { first: pagination.first } }
|
||||
: variables,
|
||||
fetchPolicy: fetchPolicy || 'no-cache',
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
data = getData(res.data, variables);
|
||||
if (data && pagination) {
|
||||
@ -317,7 +332,7 @@ function makeDataProviderInternal<
|
||||
while (updateQueue.length) {
|
||||
const delta = updateQueue.shift();
|
||||
if (delta) {
|
||||
data = update(data, delta, reload);
|
||||
data = update(data, delta, reload, variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -380,7 +395,7 @@ function makeDataProviderInternal<
|
||||
if (loading || !data) {
|
||||
updateQueue.push(delta);
|
||||
} else {
|
||||
const updatedData = update(data, delta, reload);
|
||||
const updatedData = update(data, delta, reload, variables);
|
||||
if (updatedData === data) {
|
||||
return;
|
||||
}
|
||||
@ -484,7 +499,7 @@ const memoize = <
|
||||
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
|
||||
* query: gql`query MarketMidPrice($marketId: ID!) { market(id: $marketId) { data { midPrice } } }`,
|
||||
* subscriptionQuery: gql`subscription MarketMidPriceSubscription($marketId: ID!) { marketDepthUpdate(marketId: $marketId) { market { data { midPrice } } } }`,
|
||||
* update: (draft: Draft<Data>, delta: Delta, reload: (forceReset?: boolean) => void) => { draft.midPrice = delta.midPrice }
|
||||
* update: (draft: Draft<Data>, delta: Delta, reload: Reload) => { draft.midPrice = delta.midPrice }
|
||||
* getData: (data:QueryData) => data.market.data.midPrice
|
||||
* getDelta: (delta:SubscriptionData) => delta.marketData.market
|
||||
* })
|
||||
@ -503,9 +518,15 @@ export function makeDataProvider<
|
||||
Delta,
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
>(
|
||||
params: DataProviderParams<QueryData, Data, SubscriptionData, Delta>
|
||||
params: DataProviderParams<
|
||||
QueryData,
|
||||
Data,
|
||||
SubscriptionData,
|
||||
Delta,
|
||||
Variables
|
||||
>
|
||||
): Subscribe<Data, Delta, Variables> {
|
||||
const getInstance = memoize<Data, Delta>(() =>
|
||||
const getInstance = memoize<Data, Delta, Variables>(() =>
|
||||
makeDataProviderInternal(params)
|
||||
);
|
||||
return (callback, client, variables) =>
|
||||
@ -516,13 +537,22 @@ export function makeDataProvider<
|
||||
* Dependency subscribe needs to use any as Data and Delta because it's unknown what dependencies will be used.
|
||||
* This effects in parts in combine function has any[] type
|
||||
*/
|
||||
type DependencySubscribe = Subscribe<any, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
type DependencyUpdateCallback = Parameters<DependencySubscribe>['0'];
|
||||
export type DerivedPart = Parameters<DependencyUpdateCallback>['0'];
|
||||
type DependencySubscribe<
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> = Subscribe<any, any, Variables>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
type DependencyUpdateCallback<
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> = Parameters<DependencySubscribe<Variables>>['0'];
|
||||
export type DerivedPart<
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> = Parameters<DependencyUpdateCallback<Variables>>['0'];
|
||||
export type CombineDerivedData<
|
||||
Data,
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> = (data: DerivedPart['data'][], variables?: Variables) => Data | null;
|
||||
> = (
|
||||
data: DerivedPart<Variables>['data'][],
|
||||
variables?: Variables
|
||||
) => Data | null;
|
||||
|
||||
export type CombineDerivedDelta<
|
||||
Data,
|
||||
@ -530,7 +560,7 @@ export type CombineDerivedDelta<
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> = (
|
||||
data: Data,
|
||||
parts: DerivedPart[],
|
||||
parts: DerivedPart<Variables>[],
|
||||
previousData: Data | null,
|
||||
variables?: Variables
|
||||
) => Delta | undefined;
|
||||
@ -540,7 +570,7 @@ export type CombineInsertionData<
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
> = (
|
||||
data: Data,
|
||||
parts: DerivedPart[],
|
||||
parts: DerivedPart<Variables>[],
|
||||
variables?: Variables
|
||||
) => Data | undefined;
|
||||
|
||||
@ -549,7 +579,7 @@ function makeDerivedDataProviderInternal<
|
||||
Delta,
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
>(
|
||||
dependencies: DependencySubscribe[],
|
||||
dependencies: DependencySubscribe<Variables>[],
|
||||
combineData: CombineDerivedData<Data, Variables>,
|
||||
combineDelta?: CombineDerivedDelta<Data, Delta, Variables>,
|
||||
combineInsertionData?: CombineInsertionData<Data, Variables>
|
||||
@ -558,7 +588,7 @@ function makeDerivedDataProviderInternal<
|
||||
let client: ApolloClient<object>;
|
||||
const callbacks: UpdateCallback<Data, Delta>[] = [];
|
||||
let variables: Variables | undefined;
|
||||
const parts: DerivedPart[] = [];
|
||||
const parts: DerivedPart<Variables>[] = [];
|
||||
let data: Data | null = null;
|
||||
let error: Error | undefined;
|
||||
let loading = true;
|
||||
@ -700,7 +730,7 @@ export function makeDerivedDataProvider<
|
||||
Delta,
|
||||
Variables extends OperationVariables = OperationVariables
|
||||
>(
|
||||
dependencies: DependencySubscribe[],
|
||||
dependencies: DependencySubscribe<Variables>[],
|
||||
combineData: CombineDerivedData<Data, Variables>,
|
||||
combineDelta?: CombineDerivedDelta<Data, Delta, Variables>,
|
||||
combineInsertionData?: CombineInsertionData<Data, Variables>
|
||||
|
125
libs/react-helpers/src/lib/grid/date-range-filter.tsx
Normal file
125
libs/react-helpers/src/lib/grid/date-range-filter.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import type { ChangeEvent } from 'react';
|
||||
import type { Schema as Types } from '@vegaprotocol/types';
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
||||
import { isValidDate } from '../format/date';
|
||||
|
||||
const defaultFilterValue: Types.DateRange = {};
|
||||
|
||||
const toInputValue = (value: string) => {
|
||||
const date = new Date(value);
|
||||
if (!isValidDate(date)) {
|
||||
return;
|
||||
}
|
||||
return `${date.getFullYear()}-${(date.getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}T${(
|
||||
date.getHours() + 1
|
||||
)
|
||||
.toString()
|
||||
.padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
export const DateRangeFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
const [value, setValue] = useState<Types.DateRange>(defaultFilterValue);
|
||||
|
||||
// expose AG Grid Filter Lifecycle callbacks
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
doesFilterPass(params: IDoesFilterPassParams) {
|
||||
const { api, colDef, column, columnApi, context } = props;
|
||||
const { node } = params;
|
||||
const rowValue = props.valueGetter({
|
||||
api,
|
||||
colDef,
|
||||
column,
|
||||
columnApi,
|
||||
context,
|
||||
data: node.data,
|
||||
getValue: (field) => node.data[field],
|
||||
node,
|
||||
});
|
||||
if (
|
||||
value.start &&
|
||||
rowValue &&
|
||||
new Date(rowValue) <= new Date(value.start)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
value.end &&
|
||||
rowValue &&
|
||||
new Date(rowValue) >= new Date(value.end)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
isFilterActive() {
|
||||
return value.start || value.end;
|
||||
},
|
||||
|
||||
getModel() {
|
||||
if (!this.isFilterActive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { value };
|
||||
},
|
||||
|
||||
setModel(model?: { value: Types.DateRange } | null) {
|
||||
setValue(model?.value || defaultFilterValue);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue({
|
||||
...value,
|
||||
[event.target.name]:
|
||||
event.target.value &&
|
||||
new Date(event.target.value).toISOString().replace('Z', '000000Z'),
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
props.filterChangedCallback();
|
||||
}, [value]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const start = (value.start && toInputValue(value.start)) || '';
|
||||
const end = (value.end && toInputValue(value.end)) || '';
|
||||
return (
|
||||
<div className="ag-filter-body-wrapper">
|
||||
<fieldset className="ag-simple-filter-body-wrapper">
|
||||
<label className="block" key="start">
|
||||
<span className="block">start</span>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="start"
|
||||
value={start}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
<label className="block" key="end">
|
||||
<span className="block">end</span>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="end"
|
||||
value={end}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<div className="ag-filter-apply-panel">
|
||||
<button
|
||||
type="button"
|
||||
className="ag-standard-button ag-filter-apply-panel-button"
|
||||
onClick={() => setValue(defaultFilterValue)}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -6,3 +6,5 @@ export * from './price-flash-cell';
|
||||
export * from './size';
|
||||
export * from './summary-rows';
|
||||
export * from './vol-cell';
|
||||
export * from './set-filter';
|
||||
export * from './date-range-filter';
|
||||
|
91
libs/react-helpers/src/lib/grid/set-filter.tsx
Normal file
91
libs/react-helpers/src/lib/grid/set-filter.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
||||
|
||||
export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
const [value, setValue] = useState<string[]>([]);
|
||||
|
||||
// expose AG Grid Filter Lifecycle callbacks
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
doesFilterPass(params: IDoesFilterPassParams) {
|
||||
const { api, colDef, column, columnApi, context } = props;
|
||||
const { node } = params;
|
||||
return (
|
||||
props.valueGetter({
|
||||
api,
|
||||
colDef,
|
||||
column,
|
||||
columnApi,
|
||||
context,
|
||||
data: node.data,
|
||||
getValue: (field) => node.data[field],
|
||||
node,
|
||||
}) === value
|
||||
);
|
||||
},
|
||||
|
||||
isFilterActive() {
|
||||
return value.length !== 0;
|
||||
},
|
||||
|
||||
getModel() {
|
||||
if (!this.isFilterActive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { value };
|
||||
},
|
||||
|
||||
setModel(model?: { value: string[] } | null) {
|
||||
setValue(!model ? [] : model.value);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(
|
||||
event.target.checked
|
||||
? [...value, event.target.value]
|
||||
: value.filter((v) => v !== event.target.value)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
props.filterChangedCallback();
|
||||
}, [value]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div className="ag-filter-body-wrapper">
|
||||
<fieldset className="ag-simple-filter-body-wrapper">
|
||||
{Object.keys(props.colDef.filterParams.set).map((key) => (
|
||||
<label className="flex">
|
||||
<input
|
||||
type="checkbox"
|
||||
key={key}
|
||||
value={key}
|
||||
className="mr-1"
|
||||
checked={value.includes(key)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span>{props.colDef.filterParams.set[key]}</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
<div className="ag-filter-apply-panel">
|
||||
<button
|
||||
type="button"
|
||||
className="ag-standard-button ag-filter-apply-panel-button"
|
||||
onClick={() => setValue([])}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -11,6 +11,7 @@ interface AsyncRendererProps<T> {
|
||||
noDataMessage?: string;
|
||||
children?: ReactNode | null;
|
||||
render?: (data: T) => ReactNode;
|
||||
noDataCondition?(data: T): boolean;
|
||||
}
|
||||
|
||||
export function AsyncRenderer<T = object>({
|
||||
@ -20,6 +21,7 @@ export function AsyncRenderer<T = object>({
|
||||
errorMessage,
|
||||
data,
|
||||
noDataMessage,
|
||||
noDataCondition,
|
||||
children,
|
||||
render,
|
||||
}: AsyncRendererProps<T>) {
|
||||
@ -37,7 +39,7 @@ export function AsyncRenderer<T = object>({
|
||||
return <Splash>{loadingMessage ? loadingMessage : t('Loading...')}</Splash>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
if (!data || (noDataCondition && noDataCondition(data))) {
|
||||
return <Splash>{noDataMessage ? noDataMessage : t('No data')}</Splash>;
|
||||
}
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
|
Loading…
Reference in New Issue
Block a user