Users.role теперь хранит CSV-список ролей: 'manager,measurer'.
Парсим, добавляем, отзываем — все через sheets.parse_roles / grant_role /
revoke_role / list_users_with_role. Старые однострочные значения работают
как раньше (legacy compat).
Backend:
- /api/me возвращает roles[] (массив), role (главная для legacy-UI),
plus capabilities {measurer, assembler} для staff
- /api/grant_role (admin-only) — добавить/отозвать роль
- /api/staff_list (manager-only) — список сотрудников по роли
(будет использоваться в dropdown «выбрать замерщика»)
- При role=staff отдаём отдельный кабинет; если у юзера нет measurer/
assembler — возвращаем error=no_staff_role
Bot:
- /start — 3-я reply-кнопка [🔧 Я сотрудник]. При тапе MiniApp получает
?role=staff и решает кабинет по capabilities.
- /whoami — сотрудник присылает свой Telegram ID, пересылает куратору
чтобы тот выдал роль через /api/grant_role.
MiniApp:
- renderStaff() — заглушка кабинета сотрудника с шапкой (имя, аватар,
список ролей) и пустым inbox («Пока пусто»). Если есть measurer —
быстрая кнопка «Сделать новый замер».
- При error=no_staff_role — экран с инструкцией как получить роль.
- CSS .staff-head / .staff-no-role.
Cache bust v=20260513f.
Splash was nested inside <main id='app'> — render functions wipe
app.innerHTML before hideSplash() runs, so the splash never showed.
Moved it to a sibling of #app and made hideSplash() wait for at least
700ms of total time elapsed since page load before fade-out.
Cache bust v=20260513d.
Bot: упрощён до одного шага — /start показывает 2 reply-кнопки
[👤 Я менеджер] [🏠 Я клиент], обе уже WebApp — открывают кабинет
сразу с нужным role= в query. Никаких промежуточных меню.
MiniApp: новый брендированный загрузочный экран с логотипом ZOV
(inline SVG, fill = walnut #6B4A2B), дыхательной анимацией 2.2s,
тонкой полоской прогресса и подписью «Открываем кабинет · ZOV».
Splash прячется (350мс минимум + fade-out) после рендера главного
экрана или маунта подэкрана (Podbor/Clients/Measurements).
Cache bust v=20260513c.
Now after /start, manager sees a bottom keyboard (4 rows) for fast access:
Row 1: 🤖 Подбор техники | 📐 Новый замер ← WebApp
Row 2: 👥 Мои клиенты | 🏠 Кабинет ← WebApp
Row 3: ℹ️ Что умеет бот? | 📞 Куратор ← text
Row 4: 📋 Чек-лист встречи ← text
WebApp buttons jump straight to a MiniApp screen via ?go=<podbor|measure|clients>;
app.js parses ?go on load and pre-sets location.hash so the right module mounts.
Added /menu (re-show keyboard) and /hide (remove). Text buttons trigger
in-chat info responses (bot description, contact, meeting checklist).
Cache bust v=20260513b.
5 LAYOUT PICTOGRAMS (podbor.picts.js):
- linear: одна стена с гарнитуром
- l_shape: Г-образная, две стены с подсвеченным углом
- u_shape: П-образная, три стены
- island: линейный гарнитур + отдельный остров посередине
- peninsula: Г-образная + барная стойка-полуостров
Все в стиле D · top-down view, walnut stroke, теплые градиенты
MEASUREMENTS.JS WIZARD (5 шагов):
1. client_info — имя + телефон (валидация)
2. layout — pict-карточки 5 типов
3. size — длины стен (1-3 по layout), площадь, потолок (мм)
4. openings — окно / дверь / коммуникации / заметки
5. summary — обзор + Сохранить → POST /api/measurement
BACKEND (main.py):
- New /api/measurements (POST) для списка замеров менеджера
с опц. фильтрами по client_tg_id
- _handle_measurement теперь дописывает имя+телефон клиента в notes
(если client_tg_id не зарегистрирован — это новый клиент без аккаунта)
- handlers dispatcher: 'measurements' route added
ROUTING (app.js):
- Quick-action 'Новый замер' wired to '#/measure'
- routeByHash: Measurements.mount on #/measure
CLIENT PROFILE (clients.js):
- New section 'Замеры · N' on client history page
- fetchMeasurements() filters by client_tg_id or name match in notes
- layoutLabel() shows Russian label (Прямая / Угловая Г / etc.)
- Cache bump v=20260512c
NEW FILE assets/clients.js:
- Clients.mount(container) — hash-routed view
- #/clients — list of all clients (cards: avatar, name, phone, leads count, last date)
- #/clients/client/<key> — single client history (all leads as items)
- #/clients/lead/<id> — full lead detail with re-rendered report
UI:
- Card style: avatar with initial, name + phone, footer with N подборов + дата
- Pluralization for Russian (1 подбор / 2 подбора / 5 подборов)
- Date format: 'сегодня · 14:30' or 'DD.MM.YYYY'
- Status pills: new / sent / viewed / ordered
PODBOR.JS:
- Exposed renderSavedReport(ai, leadId) for Clients module reuse
- Same renderer as live podbor — same matrix, pros/cons, links
APP.JS:
- Quick action 'Клиенты' added (icon: user)
- Hash router: #/clients → Clients.mount()
INDEX.HTML:
- clients.js script added
- Cache bumped to v=20260512a
CSS:
- .client-list, .client-card with avatar+meta+footer
- .client-detail-head (big card with avatar 56px)
- .leads-list with .lead-item (grid: date | id | status | arrow)
- .loader-inline for async fetch
- .ai-text-fallback for legacy text-only responses