feat: polish bank operation cards and dates

This commit is contained in:
2026-09-12 19:56:12 +03:00
parent dedfec10d1
commit 6b310db0b8
20 changed files with 382 additions and 26 deletions
+1
View File
@@ -111,6 +111,7 @@ describe("CardPage", () => {
expect(
await screen.findByText("Перевод на «Запасная»"),
).toBeInTheDocument();
expect(screen.getByText("03.08.2026")).toBeInTheDocument();
expect(screen.getByText("−50,01 ₽")).toBeInTheDocument();
expect(screen.getByText("-50 ₽")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Далее" }));
+12 -1
View File
@@ -14,7 +14,11 @@ import { MonthPickerInput } from "@mantine/dates";
import type { FC } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import { BankFeedback, BankSummaryView } from "@/entities/bank-card";
import {
BankFeedback,
BankOperationIcon,
BankSummaryView,
} from "@/entities/bank-card";
import { formatMoney } from "@/shared/lib";
import { EntryCard, LoadingSkeleton, StateView } from "@/shared/ui";
import classes from "./CardPage.module.css";
@@ -148,7 +152,14 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
<EntryCard
title={row.title}
metadata={row.metadata}
color={row.color}
initial={row.title[0]}
icon={
<BankOperationIcon
kind={row.item.kind}
categoryName={row.item.categoryName}
/>
}
value={row.value}
valueLabel={t(`bank.kinds.${row.item.kind}`)}
tone={row.tone}
+3 -2
View File
@@ -11,7 +11,7 @@ import {
useBankSummary,
} from "@/entities/bank-card";
import { apiErrorCode } from "@/shared/api";
import { formatMoney } from "@/shared/lib";
import { formatDate, formatMoney } from "@/shared/lib";
function useCardPageVm() {
const { cardId } = useParams();
@@ -37,13 +37,14 @@ function useCardPageVm() {
(item.kind === "transfer" && item.cardId === cardId);
return {
item,
color: outgoing ? "red" : "green",
title:
item.kind === "transfer"
? t(outgoing ? "bank.transferTo" : "bank.transferFrom", {
name: outgoing ? item.targetCardName : item.cardName,
})
: t(`bank.kinds.${item.kind}`),
metadata: [item.date, item.categoryName, item.description]
metadata: [formatDate(item.date), item.categoryName, item.description]
.filter(Boolean)
.join(" · "),
value: `${outgoing ? "−" : "+"}${formatMoney(item.amount)}`,
+2 -2
View File
@@ -8,7 +8,7 @@ import {
useBankMutations,
useBankOperation,
} from "@/entities/bank-card";
import { formatMoney } from "@/shared/lib";
import { formatDate, formatMoney } from "@/shared/lib";
type DeleteOperationProps = { operation: BankOperation; onClose: () => void };
const DeleteOperation: FC<DeleteOperationProps> = (props) => {
@@ -64,7 +64,7 @@ const DeleteOperation: FC<DeleteOperationProps> = (props) => {
<>
<Text>
{t("bank.versionHint", {
details: `${latest.date} · ${formatMoney(latest.amount)} · ${latest.description ?? ""}`,
details: `${formatDate(latest.date)} · ${formatMoney(latest.amount)} · ${latest.description ?? ""}`,
})}
</Text>
<Button
+9 -3
View File
@@ -76,6 +76,11 @@ describe("OperationForm", () => {
"Остальное",
),
);
fireEvent.change(screen.getByLabelText(/Дата операции/), {
target: { value: "07.08.2026" },
});
fireEvent.blur(screen.getByLabelText(/Дата операции/));
expect(screen.getByLabelText(/Дата операции/)).toHaveValue("07.08.2026");
let body: unknown;
server.use(
http.post("*/api/v1/bank-cards/operations", async ({ request }) => {
@@ -90,6 +95,7 @@ describe("OperationForm", () => {
await waitFor(() => expect(close).toHaveBeenCalled());
expect(body).toMatchObject({
categoryId: "default",
date: "2026-08-07",
amount: money("92233720368547758.07"),
});
});
@@ -101,7 +107,7 @@ describe("OperationForm", () => {
target: { value: "0" },
});
fireEvent.change(screen.getByLabelText(/Дата операции/), {
target: { value: "2026-07-31" },
target: { value: "31.07.2026" },
});
const bodies: unknown[] = [];
server.use(
@@ -122,7 +128,7 @@ describe("OperationForm", () => {
target: { value: "150,01" },
});
fireEvent.change(screen.getByLabelText(/Дата операции/), {
target: { value: "2026-08-02" },
target: { value: "02.08.2026" },
});
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
await screen.findByText(/Не удалось подтвердить сохранение/);
@@ -229,7 +235,7 @@ describe("OperationForm", () => {
target: { value: "500" },
});
fireEvent.change(screen.getByLabelText(/Дата операции/), {
target: { value: "2026-08-01" },
target: { value: "01.08.2026" },
});
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
await screen.findByText(/Не удалось подтвердить сохранение/);
+4 -4
View File
@@ -21,7 +21,7 @@ import {
useBankOperation,
} from "@/entities/bank-card";
import { validationMessage } from "@/shared/i18n";
import { formatMoney } from "@/shared/lib";
import { formatDate, formatMoney } from "@/shared/lib";
import classes from "./OperationForm.module.css";
import type { OperationValues } from "./OperationForm.types";
import { OperationFields, RefundFields } from "./OperationForm.view";
@@ -249,7 +249,7 @@ const OperationForm: FC<OperationFormProps> = (props) => {
expenses.data?.items.map((item) => ({
value: item.id,
label: t("bank.expenseChoice", {
date: item.date,
date: formatDate(item.date),
name: item.description || item.categoryName,
amount: formatMoney(item.amount),
available: formatMoney(item.availableRefund),
@@ -261,7 +261,7 @@ const OperationForm: FC<OperationFormProps> = (props) => {
? {
value: sourceQuery.data.id,
label: t("bank.expenseChoice", {
date: sourceQuery.data.date,
date: formatDate(sourceQuery.data.date),
name:
sourceQuery.data.description ||
sourceQuery.data.categoryName,
@@ -366,7 +366,7 @@ const OperationForm: FC<OperationFormProps> = (props) => {
<>
<Text>
{t("bank.versionHint", {
details: `${latest.date} · ${formatMoney(latest.amount)} · ${latest.description ?? ""}`,
details: `${formatDate(latest.date)} · ${formatMoney(latest.amount)} · ${latest.description ?? ""}`,
})}
</Text>
<Button
+9 -4
View File
@@ -5,6 +5,7 @@ import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
type BankOperation,
bankDateInputValue,
bankDateSchema,
bankToday,
} from "@/entities/bank-card";
@@ -97,18 +98,22 @@ const OperationFields: FC<OperationFieldsProps> = (props) => {
{...field}
label={t("bank.date")}
required
valueFormat="YYYY-MM-DD"
valueFormat="DD.MM.YYYY"
value={
bankDateSchema.safeParse(field.value).success ? field.value : null
}
dateParser={(value) =>
bankDateSchema.safeParse(value).success ? value : null
bankDateSchema.safeParse(bankDateInputValue(value)).success
? bankDateInputValue(value)
: null
}
fixOnBlur={false}
onInput={(event) => field.onChange(event.currentTarget.value)}
onInput={(event) =>
field.onChange(bankDateInputValue(event.currentTarget.value))
}
onChange={(value) => field.onChange(value ?? "")}
onBlur={(event) => {
field.onChange(event.currentTarget.value);
field.onChange(bankDateInputValue(event.currentTarget.value));
field.onBlur();
}}
maxDate={bankToday()}