feat(pwa): главный экран из двух половин — деньги сверху, выписка снизу
По образцу приложения вендора: баланс в парке и «зачисляется», под ними крупная кнопка «Пополнить», ниже — период с календарями, фильтры и список операций (аренда, пени, штрафы, пополнения). Разбивка по статьям переехала в шторку «Пополнить». Она нужна ровно в момент выбора, куда платить; всё остальное время это шесть строк, отодвигающих выписку за нижний край телефона. В шторке только те статьи, в которые платёж проводится. Экран суммы, придя из шторки, показывает выбранную категорию строкой, а не рядом чипов: второй выбор того же самого сбивает с толку. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getStatement, type StatementKind } from "@/api/driver";
|
||||
import { formatDate, formatMoney } from "@/lib/format";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
|
||||
const FILTERS: { key: StatementKind; label: string }[] = [
|
||||
{ key: "all", label: "Все операции" },
|
||||
{ key: "charges", label: "Начисления" },
|
||||
{ key: "payments", label: "Пополнения" },
|
||||
{ key: "fines", label: "Штрафы" },
|
||||
];
|
||||
|
||||
/**
|
||||
* Выписка по счёту за период — нижняя половина главного экрана.
|
||||
*
|
||||
* Это ответ на вопрос «за что списано», который иначе задают звонком в парк.
|
||||
* Строки те же, по которым 1С считает долг: аренда, пени, штрафы, пополнения.
|
||||
*/
|
||||
export function Statement({ from, to, kind, onFrom, onTo, onKind }: {
|
||||
from: string;
|
||||
to: string;
|
||||
kind: StatementKind;
|
||||
onFrom: (v: string) => void;
|
||||
onTo: (v: string) => void;
|
||||
onKind: (k: StatementKind) => void;
|
||||
}) {
|
||||
const { data, isLoading, isError, refetch } = useQuery({
|
||||
queryKey: ["statement", from, to, kind],
|
||||
queryFn: () => getStatement(from, to, kind),
|
||||
});
|
||||
const items = data?.items ?? [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
{/* Нативный date-input: календарь телефона знакомее любого нашего. */}
|
||||
<input type="date" value={from} max={to} onChange={(e) => onFrom(e.target.value)}
|
||||
aria-label="Начало периода"
|
||||
className="card flex-1 px-3 py-2 text-xs bg-white" />
|
||||
<span className="text-muted text-xs">—</span>
|
||||
<input type="date" value={to} min={from} onChange={(e) => onTo(e.target.value)}
|
||||
aria-label="Конец периода"
|
||||
className="card flex-1 px-3 py-2 text-xs bg-white" />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1.5 mb-3 overflow-x-auto">
|
||||
{FILTERS.map((f) => (
|
||||
<button
|
||||
key={f.key}
|
||||
onClick={() => onKind(f.key)}
|
||||
className={`shrink-0 px-3 py-2 rounded-xl2 text-xs border ${
|
||||
f.key === kind ? "border-line bg-surface font-semibold" : "border-line text-muted"
|
||||
}`}
|
||||
>
|
||||
{f.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-8"><Spinner /></div>
|
||||
) : isError ? (
|
||||
<div className="text-center py-6">
|
||||
<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-8">Нет операций за выбранный период</p>
|
||||
) : (
|
||||
<div>
|
||||
{items.map((op, idx) => (
|
||||
<div key={`${op.date}-${idx}`}
|
||||
className="flex justify-between items-start gap-3 py-3 border-b border-line last:border-0">
|
||||
<span className="min-w-0">
|
||||
<span className="block text-sm">{op.title}</span>
|
||||
<span className="block text-muted text-xs">
|
||||
{formatDate(op.date)}
|
||||
{op.description ? ` · ${op.description}` : ""}
|
||||
</span>
|
||||
</span>
|
||||
<b className={`shrink-0 ${op.amount < 0 ? "text-neg" : "text-pos"}`}>
|
||||
{formatMoney(op.amount)}
|
||||
</b>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user