169 lines
4.2 KiB
TypeScript
169 lines
4.2 KiB
TypeScript
import {
|
|
apiClient,
|
|
apiQueryClient,
|
|
useAuthenticatedQuery,
|
|
} from "@/shared/api";
|
|
|
|
import type { BankOperation, BankOperations, BankSummary } from "./types";
|
|
|
|
import { moneyCents } from "../model/money";
|
|
|
|
const OPERATIONS_PAGE_SIZE = 100;
|
|
|
|
function sortBankSummary(data: BankSummary): BankSummary {
|
|
return {
|
|
...data,
|
|
categories: data.categories.toSorted((a, b) => {
|
|
const left = moneyCents(a.net.value);
|
|
const right = moneyCents(b.net.value);
|
|
if (left === null || right === null)
|
|
throw new Error("Invalid bank summary amount");
|
|
if (left > right) return -1;
|
|
|
|
return left < right ? 1 : 0;
|
|
}),
|
|
};
|
|
}
|
|
|
|
function useBankCard(id?: string) {
|
|
return useAuthenticatedQuery({
|
|
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/{id}", {
|
|
params: { path: { id: id ?? "" } },
|
|
}),
|
|
enabled: Boolean(id),
|
|
});
|
|
}
|
|
|
|
function useBankCards(isIncludeArchived = false) {
|
|
return useAuthenticatedQuery(
|
|
apiQueryClient.queryOptions("get", "/api/v1/bank-cards", {
|
|
params: {
|
|
query: { includeArchived: isIncludeArchived ? "true" : "false" },
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
|
|
function useBankCategories() {
|
|
return useAuthenticatedQuery(
|
|
apiQueryClient.queryOptions("get", "/api/v1/bank-cards/categories"),
|
|
);
|
|
}
|
|
|
|
function useBankExpenses(
|
|
id: string | undefined,
|
|
query: { limit?: number; page: number },
|
|
) {
|
|
return useAuthenticatedQuery({
|
|
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/{id}/expenses", {
|
|
params: { path: { id: id ?? "" }, query },
|
|
}),
|
|
enabled: Boolean(id),
|
|
});
|
|
}
|
|
|
|
function useBankOperation(id?: string) {
|
|
return useAuthenticatedQuery({
|
|
...apiQueryClient.queryOptions(
|
|
"get",
|
|
"/api/v1/bank-cards/operations/{id}",
|
|
{ params: { path: { id: id ?? "" } } },
|
|
),
|
|
enabled: Boolean(id),
|
|
});
|
|
}
|
|
|
|
function useBankOperations(
|
|
id: string | undefined,
|
|
query: {
|
|
from?: string;
|
|
limit?: "all" | number;
|
|
page: number;
|
|
to?: string;
|
|
},
|
|
) {
|
|
const isAll = query.limit === "all";
|
|
|
|
const parameters = {
|
|
path: { id: id ?? "" },
|
|
query: {
|
|
...query,
|
|
limit: query.limit === "all" ? OPERATIONS_PAGE_SIZE : query.limit,
|
|
page: isAll ? 1 : query.page,
|
|
},
|
|
};
|
|
|
|
const options = apiQueryClient.queryOptions(
|
|
"get",
|
|
"/api/v1/bank-cards/{id}/operations",
|
|
{ params: parameters },
|
|
);
|
|
|
|
// eslint-disable-next-line @tanstack/query/exhaustive-deps -- Generated options.queryKey already contains params; retain the shared cache identity.
|
|
return useAuthenticatedQuery({
|
|
enabled: Boolean(id && query.from && query.to),
|
|
placeholderData: (
|
|
previousData: BankOperations | undefined,
|
|
previousQuery,
|
|
) => {
|
|
const previous = previousQuery?.queryKey[2]?.params;
|
|
|
|
return !isAll
|
|
&& previousQuery?.queryKey.length === options.queryKey.length
|
|
&& previous?.path.id === id
|
|
&& previous?.query?.from === query.from
|
|
&& previous?.query?.to === query.to
|
|
&& previous?.query?.limit === query.limit
|
|
? previousData
|
|
: undefined;
|
|
},
|
|
queryFn: async ({ signal }) => {
|
|
const items: BankOperation[] = [];
|
|
|
|
for (let page = parameters.query.page; ; page++) {
|
|
const { data, error } = await apiClient.GET(
|
|
"/api/v1/bank-cards/{id}/operations",
|
|
{
|
|
params: { ...parameters, query: { ...parameters.query, page } },
|
|
signal,
|
|
},
|
|
);
|
|
|
|
// eslint-disable-next-line @typescript-eslint/only-throw-error -- Preserve generated ProblemDetails for apiErrorCode and field-error consumers.
|
|
if (error) throw error;
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- An empty HTTP body can violate the generated response contract.
|
|
if (!data) throw new Error("Missing bank operations response");
|
|
if (!isAll) return data;
|
|
items.push(...data.items);
|
|
if (!data.hasNext) return { ...data, hasNext: false, items, page: 1 };
|
|
}
|
|
},
|
|
queryKey: isAll
|
|
? ([...options.queryKey, "all"] as const)
|
|
: options.queryKey,
|
|
});
|
|
}
|
|
|
|
function useBankSummary(
|
|
period: { from: string; to: string },
|
|
cardId?: string,
|
|
) {
|
|
return useAuthenticatedQuery({
|
|
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/summary", {
|
|
params: { query: { ...period, cardId } },
|
|
}),
|
|
select: sortBankSummary,
|
|
});
|
|
}
|
|
|
|
export {
|
|
sortBankSummary,
|
|
useBankCard,
|
|
useBankCards,
|
|
useBankCategories,
|
|
useBankExpenses,
|
|
useBankOperation,
|
|
useBankOperations,
|
|
useBankSummary,
|
|
};
|