Commit Graph

242 Commits

Author SHA1 Message Date
wasrusgen
b6bf2eaf80 docs: добавить принципы работы Claude в CLAUDE.md 2026-05-18 08:25:50 +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
20665d73f3 fix: ARRIVALS_FILE_ID = SHIPMENTS_FILE_ID (один файл ОТГРУЗКИ)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:23:38 +03:00
wasrusgen
c7db038659 feat(lint): WCAG-контраст в CSS-линтере + fix shipments Drive ID
- lint_css.py: проверка контраста по WCAG 4.5:1 для HEX-цветов,
  разделение ошибок/предупреждений, проверка против всех тем
- config.py: SHIPMENTS_FILE_ID обновлён на актуальный из AI АНАЛИТИКА
  ARRIVALS_FILE_ID сброшен в пустой (ID пока не найден)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 18:19:35 +03:00
wasrusgen
87b8d0f3d6 feat(ci): автоматический тестировщик — CSS-линтер + smoke API + GitHub Actions CI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:59:41 +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
a1892f11a7 chore: добавить агент /project:ui-check — обязательная проверка перед UI-коммитами
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:00:45 +03:00
wasrusgen
dbcd2f37c5 fix(bot): меню-кнопка CRM открывает ?role=manager — пропуск выбора роли
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 14:59:13 +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
16b52cdfe8 chore: добавить CLAUDE.md — pre-commit checklist для UI/CSS изменений
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:11:44 +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
cd587f846a fix(sheets): add TTL in-memory cache to prevent Sheets API 429 quota errors
Every /api/me call was reading the entire Users sheet via get_all_values(),
exhausting the 60 reads/minute quota under any load. Added a 90-second
TTL cache keyed by sheet name:
- _cached_get_all_values(): returns cached data or refreshes on miss/expiry
- _invalidate_cache(): called after every write (append_row, append_named_row,
  update_cell_by_key) so stale data is never served after mutations
- Patched: find_row, update_cell_by_key, list_users_with_role

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 00:33:52 +03:00
wasrusgen
a3b0ff511c fix: append_named_row uses RAW to preserve + in phones; fix seed script to use append_named_row 2026-05-16 13:09:34 +03:00
wasrusgen
632bce8f33 fix: wrap client_create/delete/update in try/except for proper error surfacing 2026-05-16 12:35:42 +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
2479ac05cf chore: add Claude Code PostToolUse syntax-check hook
.claude/settings.json — hook fires after every Edit/Write:
- .js files  → node --check
- .py files  → py_compile.compile (doraise=True)
- exit 2 on failure → Claude sees the error immediately and fixes it

Scripts:
- .claude/hooks/syntax_check.ps1  (Windows, primary)
- .claude/hooks/syntax_check.sh   (bash fallback)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 11:01:34 +03:00
wasrusgen
f64a64e834 feat: canonicalize Measurements schema on startup + full column-order repair
_ensure_measurements_sheet() now:
1. Creates sheet with canonical headers if missing
2. Adds any missing columns
3. If column ORDER doesn't match _measurement_columns() — migrates all
   data rows in-place: reads by column name, rewrites in canonical order

@app.on_event("startup") calls _ensure_measurements_sheet() via
asyncio.to_thread so column order is always corrected on deploy,
not just on first client_create.

This guarantees append_named_row() always finds columns in expected
positions, eliminating the silent data-in-wrong-column bug.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 11:00:52 +03:00
wasrusgen
8318b25999 fix: write Measurements rows by column name, not by position
Root cause: _row_for_measurement() returned a positional list based on
_measurement_columns() order, but the actual Google Sheet may have
columns in a different order (if the sheet was created before new
columns were added and _ensure_measurements_sheet() appended them
at the end rather than in the middle). Values ended up in wrong
columns — client_name, manager_tg_id etc. were misaligned, so
_handle_clients couldn't match any rows and returned an empty list.

Fix:
- _row_for_measurement() now returns dict {col_name: value}
- sheets.append_named_row() reads real headers from the sheet and
  builds the positional row accordingly — safe regardless of column order
- All three Measurements append calls updated to use append_named_row

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 10:56:45 +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
546c62f13f feat: ежедневные уведомления о годовщинах договоров
- Backend: новый GET /api/daily_reminders (внутренний, Bearer-токен)
  сканирует Measurements, находит клиентов у которых сегодня годовщина
  contract_date (по МСК), дедуплицирует по manager+client_key
- Backend config: поле internal_secret (INTERNAL_SECRET)
- Bot: фоновая задача _anniversary_scheduler — каждый день в 09:00 МСК
  вызывает эндпоинт и рассылает менеджерам HTML-сообщение с годовщиной
- Bot config: поля internal_secret + backend_url (BACKEND_URL)
- deploy/.env.example: добавлены INTERNAL_SECRET

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 23:00:56 +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
wasrusgen
cbea202de5 fix(note): view/edit toggle — textarea closes after save
Note block now has display mode (read-only text) and edit mode (textarea).
Default is display. "Изменить" opens editor, "Сохранить" saves and returns
to display, "Отмена" discards. No more always-open textarea confusion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:18:16 +03:00