gendesign/frontend/src/components/site-finder/MarketTab.tsx
lekss361 ade511bff3 feat(site-finder): D4 pipeline 24mo — future competition (#36)
Backend (parcels.py):
- Запрос к domrf_kn_objects в радиусе 5км с ready_dt BETWEEN NOW() AND NOW()+24mo.
- _aggregate_pipeline() — сводка: objects_count, flats_total, by_class (эконом/
  комфорт/бизнес/...), by_quarter (хронологически, для UI bar), severity
  (low <500 / medium <3000 / high) per spec, top_objects (десятка по flat_count desc).
- Поле analyze.pipeline_24mo. Backward-compat — optional.

Frontend:
- Pipeline24moBlock.tsx — severity badge + 3 summary numbers (объектов, квартир,
  горизонт/радиус), by-class chips, гистограмма bar по кварталам сдачи
  (нормирована на max), разворачиваемый top-N список с классом + датой сдачи.
- Добавлен в MarketTab выше "Market trend".
- TS типы: Pipeline24mo, PipelineObject, PipelineQuarterSlot.

Closes #36. Relates to #19 (Конкурентный 360 — закрыт ранее в #36 scope).
2026-05-12 01:00:46 +03:00

96 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import type { ParcelAnalysis } from "@/types/site-finder";
import { MarketTrendBlock } from "./MarketTrendBlock";
import { CompetitorTable } from "./CompetitorTable";
import { Pipeline24moBlock } from "./Pipeline24moBlock";
import { SuccessRecommendationBlock } from "./SuccessRecommendationBlock";
interface Props {
data: ParcelAnalysis;
}
export function MarketTab({ data }: Props) {
const hasTrend = "market_trend" in data;
const hasRecommendation = "success_recommendation" in data;
const hasPipeline = data.pipeline_24mo !== undefined;
const hasAny =
hasTrend || hasRecommendation || hasPipeline || data.competitors.length > 0;
return (
<div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
{/* D4 (#36) — Pipeline 24mo */}
{data.pipeline_24mo && <Pipeline24moBlock data={data.pipeline_24mo} />}
{/* Market trend */}
{hasTrend && (
<div
style={{
border: "1px solid #e5e7eb",
borderRadius: 10,
padding: "14px 18px",
background: "#fff",
}}
>
<MarketTrendBlock trend={data.market_trend} />
</div>
)}
{/* Competitors */}
{data.competitors.length > 0 && (
<div>
<div
style={{
fontSize: 12,
fontWeight: 600,
color: "#6b7280",
textTransform: "uppercase",
letterSpacing: "0.05em",
marginBottom: 12,
}}
>
Конкуренты
</div>
<CompetitorTable
competitors={data.competitors}
districtName={data.district?.district_name}
/>
</div>
)}
{/* Success recommendation */}
{hasRecommendation && (
<div>
<div
style={{
fontSize: 12,
fontWeight: 600,
color: "#6b7280",
textTransform: "uppercase",
letterSpacing: "0.05em",
marginBottom: 12,
}}
>
Что хорошо продаётся
</div>
<SuccessRecommendationBlock
recommendation={data.success_recommendation}
/>
</div>
)}
{!hasAny && (
<div
style={{
fontSize: 13,
color: "#9ca3af",
padding: "24px 0",
textAlign: "center",
}}
>
Рыночные данные недоступны
</div>
)}
</div>
);
}