fix(sf-13): MarketTab above-the-fold headline + 3 KPI cards #294

Merged
lekss361 merged 1 commit from fix/sf-13-above-fold-kpi into main 2026-05-17 14:10:07 +00:00
Owner

Summary

SF FixList #13 (Wave 2) — Market tab без headline-bar и KPI row выше fold (нарушение code/patterns/UI_Brief_GenDesign_May17).

Изменения

frontend/src/components/site-finder/MarketTab.tsx:

  1. Headline-bar (dark) — первой в JSX, использует var(--bg-headline, #0F172A) / var(--fg-on-dark, #E2E8F0):
    • «N строящихся в Xкм · велосити Y м²/мес · рекомендуемый mix Z%»
  2. 3 KPI cards в grid grid-cols-3 gap-3 mb-4:
    • Активные ЖК: data.competitors.length
    • Велосити: data.velocity.monthly_velocity_sqm в м²/мес
    • Срок продаж: pipeline_24mo.flats_total / (velocity / 50) (proxy через 50 м² avg)
  3. Conditional render — блок видим только при activeCount > 0 || velocityPerMonth !== null (нет ghost при absence)

Helpers: getRadiusKm, getMaxMixPct, getSalesPeriodMonths — derive из existing ParcelAnalysis fields.

Vault: fixes/Fix_SF_AboveTheFold_KPI_May17.md.

Test plan

  • cad 66:41:0702048:27 → Market tab → headline-bar выше fold
  • Headline shows real numbers (N · Y · Z%)
  • 3 KPI cards visible, tabular-nums, «—» когда данных нет
  • tsc --noEmit pass

Note: site_status='Строящиеся' filter будет точным после deploy PR #289+#290 (Wave 2/3). Сейчас competitors.length proxy.

Related: epic #271 item #13 · Wave 2

## Summary SF FixList #13 (Wave 2) — Market tab без headline-bar и KPI row выше fold (нарушение `code/patterns/UI_Brief_GenDesign_May17`). ## Изменения `frontend/src/components/site-finder/MarketTab.tsx`: 1. **Headline-bar** (dark) — первой в JSX, использует `var(--bg-headline, #0F172A)` / `var(--fg-on-dark, #E2E8F0)`: - «N строящихся в Xкм · велосити Y м²/мес · рекомендуемый mix Z%» 2. **3 KPI cards** в `grid grid-cols-3 gap-3 mb-4`: - **Активные ЖК**: `data.competitors.length` - **Велосити**: `data.velocity.monthly_velocity_sqm` в м²/мес - **Срок продаж**: `pipeline_24mo.flats_total / (velocity / 50)` (proxy через 50 м² avg) 3. Conditional render — блок видим только при `activeCount > 0 || velocityPerMonth !== null` (нет ghost при absence) Helpers: `getRadiusKm`, `getMaxMixPct`, `getSalesPeriodMonths` — derive из existing `ParcelAnalysis` fields. Vault: `fixes/Fix_SF_AboveTheFold_KPI_May17.md`. ## Test plan - [ ] cad `66:41:0702048:27` → Market tab → headline-bar выше fold - [ ] Headline shows real numbers (N · Y · Z%) - [ ] 3 KPI cards visible, tabular-nums, «—» когда данных нет - [ ] tsc --noEmit pass **Note**: `site_status='Строящиеся'` filter будет точным после deploy PR #289+#290 (Wave 2/3). Сейчас competitors.length proxy. Related: epic #271 item #13 · Wave 2
lekss361 added 1 commit 2026-05-17 14:04:16 +00:00
Add headline sentence (N строящихся · велосити · mix%) and a 3-card KPI
row (Активные / Велосити / Срок продаж) before any other content in
MarketTab, so the financial director sees the verdict above the fold
without scrolling.

- Headline uses var(--bg-headline) dark bar per UI_Brief_GenDesign_May17
- KPI cards: grid-cols-3, bg-white border border-slate-200 rounded-lg p-3
- Derives radius_km from market_trend.radius_km or pipeline_24mo.radius_km
- Derives velocity from data.velocity.monthly_velocity_sqm (existing field)
- Derives maxMixPct from success_recommendation.ranking max success_score
- Derives salesPeriodMonths from pipeline_24mo.flats_total / velocity
- site_status filter deferred to fix #2/#3 (field not yet in type)
lekss361 merged commit 4b967343f8 into main 2026-05-17 14:10:07 +00:00
Author
Owner

Merged via deep-code-reviewer — verdict APPROVE.

Cross-file impact verified:

  • Velocity.monthly_velocity_sqm: number (non-optional) — site-finder.ts:236. Optional chain data.velocity?.monthly_velocity_sqm ?? null корректен (velocity сам Optional на ParcelAnalysis).
  • Pipeline24mo.flats_total: number + radius_km?: number — derive logic OK.
  • MarketTrend.radius_km: number (non-optional) — getRadiusKm fallback chain market_trend → pipeline_24mo → 3 корректен.
  • SuccessRankingBucket.success_score: number (0..1) → Math.max * 100 → корректно для headline (description упоминала mix[*].pct — на самом деле используется ranking success_score, в коде правильно).
  • Existing blocks (Competitors, BestLayoutsBlock, Velocity, MarketTrend, Pipeline24mo, SuccessRecommendation) сохранены ниже новой above-fold секции — порядок PR #272 не нарушен.

Correctness:

  • Division-by-zero guard: if (!supply || !velocitySqm || velocitySqm <= 0) return null — корректно.
  • Conditional render showHeadlineBar = activeCount > 0 || velocityPerMonth !== null — no ghost UI.
  • TS strict: helpers принимают ParcelAnalysis (не undefined) — caller гарантирует через Props.

Style nits (не блокирующие, для следующей итерации):

  • KPI-карточки и headline-bar инлайнят hex (#fff, #e6e8ec, #111, #5b6066, #73767e) — по .claude/rules/ui-ux.md это должны быть токены (var(--bg-card), var(--border-card), var(--fg-primary), var(--fg-secondary), var(--fg-tertiary)). Только headline-bar использует var(--bg-headline, ...) правильно.
  • Не используется существующий <KpiCard> (components/analytics/KpiCard.tsx) — дублирование стилей. Refactor → KpiCard reuse в отдельном PR (его API нужно расширить для unit/hint, что не входит в scope этого hotfix).
  • Headline-bar без semantic тега (<div>, не <h2> / role="status") — небольшая a11y потеря. KPI label uppercase + letter-spacing 0.04em — соответствует ui-ux rules.
  • pipeline_24mo.flats_total / (velocity / 50) — 50 м² avg proxy документирован JSDoc, acceptable до появления real avg_unit_area из backend.

Anti-regression: main content (Competitors, BestLayouts, Velocity, MarketTrend, Pipeline24mo, SuccessRecommendation) preserved below new above-fold block. PR #272 порядок секций сохранён.

Recommended follow-up issue: refactor KPI inline styles → tokens + reuse <KpiCard> (SF FixList: low priority, UX consistency).

Merged via deep-code-reviewer — verdict APPROVE. **Cross-file impact verified:** - `Velocity.monthly_velocity_sqm: number` (non-optional) — `site-finder.ts:236`. Optional chain `data.velocity?.monthly_velocity_sqm ?? null` корректен (velocity сам Optional на ParcelAnalysis). - `Pipeline24mo.flats_total: number` + `radius_km?: number` — derive logic OK. - `MarketTrend.radius_km: number` (non-optional) — `getRadiusKm` fallback chain `market_trend → pipeline_24mo → 3` корректен. - `SuccessRankingBucket.success_score: number` (0..1) → `Math.max * 100` → корректно для headline (description упоминала `mix[*].pct` — на самом деле используется ranking success_score, в коде правильно). - Existing blocks (Competitors, BestLayoutsBlock, Velocity, MarketTrend, Pipeline24mo, SuccessRecommendation) сохранены ниже новой above-fold секции — порядок PR #272 не нарушен. **Correctness:** - Division-by-zero guard: `if (!supply || !velocitySqm || velocitySqm <= 0) return null` — корректно. - Conditional render `showHeadlineBar = activeCount > 0 || velocityPerMonth !== null` — no ghost UI. - TS strict: helpers принимают `ParcelAnalysis` (не undefined) — caller гарантирует через Props. **Style nits (не блокирующие, для следующей итерации):** - KPI-карточки и headline-bar инлайнят hex (`#fff`, `#e6e8ec`, `#111`, `#5b6066`, `#73767e`) — по `.claude/rules/ui-ux.md` это должны быть токены (`var(--bg-card)`, `var(--border-card)`, `var(--fg-primary)`, `var(--fg-secondary)`, `var(--fg-tertiary)`). Только headline-bar использует `var(--bg-headline, ...)` правильно. - Не используется существующий `<KpiCard>` (`components/analytics/KpiCard.tsx`) — дублирование стилей. Refactor → KpiCard reuse в отдельном PR (его API нужно расширить для unit/hint, что не входит в scope этого hotfix). - Headline-bar без semantic тега (`<div>`, не `<h2>` / role="status") — небольшая a11y потеря. KPI label uppercase + letter-spacing 0.04em — соответствует ui-ux rules. - `pipeline_24mo.flats_total / (velocity / 50)` — 50 м² avg proxy документирован JSDoc, acceptable до появления real avg_unit_area из backend. **Anti-regression:** main content (Competitors, BestLayouts, Velocity, MarketTrend, Pipeline24mo, SuccessRecommendation) preserved below new above-fold block. PR #272 порядок секций сохранён. Recommended follow-up issue: refactor KPI inline styles → tokens + reuse `<KpiCard>` (SF FixList: low priority, UX consistency).
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#294
No description provided.