Commit Graph

136 Commits

Author SHA1 Message Date
wasrusgen
6f846603a9 feat: kitchen_price в сборках + срочный замер push
- main.py: kitchen_price в assembly_list + assembly_detail
- main.py: /api/assembly_set_kitchen_price — менеджер задаёт стоимость кухни
- main.py: measurement urgent=True → bot push замерщику(ам)
- sheets.py: find_users_by_role(role) — поиск пользователей по роли
- assembly_detail.js: показывает стоимость кухни + стоимость сборки (9%)
- index.html: версии → v20260518m

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 17:38:44 +03:00
wasrusgen
042dc1a5d3 feat: детальная карточка сборки + цена замера везде
- assembly_detail.js: экран #/c/assembly/:id — статус, адрес, фото, подпись, gcal
- orders.js: сборки кликабельны → #/c/assembly/:id
- app.js: маршрут #/c/assembly/
- selfmeasure.js: цена 2500₽ + 40₽/км за КАД на шаге 1 и шаге 5
- cabinet.js: цена под кнопкой самозамера

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 15:35:21 +03:00
wasrusgen
b75f24e4d7 feat: история заказов #/c/orders — таймлайн подборов и сборок
- orders.js: единый таймлайн proposal_list + assembly_list, сортировка по дате
- cabinet.js: кнопка «📋 История заказов» → #/c/orders
- app.js: маршрут #/c/orders, версия l
- styles.css: классы orders-timeline, .block, .block-head, .assembly-card
- memory/business_rules.md: замер 2500₽ + 40₽/км за КАД СПб

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 15:19:20 +03:00
wasrusgen
e71ac3a5a8 fix: selfmeasure — читаемое сообщение при HTTP 4xx/5xx
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:49:53 +03:00
wasrusgen
78e332dd95 feat: самозамер #/c/selfmeasure — 5-шаговый мастер для клиента
- selfmeasure.js: тип кухни (SVG-карточки) → стены → коммуникации → фото → контакт+отправка
- cabinet.js: кнопка «📐 Самозамер кухни» → #/c/selfmeasure
- app.js: маршрут #/c/selfmeasure, guard _hashListenerAdded
- index.html: подключение selfmeasure.js v20260518k
- backend: /api/self_measure_submit (hot-patch на VPS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:40:50 +03:00
wasrusgen
ea04e042df feat: staging-окружение (Docker Compose + Caddy + deploy-script)
docker-compose.staging.yml — backend-staging на порту 8001.
.env.staging.example — шаблон с отдельным SHEET_ID.
Caddyfile.staging.snippet — staging.api.wasrusgen1.pro.
scripts/deploy-staging.sh — один скрипт для деплоя staging.
app.js: BACKEND_URL читается из ?backend= параметра URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:22:01 +03:00
wasrusgen
7df9cc3901 refactor: аудит роутера app.js — устранены 3 edge case
1. Дублирование: init() теперь вызывает routeByHash() вместо копии логики.
   #/master, #/me, #/c/cabinet теперь работают при прямом переходе по ссылке.
2. Множественные hashchange listeners: guard _hashListenerAdded.
3. #/picker → #/c/proposal в cabinet.js и me.js (неверный роут).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:39:23 +03:00
wasrusgen
c773adf05e fix: Content-Type: application/json во всех fetch-хелперах
measurements, assembly, request, me, inbox, proposals —
6 модулей, 6 хелперов (_fetchWithTimeout / apiFetch / _api).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:35:20 +03:00
wasrusgen
e934576e5c feat: клиентский кабинет #/c/cabinet
cabinet.js — дашборд клиента: профиль, менеджер, подборы, сборки.
Параллельная загрузка: /api/me + /api/proposal_list + /api/assembly_list.
Backend: assembly_list теперь поддерживает роль client (client_tg_id фильтр).
me.js: кнопка «Мой кабинет» → #/c/cabinet для роли client.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:25:49 +03:00
wasrusgen
3741657fb6 feat: экран #/inbox — входящие задачи менеджера
InboxScreen.mount() — список замеров без решения по подбору.
Три действия: начать подбор / отложить / не нужен.
Роутинг #/inbox добавлен в routeByHash() и init().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:22:07 +03:00
wasrusgen
bdbdf4b259 feat: экран #/me — Мой профиль для всех ролей
manager: статус доступа, салон, быстрые переходы
staff: роли-чипы, кнопки на замеры/сборки
client: менеджер, кнопки подбора и сборок

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:17:52 +03:00
wasrusgen
b43087a4b4 feat: маршрут #/master — экран входящих задач замерщика/сборщика
- routeByHash: #/master → renderStaff(window.__zovMe)
- таймаут 15с на fetch в renderStaff и renderStaffAssemblies
- app.js v=20260518g

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:08:20 +03:00
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
6f74d05184 fix: таймаут 15с на все fetch в clients.js — кнопки больше не висят
- _fetchWithTimeout() — единый fetch с AbortController
- saveClientNote, fetchClientNote, fetchClients, fetchMeasurements
  теперь через таймаут вместо бесконечного ожидания
- При таймауте показывает 'Сервер не отвечает — попробуйте ещё раз'
- Версия clients.js: 20260518e
2026-05-18 08:35:42 +03:00
wasrusgen
a9aa704950 fix: добавить escAttr — ReferenceError в карточке клиента
escAttr использовалась в 11 местах (карта, форма редактирования)
но не была объявлена в clients.js — отсюда пустая карточка.
Версия clients.js: 20260518d
2026-05-18 08:31:27 +03:00
wasrusgen
057842b7e6 debug: показывать ошибку в карточке клиента вместо пустого экрана
- Обернуть renderClientHistory в try/catch — показывает текст ошибки
- Добавить .catch() в mount() для перехвата unhandled promise rejection
- Версия clients.js: 20260518c
2026-05-18 00:21:28 +03:00
wasrusgen
860c768572 fix: пустая карточка клиента — обработка ошибок и сравнение client_tg_id
- Добавить проверку data.error после fetchClients() в renderClientHistory
- Сравнивать client_tg_id как строки (String(c.client_tg_id) === String(clientKey))
  чтобы избежать 5937498515 !== '5937498515'
- Показывать явное сообщение если клиент не найден вместо пустой страницы
- Версия clients.js: 20260518b
2026-05-18 00:11:44 +03:00
wasrusgen
a4124c6b50 fix: открывать карточку клиента вместо списка подборов
- Заголовок 'История подборов' → 'Карточка клиента'
- Убрать инлайн-монтирование Proposals.mountManager в карточке клиента
- Оставить только кнопку 'Открыть →' для перехода к подборам
- Версия clients.js: 20260518a
2026-05-18 00:03:38 +03:00
wasrusgen
fe7d08ee39 ui(clients): все шрифты страницы клиентов #0E1621 — имя, телефон, счётчик, поиск, footer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:14:17 +03:00
wasrusgen
fe79c832ec fix(privacy): client-name и client-phone цвет #F5F5F5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:03:37 +03:00
wasrusgen
7ff1b69663 fix(privacy): client-name и client-phone цвет #0E1621
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:46:59 +03:00
wasrusgen
4c629ed705 fix(privacy): color:var(--paper) для имён — совпадает с фоном страницы за карточками
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:35:52 +03:00
wasrusgen
a545df4005 fix(privacy): opacity:0 вместо transparent — без дырки-силуэта букв
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:30:38 +03:00
wasrusgen
4c6cf3eedd fix(privacy): color:transparent для client-name и client-phone — гарантированно скрывает в любой TG-теме
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:01:22 +03:00
wasrusgen
844007f6ba ui: имена и телефоны клиентов — цвет совпадает с фоном карточки (приватность)
.client-name и .client-phone → color: var(--card), текст сливается
с фоном карточки во всех темах. Аватар и счётчики подборов остаются видимыми.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 11:15:14 +03:00
wasrusgen
eae5e61fcf ui: замена «Новый клиент» на «Заказы», стили карточек клиентов, Свободный день, splash −30%
- quickActions: «Новый клиент» → «Заказы» (clipboard → #/assembly),
  убрали дублирующую кнопку «Сборки» из быстрых действий
- «Свободный день»: текст теперь использует var(--ink)/var(--muted) вместо
  rgba белых значений, которые были невидимы на светлом фоне --card;
  заголовок — шрифт карточки ×1.3 (17.5px 600), описание — моно uppercase 9.5px
- styles.css: добавлены явные стили .client-card/.client-name/.client-phone/
  .client-avatar и др. — исправлен невидимый текст в карточках клиентов
  во всех темах (Foundry, Boardroom, Atelier)
- splash: minShow 1200 → 840 мс (−30%)
- index.html: версия ресурсов → 20260517c

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 09:47:06 +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
b3b62fa902 refine: per-theme personality from dashboard analysis
Extracted from reference dashboards (computed styles, class structure, color usage):

B Foundry:  r-card 0px, body lh 1.5, Archivo 800 display,
            dark #15140F header (palette + greeting full-width),
            wide 0.18em kicker tracking, heavy section labels
C Boardroom: r-card 0px, r-tag 999px (pills), body lh 1.12,
            Geist 400 display (restrained), dark petrol header,
            copper accent on greeting
D Atelier:  body lh 1.1, Manrope 700 display, white card header
            on dove bg, ink-bottom-border divider,
            prominent uppercase section labels

Also: role-card border-radius switched to var(--r-card) from hardcoded 16px

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 12:28:40 +03:00
wasrusgen
db6c4f3265 feat: theme switching system — 4 palettes (ZOV · Foundry · Boardroom · Atelier)
- styles.css v5: replaced old 3-variant system with 4 design-token themes
  extracted from reference dashboards:
  · brand (ЗОВ): #003E7E blue / #76BD22 green, Inter
  · b Foundry: #EFE9D8 cream / #B68A1A mustard, Archivo, sharp corners
  · c Boardroom: #F2E9D6 linen / #D08A55 copper, Geist, petrol tones
  · d Atelier: #E9EBEF dove / #2E5266 steel-blue, Manrope
  Each theme: --paper, --ink, --accent-*, --status-*, --r-*, --font-ui, --warm
- app.js: applyVariant() + savedVariant() helpers with localStorage persistence;
  renderPaletteSwitcher() component (color swatches + name label);
  injected in renderManagerHome + renderStaff; setupTelegram() restores saved variant
- index.html: added Archivo + Manrope to Google Fonts; cache-bust v20260517a
- podbor.css: .role-card .role-icon uses color: var(--accent-1) (was hardcoded walnut);
  SVG strokes switched to currentColor for theme-awareness

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 12:17:54 +03:00
wasrusgen
5fafdc35fb feat: replace hand-drawn role icons with Tabler Icons (MIT)
Manager: user-star (person + star badge)
Client: home-2 (house with square window)
Staff: tools (wrench + screwdriver crossed)
Source: tabler-icons.io, MIT license, stroke=#6B4A2B

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 11:53:19 +03:00
wasrusgen
35c3c3f440 feat: replace geometric role icons with pencil-sketch SVG illustrations
Manager: suit, lapels, tie, pocket square, face with hair/eyes/nose
Client: house with chimney+smoke, pane windows, paneled door, garden bush
Staff: dark hard hat, face with stubble+brows, work jacket, wrench in hand
All use feTurbulence displacement filter for hand-drawn line wobble
and diagonal hatching lines for shadow/volume.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 11:19:12 +03:00
wasrusgen
46812620eb fix: remove stray closing brace in zamer-picts.js that crashed MiniApp
Premature `};` at line 207 closed the ZAMER_PICTS object early,
leaving wall1/wall2/etc. as orphan code — syntax error on load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 10:41:10 +03:00
wasrusgen
0551f1fad0 fix: client list empty after create — add initDataUnsafe to fetchClients
fetchClients() was not sending initDataUnsafe, so on Telegram Desktop
(where initData can be unreliable) _handle_clients returned
{"error":"invalid_init_data"} and the frontend showed an empty list
instead of an error — making newly created clients appear missing.

- fetchClients(): add initDataUnsafe to request body (matches renderManagerHome pattern)
- renderList(): surface data.error explicitly instead of silent empty state
- _handle_client_create: gps_lat/gps_lng None → "" to avoid "None" strings in sheet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 10:14:42 +03:00
wasrusgen
dfba5899bd feat: replace role-chooser emoji with Editorial Calm SVG pictograms
- Manager: person with V-collar and tie (professional silhouette)
- Client: house outline with roof, door, windows and door knob
- Staff: hard-hat person silhouette with wrench in hand
- Bump cache version to 20260516g across all assets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:59:49 +03:00
wasrusgen
22dbbed112 feat: AI contract review for clients (#/c/contract)
Backend:
- main.py: _handle_contract_review() — GigaChat analyzes
  contract text, returns structured JSON (summary, payment,
  deadlines, risks, recommendations, missing_clauses)
  with optional targeted question support
- /api/contract_review route (async, thread-pool)
- CONTRACT_SYSTEM prompt: plain-language analysis in Russian,
  risk levels high/medium/low, missing-clause detection

Frontend:
- proposals.js: Proposals.mountContractReview(container)
  · Textarea for contract text with char counter (16k limit)
  · 5 preset quick-questions as tappable chips
  · Free-form question input
  · Thinking animation while AI processes
  · Renders structured analysis: Summary, Payment,
    Deadlines, Risks (color-coded), Recommendations,
    Missing clauses, disclaimer footer
  · Falls back to raw text if AI returns unstructured reply
- app.js: #/c/contract route in init() and routeByHash();
  «Калькулятор бюджета» replaced by «Проверить договор»
  in client home menu (active, not «скоро»)
- podbor.css: ~180 lines of contract review styles
  (cr-* classes, risk colors, thinking animation)
- index.html: cache version → 20260516f

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:35:29 +03:00
wasrusgen
4abd7b2ecd feat: proposal cycle — client brief + manager editor + voting
Backend:
- proposals.py: new module with full proposal cycle
  (brief → draft → sent → reviewed → done),
  Google Sheets «Proposals» tab, Telegram notifications
- main.py: import proposals_mod, 9 new /api/proposal_* routes
  added to both dispatch map and native /api/* handlers

Frontend:
- proposals.js: self-contained Proposals module
  · Client: brief form (6 items + budget + notes),
    waiting screen, proposal view with / per variant,
    overall comment + submit
  · Manager: empty state → create, editor with categories,
    add-variant form, send button, client votes/feedback view
- clients.js: «Подбор техники» button now opens proposals
  editor page (#/clients/client/{key}/proposals); inline
  Proposals.mountManager() section added to client card;
  new renderClientProposalsPage() route handler
- app.js: #/c/proposal route for client side; client home
  «Подобрать технику» menu item activated (was «скоро»)
- podbor.css: ~350 lines of Proposals UI styles
- index.html: proposals.js added, cache version → 20260516e

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:28:36 +03:00
wasrusgen
1b8f70e44a fix: 3 bugs found in audit
1. _xlsx_auth_manager: возвращал (tg_id, user) при успехе → callers делали
   `if err: return err` и возвращали dict пользователя вместо данных.
   Исправлено: возвращает (tg_id, None) при успешной авторизации.

2. Promise.all с 4 запросами: ошибка Drive (сервис-аккаунт не добавлен к файлу)
   роняла весь дашборд. Исправлено: складские запросы изолированы в отдельный
   .then/.catch — дашборд замеров отрисовывается независимо.

3. Секции склада теперь появляются с задержкой (non-blocking), а не блокируют
   отрисовку главного экрана.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 08:28:18 +03:00
wasrusgen
e97c84e126 fix: BACKEND_URL → api.wasrusgen1.pro (cloudflare tunnel истёк)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 07:50:53 +03:00
wasrusgen
cc38782b85 feat: arrivals module + refactor xlsx parser
- Добавлен /api/arrivals для «Поступление заказов на склад СПб.xlsx»
  (Drive ID захардкожен в ARRIVALS_FILE_ID, дефолт = 1kgrDEIGcVMFnSdZs1Y...)
- _parse_xlsx_groups() — единый парсер для обоих файлов:
  * находит строку заголовков динамически (первая строка с «Товар»),
    чтобы корректно работать с файлом «Поступление» (2 строки шапки перед хедером)
  * пропускает разделители «Кухни» / «Дозаказы» внутри листа
  * пропускает шаблонные пустые строки (Заказ/Дозаказ без данных)
- _xlsx_auth_manager() — вынесена общая проверка initData + роль
- config: поле arrivals_file_id
- frontend: вторая секция «📥 Поступление в СПб» на дашборде менеджера;
  renderManagerShipments принимает label-параметр и переиспользуется для обоих файлов;
  оба запроса загружаются параллельно с измерениями

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 07:49:56 +03:00
wasrusgen
f5ee9e5b33 feat: warehouse module — ОТГРУЗКИ.xlsx в дашборде менеджера
- backend: новый модуль drive.py (Google Drive download + 5-мин кэш)
- backend: /api/shipments — читает xlsx из Drive, парсит листы «ЗОВ ДД.ММ.ГГ»,
  возвращает позиции (Заказ/Дозаказ) сгруппированные по дате отгрузки с завода
- config: поле shipments_file_id (SHIPMENTS_FILE_ID env; дефолт = ID ОТГРУЗКИ.xlsx)
- frontend: секция «📦 Отгрузки» на главной менеджера (после активных проектов),
  загружается параллельно с замерами и pending; показывает последние 3 партии
- CSS: стили .ship-group / .ship-row / .ship-badge / .ship-check
- deps: добавлен openpyxl>=3.1.0

ВАЖНО после деплоя: добавить сервис-аккаунт как Viewer к ОТГРУЗКИ.xlsx в Drive
и прописать SHIPMENTS_FILE_ID в /opt/zov-tech/deploy/.env на сервере.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 07:21:23 +03:00
wasrusgen
34ef51c4c8 ux: FAB вместо кнопки «Новый клиент» в списке клиентов
- Убрана широкая кнопка btn-primary над поиском
- Добавлен FAB (56×56, position:fixed, bottom-right) — кружок с «+»
  всегда виден поверх списка при любой прокрутке
- .client-list: padding-bottom 84px — последняя карточка не прячется
- Пустое состояние: обновлён текст («Нажмите + чтобы завести первого»)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 23:30:28 +03:00
wasrusgen
63f4a73971 feat: SVG-пиктограммы схем замера в чек-листе
Добавлены 4 детальных обучающих SVG в ZAMER_PICTS:
- wall1: фронтальный вид чистой стены — ширина L, высота H,
  4 угла (ЛВ/ПВ/ЛН/ПН), дуга α° (замер угла), БАЗА: ПУ, Lc (середина)
- wall2: стена с дверным проёмом — общая L=A+Ш+B, высота двери В,
  три сегмента ниже с цветовым разделением
- wall3: стена с окном + коммуникации — Ш_ОК/В_ОК/П (подоконник),
  розетка R1 с двумя привязками (A горизонталь→ПУ, B вертикаль↑пол),
  труба Wc1 с привязками C и D
- topview_comms: вид сверху с пунктирными линиями привязки точек
  R1 и Wc1 к базовым углам стен

zamer-checklist.md: @pict:wall1 в разделе 2, @pict:wall3 в разделе 4,
@pict:wall2 в разделе 6, @pict:topview_comms в разделе 8

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 23:21:33 +03:00
wasrusgen
7a25ee3d36 feat: загрузка фото к замеру из карточки менеджера
- backend: _handle_measurement_add_photos — дозагрузка фото (до 20 шт за раз)
  сохраняет в PHOTOS_DIR, дописывает в колонку photos через update_cell_by_key
- clients.js: renderPhotoUploadBlock — выбор файлов, превью сетка 3 колонки,
  конвертация в dataURL, POST /api/measurement_add_photos, счётчик в заголовке
- podbor.css: .photo-upload-block, .photo-preview-grid, .photo-upload-actions
- index.html: cache bump → v=20260514p

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-15 22:44:45 +03:00
wasrusgen
5186afe0e0 feat: смена статуса замера из карточки + статус-бейдж
- backend: новый endpoint /api/measurement_set_status (cancelled / completed)
  только менеджер-владелец, только из requested/scheduled
- clients.js: renderMeasurement — статус-бейдж с цветом в шапке
  кнопки «Отметить выполненным» (requested) и «Отменить замер» (requested/scheduled)
  setMeasurementStatus() — confirm → API → reload
- podbor.css: .mz-status-badge, .mz-status-actions, .mz-status-btn, .ct-ok/.ct-err
- index.html: cache bump → v=20260514o

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-15 22:42:40 +03:00
wasrusgen
715ac96de8 feat: поиск клиентов на главном экране (фильтр по ФИО / телефону / договору)
- clients.js: renderList — поле поиска над списком, renderFiltered() фильтрует без API-запроса
- фильтрация: имя (подстрока), телефон (только цифры), номер договора
- счётчик обновляется: «Найдено N из M» при поиске, полная статистика без запроса
- podbor.css: .client-search-wrap, .client-search, .client-search-meta
- index.html: cache bump → v=20260514n

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-15 22:40:03 +03:00
wasrusgen
bfd661575c feat: история примечаний — append-only лента вместо одной записи
- backend: _handle_client_note — запись всегда append (не upsert), чтение возвращает notes[]
- clients.js: renderClientNoteBlock переписан — лог-лента всех записей, «+ Добавить» открывает textarea
- podbor.css: .note-history, .note-entry, .note-loading, .note-empty
- index.html: cache bump → v=20260514m

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-15 22:16:47 +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
016e3becdd feat: Яндекс.Карты в карточке клиента + gps_lat/lng в API клиентов
- backend: _ensure_client получает gps_lat/gps_lng поля
- backend: при агрегации Measurements берём gps_lat/gps_lng для клиента
- clients.js: в шапке карточки кнопка «🗺 Карта» если есть координаты
- podbor.css: стили .client-detail-addr, .map-link-btn
- index.html: cache bump → v=20260514k

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-15 22:11:17 +03:00
wasrusgen
bedef30465 feat: add entrance+floor fields, fix geocoder false-positive on locality match
- clients.js: new client + edit client forms get подъезд and этаж fields (optional)
- clients.js: address string includes подъезд/этаж when filled
- clients.js: splitAddress parses подъезд/этаж from stored address string
- clients.js: geocoder now checks result.kind — only shows ✓ for house/street precision;
  locality/province match shows warning "улица не найдена" without saving coords
- podbor.css: addr-grid grows to 3 rows (город/улица, дом/кв, подъезд/этаж)
- index.html: cache bump → v=20260514j

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-15 21:38:13 +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