"use client"; /** * Section4Estimate — "2. Оценка участка" * * Layout: * HeadlineBar (score + verdict label as title) * GateVerdictBanner (conditional — gate_verdict field) * 2-col grid (desktop): * left: ScoreBreakdownPanel (top-3 plus/minus + by-group bar) * right: ScoreBreakdownStackedBar (per-category contribution) * Geology / Hydrology / GeotechRisk blocks (conditional — each field) * NspdZouitOverlapsBlock (conditional — nspd_zouit_overlaps present) * SuccessRecommendationBlock (conditional — success_recommendation present) * * Data: useParcelAnalyzeQuery (canonical) from @/lib/site-finder-api. * All sections are conditional — graceful if backend field is absent. */ import { HeadlineBar } from "@/components/ui/HeadlineBar"; import { ScoreBreakdownPanel } from "@/components/site-finder/ScoreBreakdownPanel"; import { ScoreBreakdownStackedBar } from "@/components/site-finder/ScoreBreakdownStackedBar"; import { GeologyBlock } from "@/components/site-finder/GeologyBlock"; import { HydrologyBlock } from "@/components/site-finder/HydrologyBlock"; import { GeotechRiskBlock } from "@/components/site-finder/GeotechRiskBlock"; import { NspdZouitOverlapsBlock } from "@/components/site-finder/NspdZouitOverlapsBlock"; import { SuccessRecommendationBlock } from "@/components/site-finder/SuccessRecommendationBlock"; import { useParcelAnalyzeQuery } from "@/lib/site-finder-api"; import type { ParcelAnalysis } from "@/types/site-finder"; import type { FactorContribution, ScoreGroupTotal } from "@/types/site-finder"; // ── Props ───────────────────────────────────────────────────────────────────── interface Props { cad: string; } // ── Helpers ─────────────────────────────────────────────────────────────────── function buildHeadlineTitle(analysis: ParcelAnalysis): string { // Guard: a 202 on-demand-fetch stub (#1001) has no `score` — render "—" // instead of throwing on `.toFixed` against undefined. const score = typeof analysis.score === "number" && Number.isFinite(analysis.score) ? analysis.score.toFixed(1) : "—"; const label = analysis.score_label ?? ""; if (label) { return `Оценка: ${score} · ${label.toUpperCase()}`; } return `Оценка: ${score}`; } function buildHeadlineSubtitle(analysis: ParcelAnalysis): string | undefined { const parts: string[] = []; if (analysis.gate_verdict) { parts.push(`МКД: ${analysis.gate_verdict.verdict_label}`); } if (analysis.score_explanation) { parts.push(analysis.score_explanation); } else if (analysis.confidence_label) { parts.push(`Уверенность данных: ${analysis.confidence_label}`); } return parts.length > 0 ? parts.join(" · ") : undefined; } // ── Section 4 skeleton (loading) ────────────────────────────────────────────── function Section4Skeleton() { return (
); } // ── Main component ──────────────────────────────────────────────────────────── export function Section4Estimate({ cad }: Props) { const { data, isLoading, error } = useParcelAnalyzeQuery(cad); if (isLoading) { return ; } if (error || !data) { return (
{error instanceof Error ? error.message : "Ошибка загрузки секции Оценка"}
); } // Cast to ParcelAnalysis which is the richer superset type with all optional fields. const analysis = data as unknown as ParcelAnalysis; const headlineTitle = buildHeadlineTitle(analysis); const headlineSubtitle = buildHeadlineSubtitle(analysis); // Breakdown data — may be absent on older backend deploys const topPositives: FactorContribution[] = analysis.score_top_3_positives ?? []; const topNegatives: FactorContribution[] = analysis.score_top_3_negatives ?? []; const byGroup: ScoreGroupTotal[] = analysis.score_by_group ?? []; const detailed: FactorContribution[] = analysis.score_breakdown_detailed ?? []; const hasBreakdown = topPositives.length > 0 || topNegatives.length > 0 || byGroup.length > 0 || detailed.length > 0; const hasStackedBar = detailed.length > 0; const hasZouit = analysis.nspd_zouit_overlaps !== undefined && analysis.nspd_zouit_overlaps !== null; return (
{/* ── Headline bar ─────────────────────────────────────────────────── */}
{/* Gate verdict banner перенесён НАД группой «Участок» (AnalysisPageContent, #1741) */} {/* ── Score breakdown: 2-col on desktop ───────────────────────────── */} {hasBreakdown && (
{hasStackedBar && }
)} {/* ── Risk blocks grid ─────────────────────────────────────────────── */} {(analysis.geology || analysis.hydrology || analysis.geotech_risk) && (
{analysis.geology && } {analysis.hydrology && ( )} {analysis.geotech_risk && ( )}
)} {/* ── NSPD ZOUIT overlaps ──────────────────────────────────────────── */} {hasZouit && (
Охранные зоны ЗОУИТ
)} {/* ── Success recommendation ───────────────────────────────────────── */} {analysis.success_recommendation !== undefined && (
)}
); }