Отдельный список штрафов удалён: два списка одного и того же в разных местах
сбивали с толку. Фильтр «Штрафы» внизу главного экрана теперь показывает сами
постановления, а не проводки по ним, и ведёт в карточку с фотографией.
Разница между постановлением и проводкой здесь принципиальная: штраф, который
водитель оплатил сам, парк не выставлял — проводки нет, и в выписке он бы не
появился вовсе (Морозов, штраф от 16.08). Строка «Долг по штрафам» вверху теперь
включает этот фильтр, а не уводит на другой экран.
Период действует и на штрафы: календарь над списком не должен стоять, ничего не
меняя.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
«Премиум Водитель» под иконкой не помещается: Android обрезает подпись примерно
на двенадцати символах, и на экране оставалось «Премиум» — неотличимо от других
приложений парка. Короткое имя теперь «Водитель», полное («Премиум Водитель»)
осталось для установки и списка приложений.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Водитель, оплативший штраф сам, видел пустой экран: список открывался на
«Не погашены», а там у него ноль (Морозов, штраф от 16.08 — оплачен им, парк его
не выставлял). Выглядело как «приложение потеряло мои штрафы».
Теперь вкладка выбирается по данным: есть непогашенные — открываем их, нет —
открываем «Все». Заодно один запрос вместо двух: список короткий, фильтруем на
месте, и переключение вкладок стало мгновенным.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
short_name был «Премиум» — под иконкой на телефоне стояло именно оно, и от
приложения механика оно ничем не отличалось. Плюс apple-mobile-web-app-title:
iOS подпись из манифеста не читает и берёт её только из этого мета-тега.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Предыдущая иконка была собрана из assets/logos/premium_park_favicon_pack — это
неудачный вариант знака, владелец его забраковал. Ставим то, что стоит на самом
сайте: красная плитка --s-accent с белой «П», как в pptaxi.ru/favicon.svg. Значок
в браузере, на экране телефона и на сайте теперь один.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Была нарисованная «карта» на кремовом фоне из первых набросков. Теперь знак
«Премиум Парк» из фирменного набора, на белой подложке сайта.
Собирается из вектора скриптом scripts/gen-icons.py (три пути, команды M/L/H/V/C,
сглаживание сверхдискретизацией ×4) — прежний gen-icons.mjs рисовал плитку
вручную и к логотипу отношения не имел. Готовый PNG из набора не подошёл: знак
внутри него занимает 264×195 из 512, и на иконке он бы размылился.
Отдельная maskable-версия с полями: Android режет иконку по кругу, и знак без
запаса теряет края. Ссылка apple-touch-icon в index.html — iOS манифест не
читает и берёт иконку «на экран Домой» только оттуда.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Дата в выписке и в списке штрафов встала первой строкой чернильным цветом: в
этих списках ищут «что было 12-го», а бледная подпись под заголовком для этого
не годилась.
Строка штрафа в выписке стала кнопкой в карточку — там фотография и подробности.
Остальные строки не притворяются ссылками.
Раздел «Мои пополнения» удалён вместе с кнопкой: он показывал только наши
платежи и у большинства водителей был пуст (1С проводит пополнения сама), а те
же операции видны в выписке фильтром «Пополнения».
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Светлый сине-серый фон #EEF1F5, белые карточки, чернильный текст #0F172A,
фирменный красный акцент #DC2626 у кнопки действия, зелёный #059669 у прихода
денег, янтарный #D97706 у предупреждений — токены сняты с живого сайта
(переменные --s-* в его инлайн-CSS).
Имена токенов теперь роли, а не краски: canvas / surface / line / ink / muted /
accent / pos / neg / warn. Прежние cream и ink описывали конкретный цвет, и при
смене палитры «cream» стал бы синим.
Тема браузера и манифест PWA переведены на новый фон — иначе шторка адресной
строки и заставка запуска остаются от прежней темы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
С карточки убраны строки про номинал постановления, скидку и «в ГИБДД
постановление закрыто», из списка — абзац о том, кто и как платит ГИБДД.
Остаётся то, ради чего экран делался: фотография нарушения, статья, место,
скорость, начисленная сумма и погашен ли долг.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Слово «оплачен» водитель читает как «я ничего не должен», а постановление к тому
моменту оплачено парком — не им. Поэтому в списке и карточке состояние теперь
«не погашен / погашен / не выставлялся / уточняется», долг в шапке берётся из
баланса 1С, а карточка объясняет расхождение сумм: «постановление — 750 ₽, парк
оплатил его со скидкой и выставил вам фактическую сумму».
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Список с вкладками «Неоплаченные / Все» и суммой долга в шапке; в карточке —
фотография с камеры первой, до реквизитов: по ней водитель узнаёт свою машину и
свою поездку. Дальше сумма, скидка с датой (то, ради чего заходят вовремя),
скорость и разрешённый предел, постановление, камера, ссылка на PDF.
Строка «Долг по штрафам» на главном экране теперь ведёт сюда, а не сразу к
оплате: платить, не поняв за что, человек не станет. Аренда по-прежнему ведёт к
оплате — за тем её и открывают.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
За неделю у активного водителя два десятка строк, и половина одинаковая по виду
(«аренда автомобиля» каждый день) — список сливается в стену текста. Значок даёт
зацепку для глаза: нужное находится, не читая каждую строку.
Подбор по тегу 1С, а не по знаку суммы: аренда, пени, штраф, ДТП, депозит,
пополнение, компенсация, коррекция — у каждого свой. Общий компонент, как в
складе CRM: разложенные по местам иконки разъезжаются размерами при первой правке.
Монохромные намеренно — цвет в этом списке уже занят и означает направление
денег, цветной значок с ним бы спорил.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Две статьи, которые водитель проверяет каждый день: за них он платит и из-за них
ему звонят. Стоят под итогом, нажатие ведёт сразу к оплате этой статьи — чаще
всего человек за тем и открыл приложение, а заход в шторку это лишний шаг.
Остальные статьи (депозит, повреждения, прочее) на экран не выносим: они редкие,
им хватает шторки, иначе экран снова превращается в столбик из шести строк и
выписка уезжает за нижний край телефона.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
По образцу приложения вендора: баланс в парке и «зачисляется», под ними крупная
кнопка «Пополнить», ниже — период с календарями, фильтры и список операций
(аренда, пени, штрафы, пополнения).
Разбивка по статьям переехала в шторку «Пополнить». Она нужна ровно в момент
выбора, куда платить; всё остальное время это шесть строк, отодвигающих выписку
за нижний край телефона. В шторке только те статьи, в которые платёж проводится.
Экран суммы, придя из шторки, показывает выбранную категорию строкой, а не рядом
чипов: второй выбор того же самого сбивает с толку.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Итог берётся с сервера, а не складывается из строк (1С округляет статьи до
рублей, итог держит с копейками: −8 976 против −8 976,25). Статья, в которую
платёж провести нечем, показывается, но не притворяется кнопкой — «Страховка».
Пустой снимок 1С даёт «Уточняется» вместо нулей.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Второй шаг входа сотрудника не отправлялся вовсе: у поля с кодом не было ни
обработки Enter, ни автоотправки, а «Готово» на клавиатуре телефона — это как
раз Enter. Со стороны выглядело как «код не принят», хотя запрос на /auth/2fa
не уходил ни разу (в логах бэкенда — только login с ответом «нужен второй шаг»).
Теперь шесть цифр отправляются сразу, Enter тоже работает, поле получает фокус и
autocomplete=one-time-code (подсказка кода из СМС/менеджера паролей).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Сотрудник, вошедший водителем, при переключении на чужую карточку терял
собственный вход: токен один на приложение, и «посмотреть, как выглядит у
другого» означало заново входить через Telegram.
Теперь свой сеанс откладывается и возвращается кнопкой «Вернуться к своему
аккаунту». Откладывается ровно один раз — на переходе из обычного сеанса:
смена водителя внутри просмотра не должна подменять отложенное чужим токеном.
Заодно вход для сотрудника достижим изнутри приложения: длинный тап по названию
в шапке ведёт на /dev. Ссылкой на экране это не делаем — водителю она ни к чему,
а сотруднику иначе приходится набирать адрес руками.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Приложение обкатывается на боевом контуре, поэтому интерфейс надо проверять на
живых данных — а живые данные лежат у настоящих водителей.
* «Вход для сотрудника» (/dev) — логин учётки CRM, поддержан второй шаг 2FA;
дальше поиск водителя по ФИО/телефону/id и просмотр его экранов.
* Плашка «Смотрю как …» над всеми экранами: интерфейс в этом режиме неотличим
от водительского, и чужой баланс легко принять за свой.
* Вкладка «Отладка» (/debug, водителю недоступна): смена водителя, driver_id,
режим, срок токена, адрес API, коммит и время сборки, журнал последних
40 ответивших запросов, сброс кэша сервис-воркера.
* Пополнение под просмотром идёт в учебный контур: экран /mock-pay/:orderId
выбирает, что «ответит» банк, а настоящий экран оплаты разбирает эти статусы
своим боевым кодом. Ни рубля, ни чека 54-ФЗ.
Заодно доезжает незакоммиченная работа прошлой сессии: дев-прокси в
vite.config.ts, его тест и разобранный .env.example.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- pairing-сессия хранит poll_secret и confirm_code (lib/pairing), опрос ушёл
на POST: pair_session уезжал в query-параметре и оседал в access-логах, а
это половина ключа к JWT. Вторая половина в deep-link не уходит;
- на экране ожидания видно число, которое обязан назвать бот. По чужой ссылке
человек его не увидит — подтверждать нечего, токен не выдаётся;
- онбординг больше не просит вернуться и запросить код: Telegram подтверждает
номер шарингом контакта, приложение забирает токен опросом;
- суммы с копейками. formatMoney округлял до рубля, и «к оплате 350 ₽»
расходилось и со списанием банка, и с чеком (комиссия почти всегда даёт
копейки). Клавиша разделителя, нарисованная на клавиатуре, наконец работает;
- категорию долга выбирает водитель. Хардкод «Долг аренда» отправлял платёж
в аренду даже тем, у кого её нет; список приходит с сервера и там же
проверяется;
- ошибки бэкенда показываются словами (минимум СБП, потолок суммы) вместо
молчаливого «не удалось», статусы истории переведены полностью, возврат
денег отличим от отказа.
Тесты: 33 зелёных, tsc чист.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Экран успеха рисовался только при PAID. При любом другом финальном статусе
опрос останавливался, а человек навсегда оставался на «Ожидаем оплату…» со
спиннером — то есть два из трёх тестовых сценариев СБП глазами не проверить.
- ветки отказа (в том числе исчерпанных попыток), истёкшего срока и возврата
средств: у возврата свой текст, новая попытка там ни при чём;
- ошибка запроса больше не выглядит как честное ожидание: после серии неудач
подряд экран говорит, что не может проверить оплату, и даёт повторить.
Раньше 404, 500 и рестарт бэкенда были неотличимы от «ждём оплату»;
- плашка способа оплаты показывала «СБП» и при оплате картой;
- после перезагрузки страницы сумма бралась из пустого router state и
показывалась как 0 ₽ — лучше не показать ничего, чем показать неправду.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
window.location.href=deep_link navigated the whole PWA to t.me, destroying the
/tg-wait polling screen -> the authenticated token was never fetched. Prefetch
the pairing session and open Telegram via an <a href> (OS app-switch, PWA stays
mounted polling); add an Open-Telegram fallback link on the wait screen.
Co-Authored-By: claude-flow <ruv@ruv.net>
Backend omits channels without a configured username; strict zod schema
threw on missing 'max', surfacing as 'Не удалось отправить код' instead of
routing to the onboarding screen. Make both optional + regression test.
Co-Authored-By: claude-flow <ruv@ruv.net>