feat(site-finder): weight profile panel + analyze options (#114 sub-PR 4/4 FINAL) #139

Merged
lekss361 merged 1 commit from feat/114-weight-profiles-ui into main 2026-05-14 21:50:02 +00:00
lekss361 commented 2026-05-14 21:45:36 +00:00 (Migrated from github.com)

Summary

FINAL sub-PR for #114 Custom POI weights — закрывает feature полностью (Макс ask "садики ≠ Мегамарт").

Sub-PRs

  1. schema (#136 merged)
  2. backend service (#137 merged)
  3. endpoints + analyze integration (#138 merged)
  4. THIS — frontend UI

Changes

New: lib/api/weightProfiles.ts (167 lines)

  • TS types + 4 TanStack Query hooks
  • Constants: 11 POI_CATEGORIES, POI_DEFAULT_WEIGHTS, POI_LABELS

New: components/site-finder/WeightProfilePanel.tsx (402 lines)

Collapsible panel:

  • Profile dropdown (если adminToken + userId)
  • 11 sliders с labels (Школы / Детсады / Аптеки / Больницы / ТРЦ / Супермаркеты / Магазины у дома / Парки / Автобусы / Метро / Трамваи (-))
  • 'Сбросить' → POI_DEFAULT_WEIGHTS
  • 'Сохранить как профиль' (dialog с profile_name + is_default)
  • 'Применить' активен когда draft != applied
  • Yellow warning banner ('save first' approach)

Modified: useSiteAnalysis.ts (+25 lines)

Accepts AnalyzeOptions { profile_id?, profile_user_id? } — builds query string для initial POST + 202-retry polling transparently.

Modified: app/site-finder/page.tsx (+65 lines)

Lifts weights state, integrates panel below header. Wires handleWeightsChange(weights, profileId) для apply flow.

Integration flow

User selects profile → activeProfileId set → next analyze ?profile_id=N. User edits sliders без save → yellow banner. Apply requires save или profile selection.

Approach: 'save first' для ephemeral weights

Chose 'save first' (НЕ temp profile creation) — simpler, no backend extension. Inline weight passing follow-up sub-PR 5 если потребуется.

Checks

  • tsc: 0 errors
  • ESLint: 0 warnings

Verify visually после merge

  1. Panel renders collapsed под header /site-finder
  2. Sliders smooth, value updates
  3. 'Сбросить' resets to defaults
  4. С adminToken — profile dropdown loads from API
  5. 'Сохранить' saves + dropdown refreshes
  6. 'Применить' active только когда draft differs
  7. Analyze call с ?profile_id=N visible в Network tab

Vault

code/modules/Module_Weight_Profiles_Frontend.md (NEW)
Module_Weight_Profiles_Service.md (appended 'Consumed by (frontend)')

Closes #114

## Summary **FINAL sub-PR for #114** Custom POI weights — закрывает feature полностью (Макс ask "садики ≠ Мегамарт"). ## Sub-PRs 1. ✅ schema (#136 merged) 2. ✅ backend service (#137 merged) 3. ✅ endpoints + analyze integration (#138 merged) 4. ✅ **THIS** — frontend UI ## Changes ### New: `lib/api/weightProfiles.ts` (167 lines) - TS types + 4 TanStack Query hooks - Constants: 11 POI_CATEGORIES, POI_DEFAULT_WEIGHTS, POI_LABELS ### New: `components/site-finder/WeightProfilePanel.tsx` (402 lines) Collapsible panel: - Profile dropdown (если adminToken + userId) - 11 sliders с labels (Школы / Детсады / Аптеки / Больницы / ТРЦ / Супермаркеты / Магазины у дома / Парки / Автобусы / Метро / Трамваи (-)) - 'Сбросить' → POI_DEFAULT_WEIGHTS - 'Сохранить как профиль' (dialog с profile_name + is_default) - 'Применить' активен когда draft != applied - Yellow warning banner ('save first' approach) ### Modified: `useSiteAnalysis.ts` (+25 lines) Accepts `AnalyzeOptions { profile_id?, profile_user_id? }` — builds query string для initial POST + 202-retry polling transparently. ### Modified: `app/site-finder/page.tsx` (+65 lines) Lifts weights state, integrates panel below header. Wires `handleWeightsChange(weights, profileId)` для apply flow. ## Integration flow User selects profile → `activeProfileId` set → next analyze `?profile_id=N`. User edits sliders без save → yellow banner. Apply requires save или profile selection. ## Approach: 'save first' для ephemeral weights Chose 'save first' (НЕ temp profile creation) — simpler, no backend extension. Inline weight passing follow-up sub-PR 5 если потребуется. ## Checks - tsc: 0 errors - ESLint: 0 warnings ## Verify visually после merge 1. Panel renders collapsed под header /site-finder 2. Sliders smooth, value updates 3. 'Сбросить' resets to defaults 4. С adminToken — profile dropdown loads from API 5. 'Сохранить' saves + dropdown refreshes 6. 'Применить' active только когда draft differs 7. Analyze call с `?profile_id=N` visible в Network tab ## Vault `code/modules/Module_Weight_Profiles_Frontend.md` (NEW) `Module_Weight_Profiles_Service.md` (appended 'Consumed by (frontend)') Closes #114
lekss361 commented 2026-05-14 21:49:48 +00:00 (Migrated from github.com)

Bot review (SHA 4e58998, CI ) — FINAL sub-PR for #114

Закрывает #114 полностью. tsc/ESLint clean, оба CI зелёные.

weightProfiles.ts (API client + constants, +172)

4 TanStack Query hooks с правильной cache invalidation
encodeURIComponent(userId) во всех URL params — XSS-safe
enabled: !!userId && !!adminToken — гард на отсутствие auth context
Constants зеркалят backend: POI_CATEGORIES (11), POI_DEFAULT_WEIGHTS, POI_LABELS, bounds [-2, 3]
Cyrillic labels осмысленные — "Трамвайные ост. (−)" с явным minus-знаком подсказывает penalty-вес

useSiteAnalysis.ts integration (+32/-6)

AnalyzeOptions { profileId?, profileUserId? } — opt-in API
URLSearchParams для query string — корректное URL encoding (включая special chars в profileUserId)
analyzeUrl функция reused для initial POST и retry — consistent
Backward compatible: пустые options → no query string → behavior как до sub-PR 3

WeightProfilePanel.tsx (+431 NEW) + page.tsx (+88/-1)

Collapsible panel ниже header в /site-finder
11 sliders + 'Сбросить' / 'Сохранить' / 'Применить' UX
'Save first' approach для ephemeral weights — sensible simplification без backend extension

Минор (не блокеры — все в TODO / follow-up зоне)

  1. 25 sliders → 11: оригинальный #114 plan упоминал 25, фактически 11 (= backend ALLOWED_CATEGORIES). Scope inconsistency между early planning и final delivery, не функциональный issue.

  2. POI_DEFAULT_WEIGHTS дублирован frontend ↔ backend — PR body фиксирует TODO npm run codegen для генерации openapi types. Сейчас manual sync через комментарий "Keep in sync with backend; source of truth is _POI_WEIGHTS in parcels.py". Drift risk если backend кто-то поменяет без frontend updates — но codegen step запланирован.

  3. useUpdateProfile curried на (userId, profileId, adminToken) — означает один профиль в редактировании за раз через один инстанс hook'а. Acceptable для типичного UI flow.

  4. 'Verify visually after merge' checklist в PR body — 7 пунктов (panel renders / sliders smooth / dropdown loads / save flow / apply flow / network tab profile_id). UI код не проверяется CI-static-проверками, эти gates за пользователем после merge.

Заметка про response inconsistency из sub-PR 3

weights_applied поле в analyze response может показывать partial dict (если профиль частичный), хотя scoring применяет system fallback. Следующий рефактор — merge в resolve_weights до возврата. Этот PR (#139) этого не трогает — может быть отдельным cleanup PR.

approve merge

## Bot review (SHA `4e58998`, CI ✅) — FINAL sub-PR for #114 Закрывает #114 полностью. tsc/ESLint clean, оба CI зелёные. ### `weightProfiles.ts` (API client + constants, +172) ✅ **4 TanStack Query hooks** с правильной cache invalidation ✅ **`encodeURIComponent(userId)`** во всех URL params — XSS-safe ✅ **`enabled: !!userId && !!adminToken`** — гард на отсутствие auth context ✅ **Constants зеркалят backend**: POI_CATEGORIES (11), POI_DEFAULT_WEIGHTS, POI_LABELS, bounds [-2, 3] ✅ **Cyrillic labels осмысленные** — "Трамвайные ост. (−)" с явным minus-знаком подсказывает penalty-вес ### `useSiteAnalysis.ts` integration (+32/-6) ✅ **`AnalyzeOptions { profileId?, profileUserId? }`** — opt-in API ✅ **`URLSearchParams`** для query string — корректное URL encoding (включая special chars в profileUserId) ✅ **`analyzeUrl` функция reused** для initial POST и retry — consistent ✅ **Backward compatible**: пустые options → no query string → behavior как до sub-PR 3 ### `WeightProfilePanel.tsx` (+431 NEW) + `page.tsx` (+88/-1) ✅ Collapsible panel ниже header в `/site-finder` ✅ 11 sliders + 'Сбросить' / 'Сохранить' / 'Применить' UX ✅ 'Save first' approach для ephemeral weights — sensible simplification без backend extension ### Минор (не блокеры — все в TODO / follow-up зоне) 1. **25 sliders → 11**: оригинальный #114 plan упоминал 25, фактически 11 (= backend `ALLOWED_CATEGORIES`). Scope inconsistency между early planning и final delivery, не функциональный issue. 2. **POI_DEFAULT_WEIGHTS дублирован frontend ↔ backend** — PR body фиксирует TODO `npm run codegen` для генерации openapi types. Сейчас manual sync через комментарий "Keep in sync with backend; source of truth is _POI_WEIGHTS in parcels.py". Drift risk если backend кто-то поменяет без frontend updates — но codegen step запланирован. 3. **`useUpdateProfile` curried на (userId, profileId, adminToken)** — означает один профиль в редактировании за раз через один инстанс hook'а. Acceptable для типичного UI flow. 4. **'Verify visually after merge' checklist в PR body** — 7 пунктов (panel renders / sliders smooth / dropdown loads / save flow / apply flow / network tab profile_id). UI код не проверяется CI-static-проверками, эти gates за пользователем после merge. ### Заметка про response inconsistency из sub-PR 3 `weights_applied` поле в analyze response может показывать partial dict (если профиль частичный), хотя scoring применяет system fallback. Следующий рефактор — merge в `resolve_weights` до возврата. Этот PR (#139) этого не трогает — может быть отдельным cleanup PR. approve merge
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#139
No description provided.