"use client"; import { useState } from "react"; import Link from "next/link"; import { useQueryClient } from "@tanstack/react-query"; import { HorizonSelector } from "@/components/site-finder/HorizonSelector"; import { Section1ParcelInfo } from "@/components/site-finder/analysis/Section1ParcelInfo"; import { Section2NetworksUtilities } from "@/components/site-finder/analysis/Section2NetworksUtilities"; import { Section3SettingsAndCompetitors } from "@/components/site-finder/analysis/Section3SettingsAndCompetitors"; import { Section4Estimate } from "@/components/site-finder/analysis/Section4Estimate"; import { Section5Atmosphere } from "@/components/site-finder/analysis/Section5Atmosphere"; import { Section6Forecast } from "@/components/site-finder/analysis/Section6Forecast"; import { useParcelAnalyzeQuery } from "@/lib/site-finder-api"; import type { ParcelAnalysis } from "@/types/site-finder"; // ── Props ───────────────────────────────────────────────────────────────────── interface Props { cad: string; } // ── Page Content (client — needs TanStack Query) ─────────────────────────────── export function AnalysisPageContent({ cad }: Props) { const [horizon, setHorizon] = useState(12); const queryClient = useQueryClient(); const { data, isLoading, isFetching, error } = useParcelAnalyzeQuery( cad, horizon, ); // Changing the horizon re-runs /analyze (expensive ~10-30s), which re-enqueues // the async §22 forecast (recompute ~30-180s). Invalidating ["parcel-forecast", // cad] forces Section 6's poll to re-fetch; until the new run is ready it may // still show the previous run's emphasis. The selectedHorizon prop passed to // Section6Forecast gives an instant client-side target highlight regardless — // that's intended. function handleHorizonChange(h: number) { setHorizon(h); void queryClient.invalidateQueries({ queryKey: ["parcel-forecast", cad] }); } // ── Loading ──────────────────────────────────────────────────────────────── if (isLoading) { return (
Анализируем участок {cad}…
); } // ── Error ────────────────────────────────────────────────────────────────── if (error || !data) { const msg = error instanceof Error ? error.message : "Ошибка загрузки анализа"; return (
{msg}
Вернуться к Site Finder
); } // ── Results layout ───────────────────────────────────────────────────────── // Cast to ParcelAnalysis (superset of ParcelAnalyzeResponse) — both describe // the same /analyze endpoint; Section3 requires the richer type. const analysis = data as unknown as ParcelAnalysis; const districtName = analysis.district?.district_name ?? "—"; const areaHa = analysis.geometry_suitability?.area_ha; const areaStr = areaHa != null ? `${areaHa.toFixed(2)} га` : null; return (
{/* Breadcrumb */} {/* Controls row — horizon selector drives the analyze + forecast queries */}
{/* Page layout: sidebar (240px) + main scroll */}
{/* ── Sidebar nav ───────────────────────────────────────────────── */} {/* ── Main scroll area ──────────────────────────────────────────── */}
{/* Section 1 — IMPLEMENTED in A5 */} {/* Section 2 — IMPLEMENTED in A6 */} {/* Section 3 — IMPLEMENTED in A7 */} {/* Section 4 — IMPLEMENTED in A10 */} {/* Section 5 — IMPLEMENTED in A11 */} {/* Section 6 — IMPLEMENTED in 958-B3 (§22 forecast) */}
); } // ── Breadcrumb ───────────────────────────────────────────────────────────────── function Breadcrumb({ cad, districtName, areaStr, }: { cad: string; districtName?: string; areaStr?: string | null; }) { const parts = [cad]; if (districtName && districtName !== "—") parts.push(districtName); if (areaStr) parts.push(areaStr); return (
Site Finder / {parts.map((part, i) => ( {i > 0 && ( · )} {part} ))} · анализ
); } // ── Sidebar nav ──────────────────────────────────────────────────────────────── const NAV_ITEMS = [ { id: "section-1", label: "1. Информация" }, { id: "section-2", label: "2. Сети" }, { id: "section-3", label: "3. Продажи конкурентов", children: [ { id: "section-3-1", label: "3.1 Настройки выборки" }, { id: "section-3-2", label: "3.2 Планировки" }, { id: "section-3-3", label: "3.3 Остатки и скорость" }, ], }, { id: "section-4", label: "4. Оценка" }, { id: "section-5", label: "5. Атмосфера" }, { id: "section-6", label: "6. Прогноз", children: [ { id: "section-6-1", label: "6.1 Прогноз по горизонтам" }, { id: "section-6-2", label: "6.2 Сценарии" }, { id: "section-6-3", label: "6.3 Уверенность" }, { id: "section-6-4", label: "6.4 Рекомендация по продукту" }, ], }, ]; function AnalysisSidebarNav() { function scrollTo(id: string) { const el = document.getElementById(id); if (el) el.scrollIntoView({ behavior: "smooth" }); } return ( ); } // ── Section placeholder ──────────────────────────────────────────────────────── function SectionPlaceholder({ number, title, note, }: { number: string; title: string; note: string; }) { return ( <>
{number}. {title}
{note}
); }