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:
2026-09-28 16:36:40 +03:00
committed by ksv741
parent 57630102e4
commit 72d7835227
253 changed files with 10059 additions and 8864 deletions
+125 -124
View File
@@ -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,