Reports: dark export cards reusing real infra — PDF snapshot (/snapshot.pdf via shared API_BASE_URL+triggerDownload), client-side CSV (formula-injection guard + UTF-8 BOM preserved), §22 forecast DOCX/PPTX gated on forecast-ready with honest 404; full-report DOCX/PPTX disabled «скоро» (no endpoint). Section checklist mirrors the snapshot TOC. Compare: reuses compare-shortlist (localStorage, COMPARE_MAX) + ParcelCompareLoader for real per-cad analyze metrics (shared cache, rules-of-hooks safe via keyed loader). Current cad auto-pinned + non-removable, validated кадастр input, removable chips. Dark comparison table with winner highlight + colour-coded verdict (Можно/С огранич./Нельзя). Dark styles scoped under .pticaRoot[data-theme=dark]; reuse-only (ExportButtons, compare page, loaders untouched); no new deps. TS strict, no any.
102 lines
4.1 KiB
TypeScript
102 lines
4.1 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* PticaPageContent — client root of the ПТИЦА cockpit (INCREMENT 1).
|
||
*
|
||
* Wires the SAME /analyze data as the existing light analysis page via
|
||
* useParcelAnalyzeQuery, narrows the response to ParcelAnalysis (the sanctioned
|
||
* `as unknown as` pattern — see AnalysisPageContent.tsx:111), and composes the
|
||
* dark operator-terminal shell. Only the 'analysis' tab renders content in PR#1;
|
||
* Scenarios / Reports / Compare show an honest "В разработке" panel.
|
||
*
|
||
* The root carries BOTH `.pticaRoot` and `data-theme="dark"` so the scoped dark
|
||
* tokens apply here and never leak to the light pages.
|
||
*/
|
||
|
||
import { useState } from "react";
|
||
import Link from "next/link";
|
||
|
||
import styles from "./ptica.module.css";
|
||
import {
|
||
useParcelAnalyzeQuery,
|
||
useParcelForecastQuery,
|
||
} from "@/lib/site-finder-api";
|
||
import type { ParcelAnalysis } from "@/types/site-finder";
|
||
import { PticaShell } from "@/components/site-finder/ptica/PticaShell";
|
||
import type { PticaTab } from "@/components/site-finder/ptica/PticaShell";
|
||
import { PticaHero } from "@/components/site-finder/ptica/PticaHero";
|
||
import { DevelopmentScan } from "@/components/site-finder/ptica/DevelopmentScan";
|
||
import { PticaScenarios } from "@/components/site-finder/ptica/scenarios/PticaScenarios";
|
||
import { PticaReports } from "@/components/site-finder/ptica/reports/PticaReports";
|
||
import { PticaCompare } from "@/components/site-finder/ptica/compare/PticaCompare";
|
||
|
||
interface Props {
|
||
cad: string;
|
||
}
|
||
|
||
export function PticaPageContent({ cad }: Props) {
|
||
const [horizon, setHorizon] = useState<number>(12);
|
||
const [tab, setTab] = useState<PticaTab>("analysis");
|
||
const { data, isLoading, error } = useParcelAnalyzeQuery(cad, horizon);
|
||
// §22 forecast is keyed/cached by cad — call unconditionally; it polls the
|
||
// async report (enqueued by /analyze) and feeds both the hero invest-score and
|
||
// the Scenarios tab. Pending state is handled inside each consumer.
|
||
const { data: forecastData } = useParcelForecastQuery(cad);
|
||
const forecastReport =
|
||
forecastData?.status === "ready" ? forecastData.report : undefined;
|
||
|
||
// ── Loading ────────────────────────────────────────────────────────────────
|
||
if (isLoading) {
|
||
return (
|
||
<div className={styles.pticaRoot} data-theme="dark">
|
||
<div className={styles.stateScreen}>
|
||
<div className={styles.stateBox}>Анализируем участок {cad}…</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ── Error ──────────────────────────────────────────────────────────────────
|
||
if (error || !data) {
|
||
const msg =
|
||
error instanceof Error ? error.message : "Ошибка загрузки анализа";
|
||
return (
|
||
<div className={styles.pticaRoot} data-theme="dark">
|
||
<div className={styles.stateScreen}>
|
||
<div>
|
||
<div className={styles.stateError}>{msg}</div>
|
||
<Link href="/site-finder" className={styles.stateLink}>
|
||
← Вернуться к Site Finder
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// Narrow to ParcelAnalysis (superset of ParcelAnalyzeResponse) — both describe
|
||
// the same /analyze endpoint. Sanctioned pattern (AnalysisPageContent.tsx:111).
|
||
const analysis = data as unknown as ParcelAnalysis;
|
||
|
||
return (
|
||
<div className={styles.pticaRoot} data-theme="dark">
|
||
<PticaShell activeTab={tab} onTabChange={setTab}>
|
||
{tab === "analysis" && (
|
||
<>
|
||
<PticaHero analysis={analysis} forecastReport={forecastReport} />
|
||
<DevelopmentScan analysis={analysis} />
|
||
</>
|
||
)}
|
||
{tab === "scenarios" && (
|
||
<PticaScenarios
|
||
cad={cad}
|
||
horizon={horizon}
|
||
onHorizonChange={setHorizon}
|
||
/>
|
||
)}
|
||
{tab === "reports" && <PticaReports cad={cad} analysis={analysis} />}
|
||
{tab === "compare" && <PticaCompare cad={cad} />}
|
||
</PticaShell>
|
||
</div>
|
||
);
|
||
}
|