From 6becdd036c2abfe39a78ae1629a09b819a7c1a63 Mon Sep 17 00:00:00 2001
From: vladtechno
Date: Tue, 18 Aug 2026 17:02:13 +1000
Subject: [PATCH] =?UTF-8?q?feat(pwa):=20=D0=B0=D1=80=D0=B5=D0=BD=D0=B4?=
=?UTF-8?q?=D0=B0=20=D0=B8=20=D1=88=D1=82=D1=80=D0=B0=D1=84=D1=8B=20=D0=B2?=
=?UTF-8?q?=D0=B8=D0=B4=D0=BD=D1=8B=20=D0=BF=D1=80=D1=8F=D0=BC=D0=BE=20?=
=?UTF-8?q?=D0=BD=D0=B0=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE=D0=BC=20?=
=?UTF-8?q?=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Две статьи, которые водитель проверяет каждый день: за них он платит и из-за них
ему звонят. Стоят под итогом, нажатие ведёт сразу к оплате этой статьи — чаще
всего человек за тем и открыл приложение, а заход в шторку это лишний шаг.
Остальные статьи (депозит, повреждения, прочее) на экран не выносим: они редкие,
им хватает шторки, иначе экран снова превращается в столбик из шести строк и
выписка уезжает за нижний край телефона.
Co-Authored-By: Claude Opus 5
---
.../frontend/src/pages/BalancePage.test.tsx | 42 ++++++++++++++-----
driver-pwa/frontend/src/pages/BalancePage.tsx | 29 ++++++++++++-
2 files changed, 60 insertions(+), 11 deletions(-)
diff --git a/driver-pwa/frontend/src/pages/BalancePage.test.tsx b/driver-pwa/frontend/src/pages/BalancePage.test.tsx
index b6b69b7..cbc2685 100644
--- a/driver-pwa/frontend/src/pages/BalancePage.test.tsx
+++ b/driver-pwa/frontend/src/pages/BalancePage.test.tsx
@@ -1,7 +1,7 @@
import { describe, it, expect, vi } from "vitest";
-import { render, screen } from "@testing-library/react";
+import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import { MemoryRouter } from "react-router-dom";
+import { MemoryRouter, Route, Routes } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
// Ответ как у настоящего водителя: статьи 1С, итог отдельным числом.
@@ -34,7 +34,12 @@ function wrap() {
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return render(
-
+
+
+ } />
+ экран пополнения
} />
+
+
);
}
@@ -63,22 +68,39 @@ describe("главный экран", () => {
expect(calls[calls.length - 1][2]).toBe("fines");
});
- it("категории живут в шторке «Пополнить», а не на экране", async () => {
+ it("аренда и штрафы видны сразу, остальные статьи — нет", async () => {
wrap();
await screen.findByText("−8 976,25 ₽");
- expect(screen.queryByText("Долг аренда")).not.toBeInTheDocument();
-
- await userEvent.click(screen.getByRole("button", { name: /Пополнить/ }));
- expect(await screen.findByRole("dialog", { name: "Куда пополнить" })).toBeInTheDocument();
+ // Эти две водитель проверяет каждый день — они на экране.
expect(screen.getByText("Долг аренда")).toBeInTheDocument();
- // «Страховка» платежом не закрывается — в списке выбора ей делать нечего.
+ expect(screen.getByText("Долг по штрафам")).toBeInTheDocument();
+ expect(screen.getByText("−3 050 ₽")).toBeInTheDocument();
+ // Редкие статьи на экран не лезут: для них есть шторка.
expect(screen.queryByText("Страховка")).not.toBeInTheDocument();
});
+ it("нажатие на строку долга ведёт сразу к оплате этой статьи", async () => {
+ wrap();
+ await userEvent.click(await screen.findByRole("button", { name: /Долг по штрафам/ }));
+ expect(await screen.findByText("экран пополнения")).toBeInTheDocument();
+ });
+
+ it("категории для выбора живут в шторке «Пополнить»", async () => {
+ wrap();
+ await screen.findByText("−8 976,25 ₽");
+ await userEvent.click(screen.getByRole("button", { name: /^Пополнить$/ }));
+ const sheet = await screen.findByRole("dialog", { name: "Куда пополнить" });
+ const inSheet = within(sheet);
+ expect(inSheet.getByText("Долг аренда")).toBeInTheDocument();
+ expect(inSheet.getByText("Долг по штрафам")).toBeInTheDocument();
+ // «Страховка» платежом не закрывается — в списке выбора ей делать нечего.
+ expect(inSheet.queryByText("Страховка")).not.toBeInTheDocument();
+ });
+
it("шторка закрывается тапом мимо", async () => {
wrap();
await screen.findByText("−8 976,25 ₽");
- await userEvent.click(screen.getByRole("button", { name: /Пополнить/ }));
+ await userEvent.click(screen.getByRole("button", { name: /^Пополнить$/ }));
await userEvent.click(await screen.findByRole("button", { name: "Закрыть" }));
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
diff --git a/driver-pwa/frontend/src/pages/BalancePage.tsx b/driver-pwa/frontend/src/pages/BalancePage.tsx
index 5e62e94..b8b8c31 100644
--- a/driver-pwa/frontend/src/pages/BalancePage.tsx
+++ b/driver-pwa/frontend/src/pages/BalancePage.tsx
@@ -10,6 +10,11 @@ import { Sheet } from "@/components/Sheet";
import { Spinner } from "@/components/Spinner";
import { Statement } from "@/components/Statement";
+// Две статьи, которые водитель проверяет каждый день: за них он платит и из-за
+// них ему звонят. Остальные (депозит, повреждения, прочее) — редкие, им хватает
+// шторки «Пополнить», иначе экран снова превращается в столбик из шести строк.
+const MAIN_BUCKETS = ["Долг аренда", "Долг по штрафам"];
+
/**
* Главный экран из двух половин: сверху деньги, снизу — за что они списаны.
*
@@ -32,6 +37,9 @@ export function BalancePage() {
const pending = data?.pending_topup ?? 0;
const noData = !isLoading && !isError && accounts.length === 0;
const payable = accounts.filter((a) => a.payable);
+ const main = MAIN_BUCKETS.map((b) => accounts.find((a) => a.bucket === b)).filter(
+ (a): a is NonNullable => a != null,
+ );
function pick(bucket?: string) {
setSheet(false);
@@ -54,10 +62,29 @@ export function BalancePage() {
{formatMoney(total)}
)}
+ {main.length > 0 && (
+
+ {main.map((a) => (
+ // Нажатие ведёт сразу к оплате этой статьи: чаще всего человек за
+ // тем и открыл приложение, а лишний заход в шторку — лишний шаг.
+
+ ))}
+
+ )}
{pending > 0 && (
// Платёж принят нами, но 1С его ещё не провела. В сумму не входит: сложить
// оба источника значило бы показать пополнение дважды после проведения.
-