-
-
-
-
-
-
-
- {/* ROW 1 — map · passport · gauges */}
-
-
- {/* ROW 2 — 6 scan cards */}
-
-
- {/* ROW 3 — ОКС · potential · product · insolation */}
-
-
- {/* ROW 4 — clearance · legal · verdict */}
-
-
- {/* Среда · экология — honest atmospheric scan (not in the prototype
- 4-row grid but ported from the adapter so the data isn't dropped). */}
-
-
-
-
-
-
-
- МОДЕЛЬ ОЦЕНКИ: ПТИЦА · Site Finder v2
-
- ·
-
- УЧАСТОК: {analysis.cad_num}
-
-
-
- НЕ ЯВЛЯЕТСЯ ПУБЛИЧНОЙ ОФЕРТОЙ · ИНФОРМАЦИЯ НОСИТ ОЦЕНОЧНЫЙ ХАРАКТЕР
-
-
-
-
- {/* Theme toggle */}
-
- {theme === "light" ? "☾" : "☀"}
-
-
- {/* Chat FAB */}
-
-
-
-
-
-
-
- {/* Shared scrim */}
-
{
- handleCloseDrawer();
- closeChat();
- }}
- aria-hidden="true"
- />
-
- {/* Drawer */}
-
- {drawerEntry?.content}
-
-
- {/* Chat */}
-
-
- {/* Toast */}
- {toast && (
-
{toast}
- )}
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2Drawer.tsx b/frontend/src/components/site-finder/ptica-v2/V2Drawer.tsx
deleted file mode 100644
index 39ef3d5c..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2Drawer.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-"use client";
-
-/**
- * V2Drawer — right slide-in panel (prototype `.drawer`, 480px). Controlled by
- * `open`; renders the eyebrow / title / summary header + scrollable body. The
- * shared scrim is owned by V2Cockpit (so it can also back the chat panel).
- */
-
-import type { ReactNode } from "react";
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-
-interface Props {
- open: boolean;
- eyebrow?: string;
- title: string;
- sum?: string;
- onClose: () => void;
- children: ReactNode;
-}
-
-export function V2Drawer({ open, eyebrow, title, sum, onClose, children }: Props) {
- return (
-
-
-
- {eyebrow &&
{eyebrow}
}
-
{title}
- {sum &&
{sum}
}
-
-
- ✕
-
-
- {children}
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2DrawerPrimitives.tsx b/frontend/src/components/site-finder/ptica-v2/V2DrawerPrimitives.tsx
deleted file mode 100644
index 6b593bc6..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2DrawerPrimitives.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-"use client";
-
-/**
- * V2DrawerPrimitives — shared drawer-content building blocks (prototype `.dsec` /
- * `.drow` / `.dsource` / `.dfac`). Each consumes the ptica-adapt view-models so
- * REAL data renders normally and placeholders render muted with their caption as
- * a title tooltip.
- */
-
-import type { ReactNode } from "react";
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type {
- DrawerKvRow,
- PticaFactorBar,
-} from "@/components/site-finder/ptica/ptica-adapt";
-import { V2ShieldIcon } from "@/components/site-finder/ptica-v2/V2Icons";
-
-export function DSec({
- title,
- badge,
- children,
-}: {
- title: string;
- badge?: ReactNode;
- children: ReactNode;
-}) {
- return (
-
-
- {title}
- {badge}
-
- {children}
-
- );
-}
-
-export function DRow({ row }: { row: DrawerKvRow }) {
- return (
-
- {row.k}
-
- {row.field.value}
-
-
- );
-}
-
-export function DRows({ rows }: { rows: DrawerKvRow[] }) {
- return (
- <>
- {rows.map((r) => (
-
- ))}
- >
- );
-}
-
-const BAR_COLOR: Record
= {
- good: "var(--accent-green)",
- bad: "var(--accent-red)",
- neutral: "var(--accent-cyan)",
-};
-
-export function DFactor({ bar }: { bar: PticaFactorBar }) {
- const pct = Math.max(0, Math.min(100, bar.pct));
- return (
-
-
- {bar.name}
- {bar.value}
-
-
-
-
-
- );
-}
-
-export function DNote({ children }: { children: ReactNode }) {
- return {children}
;
-}
-
-export function DSource({ children }: { children: ReactNode }) {
- return (
-
-
- {children}
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2Gauge.tsx b/frontend/src/components/site-finder/ptica-v2/V2Gauge.tsx
deleted file mode 100644
index 84eece96..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2Gauge.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-"use client";
-
-/**
- * V2Gauge — SVG progress ring (prototype `.gauge-wrap` / `.minigauge`). Renders a
- * faint track + a colored arc whose length maps a 0..100 value. `null` value →
- * muted "—" center (honest no-data, never a fake percentage). Tone selects the
- * arc color via the scoped CSS tokens.
- *
- * Geometry mirrors the prototype: r=50 on a 120-box (big), r=30 on a 74-box
- * (mini), r=26 on a 66-box (buy). dashoffset = circumference × (1 − value/100).
- */
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type { PticaGauge } from "@/components/site-finder/ptica/ptica-adapt";
-
-type Size = "big" | "mini" | "buy";
-
-const GEOM: Record = {
- big: { box: 120, r: 50, stroke: 9 },
- mini: { box: 74, r: 30, stroke: 7 },
- buy: { box: 66, r: 26, stroke: 7 },
-};
-
-// Tone → stroke color (map exception: Leaflet/SVG can't read CSS custom props in
-// stroke attr reliably across themes, so we reference the var() here directly).
-const TONE_STROKE: Record = {
- good: "var(--accent-green)",
- warn: "var(--accent-yellow)",
- bad: "var(--accent-red)",
- neutral: "var(--accent-cyan)",
-};
-
-interface Props {
- gauge: PticaGauge;
- size?: Size;
-}
-
-export function V2Gauge({ gauge, size = "big" }: Props) {
- const { box, r, stroke } = GEOM[size];
- const c = box / 2;
- const circ = 2 * Math.PI * r;
- const pct = gauge.value == null ? 0 : Math.max(0, Math.min(100, gauge.value));
- const offset = circ * (1 - pct / 100);
- const color = gauge.value == null ? "var(--text-soft)" : TONE_STROKE[gauge.tone];
-
- return (
-
-
- {gauge.value != null && (
-
- )}
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2Icons.tsx b/frontend/src/components/site-finder/ptica-v2/V2Icons.tsx
deleted file mode 100644
index dceb7815..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2Icons.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client";
-
-/**
- * V2Icons — inline SVG glyphs ported 1:1 from the prototype (ptica-v2/index.html).
- * Kept in one module so the topbar / rail / drawers / chat share identical marks.
- */
-
-import type { ReactNode } from "react";
-
-export function V2BirdMark(): ReactNode {
- return (
-
-
-
- );
-}
-
-export function V2CheckIcon(): ReactNode {
- return (
-
-
-
- );
-}
-
-export function V2WarnIcon(): ReactNode {
- return (
-
-
-
-
- );
-}
-
-export function V2ShieldIcon(): ReactNode {
- return (
-
-
-
- );
-}
-
-export function V2CopyIcon(): ReactNode {
- return (
-
-
-
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2LowerGrid.tsx b/frontend/src/components/site-finder/ptica-v2/V2LowerGrid.tsx
deleted file mode 100644
index d12c4b5e..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2LowerGrid.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-"use client";
-
-/**
- * V2LowerGrid — row 3 (prototype `.row3`): ОКС · Development Potential ·
- * Recommended Product · Инсоляционная матрица. ОКС is an honest empty-state (no
- * реестр ОКС in /analyze); Potential reads REAL ёмкость from геометрия + НСПД;
- * Product reads the §22 product_tz (class + quartile mix) or shows «после
- * прогноза»; Insolation stays «скоро» (not computed yet).
- */
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type { ParcelAnalysis } from "@/types/site-finder";
-import type { ForecastReport } from "@/types/forecast";
-import {
- adaptOksLowerCard,
- adaptPotentialCard,
- adaptProductDrawer,
-} from "@/components/site-finder/ptica/ptica-adapt";
-import type { V2DrawerKey } from "@/components/site-finder/ptica-v2/v2-drawer-registry";
-
-interface Props {
- analysis: ParcelAnalysis;
- forecastReport?: ForecastReport;
- onOpenDrawer: (key: V2DrawerKey) => void;
-}
-
-export function V2LowerGrid({ analysis, forecastReport, onOpenDrawer }: Props) {
- const oks = adaptOksLowerCard();
- const potential = adaptPotentialCard(analysis);
- const product = adaptProductDrawer(forecastReport);
-
- return (
-
- {/* ОКС */}
-
-
-
- ОКС
- объекты кап. стр-ва
-
-
-
-
-
-
-
-
-
-
- {oks.rows.map((r) => (
-
- {r.k}
-
- {r.field.value}
-
-
- ))}
-
-
-
- onOpenDrawer("oks")}
- >
- ПОДРОБНЕЕ →
-
-
-
-
-
- {/* Development Potential */}
-
-
-
- Development Potential
-
-
-
-
- {potential.metrics.map((m) => (
-
-
{m.label}
-
- {m.value}
- {m.unit && {m.unit} }
-
-
- ))}
-
-
- onOpenDrawer("potential")}
- >
- ПОДРОБНЕЕ →
-
-
-
-
-
- {/* Recommended Product */}
-
-
-
- Recommended Product
- продуктовая стратегия
-
-
-
-
- Класс
-
- {product.objClass.value}
-
-
- {product.available && product.mix.length > 0 ? (
- <>
-
Квартирография
-
- {product.mix.map((m) => (
-
- {m.bucket}
-
-
-
- {m.pct}%
-
- ))}
-
- >
- ) : (
-
после прогноза (Сценарии)
- )}
-
- onOpenDrawer("product")}
- >
- ПОДРОБНЕЕ →
-
-
-
-
-
- {/* Insolation matrix — «скоро» */}
-
-
-
- Инсоляционная матрица
-
-
-
-
-
-
СКОРО
-
- Расчёт инсоляции и КЕО в следующей версии
-
-
-
-
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2Map.tsx b/frontend/src/components/site-finder/ptica-v2/V2Map.tsx
deleted file mode 100644
index b9b60a38..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2Map.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-"use client";
-
-/**
- * V2Map — the cockpit map card. Reuses the existing rich Leaflet plumbing
- * (PticaMapInner — parcel polygon, POI / ЖК / pipeline glyph pins, ЗОУИТ zones,
- * connection-point lines, custom POI, dark/satellite tiles) via a SINGLE shared
- * implementation loaded through dynamic(ssr:false) so Leaflet never touches the
- * server. Connection points + custom POIs are fetched here (same TanStack hooks
- * as PticaMap) and threaded into the inner map.
- *
- * The inner map brings its own overlay/legend/controls + base-layer toggle
- * (Спутник | Схема) and dark CartoDB tiles, matching the prototype map block.
- * The map title chip is rendered here over the card per the prototype `.map-head`.
- */
-
-import dynamic from "next/dynamic";
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type { ParcelAnalysis } from "@/types/site-finder";
-import { useConnectionPoints } from "@/hooks/useConnectionPoints";
-import { useCustomPois } from "@/hooks/useCustomPois";
-
-const PticaMapInner = dynamic(
- () => import("@/components/site-finder/ptica/PticaMapInner"),
- {
- ssr: false,
- loading: () => Загрузка карты…
,
- },
-);
-
-interface Props {
- analysis: ParcelAnalysis;
-}
-
-export function V2Map({ analysis }: Props) {
- const cad = analysis.cad_num;
- const { data: connectionPoints } = useConnectionPoints(cad);
- const { data: customPois } = useCustomPois(cad);
-
- return (
-
-
-
-
- {/* Map title — text differs per theme; only one span is shown at a time
- via the [data-theme="..."] selectors in v2.module.css (mirrors the
- prototype `.tnum-d` / `.tnum-l` toggle pattern). */}
-
-
- СПУТНИК · СВЕЖИЙ СНИМОК
-
-
- СПУТНИК · СХЕМА ПОДКЛЮЧЕНИЙ
-
-
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2Passport.tsx b/frontend/src/components/site-finder/ptica-v2/V2Passport.tsx
deleted file mode 100644
index d86d7c93..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2Passport.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-"use client";
-
-/**
- * V2Passport — «Паспорт участка» card (prototype `.passport`). Renders REAL ЕГРН
- * fields via adaptPassport; absent fields show the honest «—» placeholder. The
- * "ПОДРОБНЕЕ →" link opens the passport drawer.
- */
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type { ParcelAnalysis } from "@/types/site-finder";
-import { adaptPassport } from "@/components/site-finder/ptica/ptica-adapt";
-import type { PticaField } from "@/components/site-finder/ptica/ptica-adapt";
-import { V2CopyIcon } from "@/components/site-finder/ptica-v2/V2Icons";
-import type { V2DrawerKey } from "@/components/site-finder/ptica-v2/v2-drawer-registry";
-
-function Cell({
- label,
- field,
- wide,
- big,
-}: {
- label: string;
- field: PticaField;
- wide?: boolean;
- big?: boolean;
-}) {
- return (
-
-
{label}
-
- {field.value}
-
-
- );
-}
-
-interface Props {
- analysis: ParcelAnalysis;
- onOpenDrawer: (key: V2DrawerKey) => void;
-}
-
-export function V2Passport({ analysis, onOpenDrawer }: Props) {
- const p = adaptPassport(analysis);
-
- return (
-
-
-
- Паспорт участка
-
-
onOpenDrawer("parcel")}
- >
- ПОДРОБНЕЕ →
-
-
-
-
- ID
- {p.cadNum}
-
-
-
-
-
-
|
-
|
-
|
-
|
-
|
-
-
Статус
-
- {p.status.value}
-
-
-
|
-
-
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2Rail.tsx b/frontend/src/components/site-finder/ptica-v2/V2Rail.tsx
deleted file mode 100644
index ca99aa57..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2Rail.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-"use client";
-
-/**
- * V2Rail — left icon rail (prototype `.rail`). Each item opens its detail drawer
- * via the shared `onOpenDrawer` callback (V2 drawer registry keys). The currently
- * open drawer key highlights the matching rail item (prototype `.rail-on`).
- */
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type { V2DrawerKey } from "@/components/site-finder/ptica-v2/v2-drawer-registry";
-
-interface RailItem {
- key: string;
- label: string;
- drawer: V2DrawerKey;
- icon: React.ReactNode;
-}
-
-const RAIL: RailItem[] = [
- {
- key: "parcel",
- label: "Участок",
- drawer: "parcel",
- icon: (
-
-
-
-
- ),
- },
- {
- key: "potential",
- label: "Потенциал",
- drawer: "potential",
- icon: (
-
-
-
-
- ),
- },
- {
- key: "product",
- label: "Продукт",
- drawer: "product",
- icon: (
-
-
-
-
- ),
- },
- {
- key: "economics",
- label: "Экономика",
- drawer: "economics",
- icon: (
-
-
-
- ),
- },
- {
- key: "risks",
- label: "Риски",
- drawer: "risks",
- icon: (
-
-
-
-
-
- ),
- },
- {
- key: "compare",
- label: "Сравнение",
- drawer: "compare",
- icon: (
-
-
-
- ),
- },
- {
- key: "reports",
- label: "Отчёты",
- drawer: "reports",
- icon: (
-
-
-
-
- ),
- },
-];
-
-interface Props {
- openDrawer: V2DrawerKey | null;
- onOpenDrawer: (key: V2DrawerKey) => void;
-}
-
-export function V2Rail({ openDrawer, onOpenDrawer }: Props) {
- return (
-
- {RAIL.map((item) => {
- const active = openDrawer === item.drawer;
- return (
- onOpenDrawer(item.drawer)}
- >
- {item.icon}
- {item.label}
-
- );
- })}
-
-
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2RightColumn.tsx b/frontend/src/components/site-finder/ptica-v2/V2RightColumn.tsx
deleted file mode 100644
index 558a4f33..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2RightColumn.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-"use client";
-
-/**
- * V2RightColumn — row-1 right stack (prototype `.col-stack`): the Buildability
- * gauge (SVG ring) over the Инвест-скор block. Both honest: the gauge shows a
- * muted «—» when there is no data; the invest-score shows «—» until the §22
- * forecast surfaces the overall_score. Values flow through ptica-adapt.
- */
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type { ParcelAnalysis } from "@/types/site-finder";
-import type { ForecastReport } from "@/types/forecast";
-import {
- adaptBuildabilityGauge,
- adaptInvestScore,
-} from "@/components/site-finder/ptica/ptica-adapt";
-import type { PticaValueTone } from "@/components/site-finder/ptica/ptica-adapt";
-import { V2Gauge } from "@/components/site-finder/ptica-v2/V2Gauge";
-import type { V2DrawerKey } from "@/components/site-finder/ptica-v2/v2-drawer-registry";
-
-const TONE_CLASS: Record = {
- good: "",
- warn: "amb",
- none: "none",
-};
-
-// Static spark bars (decorative only — prototype `.spark`).
-const SPARK = [12, 16, 20, 15, 22, 17, 24];
-
-interface Props {
- analysis: ParcelAnalysis;
- forecastReport?: ForecastReport;
- onOpenDrawer: (key: V2DrawerKey) => void;
-}
-
-export function V2RightColumn({ analysis, forecastReport, onOpenDrawer }: Props) {
- const gauge = adaptBuildabilityGauge(analysis);
- const invest = adaptInvestScore(analysis, forecastReport);
- const pct = gauge.value == null ? "—" : `${gauge.value}%`;
- const gaugeColor =
- gauge.value == null
- ? "var(--text-soft)"
- : gauge.tone === "good"
- ? "var(--accent-green)"
- : gauge.tone === "warn"
- ? "var(--accent-yellow)"
- : gauge.tone === "bad"
- ? "var(--accent-red)"
- : "var(--accent-cyan)";
-
- return (
-
- {/* Buildability gauge */}
-
-
-
-
-
- Buildability Index
-
-
-
-
-
-
-
- {pct}
-
-
- {gauge.label}
-
-
-
- {gauge.footnote && (
-
{gauge.footnote}
- )}
-
-
-
- {/* Invest score */}
-
-
-
- Инвест-скор
-
-
onOpenDrawer("economics")}
- >
- ПОДРОБНЕЕ →
-
-
-
-
-
- {invest.score.value}
- /10
-
-
-
- {SPARK.map((h, i) => (
-
- ))}
-
-
-
-
-
-
Потенциал
-
- {invest.potential.value}
-
-
-
-
Риск
-
- {invest.risk.value}
-
-
-
-
-
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2RiskCard.tsx b/frontend/src/components/site-finder/ptica-v2/V2RiskCard.tsx
deleted file mode 100644
index 935316bc..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2RiskCard.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-"use client";
-
-/**
- * V2RiskCard — the «Риски» mini-gauge card (prototype `.risk-card`). Renders the
- * derived risk proxy (adaptRiskGauge, «предв.») as a small SVG ring + label;
- * muted «—» when there is no data. Opens the risks drawer.
- */
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type { ParcelAnalysis } from "@/types/site-finder";
-import { adaptRiskGauge } from "@/components/site-finder/ptica/ptica-adapt";
-import { V2Gauge } from "@/components/site-finder/ptica-v2/V2Gauge";
-import type { V2DrawerKey } from "@/components/site-finder/ptica-v2/v2-drawer-registry";
-
-interface Props {
- analysis: ParcelAnalysis;
- onOpenDrawer: (key: V2DrawerKey) => void;
-}
-
-export function V2RiskCard({ analysis, onOpenDrawer }: Props) {
- const gauge = adaptRiskGauge(analysis);
- const pct = gauge.value == null ? "—" : `${gauge.value}%`;
- const color =
- gauge.value == null
- ? "var(--text-soft)"
- : gauge.tone === "good"
- ? "var(--accent-green)"
- : gauge.tone === "warn"
- ? "var(--accent-yellow)"
- : "var(--accent-red)";
-
- return (
-
-
-
-
Сводный риск
-
-
- {gauge.label}
-
-
onOpenDrawer("risks")}
- >
- ПОДРОБНЕЕ →
-
-
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2ScanCard.tsx b/frontend/src/components/site-finder/ptica-v2/V2ScanCard.tsx
deleted file mode 100644
index 3219335c..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2ScanCard.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-"use client";
-
-/**
- * V2ScanCard — generic «scan» card for row 2 (prototype `.row2 .card`). Renders a
- * PticaScanCard view-model: a title (+ optional badge), kv-rows OR status-rows
- * (dot + state, used by Инженерия), an empty-state, and a "ПОДРОБНЕЕ →" link to
- * the card's drawer. All values come from the ptica-adapt layer (REAL or honest
- * «—»).
- */
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type {
- PticaScanCard,
- PticaScanRow,
- PticaStatusTone,
-} from "@/components/site-finder/ptica/ptica-adapt";
-import type { V2DrawerKey } from "@/components/site-finder/ptica-v2/v2-drawer-registry";
-
-const STATUS_COLOR: Record = {
- ok: "var(--accent-green)",
- warn: "var(--accent-orange)",
- bad: "var(--accent-red)",
-};
-
-const STATUS_LABEL: Record = {
- ok: "Доступно",
- warn: "Огранич.",
- bad: "Нет",
-};
-
-const STATUS_CLASS: Record = {
- ok: styles.ok,
- warn: styles.warnc,
- bad: styles.warnc,
-};
-
-function KvRow({ row }: { row: PticaScanRow }) {
- return (
-
- {row.key}
-
- {row.field.value}
-
-
- );
-}
-
-function StatusRow({ row }: { row: PticaScanRow }) {
- const tone = row.status ?? "ok";
- return (
-
-
-
- {row.key}
-
-
-
- {STATUS_LABEL[tone]}
-
-
- {row.field.value}
-
-
-
- );
-}
-
-interface Props {
- card: PticaScanCard;
- drawer: V2DrawerKey;
- onOpenDrawer: (key: V2DrawerKey) => void;
-}
-
-export function V2ScanCard({ card, drawer, onOpenDrawer }: Props) {
- return (
-
-
-
- {card.title}
- {card.badge && {card.badge} }
-
-
-
- {card.rows.length === 0 ? (
-
- {card.emptyState ?? "Нет данных"}
-
- ) : (
- card.rows.map((row) =>
- row.status ? (
-
- ) : (
-
- ),
- )
- )}
-
- onOpenDrawer(drawer)}
- >
- ПОДРОБНЕЕ →
-
-
-
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/V2Topbar.tsx b/frontend/src/components/site-finder/ptica-v2/V2Topbar.tsx
deleted file mode 100644
index bb373281..00000000
--- a/frontend/src/components/site-finder/ptica-v2/V2Topbar.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-"use client";
-
-/**
- * V2Topbar — cockpit top bar (prototype `.topbar`): ПТИЦА wordmark (links to
- * Site Finder), 4 режимных tabs (visual — the cockpit is a single analysis view),
- * a live clock + version chip + two icon buttons.
- *
- * The clock is mounted-only (state gated by an effect) to avoid an SSR/client
- * hydration mismatch; the effect drives a UI clock — no HTTP — which is the
- * sanctioned use of useEffect here.
- */
-
-import { useEffect, useState } from "react";
-import Link from "next/link";
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import { V2BirdMark } from "@/components/site-finder/ptica-v2/V2Icons";
-
-const TABS = [
- "Анализ участка",
- "Сценарии",
- "Отчёты",
- "Сравнение",
-] as const;
-
-interface Props {
- cad: string;
-}
-
-export function V2Topbar({ cad }: Props) {
- const [now, setNow] = useState<{ time: string; date: string } | null>(null);
-
- useEffect(() => {
- function tick() {
- const d = new Date();
- const pad = (n: number) => String(n).padStart(2, "0");
- setNow({
- time: `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`,
- date: `${d.getFullYear()}.${pad(d.getMonth() + 1)}.${pad(d.getDate())}`,
- });
- }
- tick();
- const id = window.setInterval(tick, 1000);
- return () => window.clearInterval(id);
- }, []);
-
- return (
-
- );
-}
diff --git a/frontend/src/components/site-finder/ptica-v2/v2-drawer-registry.tsx b/frontend/src/components/site-finder/ptica-v2/v2-drawer-registry.tsx
deleted file mode 100644
index c3b2e997..00000000
--- a/frontend/src/components/site-finder/ptica-v2/v2-drawer-registry.tsx
+++ /dev/null
@@ -1,465 +0,0 @@
-"use client";
-
-/**
- * v2-drawer-registry — the 11 detail drawers of the v2 cockpit (prototype
- * `DRAWERS`). Each entry = { rail?, eyebrow, title, sum, content }. Content is
- * built from the ptica-adapt drawer view-models so REAL data renders and absent
- * fields stay honest «—». Mirrors the prototype 1:1 in copy + layout; demo
- * numbers from the prototype are NOT hard-coded — values come from /analyze.
- */
-
-import type { ReactNode } from "react";
-
-import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
-import type { ParcelAnalysis } from "@/types/site-finder";
-import type { ForecastReport } from "@/types/forecast";
-import {
- adaptPassportDrawer,
- adaptPotentialDrawer,
- adaptBuildabilityDrawer,
- adaptProductDrawer,
- adaptEconomyDrawer,
- adaptRisksDrawer,
- adaptRestrictionsDrawer,
- adaptEngineeringDrawer,
- adaptMarketDrawer,
- adaptOksDrawer,
-} from "@/components/site-finder/ptica/ptica-adapt";
-import {
- DSec,
- DRow,
- DRows,
- DFactor,
- DNote,
- DSource,
-} from "@/components/site-finder/ptica-v2/V2DrawerPrimitives";
-
-export const V2_DRAWER_KEYS = [
- "parcel",
- "potential",
- "product",
- "economics",
- "risks",
- "compare",
- "reports",
- "restrictions",
- "engineering",
- "market",
- "oks",
-] as const;
-
-export type V2DrawerKey = (typeof V2_DRAWER_KEYS)[number];
-
-export function asV2DrawerKey(
- value: string | null | undefined,
-): V2DrawerKey | null {
- if (!value) return null;
- return (V2_DRAWER_KEYS as readonly string[]).includes(value)
- ? (value as V2DrawerKey)
- : null;
-}
-
-export interface V2DrawerEntry {
- eyebrow: string;
- title: string;
- sum: string;
- content: ReactNode;
-}
-
-// ── Reports / export buttons (demo — actual export is in the heavy analysis page) ─
-
-const EXPORT_FORMATS: Array<{ id: string; sub: string }> = [
- { id: "PDF", sub: "сводный паспорт" },
- { id: "CSV", sub: "таблица метрик" },
- { id: "DOCX §22", sub: "пояснит. записка" },
- { id: "PPTX", sub: "презентация" },
-];
-
-function ReportsContent({ onExport }: { onExport: (fmt: string) => void }) {
- return (
- <>
-
-
- {EXPORT_FORMATS.map((f) => (
-
onExport(f.id)}
- >
-
-
-
-
-
- {f.id}
- {f.sub}
-
-
- ))}
-
-
-
- Полная выгрузка отчётов доступна на странице глубокого анализа участка.
-
- Источник: генератор отчётов ПТИЦА
- >
- );
-}
-
-// ── Compare (advisory — single-parcel cockpit; full сравнение в отдельном модуле) ─
-
-function CompareContent({ cad }: { cad: string }) {
- return (
- <>
-
-
-
-
- Сравнение с участками-кандидатами доступно в модуле «Сравнение» Site
- Finder — выберите несколько участков, чтобы сопоставить их по ёмкости,
- рынку и риску.
-
- Источник: реестр кандидатов Site Finder
- >
- );
-}
-
-// ── Registry ──────────────────────────────────────────────────────────────────
-
-export function getV2DrawerEntry(
- key: V2DrawerKey,
- analysis: ParcelAnalysis,
- forecastReport: ForecastReport | undefined,
- onExport: (fmt: string) => void,
-): V2DrawerEntry {
- switch (key) {
- case "parcel": {
- const d = adaptPassportDrawer(analysis);
- return {
- eyebrow: "01 · ЕГРН",
- title: "Паспорт участка",
- sum: d.summary,
- content: (
- <>
-
-
-
- {d.zouitOverlaps.length > 0 && (
-
- {d.zouitCount} зон
-
- }
- >
- {d.zouitOverlaps.map((z, i) => (
-
- ))}
-
- )}
- Источник: ЕГРН · Росреестр · НСПД
- >
- ),
- };
- }
- case "potential": {
- const cap = adaptPotentialDrawer(analysis);
- const build = adaptBuildabilityDrawer(analysis);
- return {
- eyebrow: "02 · ГРАДРЕГЛАМЕНТ",
- title: "Потенциал застройки",
- sum: cap.summary,
- content: (
- <>
-
-
-
-
- {build.gauge.value == null ? "—" : `${build.gauge.value}%`} ·
- предв.
-
- }
- >
- {build.factors.map((f) => (
-
- ))}
-
- {build.summary}
- Источник: ПЗЗ / НСПД · расчёт ПТИЦА
- >
- ),
- };
- }
- case "product": {
- const d = adaptProductDrawer(forecastReport);
- return {
- eyebrow: "03 · ПРОДУКТ",
- title: "Рекомендованный продукт",
- sum: d.summary,
- content: (
- <>
-
-
-
- {d.available && d.mix.length > 0 && (
-
- {d.mix.map((m, i) => (
-
- ))}
-
- )}
- {d.usp.length > 0 && (
-
- {d.usp.map((u, i) => (
-
- ))}
-
- )}
-
- {d.available
- ? "Класс и квартирография — рекомендация прогноза §22 (advisory)."
- : "Продуктовая стратегия формируется в прогнозе §22 — откройте «Сценарии»."}
-
- Источник: продуктовая модель ПТИЦА
- >
- ),
- };
- }
- case "economics": {
- const d = adaptEconomyDrawer();
- return {
- eyebrow: "04 · ФИНМОДЕЛЬ",
- title: "Экономика проекта",
- sum: d.summary,
- content: (
- <>
-
-
-
- {d.summary}
- Источник: финмодель ПТИЦА (предв.)
- >
- ),
- };
- }
- case "risks": {
- const d = adaptRisksDrawer(analysis);
- return {
- eyebrow: "05 · РИСКИ",
- title: "Риски участка",
- sum: d.summary,
- content: (
- <>
-
- {d.gauge.value == null ? "—" : `${d.gauge.value}%`} ·{" "}
- {d.gauge.label}
-
- }
- >
- {d.breakdown.map((f) => (
-
- ))}
-
-
-
-
-
- {d.summary}
- Источник: гейт-модель ПТИЦА (предв.)
- >
- ),
- };
- }
- case "compare":
- return {
- eyebrow: "06 · СРАВНЕНИЕ",
- title: "Сравнение участков",
- sum: "Сопоставление участков-кандидатов по ключевым метрикам.",
- content: ,
- };
- case "reports":
- return {
- eyebrow: "07 · ЭКСПОРТ",
- title: "Отчёты и экспорт",
- sum: "Выгрузка аналитики по участку.",
- content: ,
- };
- case "restrictions": {
- const d = adaptRestrictionsDrawer(analysis);
- return {
- eyebrow: "ОГРАНИЧЕНИЯ",
- title: "Ограничения и ЗОУИТ",
- sum: d.summary,
- content: (
- <>
-
- {d.statusRows.map((r, i) => (
-
- ))}
-
- {d.zouitRows.length > 0 && (
-
- {d.zouitRows.map((z, i) => (
-
- ))}
-
- )}
- {d.summary}
- Источник: ИСОГД · ЕГРН · НСПД
- >
- ),
- };
- }
- case "engineering": {
- const d = adaptEngineeringDrawer(analysis);
- return {
- eyebrow: "ИНЖЕНЕРИЯ",
- title: "Инженерная обеспеченность",
- sum: d.summary,
- content: (
- <>
- {d.hasData ? (
- <>
-
- {d.rows.map((r, i) => (
-
- ))}
-
-
- {d.bars.map((b) => (
-
- ))}
-
- >
- ) : (
-
-
- Нет данных по инженерным сетям
-
-
- )}
- {d.summary}
- Источник: ГКХ · сети РСО · НСПД
- >
- ),
- };
- }
- case "market": {
- const d = adaptMarketDrawer(analysis);
- return {
- eyebrow: "РЫНОК",
- title: "Рынок района",
- sum: d.summary,
- content: (
- <>
-
-
-
-
-
- {d.competitors.length > 0 && (
-
-
-
-
- ЖК
- Класс
- Кв.
- Дист.
-
-
-
- {d.competitors.map((c, i) => (
-
- {c.name}
- {c.cls}
- {c.flats}
- {c.distance}
-
- ))}
-
-
-
- )}
- {d.summary}
- Источник: рыночная аналитика ПТИЦА
- >
- ),
- };
- }
- case "oks": {
- const d = adaptOksDrawer();
- return {
- eyebrow: "ОКС",
- title: "Объекты кап. строительства",
- sum: d.summary,
- content: (
- <>
-
-
- Нет данных реестра ОКС (ЕГРН)
-
-
- {d.summary}
- Источник: ЕГРН (ОКС)
- >
- ),
- };
- }
- }
-}