"use client"; /** * PticaScenarios — root of the ПТИЦА dark «Сценарии» tab (§22 Прогноз). * * Reads the REAL async forecast via useParcelForecastQuery(cad). The forecast is * enqueued fire-and-forget by /analyze on page mount — this hook only polls. While * the envelope is `pending` (or the first request is loading) we render a CALM * skeleton («Прогноз считается…»), NOT an error: the poll resolves once the report * is assembled. Once `report` is present we compose the sub-blocks: * 3 scenario cards · горизонты (chart + table) · сравнение · уверенность + * рекомендованный продукт · прозрачность скоринга. * * The horizon (6/12/18 мес) selector lives in this tab's header; its state is * lifted to PticaPageContent so the hero/other tabs can share it. */ import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css"; import { useParcelForecastQuery } from "@/lib/site-finder-api"; import type { ForecastReport, ForecastSegment } from "@/types/forecast"; import { ScenarioCards } from "./ScenarioCards"; import { HorizonForecast } from "./HorizonForecast"; import { ScenarioCompareTable } from "./ScenarioCompareTable"; import { ConfidencePanel } from "./ConfidencePanel"; import { RecommendedProduct } from "./RecommendedProduct"; import { ScoringTransparency } from "./ScoringTransparency"; interface Props { cad: string; horizon: number; onHorizonChange: (h: number) => void; } const HORIZONS = [6, 12, 18]; function segmentLabel(segment: ForecastSegment | undefined): string | null { if (!segment) return null; const parts = [segment.obj_class, segment.room_bucket].filter( (p): p is string => !!p, ); return parts.length > 0 ? parts.join(" · ") : null; } export function PticaScenarios({ cad, horizon, onHorizonChange }: Props) { const { data, isLoading, error } = useParcelForecastQuery(cad); const header = (

Сценарии{" "} прогноз спроса и предложения · §22

Горизонт
{HORIZONS.map((h) => ( ))}
); // ── Pending / loading → calm skeleton (NOT an error) ───────────────────────── const report: ForecastReport | undefined = data?.status === "ready" ? data.report : undefined; const pending = isLoading || (!report && !error); if (pending) { return (
{header}
); } // ── Error / no report → honest empty panel ─────────────────────────────────── if (error || !report) { return (
{header}
Прогноз §22 недоступен для этого участка.
); } const fm = report.future_market; const targetHorizon = horizon; return (
{header}
6.3 · уверенность · 6.4 · рекомендация по продукту
{report.product_tz ? ( ) : (
Рекомендация по продукту недоступна.
)}
{report.scoring && }
); }