Commit Graph

14 Commits

Author SHA1 Message Date
wasrusgen
0fb0597b8d fix: добавить 15-секундный таймаут fetch во все модули (measurements, assembly, proposals, request)
Кнопки больше не зависают бесконечно при медленном или недоступном бэкенде.
AbortController + дружелюбное сообщение «Сервер не отвечает — попробуйте ещё раз».

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:27:53 +03:00
wasrusgen
2f50f6e920 fix(miniapp): remove location.reload() on back-to-home navigation (splash bug)
Every "Назад" / "На главную" button was calling location.reload() which
triggered a full page reload → splash screen appeared again. Fix: replace
reload() with routeByHash() call (global router function from app.js) which
re-renders the role-appropriate home screen from cached window.__zovMe
without any network round-trips.

Affected files: app.js, clients.js, measurements.js, request.js,
assembly.js, podbor.js. Bump asset versions to 20260517b to bust cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 00:39:12 +03:00
wasrusgen
0d2973ea77 feat: адрес замера — 6 раздельных полей (город/улица/дом/кв/подъезд/этаж)
- measurements.js: renderClientPicker — одно поле адреса заменено на addr-grid
- добавлен локальный _splitAddr() — разбирает строку адреса обратно в поля
- при выборе клиента из пикера адрес автоматически раскладывается по полям
- каждое поле собирает state.address через readAndSaveAddr()
- index.html: cache bump → v=20260514l

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-15 22:13:20 +03:00
wasrusgen
44799362c1 feat: client picker in measurement form, address geocoding, edit-client 4-field addr
- measurements.js: replace manual name/phone inputs with client picker overlay
  (search by ФИО or phone, sorted alphabetically, picks from /api/clients)
- clients.js: new-client and edit-client forms now use 4 split address fields
  (город, улица, дом, кв./офис) with geocode validation on save
- clients.js: add splitAddress() helper to pre-fill edit form from stored address
- clients.js: voice engine refactored (continuous=false + auto-restart, no duplication)
- clients.js: note block view/edit toggle (textarea closes after save)
- podbor.css: styles for picker overlay, picker-row, chosen-card, addr-grid, geo-status
- backend main.py: _handle_client_create and _handle_client_update accept gps_lat/gps_lng
- index.html: cache bump → v=20260514i

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:51:25 +03:00
wasrusgen
dd8691671a fix(voice): continuous=false + auto-restart — eliminates word duplication
Replaced continuous=true with phrase-by-phrase mode in all 3 voice inputs
(setupVoiceMicForField, setupVoiceInput, setupVoiceMic). Chrome mobile
resets ev.results mid-session with continuous=true causing repeated words.
Now each phrase is an independent SR() session; baseText grows cleanly.
Shared _buildVoiceEngine factory in clients.js.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:06:48 +03:00
wasrusgen
34b83899b5 fix client create — 7 багов + правило самопроверки
#7 — submit-flow:
  - При успехе скрываем CTA с «Сохраняем...» (style.display=none)
  - Обработчики «Ещё клиент» / «Открыть карточку» прикрепляются к
    result-блоку (был form.querySelector — там их нет)
  - Backend возвращает client_key = name.lower() — совместимо
    с тем как ищет _handle_clients
  - clientsCache = null после успеха

#3 — голос дублировался:
  - Переписан алгоритм: финальные транскрипты пересчитываются с нуля
    из ev.results[0..N] каждое событие (не аккумулируем дельтами).
  - confirmedFinal фиксируется в baseText только на onend.
  - Применено в measurements.js + clients.js

#2 — телефон:
  - Frontend normalizePhone: убирает не-цифры, +7/8 → +7, добавляет +7
    к 10-значным; auto-нормализация на blur
  - Backend _normalize_phone(): тот же алгоритм
  - Валидация: ровно 11 цифр начиная с 7
  - Field-error «Введите корректный российский номер...»

#1 — адрес:
  - Min 5 chars (улица + дом)
  - Backend проверка длины
  - Hint «Укажите город, улицу, дом, кв.»

#5 — номер клиента:
  - Новая колонка client_no
  - _next_client_no() — максимум для текущего менеджера + 1
  - Шильд #N рядом с именем в карточке клиента

#6 — номер договора:
  - Новые колонки contract_no, contract_date
  - Поля в форме «Новый клиент» (опционально)
  - Шильд «📋 договор N» в карточке клиента

#4 — удаление клиента:
  - Soft-delete через колонку archived_at
  - Endpoint /api/client_delete
  - «⚠️ Опасная зона» в карточке клиента (collapsible)
  - Confirm dialog через Telegram.WebApp.showConfirm
  - Архивированные клиенты не показываются в /api/clients

#8 — правило самопроверки:
  - docs/SELF_CHECK_RULE.md — 10 пунктов чек-листа перед «готово»
    (end-to-end, ключи, UI-состояния, валидация, голос, deploy, логи)

