codex/broker-operations-ui #18
@ -1,5 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { getBrokerOperations } from './broker';
|
||||
import { getBrokerOperations, getBrokerPositions } from './broker';
|
||||
|
||||
describe('broker api', () => {
|
||||
afterEach(() => {
|
||||
@ -24,4 +24,23 @@ describe('broker api', () => {
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it('serializes positions query parameters', async () => {
|
||||
vi.spyOn(globalThis, 'fetch').mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
data: {
|
||||
data: { accountId: 'acc-1', items: [], nextCursor: null, hasNext: false, asOf: 'now' },
|
||||
meta: { fromCache: false, cachedAt: null },
|
||||
},
|
||||
}),
|
||||
} as Response);
|
||||
|
||||
await getBrokerPositions('acc-1', { cursor: 'pos-1', limit: 5 });
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/api/v1/broker/accounts/acc-1/positions?cursor=pos-1&limit=5'),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import type {
|
||||
BrokerAccount,
|
||||
BrokerOperationsPage,
|
||||
BrokerPortfolio,
|
||||
BrokerPositionsPage,
|
||||
} from './responses';
|
||||
|
||||
export type BrokerOperationQuery = {
|
||||
@ -49,3 +50,16 @@ export function getBrokerOperations(
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function getBrokerPositions(
|
||||
accountId: string,
|
||||
query: { cursor?: string; limit?: number } = {},
|
||||
): Promise<{ data: BrokerPositionsPage; meta: ApiResponseMeta }> {
|
||||
return request<BrokerPositionsPage>(
|
||||
`/api/v1/broker/accounts/${encodeURIComponent(accountId)}/positions`,
|
||||
{
|
||||
cursor: query.cursor,
|
||||
limit: query.limit ? String(query.limit) : undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -299,7 +299,6 @@ export interface BrokerPortfolio {
|
||||
};
|
||||
cash: BrokerMoney[];
|
||||
blockedCash: BrokerMoney[];
|
||||
positions: BrokerPosition[];
|
||||
asOf: string;
|
||||
}
|
||||
|
||||
@ -314,6 +313,7 @@ export interface BrokerOperation {
|
||||
type: string;
|
||||
category: BrokerOperationCategory;
|
||||
description: string | null;
|
||||
name: string | null;
|
||||
state: string | null;
|
||||
instrumentUid: string | null;
|
||||
figi: string | null;
|
||||
@ -336,3 +336,11 @@ export interface BrokerOperationsPage {
|
||||
hasNext: boolean;
|
||||
asOf: string;
|
||||
}
|
||||
|
||||
export interface BrokerPositionsPage {
|
||||
accountId: string;
|
||||
items: BrokerPosition[];
|
||||
nextCursor: string | null;
|
||||
hasNext: boolean;
|
||||
asOf: string;
|
||||
}
|
||||
|
||||
18
apps/frontend/src/hooks/useBrokerPositions.ts
Normal file
18
apps/frontend/src/hooks/useBrokerPositions.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { keepPreviousData, useQuery } from '@tanstack/react-query';
|
||||
import { getBrokerPositions } from '../api/broker';
|
||||
import type { BrokerPositionsPage } from '../api/responses';
|
||||
|
||||
export function useBrokerPositions(
|
||||
accountId: string | undefined,
|
||||
query: { cursor?: string; limit?: number } = {},
|
||||
) {
|
||||
return useQuery<BrokerPositionsPage>({
|
||||
queryKey: ['broker', 'positions', accountId, query],
|
||||
enabled: Boolean(accountId),
|
||||
queryFn: async () => (await getBrokerPositions(accountId!, query)).data,
|
||||
staleTime: 60_000,
|
||||
retry: 2,
|
||||
placeholderData: keepPreviousData,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user