fix(pwa): штрафы не прячутся за вкладкой «Не погашены»
Водитель, оплативший штраф сам, видел пустой экран: список открывался на «Не погашены», а там у него ноль (Морозов, штраф от 16.08 — оплачен им, парк его не выставлял). Выглядело как «приложение потеряло мои штрафы». Теперь вкладка выбирается по данным: есть непогашенные — открываем их, нет — открываем «Все». Заодно один запрос вместо двух: список короткий, фильтруем на месте, и переключение вкладок стало мгновенным. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,10 +14,8 @@ const FINE = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
vi.mock("@/api/driver", () => ({
|
vi.mock("@/api/driver", () => ({
|
||||||
getFines: vi.fn(async (status: string) => ({
|
getFines: vi.fn(async () => ({
|
||||||
items: status === "open"
|
items: [FINE, { ...FINE, id: 13, settled: "paid" as const, amount: 500 }],
|
||||||
? [FINE]
|
|
||||||
: [FINE, { ...FINE, id: 13, settled: "paid" as const, amount: 500 }],
|
|
||||||
open_count: 1, debt: -5156,
|
open_count: 1, debt: -5156,
|
||||||
})),
|
})),
|
||||||
getFine: vi.fn(async () => ({
|
getFine: vi.fn(async () => ({
|
||||||
@@ -50,12 +48,22 @@ function wrap(path = "/fines") {
|
|||||||
describe("список штрафов", () => {
|
describe("список штрафов", () => {
|
||||||
beforeEach(() => vi.clearAllMocks());
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("нет непогашенных — открываем «Все», а не пустой экран", async () => {
|
||||||
|
// Водитель оплатил штраф сам: долг нулевой, но штрафы у него есть. Раньше
|
||||||
|
// список открывался на «Не погашены» и выглядел пустым (Морозов, 18.08.2026).
|
||||||
|
vi.mocked(driver.getFines).mockResolvedValueOnce({
|
||||||
|
items: [{ ...FINE, settled: "paid" as const }], open_count: 0, debt: 0,
|
||||||
|
} as never);
|
||||||
|
wrap();
|
||||||
|
expect(await screen.findByText("погашен")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/Непогашенных штрафов нет/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("долг берётся из баланса 1С, а не из сумм постановлений", async () => {
|
it("долг берётся из баланса 1С, а не из сумм постановлений", async () => {
|
||||||
wrap();
|
wrap();
|
||||||
// Парк гасит ГИБДД авансом: если считать по «оплачен/не оплачен» из
|
// Парк гасит ГИБДД авансом: если считать по «оплачен/не оплачен» из
|
||||||
// постановлений, экран покажет «долгов нет» при −5 156 ₽ на главном.
|
// постановлений, экран покажет «долгов нет» при −5 156 ₽ на главном.
|
||||||
expect(await screen.findByText(/Долг по штрафам −5 156 ₽ · не погашено: 1/)).toBeInTheDocument();
|
expect(await screen.findByText(/Долг по штрафам −5 156 ₽ · не погашено: 1/)).toBeInTheDocument();
|
||||||
expect(vi.mocked(driver.getFines).mock.calls[0][0]).toBe("open");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("закрытый в ГИБДД штраф остаётся непогашенным для водителя", async () => {
|
it("закрытый в ГИБДД штраф остаётся непогашенным для водителя", async () => {
|
||||||
|
|||||||
@@ -22,12 +22,18 @@ import { Spinner } from "@/components/Spinner";
|
|||||||
*/
|
*/
|
||||||
export function FinesPage() {
|
export function FinesPage() {
|
||||||
const nav = useNavigate();
|
const nav = useNavigate();
|
||||||
const [tab, setTab] = useState<"open" | "all">("open");
|
// Запрашиваем ВСЕ и фильтруем на месте: список короткий, зато вкладка
|
||||||
|
// переключается мгновенно и, главное, известно, есть ли непогашенные ещё до
|
||||||
|
// того, как выбрана вкладка.
|
||||||
const { data, isLoading, isError, refetch } = useQuery({
|
const { data, isLoading, isError, refetch } = useQuery({
|
||||||
queryKey: ["fines", tab],
|
queryKey: ["fines"],
|
||||||
queryFn: () => getFines(tab),
|
queryFn: () => getFines("all"),
|
||||||
});
|
});
|
||||||
const items = data?.items ?? [];
|
const [tab, setTab] = useState<"open" | "all" | null>(null);
|
||||||
|
// Долгов нет — открываем «Все». Иначе водитель с оплаченными штрафами видел
|
||||||
|
// пустой экран и решал, что приложение их потеряло (Морозов, 18.08.2026).
|
||||||
|
const active = tab ?? (data && data.open_count > 0 ? "open" : "all");
|
||||||
|
const items = (data?.items ?? []).filter((f) => active === "all" || f.settled === "owed");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
@@ -47,7 +53,7 @@ export function FinesPage() {
|
|||||||
key={key}
|
key={key}
|
||||||
onClick={() => setTab(key)}
|
onClick={() => setTab(key)}
|
||||||
className={`px-3 py-2 rounded-xl2 text-xs border ${
|
className={`px-3 py-2 rounded-xl2 text-xs border ${
|
||||||
tab === key ? "border-line bg-surface font-semibold" : "border-line text-muted"
|
active === key ? "border-ink bg-surface font-semibold" : "border-line text-muted"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
@@ -64,7 +70,7 @@ export function FinesPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : items.length === 0 ? (
|
||||||
<p className="text-muted text-sm text-center py-10">
|
<p className="text-muted text-sm text-center py-10">
|
||||||
{tab === "open" ? "Непогашенных штрафов нет" : "Штрафов не найдено"}
|
{active === "open" ? "Непогашенных штрафов нет" : "Штрафов не найдено"}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user