Cache bust v=20260514a.
2026-05-14 00:09:14 +03:00
wasrusgen
e808880d8e A+B: голос в мастере замера + Google Calendar события
A — голосовой ввод заметок в мастере замера:
- Кнопка 🎤 Диктовать рядом с textarea «Заметки»
- Web Speech API ru-RU, interimResults показывает диктовку в реальном времени
- Текст накапливается + сохраняется в state
- Красная пульсация во время записи

B — Google Calendar:
- Новый модуль app/gcalendar.py — service account + Calendar API
- Создание/обновление события при /api/measurement_schedule
- 2 новые колонки в Measurements: gcal_event_id, gcal_event_url
- При ошибке (нет API/прав) — fail gracefully, лог warning
- Ссылка «📅 Открыть в Google Calendar» в карточке заявки
- В DM менеджеру при назначении — clickable ссылка на событие
- Требует env: GOOGLE_CALENDAR_ID + SA добавлен в редакторы календаря

ДОПОЛНИТЕЛЬНО — заведение клиента менеджером:
- Новый endpoint /api/client_create
- /api/clients теперь читает И Leads И Measurements (включая draft)
- UI: action card «Новый клиент» в quick-actions + кнопка
  «+ Новый клиент» в шапке списка клиентов
- Форма (ФИО / Тел / Адрес / Примечание с 🎤 диктовкой)
- После сохранения — переход в карточку клиента
- has_role проверка вместо устаревшего user.role

Cache bust v=20260513zn.
2026-05-13 23:49:20 +03:00
wasrusgen
7a5df7d011 checklist: 5 SVG-эскизов в стиле Editorial Calm
По аналогии с пиктограммами категорий техники добавил инструктивные
рисунки к чек-листу замера. Walnut stroke + cream fill + лёгкая тень.

1. topview — вид сверху, комната с пронумерованными стенами + компас.
2. clockwise — стрелка по часовой стрелке с точкой «старт».
3. openings — фронт стены с дверью / окном / балконом + размеры
   (ширина, высота, низ подоконника).
4. comms — стена с точками R1 / Sw1 / Wc1, двумя размерами на каждую
   (горизонталь до угла-базы + вертикаль до пола), указанная БАЗА: ПУ.
5. levels — разрез помещения с нулевым полом (волнистая стяжка),
   плитой +88, потолком и коробом справа, размеры H1/H2.

Реализация:
- Новый файл miniapp/assets/zamer-picts.js — экспортирует ZAMER_PICTS.
- renderMarkdown в measurements.js поддерживает директиву
  «@pict:KEY» на отдельной строке → подставляет SVG.
- Эскизы вставлены в ЧЕКЛИСТ_ЗАМЕРА.md в соответствующие разделы
  (1, 4, 6, 8). По часовой стрелке — после § 1.
- CSS .cl-pict — рамка пунктиром + цент., max-height 220.

Cache bust v=20260513x.
2026-05-13 18:00:43 +03:00
wasrusgen
a437b55447 measurements: auto-suggest № замера, активные галочки чек-листа, убрана стяжка
1. № замера подбирается автоматически:
   - POST /api/measurement_next_no возвращает max(zamer_no) + 1
   - Wizard при открытии вызывает endpoint и заполняет input
   - Менеджер может переписать вручную (поле редактируемое)
   - Подпись «Подобран автоматически — можно изменить»

2. Поле «Стяжка / нулевой пол» удалено из формы:
   - По логике пользователя — стяжка пишется на самих фото с замером
   - Backend колонка floor_base остаётся для backward compat (старые записи)

3. Чек-лист стал интерактивным:
   - Каждый [ ] item теперь .cl-item с cursor:pointer
   - Тап переключает галочку (☐ ↔ ☑) + страйкаут текста
   - Состояние сохраняется в localStorage по measurement_id (или draft)
   - Sticky прогресс-бар сверху: «N из M · X%» + градиентная полоса
   - Кнопка ↺ в шапке — сбросить все галочки
   - Hapt-фидбэк на каждый тап

Cache bust v=20260513m.
2026-05-13 07:29:18 +03:00
wasrusgen
121927ab2d measurements: структура фото + чек-лист + общая инфа
По чек-листу ЗАМЕРОВ (D:\!!! GOOGLE DISK\ЗАМЕРЫ\...\ЧЕКЛИСТ_ЗАМЕРА.md):
каждая стена снимается отдельно, имя файла отражает тип.

Wizard:
- Каждое фото получает dropdown «Что это»:
  Стена 1, 2, 3, 4 · План комнаты · Общий вид · Деталь
- Авто-предложение типа: w1 → w2 → w3 → w4 → plan → general
- Добавлены поля общей инфы:
  · № замера (опционально)
  · Дата замера (auto-сегодня)
  · Стяжка / нулевой пол (default «0,000 = +88 мм над плитой»)
- В шапке кнопка 📋 — открывает чек-лист отдельной страницей
- Inline-рендер markdown с поддержкой заголовков, списков, таблиц, code

