feat: improve card operations pagination and scroll behavior
This commit is contained in:
@@ -120,6 +120,45 @@ function useReadModels() {
|
||||
describe("banking API", () => {
|
||||
beforeEach(authenticate);
|
||||
|
||||
it("loads every page for all mode and fails without exposing partial results", async () => {
|
||||
const pages: number[] = [];
|
||||
let fail = false;
|
||||
server.use(
|
||||
http.get("*/api/v1/bank-cards/a/operations", ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
const page = Number(url.searchParams.get("page"));
|
||||
pages.push(page);
|
||||
expect(url.searchParams.get("limit")).toBe("100");
|
||||
expect(url.searchParams.get("month")).toBe("2026-09");
|
||||
if (fail && page === 2)
|
||||
return HttpResponse.json({ code: "FAILED" }, { status: 500 });
|
||||
return HttpResponse.json({
|
||||
items: [{ ...transfer, id: String(page) }],
|
||||
total: 2,
|
||||
page,
|
||||
hasNext: page === 1,
|
||||
});
|
||||
}),
|
||||
);
|
||||
const { result } = renderHook(
|
||||
() => useBankOperations("a", { month: "2026-09", page: 1, limit: "all" }),
|
||||
{ wrapper: createQueryWrapper().wrapper },
|
||||
);
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
expect(pages).toEqual([1, 2]);
|
||||
expect(result.current.data?.items.map((item) => item.id)).toEqual([
|
||||
"1",
|
||||
"2",
|
||||
]);
|
||||
fail = true;
|
||||
const failed = renderHook(
|
||||
() => useBankOperations("a", { month: "2026-09", page: 1, limit: "all" }),
|
||||
{ wrapper: createQueryWrapper().wrapper },
|
||||
);
|
||||
await waitFor(() => expect(failed.result.current.isError).toBe(true));
|
||||
expect(failed.result.current.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses session-aware banking endpoints and explicit month/page without T-Bank", async () => {
|
||||
const counts = new Map<string, number>();
|
||||
server.use(...readHandlers(counts));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
apiClient,
|
||||
apiQueryClient,
|
||||
type paths,
|
||||
useAuthenticatedQuery,
|
||||
@@ -8,6 +9,8 @@ type BankCard =
|
||||
paths["/api/v1/bank-cards/{id}"]["get"]["responses"][200]["content"]["application/json"];
|
||||
type BankOperation =
|
||||
paths["/api/v1/bank-cards/operations/{id}"]["get"]["responses"][200]["content"]["application/json"];
|
||||
type BankOperations =
|
||||
paths["/api/v1/bank-cards/{id}/operations"]["get"]["responses"][200]["content"]["application/json"];
|
||||
type BankSummary =
|
||||
paths["/api/v1/bank-cards/summary"]["get"]["responses"][200]["content"]["application/json"];
|
||||
type ExpenseCategory =
|
||||
@@ -52,14 +55,54 @@ function useBankCategories() {
|
||||
|
||||
function useBankOperations(
|
||||
id: string | undefined,
|
||||
query: { month: string; page: number; limit?: number },
|
||||
query: { month: string; page: number; limit?: number | "all" },
|
||||
) {
|
||||
const all = query.limit === "all";
|
||||
const params = {
|
||||
path: { id: id ?? "" },
|
||||
query: {
|
||||
...query,
|
||||
page: all ? 1 : query.page,
|
||||
limit: query.limit === "all" ? 100 : query.limit,
|
||||
},
|
||||
};
|
||||
const options = apiQueryClient.queryOptions(
|
||||
"get",
|
||||
"/api/v1/bank-cards/{id}/operations",
|
||||
{ params },
|
||||
);
|
||||
return useAuthenticatedQuery({
|
||||
...apiQueryClient.queryOptions(
|
||||
"get",
|
||||
"/api/v1/bank-cards/{id}/operations",
|
||||
{ params: { path: { id: id ?? "" }, query } },
|
||||
),
|
||||
queryKey: all ? ([...options.queryKey, "all"] as const) : options.queryKey,
|
||||
placeholderData: (
|
||||
previousData: BankOperations | undefined,
|
||||
previousQuery,
|
||||
) => {
|
||||
const previous = previousQuery?.queryKey[2]?.params;
|
||||
return !all &&
|
||||
previousQuery?.queryKey.length === 3 &&
|
||||
previous?.path?.id === id &&
|
||||
previous?.query?.month === query.month &&
|
||||
previous?.query?.limit === query.limit
|
||||
? previousData
|
||||
: undefined;
|
||||
},
|
||||
queryFn: async ({ signal }) => {
|
||||
const items: BankOperation[] = [];
|
||||
for (let page = params.query.page; ; page++) {
|
||||
const { data, error } = await apiClient.GET(
|
||||
"/api/v1/bank-cards/{id}/operations",
|
||||
{
|
||||
params: { ...params, query: { ...params.query, page } },
|
||||
signal,
|
||||
},
|
||||
);
|
||||
if (error) throw error;
|
||||
if (!data) throw new Error("Missing bank operations response");
|
||||
if (!all) return data;
|
||||
items.push(...data.items);
|
||||
if (!data.hasNext) return { ...data, items, page: 1, hasNext: false };
|
||||
}
|
||||
},
|
||||
enabled: Boolean(id && query.month),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user