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
@@ -1,9 +1,11 @@
import { renderHook, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { configureAuthClient } from "@/shared/api";
import { createQueryWrapper } from "@/test/query";
import {
brokerageOperationsQueryParams,
brokerageOperationsQueryParams as brokerageOperationsQueryParameters,
useBrokerageOperations,
} from "./useBrokerageOperations";
@@ -11,11 +13,11 @@ describe("useBrokerageOperations", () => {
it("normalizes categories, preserves the cursor and loads a page", async () => {
configureAuthClient({
accessToken: () => "token",
refresh: async () => true,
clear: vi.fn(),
refresh: () => Promise.resolve(true),
});
expect(
brokerageOperationsQueryParams({
brokerageOperationsQueryParameters({
categories: ["tax", "income"],
cursor: "next",
}),
@@ -1,16 +1,16 @@
import { apiQueryClient, type paths, useResourceQuery } from "@/shared/api";
type OperationsEndpoint =
paths["/api/v1/brokerage-accounts/{accountId}/operations"]["get"];
type BrokerageOperation = NonNullable<
OperationsEndpoint["responses"][200]["content"]["application/json"]["data"]
>["items"][number];
type BrokerageOperationsQuery = Omit<
NonNullable<OperationsEndpoint["parameters"]["query"]>,
"sync"
>;
type OperationCategory = BrokerageOperation["category"];
type OperationsEndpoint
= paths["/api/v1/brokerage-accounts/{accountId}/operations"]["get"];
const operationCategories = [
"trade",
@@ -21,19 +21,6 @@ const operationCategories = [
"other",
] as const satisfies readonly OperationCategory[];
function brokerageOperationsQueryParams(query: BrokerageOperationsQuery) {
return {
...(query.instrumentUid ? { instrumentUid: query.instrumentUid } : {}),
...(query.from ? { from: query.from } : {}),
...(query.to ? { to: query.to } : {}),
...(query.categories?.length
? { categories: [...query.categories].sort() }
: {}),
...(query.cursor ? { cursor: query.cursor } : {}),
...(query.limit ? { limit: query.limit } : {}),
};
}
function brokerageOperationsQueryOptions(
accountId: string | undefined,
query: BrokerageOperationsQuery = {},
@@ -44,12 +31,23 @@ function brokerageOperationsQueryOptions(
{
params: {
path: { accountId: accountId ?? "" },
query: brokerageOperationsQueryParams(query),
query: brokerageOperationsQueryParameters(query),
},
},
);
}
function brokerageOperationsQueryParameters(query: BrokerageOperationsQuery) {
return {
...(query.instrumentUid && { instrumentUid: query.instrumentUid }),
...(query.from && { from: query.from }),
...(query.to && { to: query.to }),
...(query.categories?.length && { categories: query.categories.toSorted((left, right) => left.localeCompare(right)) }),
...(query.cursor && { cursor: query.cursor }),
...(query.limit && { limit: query.limit }),
};
}
function useBrokerageOperations(
accountId: string | undefined,
query: BrokerageOperationsQuery = {},
@@ -65,8 +63,8 @@ function useBrokerageOperations(
export {
type BrokerageOperation,
brokerageOperationsQueryOptions,
brokerageOperationsQueryParams,
type OperationCategory,
brokerageOperationsQueryParameters as brokerageOperationsQueryParams,
operationCategories,
type OperationCategory,
useBrokerageOperations,
};
+1 -1
View File
@@ -1,8 +1,8 @@
export {
type BrokerageOperation,
brokerageOperationsQueryOptions,
type OperationCategory,
operationCategories,
type OperationCategory,
useBrokerageOperations,
} from "./api/useBrokerageOperations";
export { LedgerEntryRow } from "./ui/LedgerEntryRow";
+40 -41
View File
@@ -1,10 +1,11 @@
import { formatMoney } from "@/shared/lib";
import type { BrokerageOperation } from "../api/useBrokerageOperations";
type PaymentPresentation = {
amount: string;
label: string;
tone: "positive" | "negative" | "neutral";
tone: "negative" | "neutral" | "positive";
};
function entryLabelKey(operation: BrokerageOperation): string {
@@ -15,6 +16,44 @@ function entryLabelKey(operation: BrokerageOperation): string {
return `ledger.category.${operation.category}`;
}
function isZeroDecimal(value: string): boolean {
return /^-?0$|^-?0\.0+$/.test(value);
}
function operationLabelKey(type: string): string {
if (
[
"OPERATION_TYPE_BUY",
"OPERATION_TYPE_BUY_CARD",
"OPERATION_TYPE_BUY_MARGIN",
].includes(type)
)
return "ledger.operationType.buy";
if (
[
"OPERATION_TYPE_SELL",
"OPERATION_TYPE_SELL_CARD",
"OPERATION_TYPE_SELL_MARGIN",
].includes(type)
)
return "ledger.operationType.sell";
if (
[
"OPERATION_TYPE_DIV_EXT",
"OPERATION_TYPE_DIVIDEND",
"OPERATION_TYPE_DIVIDEND_TRANSFER",
].includes(type)
)
return "ledger.operationType.dividend";
if (type.includes("TAX")) return "ledger.operationType.tax";
if (type.includes("FEE")) return "ledger.operationType.fee";
if (type === "OPERATION_TYPE_INPUT_SECURITIES")
return "ledger.operationType.input";
if (type === "OPERATION_TYPE_OUTPUT_SECURITIES")
return "ledger.operationType.output";
return type.startsWith("OPERATION_TYPE_TRANS_") ? "ledger.operationType.transfer" : "ledger.operationType.other";
}
function paymentPresentation(
operation: BrokerageOperation,
): PaymentPresentation {
@@ -46,44 +85,4 @@ function paymentPresentation(
};
}
function isZeroDecimal(value: string): boolean {
return /^-?0(?:\.0+)?$/.test(value);
}
function operationLabelKey(type: string): string {
if (
[
"OPERATION_TYPE_BUY",
"OPERATION_TYPE_BUY_CARD",
"OPERATION_TYPE_BUY_MARGIN",
].includes(type)
)
return "ledger.operationType.buy";
if (
[
"OPERATION_TYPE_SELL",
"OPERATION_TYPE_SELL_CARD",
"OPERATION_TYPE_SELL_MARGIN",
].includes(type)
)
return "ledger.operationType.sell";
if (
[
"OPERATION_TYPE_DIVIDEND",
"OPERATION_TYPE_DIV_EXT",
"OPERATION_TYPE_DIVIDEND_TRANSFER",
].includes(type)
)
return "ledger.operationType.dividend";
if (type.includes("TAX")) return "ledger.operationType.tax";
if (type.includes("FEE")) return "ledger.operationType.fee";
if (type === "OPERATION_TYPE_INPUT_SECURITIES")
return "ledger.operationType.input";
if (type === "OPERATION_TYPE_OUTPUT_SECURITIES")
return "ledger.operationType.output";
if (type.startsWith("OPERATION_TYPE_TRANS_"))
return "ledger.operationType.transfer";
return "ledger.operationType.other";
}
export { operationLabelKey, paymentPresentation };
@@ -1,15 +1,19 @@
import type { FC } from "react";
import { Anchor, Group, Text } from "@mantine/core";
import { EntryCard } from "@wealthledger/ui";
import type { FC } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import {
formatDecimal,
formatMoney,
formatSnapshotTime,
instrumentLogoUrl,
} from "@/shared/lib";
import type { BrokerageOperation } from "../api/useBrokerageOperations";
import { operationLabelKey, paymentPresentation } from "../model/display";
type LedgerEntryRowProps = {
@@ -20,55 +24,57 @@ type LedgerEntryRowProps = {
const LedgerEntryRow: FC<LedgerEntryRowProps> = (props) => {
const { operation } = props;
const { t } = useTranslation();
const instrument =
operation.ticker ?? operation.instrumentType ?? t("ledger.noInstrument");
const instrument
= operation.ticker ?? operation.instrumentType ?? t("ledger.noInstrument");
const payment = paymentPresentation(operation);
const metadata = [
t("ledger.occurredAt", { date: formatSnapshotTime(operation.occurredAt) }),
instrument,
props.withDetails ? null : operation.account?.name,
props.withDetails ? /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null : operation.account?.name,
]
.filter(Boolean)
.join(" · ");
return (
<EntryCard
title={operation.description || t(operationLabelKey(operation.type))}
metadata={metadata}
details={
props.withDetails ? (
<>
<Group gap="sm">
{operation.quantity != null ? (
<Text size="xs" c="dimmed">
{t("ledger.quantity", {
value: formatDecimal(operation.quantity),
})}
</Text>
) : null}
{operation.price ? (
<Text size="xs" c="dimmed">
{t("ledger.price", { value: formatMoney(operation.price) })}
</Text>
) : null}
</Group>
{operation.account ? (
<Anchor
component={Link}
size="sm"
to={`/accounts/${encodeURIComponent(operation.account.id)}`}
>
{operation.account.name}
</Anchor>
) : null}
</>
) : undefined
(props.withDetails)
? ((
<>
<Group gap="sm">
{operation.quantity != undefined && ((
<Text c="dimmed" size="xs">
{t("ledger.quantity", {
value: formatDecimal(operation.quantity),
})}
</Text>
))}
{!!(operation.price) && ((
<Text c="dimmed" size="xs">
{t("ledger.price", { value: formatMoney(operation.price) })}
</Text>
))}
</Group>
{!!(operation.account) && ((
<Anchor
component={Link}
size="sm"
to={`/accounts/${encodeURIComponent(operation.account.id)}`}
>
{operation.account.name}
</Anchor>
))}
</>
))
: undefined
}
initial={(instrument.trim()[0] ?? "?").toUpperCase()}
imageSrc={instrumentLogoUrl(operation.logoName)}
initial={(instrument.trim()[0] ?? "?").toUpperCase()}
metadata={metadata}
title={operation.description || t(operationLabelKey(operation.type))}
tone={payment.tone}
value={payment.amount}
valueLabel={t(payment.label)}
tone={payment.tone}
/>
);
};