Дата в выписке и в списке штрафов встала первой строкой чернильным цветом: в этих списках ищут «что было 12-го», а бледная подпись под заголовком для этого не годилась. Строка штрафа в выписке стала кнопкой в карточку — там фотография и подробности. Остальные строки не притворяются ссылками. Раздел «Мои пополнения» удалён вместе с кнопкой: он показывал только наши платежи и у большинства водителей был пуст (1С проводит пополнения сама), а те же операции видны в выписке фильтром «Пополнения». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
4.4 KiB
TypeScript
100 lines
4.4 KiB
TypeScript
import { useState } from "react";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import { useNavigate } from "react-router-dom";
|
||
import { ChevronRight, Image as ImageIcon } from "lucide-react";
|
||
import { getFines } from "@/api/driver";
|
||
import { fineStatusText, isOwed } from "@/lib/fineStatus";
|
||
import { formatDate, formatMoney } from "@/lib/format";
|
||
import { OperationIcon } from "@/components/operationIcon";
|
||
import { Spinner } from "@/components/Spinner";
|
||
|
||
/**
|
||
* Штрафы водителя списком.
|
||
*
|
||
* Строка «Долг по штрафам −5 156 ₽» на балансе не отвечает на вопрос «за что»,
|
||
* и водитель идёт выяснять звонком. Здесь он видит каждое постановление, а в
|
||
* карточке — фотографию с камеры.
|
||
*
|
||
* Долг в шапке берём из баланса 1С, а не суммой строк: постановление парк гасит
|
||
* авансом, и по бумагам ГИБДД оно закрыто в тот же день — долг перед парком от
|
||
* этого никуда не девается. Список без этой оговорки показывал бы «неоплаченных
|
||
* нет» при −5 156 ₽ на главном экране.
|
||
*/
|
||
export function FinesPage() {
|
||
const nav = useNavigate();
|
||
const [tab, setTab] = useState<"open" | "all">("open");
|
||
const { data, isLoading, isError, refetch } = useQuery({
|
||
queryKey: ["fines", tab],
|
||
queryFn: () => getFines(tab),
|
||
});
|
||
const items = data?.items ?? [];
|
||
|
||
return (
|
||
<div className="pt-2">
|
||
<button className="text-muted text-xs mb-3" onClick={() => nav("/")}>‹ Назад</button>
|
||
<h1 className="text-lg font-extrabold mb-1">Штрафы</h1>
|
||
<p className="text-muted text-xs mb-4">
|
||
{data
|
||
? data.debt !== 0 || data.open_count > 0
|
||
? `Долг по штрафам ${formatMoney(data.debt)} · не погашено: ${data.open_count}`
|
||
: "Долгов по штрафам нет"
|
||
: " "}
|
||
</p>
|
||
|
||
<div className="flex gap-1.5 mb-3">
|
||
{([["open", "Не погашены"], ["all", "Все"]] as const).map(([key, label]) => (
|
||
<button
|
||
key={key}
|
||
onClick={() => setTab(key)}
|
||
className={`px-3 py-2 rounded-xl2 text-xs border ${
|
||
tab === key ? "border-line bg-surface font-semibold" : "border-line text-muted"
|
||
}`}
|
||
>
|
||
{label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{isLoading ? (
|
||
<div className="flex justify-center py-10"><Spinner /></div>
|
||
) : isError ? (
|
||
<div className="text-center py-8">
|
||
<p className="text-muted text-sm mb-3">Не удалось загрузить штрафы</p>
|
||
<button className="btn-ghost" onClick={() => refetch()}>Повторить</button>
|
||
</div>
|
||
) : items.length === 0 ? (
|
||
<p className="text-muted text-sm text-center py-10">
|
||
{tab === "open" ? "Непогашенных штрафов нет" : "Штрафов не найдено"}
|
||
</p>
|
||
) : (
|
||
<div>
|
||
{items.map((f) => (
|
||
<button
|
||
key={f.id}
|
||
onClick={() => nav(`/fines/${f.id}`)}
|
||
className="w-full flex items-center gap-3 py-3 border-b border-line last:border-0 text-left"
|
||
>
|
||
<OperationIcon title="Штраф" />
|
||
<span className="min-w-0 flex-1">
|
||
<span className="flex items-center gap-1.5 text-xs font-semibold">
|
||
{formatDate(f.date)}
|
||
{f.has_photo && <ImageIcon size={12} strokeWidth={1.5} className="text-muted" />}
|
||
</span>
|
||
<span className="block text-sm truncate">{f.article}</span>
|
||
{f.car_number && (
|
||
<span className="block text-muted text-xs">{f.car_number}</span>
|
||
)}
|
||
</span>
|
||
<span className="shrink-0 text-right">
|
||
<b className={isOwed(f) ? "text-neg" : "text-muted"}>{formatMoney(f.amount)}</b>
|
||
<span className="block text-muted text-[11px]">{fineStatusText(f)}</span>
|
||
</span>
|
||
<ChevronRight size={16} className="text-muted shrink-0" />
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|