feat: polish bank operation cards and dates
This commit is contained in:
@@ -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: "Далее" }));
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}`,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(/Не удалось подтвердить сохранение/);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()}
|
||||
|
||||
Reference in New Issue
Block a user