From 9187b5766e4305237618a5b62f2339cc7b8f381f Mon Sep 17 00:00:00 2001 From: vladtechno Date: Tue, 18 Aug 2026 17:06:02 +1000 Subject: [PATCH] =?UTF-8?q?feat(pwa):=20=D0=B7=D0=BD=D0=B0=D1=87=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=B2=20=D0=B2=D1=8B=D0=BF=D0=B8=D1=81=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit За неделю у активного водителя два десятка строк, и половина одинаковая по виду («аренда автомобиля» каждый день) — список сливается в стену текста. Значок даёт зацепку для глаза: нужное находится, не читая каждую строку. Подбор по тегу 1С, а не по знаку суммы: аренда, пени, штраф, ДТП, депозит, пополнение, компенсация, коррекция — у каждого свой. Общий компонент, как в складе CRM: разложенные по местам иконки разъезжаются размерами при первой правке. Монохромные намеренно — цвет в этом списке уже занят и означает направление денег, цветной значок с ним бы спорил. Co-Authored-By: Claude Opus 5 --- .../frontend/src/components/Statement.tsx | 6 ++- .../src/components/operationIcon.test.ts | 25 +++++++++ .../frontend/src/components/operationIcon.tsx | 53 +++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 driver-pwa/frontend/src/components/operationIcon.test.ts create mode 100644 driver-pwa/frontend/src/components/operationIcon.tsx diff --git a/driver-pwa/frontend/src/components/Statement.tsx b/driver-pwa/frontend/src/components/Statement.tsx index 90f6999..3a108b0 100644 --- a/driver-pwa/frontend/src/components/Statement.tsx +++ b/driver-pwa/frontend/src/components/Statement.tsx @@ -1,6 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { getStatement, type StatementKind } from "@/api/driver"; import { formatDate, formatMoney } from "@/lib/format"; +import { OperationIcon } from "@/components/operationIcon"; import { Spinner } from "@/components/Spinner"; const FILTERS: { key: StatementKind; label: string }[] = [ @@ -70,8 +71,9 @@ export function Statement({ from, to, kind, onFrom, onTo, onKind }: {
{items.map((op, idx) => (
- + className="flex items-center gap-3 py-2.5 border-b border-line last:border-0"> + + {op.title} {formatDate(op.date)} diff --git a/driver-pwa/frontend/src/components/operationIcon.test.ts b/driver-pwa/frontend/src/components/operationIcon.test.ts new file mode 100644 index 0000000..f4978dd --- /dev/null +++ b/driver-pwa/frontend/src/components/operationIcon.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from "vitest"; +import { operationIcon } from "./operationIcon"; + +/** Названия — настоящие теги 1С из выписки (finance_ledger_entries.tag). */ +describe("значок операции", () => { + it("разные виды операций различаются", () => { + const names = [ + "Аренда автомобиля", + "Пени", + "Штраф ГИБДД", + "Пополнение баланса через мобильное приложение", + "ДТП", + "Перевод с депозита на баланс", + ].map((t) => operationIcon(t)); + expect(new Set(names).size).toBe(names.length); + }); + + it("штраф ГИБДД и штраф компании — один значок: для водителя это одно и то же", () => { + expect(operationIcon("Штраф ГИБДД")).toBe(operationIcon("Штраф компании")); + }); + + it("незнакомый тег получает нейтральный значок, а не пустоту", () => { + expect(operationIcon("Списание за что-то новое")).toBeTruthy(); + }); +}); diff --git a/driver-pwa/frontend/src/components/operationIcon.tsx b/driver-pwa/frontend/src/components/operationIcon.tsx new file mode 100644 index 0000000..c9be2c7 --- /dev/null +++ b/driver-pwa/frontend/src/components/operationIcon.tsx @@ -0,0 +1,53 @@ +import { + Banknote, Car, Flag, Gavel, Gift, HandCoins, Percent, PiggyBank, Receipt, + RefreshCcw, ShieldAlert, TriangleAlert, Wrench, type LucideIcon, +} from "lucide-react"; + +/** + * Значок операции в выписке — по тегу 1С, а не по знаку суммы. + * + * Список сливается в стену текста: за неделю у активного водителя два десятка + * строк, и половина из них — одинаковая по виду «аренда автомобиля». Значок + * даёт зацепку для глаза, чтобы взгляд находил нужное, не читая каждую строку. + * + * Общий компонент, а не иконка на месте применения: тот же приём, что в складе + * CRM (modules/warehouse/categoryIcons.tsx). Разложены по местам — и размеры с + * толщиной линии разъезжаются при первой же правке. + * + * Монохромные намеренно: цвет в этом списке уже занят и означает направление + * денег (красное — списали, зелёное — пришло). Цветной значок спорил бы с ним. + */ +const BY_TAG: [RegExp, LucideIcon][] = [ + [/аренда/i, Car], + [/пени/i, Percent], + [/штраф/i, Gavel], + [/дтп|повреждени/i, TriangleAlert], + [/депозит/i, PiggyBank], + [/пополнение|оплата/i, Banknote], + [/ремонт|запчаст/i, Wrench], + [/компенсац/i, HandCoins], + [/акци|бонус|подар/i, Gift], + [/коррекц|сторно|перерасч/i, RefreshCcw], + [/страхов/i, ShieldAlert], + [/остаток|opening/i, Flag], +]; + +export function operationIcon(title: string): LucideIcon { + for (const [re, icon] of BY_TAG) { + if (re.test(title)) return icon; + } + return Receipt; +} + +/** Значок в рамке — как в списке склада: одинаковый размер у всех строк. */ +export function OperationIcon({ title }: { title: string }) { + const Icon = operationIcon(title); + return ( + + + + ); +}