Backend:
- _save_measurement_photo принимает kind+kind_seq → имена файлов
  структурные: w1.jpg, w2.jpg, plan.jpg, general_2.jpg, detail_1.jpg.
  Это упрощает дальнейшую обработку для генерации DWG.
- Расширена схема Measurements: zamer_no, zamer_date, floor_base, photos_meta.
- /api/measurement_detail отдаёт новые поля.

Cache bust v=20260513l.
2026-05-13 07:19:25 +03:00
wasrusgen
5c2a5bb335 measurements: упрощение — только фото + заметки (DWG потом)
По логике пользователя: задача замера в боте — только сфоткать
рукописные эскизы с размерами + общие фото помещения. Перевод
в DWG-чертёж происходит отдельным процессом (вручную технологом
или через AI vision позже).

Что убрано:
- Шаг «Форма кухни» (linear / l_shape / u_shape / island / peninsula)
- Шаг «Размеры стен + потолок + площадь»
- Шаг «Окна и двери»

Что осталось:
- Клиент (имя/телефон/адрес) — только для прямого ввода менеджером.
  При закрытии заявки замерщиком берётся из заявки read-only.
- Фото (до 20, со сжатием до 1800px на клиенте)
- Заметки опционально

Wizard стал одностраничным: всё на одном экране, единый submit.
В update-mode (#/measure?id=...) wizard сразу подгружает заявку
и не спрашивает данные клиента.

Backend схема не менялась — старые поля (layout, walls и т.п.)
просто остаются пустыми. Существующие замеры с заполненными полями
отрисовываются без изменений.

Cache bust v=20260513k.
2026-05-13 07:03:45 +03:00
wasrusgen
67034e011a workflow B: заявка на замер от менеджера → инбокс замерщика → завершение
End-to-end поток:

1. Менеджер на главной тапает «Заказать замер» → #/request → форма:
   ФИО · телефон · адрес · dropdown «Кому назначить» · заметки.
   Submit → POST /api/measurement_request → строка в Measurements
   со status=requested + assigned_to_tg_id. Бот шлёт DM замерщику.

2. Замерщик открывает кабинет (?role=staff) → видит inbox с заявкой.
   Тап → #/inbox/<id> → карточка с реквизитами + поле datetime-local.
   Сохранить дату → POST /api/measurement_schedule → status=scheduled.
   Бот уведомляет менеджера.

3. В нужный день замерщик тапает «📐 Сделать замер сейчас» →
   wizard открывается в update-mode (#/measure?id=<id>), pre-fill
   client_name/phone из заявки, пропускает шаг «Клиент». После submit
   → backend обновляет ту же строку (status=completed) + DM менеджеру.

Backend changes:
- Расширена схема Measurements: assigned_to_tg_id, requested_by_tg_id,
  scheduled_at, address, client_name, client_phone (отдельные колонки).
  ensure_measurements_sheet() автоматически дополняет колонки.
- _handle_measurement переписан под 2 режима (create/update).
- 3 новые ручки: /api/measurement_request, /api/measurement_inbox,
  /api/measurement_schedule. Все с правильной проверкой ролей.
- Telegram-уведомления на каждом переходе статуса.

MiniApp:
- Новый модуль request.js — wizard заявки с dropdown замерщиков
  (грузится из /api/staff_list?role=measurer).
- renderStaff теперь грузит реальный инбокс из /api/measurement_inbox.
- renderInboxDetail — карточка заявки с datetime-picker.
- В quick-actions менеджера: «Заказать замер» (primary) +
  «Замер сейчас» (legacy direct fill).
- measurements.js поддерживает update-mode через ?id=.

Cache bust v=20260513g.
2026-05-12 20:00:16 +03:00
wasrusgen
a084542bbf measurements: photo upload + measurement detail page + PDF/print
Wizard: new 'photos' step (6 total) — camera/gallery input, client-side
canvas compression to 1600px @ ~78% JPEG, max 12 photos. Thumbnails
with delete in step; preview in summary.

Backend: POST /api/measurement now decodes data-URL photos and saves
to /app/photos/<id>/N.jpg (volume-mounted). New GET /api/photo/{id}/{n}
serves files with path-traversal protection. New POST /api/measurement_detail
returns full measurement record (walls/openings/photos/notes/...).

Clients page: measurement rows now clickable → renderMeasurement detail
view with key-value grid + photo gallery + 'Скачать PDF / Печать'.
Print stylesheet (@media print) hides navigation/buttons/uploaders and
prints clean A4-friendly layout.

Podbor report: existing 'Печать → PDF' now falls back to inline
window.print() inside Telegram WebApp (popups are blocked there).

Cache bust v=20260513a.
2026-05-12 18:11:29 +03:00
wasrusgen
10bcc75b13 measurements: kitchen layout wizard + 5 layout pictograms + profile integration
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
2026-05-12 17:41:01 +03:00