fix(sf-11): Top layouts — sales period column + drill-in expand #285
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#285
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/sf-11-sales-period-drill-in"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
SF FixList #11 (Wave 2) — frontend-only: добавлена колонка «Срок продажи» (formula
supply / velocity_per_month) + click-to-expand row с источникамиcompetitor_obj_ids.Изменения
frontend/src/components/site-finder/BestLayoutsBlock.tsx:Math.ceil(supply / velocity), 3-color semantic (text-emerald-600<12,text-slate-60012-24,text-amber-600>24),—дляvelocity<=0expandedRows: Set<string>, click toggle, chevron icon (CSS rotate), competitor_obj_ids как DOM.РФ badges (preview 5 + «и ещё N» / «свернуть»)Test plan
66:41:0702048:27→ Market → BestLayoutsBlock → РассчитатьNo backend changes — поля
competitor_obj_idsиsupply_count_snapshotуже есть вTopLayoutRow.Related: epic #271 item #11 · Wave 2
Deep review — verdict: 🟠 HIGH (не мержу)
Логика sales period и drill-in корректна, vault entry на месте. Но есть 1 high-issue про неработающие ссылки + 2 medium про A11y/Fragment key.
🟠 High — DOM.РФ URL вероятно 404
href={\https://наш.дом.рф/сервисы/каталог-новостроек/объект/${id}`}— кириллический slug. Реальный путь DOM.РФ catalog — латиница (/services/catalog-newbuildings/object/{id}или/наш.дом.рф/services/calculator/...). Браузер URL-encode кириллицу в%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D1%8B/...`, и сервер DOM.РФ скорее всего вернёт 404.Verify руками перед мержем — открой
https://наш.дом.рф/сервисы/каталог-новостроек/объект/12345(любой реальныйidиз ответа/best-layouts). Если 404 → нужен правильный URL pattern. В codebase нет precedent (grep "наш.дом.рф"— только UI-надпись).🟡 Medium — React Fragment без key
.map()возвращает Fragment'ы без key → React warning. Fix:🟡 Medium — A11y:
<tr onClick>без keyboard<tr onClick={() => toggleRow(...)}>— div-onClick anti-pattern (ui-ux.md: «Кликабельный элемент —<button>или<a>, не<div onClick>»). Нетrole="button",tabIndex={0},aria-expanded, keyboard (Enter/Space) handler. Финдиректор с клавиатуры дрилл-ин не откроет.Минимум:
tabIndex={hasCompetitors ? 0 : undefined},aria-expanded={isExpanded},onKeyDownдля Enter/Space.🟡 Medium — inline hex вне токенов (
ui-ux.mdHARD)color: "#9ca3af"(em-dash, chevron) →var(--fg-tertiary)background: "#f0f4ff"(drill-in row) →var(--accent-soft)(#DBEAFE) или новый tokencolor: "#6b7280"(ЖК-источники label) →var(--fg-secondary)bg-blue-100 text-blue-700(badges) — поui-ux.mdдолжен быть<Badge variant="neutral">. Допустимо отложить, но flag.✅ Что OK
salesPeriodMonthsguardvelocity <= 0 → null— корректно для PR #284 unmapped (velocity=0), negative, NaN protection.Setupdate:new Set(prev); next.has(k) ? next.delete : next.add— правильный паттерн.<12strict,<=24neutral,>24warn — consistent.competitor_obj_ids: number[]в типе →.length,.slice,.maptype-safe.fixes/Fix_SF_Sales_Period_DrillIn_May17.mdсоздана.target="_blank"+rel="noopener noreferrer"на DOM.РФ ссылках.Edge cases verified
supply=0, velocity>0→Math.ceil(0/v) = 0→ emerald «0 мес». Acceptable, но если хочешь — guardsupply <= 0 → null.hasCompetitors=false→cursor: "default", chevron не рендерится, click не дёргает state. OK.Что сделать перед merge
tabIndex+aria-expanded+onKeyDown) — 5 строк.После 1+2+3 — мержу.
Related: epic #271 item #11 · Wave 2 · review by deep-code-reviewer
Зафикшено в commit
1959283:🟠 HIGH — DOM.РФ URL — заменил кириллический slug на латинский:
/сервисы/каталог-новостроек/объект/${id}→/services/catalog-newbuildings/object/${id}🟡 Fragment key —
<>без key →<Fragment key={row.signature}>(import добавлен из react); закрывающий</>→</Fragment>.key={row.signature}снят с<tr>(Fragment теперь несёт key).🟡 A11y —
<tr onClick>теперь:tabIndex={hasCompetitors ? 0 : undefined}— keyboard focusrole={hasCompetitors ? "button" : undefined}aria-expanded={hasCompetitors ? isExpanded : undefined}— screen readeronKeyDownhandler: Enter/Space → toggleRow + preventDefault🟡 Inline hex tokens — отложено в follow-up PR (epic #271 item #19 уже existing track для полного hex→tokens рефактора BestLayoutsBlock).
Готов к re-review.
Deep Code Review — round 2 — verdict APPROVE
Round 1 (
d3a51c3) blockers перепроверены на head1959283a:/сервисы/каталог-новостроек/объект/{id}→ 404/services/catalog-newbuildings/object/{id}(host остался Cyrillic — корректно обрабатывается IDN)<>...</>без key → React warningimport { Fragment }+<Fragment key={row.signature}>...</Fragment><tr onClick>без keyboard supporttabIndex={0},role="button",aria-expanded,onKeyDown(Enter/Space) сpreventDefault. Conditional наhasCompetitors— non-interactive rows не фокусируются#9ca3af/bg-blue-100Anti-regression — все сохранены:
salesPeriodMonths:velocity <= 0 → nullguard intact<12emerald,<=24slate,>24amber — без измененийtoggleRow:new Set(prev)→ mutate → return (immutability OK)Bonus changes (+package.json/+package-lock.json):
typescript: "^5.5.0"→"5.9.3"exact pin. Devdep, harmless. Acceptable scope creep.ARIA note:
<tr role="button">— стандартный composite pattern для expandable rows, screen readers announce корректно.Smoke checks для прода:
velocity=0row → колонка показывает—Merged:
eb865a08bca58b058d68f80619361dd7ef3927ab