chore: finish frontend ESLint migration #18
Enable every plugin recommended preset and replace Biome with ESLint formatting. Preserve API contracts and JSX whitespace; verify source, tests, builds and tooling.
This commit is contained in:
@@ -7,39 +7,43 @@ import {
|
||||
|
||||
import { moneyCents } from "../model/form-validation";
|
||||
|
||||
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 =
|
||||
paths["/api/v1/bank-cards/categories"]["get"]["responses"][200]["content"]["application/json"][number];
|
||||
type CreateBankCard =
|
||||
paths["/api/v1/bank-cards"]["post"]["requestBody"]["content"]["application/json"];
|
||||
type UpdateBankCard =
|
||||
paths["/api/v1/bank-cards/{id}"]["patch"]["requestBody"]["content"]["application/json"];
|
||||
type CreateBankOperation =
|
||||
paths["/api/v1/bank-cards/operations"]["post"]["requestBody"]["content"]["application/json"];
|
||||
type UpdateBankOperation =
|
||||
paths["/api/v1/bank-cards/operations/{id}"]["patch"]["requestBody"]["content"]["application/json"];
|
||||
type CreateExpenseCategory =
|
||||
paths["/api/v1/bank-cards/categories"]["post"]["requestBody"]["content"]["application/json"];
|
||||
type UpdateExpenseCategory =
|
||||
paths["/api/v1/bank-cards/categories/{id}"]["patch"]["requestBody"]["content"]["application/json"];
|
||||
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 CreateBankCard
|
||||
= paths["/api/v1/bank-cards"]["post"]["requestBody"]["content"]["application/json"];
|
||||
type CreateBankOperation
|
||||
= paths["/api/v1/bank-cards/operations"]["post"]["requestBody"]["content"]["application/json"];
|
||||
type CreateExpenseCategory
|
||||
= paths["/api/v1/bank-cards/categories"]["post"]["requestBody"]["content"]["application/json"];
|
||||
type ExpenseCategory
|
||||
= paths["/api/v1/bank-cards/categories"]["get"]["responses"][200]["content"]["application/json"][number];
|
||||
type UpdateBankCard
|
||||
= paths["/api/v1/bank-cards/{id}"]["patch"]["requestBody"]["content"]["application/json"];
|
||||
type UpdateBankOperation
|
||||
= paths["/api/v1/bank-cards/operations/{id}"]["patch"]["requestBody"]["content"]["application/json"];
|
||||
type UpdateExpenseCategory
|
||||
= paths["/api/v1/bank-cards/categories/{id}"]["patch"]["requestBody"]["content"]["application/json"];
|
||||
|
||||
const OPERATIONS_PAGE_SIZE = 100;
|
||||
|
||||
function useBankCards(includeArchived = false) {
|
||||
return useAuthenticatedQuery(
|
||||
apiQueryClient.queryOptions("get", "/api/v1/bank-cards", {
|
||||
params: {
|
||||
query: { includeArchived: includeArchived ? "true" : "false" },
|
||||
},
|
||||
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 | undefined) {
|
||||
@@ -51,79 +55,25 @@ function useBankCard(id: string | undefined) {
|
||||
});
|
||||
}
|
||||
|
||||
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 useBankOperations(
|
||||
id: string | undefined,
|
||||
query: {
|
||||
month?: string;
|
||||
from?: string;
|
||||
to?: 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" ? OPERATIONS_PAGE_SIZE : query.limit,
|
||||
},
|
||||
};
|
||||
const options = apiQueryClient.queryOptions(
|
||||
"get",
|
||||
"/api/v1/bank-cards/{id}/operations",
|
||||
{ params },
|
||||
);
|
||||
// eslint-disable-next-line @tanstack/query/exhaustive-deps -- Generated options.queryKey already contains params; retain the shared cache identity.
|
||||
return useAuthenticatedQuery({
|
||||
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 === options.queryKey.length &&
|
||||
previous?.path?.id === id &&
|
||||
previous?.query?.month === query.month &&
|
||||
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 = params.query.page; ; page++) {
|
||||
const { data, error } = await apiClient.GET(
|
||||
"/api/v1/bank-cards/{id}/operations",
|
||||
{
|
||||
params: { ...params, query: { ...params.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;
|
||||
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 || (query.from && query.to))),
|
||||
});
|
||||
}
|
||||
|
||||
function useBankExpenses(
|
||||
id: string | undefined,
|
||||
query: { page: number; limit?: number },
|
||||
query: { limit?: number; page: number },
|
||||
) {
|
||||
return useAuthenticatedQuery({
|
||||
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/{id}/expenses", {
|
||||
@@ -133,36 +83,6 @@ function useBankExpenses(
|
||||
});
|
||||
}
|
||||
|
||||
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");
|
||||
return left > right ? -1 : left < right ? 1 : 0;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function useBankSummary(
|
||||
period: string | { from: string; to: string },
|
||||
cardId?: string,
|
||||
) {
|
||||
const query =
|
||||
typeof period === "string"
|
||||
? { month: period, cardId }
|
||||
: { ...period, cardId };
|
||||
return useAuthenticatedQuery({
|
||||
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/summary", {
|
||||
params: { query },
|
||||
}),
|
||||
select: sortBankSummary,
|
||||
enabled: Boolean(period),
|
||||
});
|
||||
}
|
||||
|
||||
function useBankOperation(id: string | undefined) {
|
||||
return useAuthenticatedQuery({
|
||||
...apiQueryClient.queryOptions(
|
||||
@@ -174,6 +94,87 @@ function useBankOperation(id: string | undefined) {
|
||||
});
|
||||
}
|
||||
|
||||
function useBankOperations(
|
||||
id: string | undefined,
|
||||
query: {
|
||||
from?: string;
|
||||
limit?: "all" | number;
|
||||
month?: string;
|
||||
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.month || (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?.month === query.month
|
||||
&& 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;
|
||||
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: string | { from: string; to: string },
|
||||
cardId?: string,
|
||||
) {
|
||||
const query
|
||||
= typeof period === "string"
|
||||
? { cardId, month: period }
|
||||
: { ...period, cardId };
|
||||
return useAuthenticatedQuery({
|
||||
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/summary", {
|
||||
params: { query },
|
||||
}),
|
||||
enabled: Boolean(period),
|
||||
select: sortBankSummary,
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
type BankCard,
|
||||
type BankOperation,
|
||||
|
||||
Reference in New Issue
Block a user