feat(driver-pwa): balance screen (hero total + accounts)

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-06-18 19:30:59 +10:00
co-authored by claude-flow
parent 6f76809e9e
commit d769845d88
2 changed files with 90 additions and 1 deletions
@@ -0,0 +1,35 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
vi.mock("@/api/driver", () => ({
getBalance: vi.fn(async () => ({
accounts: [
{ bucket: "Долг аренда", balance: -3200 },
{ bucket: "Депозит", balance: 5000 },
],
})),
}));
import { BalancePage } from "./BalancePage";
function wrap() {
const qc = new QueryClient();
return render(
<QueryClientProvider client={qc}>
<MemoryRouter><BalancePage /></MemoryRouter>
</QueryClientProvider>
);
}
describe("BalancePage", () => {
it("shows hero total (sum of negatives) and account rows", async () => {
wrap();
expect(await screen.findByText("Долг аренда")).toBeInTheDocument();
expect(screen.getByText("Депозит")).toBeInTheDocument();
// Appears in both the hero total and the single debt account row.
expect(screen.getAllByText("3 200 ₽").length).toBeGreaterThan(0);
expect(screen.getByRole("button", { name: /Пополнить/i })).toBeInTheDocument();
});
});
+55 -1
View File
@@ -1 +1,55 @@
export function BalancePage() { return null; }
import { useQuery } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { History, LogOut } from "lucide-react";
import { getBalance } from "@/api/driver";
import { formatMoney } from "@/lib/format";
import { useAuth } from "@/store/auth";
import { AppHeader } from "@/components/AppHeader";
import { Spinner } from "@/components/Spinner";
export function BalancePage() {
const nav = useNavigate();
const clear = useAuth((s) => s.clear);
const { data, isLoading } = useQuery({ queryKey: ["balance"], queryFn: getBalance });
const accounts = data?.accounts ?? [];
const totalDebt = accounts.filter((a) => a.balance < 0).reduce((s, a) => s + a.balance, 0);
return (
<div>
<AppHeader initials="" />
<div className="card p-4 mb-4 text-center">
<p className="text-muted text-xs mb-1">Общий долг</p>
<p className={`text-2xl font-extrabold ${totalDebt < 0 ? "text-neg" : ""}`}>{formatMoney(totalDebt)}</p>
</div>
{isLoading ? (
<div className="flex justify-center py-10"><Spinner /></div>
) : (
<div className="mb-4">
{accounts.map((a) => (
<button
key={a.bucket}
onClick={() => nav("/topup", { state: { bucket: a.bucket } })}
className="w-full flex justify-between items-center py-3 border-b border-line last:border-0 text-left"
>
<span className="text-sm">{a.bucket}</span>
<b className={a.balance < 0 ? "text-neg" : "text-pos"}>{formatMoney(a.balance)}</b>
</button>
))}
</div>
)}
<button className="btn-primary" onClick={() => nav("/topup")}>Пополнить</button>
<div className="flex justify-center gap-6 mt-6 text-muted text-xs">
<button className="flex items-center gap-1.5" onClick={() => nav("/history")}>
<History size={15} /> История
</button>
<button className="flex items-center gap-1.5" onClick={() => { clear(); nav("/login"); }}>
<LogOut size={15} /> Выйти
</button>
</div>
</div>
);
}