feat(cards): filter operations by date range
This commit is contained in:
@@ -57,7 +57,13 @@ function useBankCategories() {
|
|||||||
|
|
||||||
function useBankOperations(
|
function useBankOperations(
|
||||||
id: string | undefined,
|
id: string | undefined,
|
||||||
query: { month: string; page: number; limit?: number | "all" },
|
query: {
|
||||||
|
month?: string;
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
page: number;
|
||||||
|
limit?: number | "all";
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const all = query.limit === "all";
|
const all = query.limit === "all";
|
||||||
const params = {
|
const params = {
|
||||||
@@ -84,6 +90,8 @@ function useBankOperations(
|
|||||||
previousQuery?.queryKey.length === 3 &&
|
previousQuery?.queryKey.length === 3 &&
|
||||||
previous?.path?.id === id &&
|
previous?.path?.id === id &&
|
||||||
previous?.query?.month === query.month &&
|
previous?.query?.month === query.month &&
|
||||||
|
previous?.query?.from === query.from &&
|
||||||
|
previous?.query?.to === query.to &&
|
||||||
previous?.query?.limit === query.limit
|
previous?.query?.limit === query.limit
|
||||||
? previousData
|
? previousData
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -105,7 +113,7 @@ function useBankOperations(
|
|||||||
if (!data.hasNext) return { ...data, items, page: 1, hasNext: false };
|
if (!data.hasNext) return { ...data, items, page: 1, hasNext: false };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled: Boolean(id && query.month),
|
enabled: Boolean(id && (query.month || (query.from && query.to))),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,13 +142,20 @@ function sortBankSummary(data: BankSummary): BankSummary {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function useBankSummary(month: string, cardId?: string) {
|
function useBankSummary(
|
||||||
|
period: string | { from: string; to: string },
|
||||||
|
cardId?: string,
|
||||||
|
) {
|
||||||
|
const query =
|
||||||
|
typeof period === "string"
|
||||||
|
? { month: period, cardId }
|
||||||
|
: { ...period, cardId };
|
||||||
return useAuthenticatedQuery({
|
return useAuthenticatedQuery({
|
||||||
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/summary", {
|
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/summary", {
|
||||||
params: { query: { month, cardId } },
|
params: { query },
|
||||||
}),
|
}),
|
||||||
select: sortBankSummary,
|
select: sortBankSummary,
|
||||||
enabled: Boolean(month),
|
enabled: Boolean(period),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ function setup(
|
|||||||
clear: () => {},
|
clear: () => {},
|
||||||
});
|
});
|
||||||
const requests: string[] = [];
|
const requests: string[] = [];
|
||||||
|
const summaryRequests: string[] = [];
|
||||||
server.use(
|
server.use(
|
||||||
http.get("*/api/v1/bank-cards/a", () =>
|
http.get("*/api/v1/bank-cards/a", () =>
|
||||||
options.missing
|
options.missing
|
||||||
@@ -82,8 +83,9 @@ function setup(
|
|||||||
hasNext: Number(new URL(request.url).searchParams.get("page")) < 3,
|
hasNext: Number(new URL(request.url).searchParams.get("page")) < 3,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
http.get("*/api/v1/bank-cards/summary", () =>
|
http.get("*/api/v1/bank-cards/summary", ({ request }) => {
|
||||||
options.summaryError
|
summaryRequests.push(new URL(request.url).search);
|
||||||
|
return options.summaryError
|
||||||
? HttpResponse.error()
|
? HttpResponse.error()
|
||||||
: HttpResponse.json({
|
: HttpResponse.json({
|
||||||
month: "2026-08",
|
month: "2026-08",
|
||||||
@@ -92,8 +94,8 @@ function setup(
|
|||||||
refunds: money("50"),
|
refunds: money("50"),
|
||||||
net: money("-50"),
|
net: money("-50"),
|
||||||
categories: [],
|
categories: [],
|
||||||
}),
|
});
|
||||||
),
|
}),
|
||||||
);
|
);
|
||||||
const client = createTestQueryClient();
|
const client = createTestQueryClient();
|
||||||
render(
|
render(
|
||||||
@@ -107,18 +109,18 @@ function setup(
|
|||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
</QueryClientProvider>,
|
</QueryClientProvider>,
|
||||||
);
|
);
|
||||||
return { requests, client };
|
return { requests, summaryRequests, client };
|
||||||
}
|
}
|
||||||
describe("CardPage", () => {
|
describe("CardPage", () => {
|
||||||
it("shares month with history, pages the journal and keeps current balance", async () => {
|
it("shares date range with history, pages the journal and keeps current balance", async () => {
|
||||||
const { requests } = setup();
|
const { requests, summaryRequests } = setup();
|
||||||
await screen.findByRole("heading", { name: "Повседневная" });
|
await screen.findByRole("heading", { name: "Повседневная" });
|
||||||
expect(
|
expect(
|
||||||
await screen.findByText("Перевод на «Запасная»"),
|
await screen.findByText("Перевод на «Запасная»"),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(screen.getByText("03.08.2026")).toBeInTheDocument();
|
expect(screen.getByText("03.08.2026")).toBeInTheDocument();
|
||||||
expect(screen.getByText("−50,01 ₽")).toBeInTheDocument();
|
expect(screen.getByText("−50,01 ₽")).toBeInTheDocument();
|
||||||
expect(screen.getByText("-50 ₽")).toBeInTheDocument();
|
expect(await screen.findByText("-50,00 ₽")).toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: "Назад" })).toBeDisabled();
|
expect(screen.getByRole("button", { name: "Назад" })).toBeDisabled();
|
||||||
expect(requests.every((value) => value.includes("limit=10"))).toBe(true);
|
expect(requests.every((value) => value.includes("limit=10"))).toBe(true);
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Далее" }));
|
fireEvent.click(screen.getByRole("button", { name: "Далее" }));
|
||||||
@@ -135,15 +137,30 @@ describe("CardPage", () => {
|
|||||||
expect(
|
expect(
|
||||||
screen.getByRole("button", { name: "Страница 3" }),
|
screen.getByRole("button", { name: "Страница 3" }),
|
||||||
).toHaveTextContent("3");
|
).toHaveTextContent("3");
|
||||||
fireEvent.click(screen.getByLabelText("Месяц"));
|
fireEvent.click(screen.getByLabelText("Период"));
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Jul" }));
|
fireEvent.click(
|
||||||
|
await screen.findByRole("button", { name: "10 August 2026" }),
|
||||||
|
);
|
||||||
|
fireEvent.click(
|
||||||
|
await screen.findByRole("button", { name: "20 August 2026" }),
|
||||||
|
);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(
|
expect(
|
||||||
requests.some(
|
requests.some(
|
||||||
(v) => v.includes("month=2026-07") && v.includes("page=1"),
|
(v) =>
|
||||||
|
v.includes("from=2026-08-10") &&
|
||||||
|
v.includes("to=2026-08-20") &&
|
||||||
|
v.includes("page=1"),
|
||||||
),
|
),
|
||||||
).toBe(true),
|
).toBe(true),
|
||||||
);
|
);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(summaryRequests.at(-1)).toContain("from=2026-08-10&to=2026-08-20"),
|
||||||
|
);
|
||||||
|
expect(screen.getByRole("link", { name: "Карта" })).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
"/cards/b?from=2026-08-10&to=2026-08-20",
|
||||||
|
);
|
||||||
expect(screen.getByText(/10\s000 ₽/)).toBeInTheDocument();
|
expect(screen.getByText(/10\s000 ₽/)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it("keeps the journal in place while the next page loads", async () => {
|
it("keeps the journal in place while the next page loads", async () => {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { MonthPickerInput } from "@mantine/dates";
|
import { DatePickerInput } from "@mantine/dates";
|
||||||
import { type FC, useLayoutEffect, useRef } from "react";
|
import { type FC, useLayoutEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import {
|
import {
|
||||||
@@ -25,6 +25,39 @@ import { EntryCard, LoadingSkeleton, StateView } from "@/shared/ui";
|
|||||||
import classes from "./CardPage.module.css";
|
import classes from "./CardPage.module.css";
|
||||||
import type { CardPageVm } from "./CardPage.vm";
|
import type { CardPageVm } from "./CardPage.vm";
|
||||||
|
|
||||||
|
type PeriodPickerProps = {
|
||||||
|
period: CardPageVm["period"];
|
||||||
|
onChange: CardPageVm["setPeriod"];
|
||||||
|
};
|
||||||
|
const PeriodPicker: FC<PeriodPickerProps> = (props) => {
|
||||||
|
const { period, onChange } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const scope = `${period.from}-${period.to}`;
|
||||||
|
const [draft, setDraft] = useState<{
|
||||||
|
scope: string;
|
||||||
|
value: [string | null, string | null];
|
||||||
|
}>();
|
||||||
|
const value: [string | null, string | null] =
|
||||||
|
draft?.scope === scope ? draft.value : [period.from, period.to];
|
||||||
|
return (
|
||||||
|
<DatePickerInput
|
||||||
|
type="range"
|
||||||
|
allowSingleDateInRange
|
||||||
|
label={t("bank.period")}
|
||||||
|
valueFormat="DD.MM.YYYY"
|
||||||
|
value={value}
|
||||||
|
onChange={(next) => {
|
||||||
|
setDraft({ scope, value: next });
|
||||||
|
onChange(next);
|
||||||
|
}}
|
||||||
|
popoverProps={{
|
||||||
|
returnFocus: true,
|
||||||
|
middlewares: { shift: { crossAxis: true } },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
type CardPageViewProps = { vm: CardPageVm };
|
type CardPageViewProps = { vm: CardPageVm };
|
||||||
const CardPageView: FC<CardPageViewProps> = (props) => {
|
const CardPageView: FC<CardPageViewProps> = (props) => {
|
||||||
const { vm } = props;
|
const { vm } = props;
|
||||||
@@ -36,7 +69,7 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
|
|||||||
page: number;
|
page: number;
|
||||||
top: number;
|
top: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const scope = `${card?.id}-${vm.month}-${vm.pageSize}`;
|
const scope = `${card?.id}-${vm.period.from}-${vm.period.to}-${vm.pageSize}`;
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const pending = pendingPage.current;
|
const pending = pendingPage.current;
|
||||||
if (!pending) return;
|
if (!pending) return;
|
||||||
@@ -143,14 +176,7 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
|
|||||||
retry={() => void vm.cards.refetch()}
|
retry={() => void vm.cards.refetch()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MonthPickerInput
|
<PeriodPicker period={vm.period} onChange={vm.setPeriod} />
|
||||||
label={t("bank.month")}
|
|
||||||
value={`${vm.month}-01`}
|
|
||||||
onChange={(value) => {
|
|
||||||
if (value) vm.setMonth(value.slice(0, 7));
|
|
||||||
}}
|
|
||||||
popoverProps={{ middlewares: { shift: { crossAxis: true } } }}
|
|
||||||
/>
|
|
||||||
<BankSummaryView
|
<BankSummaryView
|
||||||
data={vm.summary.data}
|
data={vm.summary.data}
|
||||||
loading={vm.summary.isLoading}
|
loading={vm.summary.isLoading}
|
||||||
@@ -177,7 +203,7 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<Stack
|
<Stack
|
||||||
key={`${vm.month}-${vm.pageSize}-${vm.history.data?.page ?? "loading"}`}
|
key={`${vm.period.from}-${vm.period.to}-${vm.pageSize}-${vm.history.data?.page ?? "loading"}`}
|
||||||
gap="lg"
|
gap="lg"
|
||||||
className={classes.operations}
|
className={classes.operations}
|
||||||
aria-busy={vm.history.isFetching}
|
aria-busy={vm.history.isFetching}
|
||||||
@@ -209,7 +235,7 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
|
|||||||
row.otherCard ? (
|
row.otherCard ? (
|
||||||
<Anchor
|
<Anchor
|
||||||
component={Link}
|
component={Link}
|
||||||
to={`/cards/${row.otherCard}?month=${vm.month}`}
|
to={`/cards/${row.otherCard}?from=${vm.period.from}&to=${vm.period.to}`}
|
||||||
>
|
>
|
||||||
{t("bank.card")}
|
{t("bank.card")}
|
||||||
</Anchor>
|
</Anchor>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useParams, useSearchParams } from "react-router";
|
import { useParams, useSearchParams } from "react-router";
|
||||||
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
type BankOperation,
|
type BankOperation,
|
||||||
bankMonth,
|
bankMonth,
|
||||||
@@ -13,6 +15,8 @@ import {
|
|||||||
import { apiErrorCode } from "@/shared/api";
|
import { apiErrorCode } from "@/shared/api";
|
||||||
import { formatDate, formatMoney } from "@/shared/lib";
|
import { formatDate, formatMoney } from "@/shared/lib";
|
||||||
|
|
||||||
|
const calendarDate = z.string().date();
|
||||||
|
|
||||||
const PAGE_SIZES = ["5", "10", "20", "50", "100", "all"];
|
const PAGE_SIZES = ["5", "10", "20", "50", "100", "all"];
|
||||||
|
|
||||||
function useCardPageVm() {
|
function useCardPageVm() {
|
||||||
@@ -20,6 +24,20 @@ function useCardPageVm() {
|
|||||||
const [params, setParams] = useSearchParams();
|
const [params, setParams] = useSearchParams();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const month = bankMonth(params.get("month"));
|
const month = bankMonth(params.get("month"));
|
||||||
|
const from = params.get("from");
|
||||||
|
const to = params.get("to");
|
||||||
|
const period =
|
||||||
|
from &&
|
||||||
|
to &&
|
||||||
|
calendarDate.safeParse(from).success &&
|
||||||
|
calendarDate.safeParse(to).success &&
|
||||||
|
from >= "0001-01-01" &&
|
||||||
|
from <= to
|
||||||
|
? { from, to }
|
||||||
|
: {
|
||||||
|
from: `${month}-01`,
|
||||||
|
to: dayjs(`${month}-01`).endOf("month").format("YYYY-MM-DD"),
|
||||||
|
};
|
||||||
const requestedSize = params.get("limit") ?? "10";
|
const requestedSize = params.get("limit") ?? "10";
|
||||||
const pageSize = PAGE_SIZES.includes(requestedSize) ? requestedSize : "10";
|
const pageSize = PAGE_SIZES.includes(requestedSize) ? requestedSize : "10";
|
||||||
const rawPage = Number(params.get("page") ?? 1);
|
const rawPage = Number(params.get("page") ?? 1);
|
||||||
@@ -32,11 +50,11 @@ function useCardPageVm() {
|
|||||||
const [archiveError, setArchiveError] = useState<unknown>();
|
const [archiveError, setArchiveError] = useState<unknown>();
|
||||||
const cards = useBankCards(true);
|
const cards = useBankCards(true);
|
||||||
const history = useBankOperations(cardId, {
|
const history = useBankOperations(cardId, {
|
||||||
month,
|
...period,
|
||||||
page,
|
page,
|
||||||
limit: pageSize === "all" ? "all" : Number(pageSize),
|
limit: pageSize === "all" ? "all" : Number(pageSize),
|
||||||
});
|
});
|
||||||
const summary = useBankSummary(month, cardId);
|
const summary = useBankSummary(period, cardId);
|
||||||
const [editingCard, setEditingCard] = useState(false);
|
const [editingCard, setEditingCard] = useState(false);
|
||||||
const [operation, setOperation] = useState<BankOperation | "new" | null>(
|
const [operation, setOperation] = useState<BankOperation | "new" | null>(
|
||||||
null,
|
null,
|
||||||
@@ -94,7 +112,7 @@ function useCardPageVm() {
|
|||||||
},
|
},
|
||||||
history,
|
history,
|
||||||
summary,
|
summary,
|
||||||
month,
|
period,
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
totalPages:
|
totalPages:
|
||||||
@@ -109,15 +127,16 @@ function useCardPageVm() {
|
|||||||
deleting,
|
deleting,
|
||||||
setDeleting,
|
setDeleting,
|
||||||
notFound: apiErrorCode(card.error) === "BANK_NOT_FOUND",
|
notFound: apiErrorCode(card.error) === "BANK_NOT_FOUND",
|
||||||
setMonth: (value: string) => {
|
setPeriod: (value: [string | null, string | null]) => {
|
||||||
if (value) setParams({ month: bankMonth(value), limit: pageSize });
|
if (value[0] && value[1])
|
||||||
|
setParams({ from: value[0], to: value[1], limit: pageSize });
|
||||||
},
|
},
|
||||||
setPageSize: (value: string | null) => {
|
setPageSize: (value: string | null) => {
|
||||||
if (value && PAGE_SIZES.includes(value))
|
if (value && PAGE_SIZES.includes(value))
|
||||||
setParams({ month, limit: value });
|
setParams({ ...period, limit: value });
|
||||||
},
|
},
|
||||||
setPage: (value: number) =>
|
setPage: (value: number) =>
|
||||||
setParams({ month, page: String(value), limit: pageSize }),
|
setParams({ ...period, page: String(value), limit: pageSize }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
type CardPageVm = ReturnType<typeof useCardPageVm>;
|
type CardPageVm = ReturnType<typeof useCardPageVm>;
|
||||||
|
|||||||
Vendored
+19
-5
@@ -764,7 +764,11 @@ export interface components {
|
|||||||
net: components["schemas"]["MoneyDto"];
|
net: components["schemas"]["MoneyDto"];
|
||||||
};
|
};
|
||||||
BankSummaryDto: {
|
BankSummaryDto: {
|
||||||
month: string;
|
month?: string;
|
||||||
|
/** Format: date */
|
||||||
|
from?: string;
|
||||||
|
/** Format: date */
|
||||||
|
to?: string;
|
||||||
cardId: string | null;
|
cardId: string | null;
|
||||||
expenses: components["schemas"]["MoneyDto"];
|
expenses: components["schemas"]["MoneyDto"];
|
||||||
refunds: components["schemas"]["MoneyDto"];
|
refunds: components["schemas"]["MoneyDto"];
|
||||||
@@ -2109,8 +2113,13 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
BankCardsController_summary: {
|
BankCardsController_summary: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query: {
|
query?: {
|
||||||
month: string;
|
/** @description Use month or both from and to, exclusively. */
|
||||||
|
month?: string;
|
||||||
|
/** @description Inclusive start date; requires to. */
|
||||||
|
from?: string;
|
||||||
|
/** @description Inclusive end date; requires from. */
|
||||||
|
to?: string;
|
||||||
cardId?: string;
|
cardId?: string;
|
||||||
};
|
};
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -2536,10 +2545,15 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
BankCardsController_history: {
|
BankCardsController_history: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query: {
|
query?: {
|
||||||
page?: number;
|
page?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
month: string;
|
/** @description Use month or both from and to, exclusively. */
|
||||||
|
month?: string;
|
||||||
|
/** @description Inclusive start date; requires to. */
|
||||||
|
from?: string;
|
||||||
|
/** @description Inclusive end date; requires from. */
|
||||||
|
to?: string;
|
||||||
};
|
};
|
||||||
header?: never;
|
header?: never;
|
||||||
path: {
|
path: {
|
||||||
|
|||||||
@@ -914,13 +914,34 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "month",
|
"name": "month",
|
||||||
"required": true,
|
"required": false,
|
||||||
"in": "query",
|
"in": "query",
|
||||||
|
"description": "Use month or both from and to, exclusively.",
|
||||||
"schema": {
|
"schema": {
|
||||||
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$",
|
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "from",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Inclusive start date; requires to.",
|
||||||
|
"schema": {
|
||||||
|
"format": "date",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Inclusive end date; requires from.",
|
||||||
|
"schema": {
|
||||||
|
"format": "date",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "cardId",
|
"name": "cardId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -1487,12 +1508,33 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "month",
|
"name": "month",
|
||||||
"required": true,
|
"required": false,
|
||||||
"in": "query",
|
"in": "query",
|
||||||
|
"description": "Use month or both from and to, exclusively.",
|
||||||
"schema": {
|
"schema": {
|
||||||
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$",
|
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "from",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Inclusive start date; requires to.",
|
||||||
|
"schema": {
|
||||||
|
"format": "date",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Inclusive end date; requires from.",
|
||||||
|
"schema": {
|
||||||
|
"format": "date",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -4971,6 +5013,14 @@
|
|||||||
"month": {
|
"month": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"from": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
"cardId": {
|
"cardId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
@@ -4992,7 +5042,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"month",
|
|
||||||
"cardId",
|
"cardId",
|
||||||
"expenses",
|
"expenses",
|
||||||
"refunds",
|
"refunds",
|
||||||
|
|||||||
@@ -55,12 +55,13 @@ const ru = {
|
|||||||
summary: "Расходы",
|
summary: "Расходы",
|
||||||
allSummary: "Расходы по всем картам",
|
allSummary: "Расходы по всем картам",
|
||||||
month: "Месяц",
|
month: "Месяц",
|
||||||
|
period: "Период",
|
||||||
expenses: "Потрачено",
|
expenses: "Потрачено",
|
||||||
refunds: "Возвращено",
|
refunds: "Возвращено",
|
||||||
net: "После возвратов",
|
net: "После возвратов",
|
||||||
summaryHint:
|
summaryHint:
|
||||||
"Возвраты учитываются в месяце получения. Итог может быть отрицательным.",
|
"Возвраты учитываются по дате получения. Итог может быть отрицательным.",
|
||||||
noExpenses: "За этот месяц операций по расходам нет.",
|
noExpenses: "За этот период операций по расходам нет.",
|
||||||
categories: "Категории",
|
categories: "Категории",
|
||||||
category: "Категория",
|
category: "Категория",
|
||||||
categoryName: "Название категории",
|
categoryName: "Название категории",
|
||||||
@@ -70,7 +71,7 @@ const ru = {
|
|||||||
notFound: "Карта не найдена",
|
notFound: "Карта не найдена",
|
||||||
back: "К картам",
|
back: "К картам",
|
||||||
operations: "Операции",
|
operations: "Операции",
|
||||||
noOperations: "Операций за месяц нет.",
|
noOperations: "Операций за период нет.",
|
||||||
addOperation: "Добавить операцию",
|
addOperation: "Добавить операцию",
|
||||||
editOperation: "Изменить операцию",
|
editOperation: "Изменить операцию",
|
||||||
kind: "Вид операции",
|
kind: "Вид операции",
|
||||||
|
|||||||
Reference in New Issue
Block a user