gendesign/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx

345 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Link from "next/link";
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 { 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 { data, isLoading, error } = useParcelAnalyzeQuery(cad);
// ── Loading ────────────────────────────────────────────────────────────────
if (isLoading) {
return (
<main style={{ padding: "24px 20px", maxWidth: 1400, margin: "0 auto" }}>
<Breadcrumb cad={cad} />
<div
style={{
marginTop: 24,
padding: "40px 24px",
textAlign: "center",
color: "var(--fg-tertiary, #73767E)",
fontSize: 14,
background: "var(--bg-card-alt, #FAFBFC)",
border: "1px solid var(--border-soft, #EEF0F3)",
borderRadius: 12,
}}
>
Анализируем участок {cad}
</div>
</main>
);
}
// ── Error ──────────────────────────────────────────────────────────────────
if (error || !data) {
const msg =
error instanceof Error ? error.message : "Ошибка загрузки анализа";
return (
<main style={{ padding: "24px 20px", maxWidth: 1400, margin: "0 auto" }}>
<Breadcrumb cad={cad} />
<div
style={{
marginTop: 24,
padding: "16px 20px",
background: "var(--danger-soft, #FEE2E2)",
border: "1px solid var(--danger, #B3261E)",
borderRadius: 12,
color: "var(--danger, #B3261E)",
fontSize: 13,
}}
>
{msg}
</div>
<div style={{ marginTop: 16 }}>
<Link
href="/site-finder"
style={{
fontSize: 13,
color: "var(--accent, #1D4ED8)",
textDecoration: "none",
}}
>
Вернуться к Site Finder
</Link>
</div>
</main>
);
}
// ── 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 (
<main style={{ padding: "24px 20px", maxWidth: 1400, margin: "0 auto" }}>
{/* Breadcrumb */}
<Breadcrumb cad={cad} districtName={districtName} areaStr={areaStr} />
{/* Page layout: sidebar (240px) + main scroll */}
<div
style={{
display: "grid",
gridTemplateColumns: "240px 1fr",
gap: 24,
marginTop: 20,
alignItems: "start",
}}
>
{/* ── Sidebar nav ───────────────────────────────────────────────── */}
<aside
style={{
position: "sticky",
top: 72,
alignSelf: "start",
}}
>
<AnalysisSidebarNav />
</aside>
{/* ── Main scroll area ──────────────────────────────────────────── */}
<div style={{ display: "flex", flexDirection: "column", gap: 32 }}>
{/* Section 1 — IMPLEMENTED in A5 */}
<Section1ParcelInfo cad={cad} />
{/* Section 2 — IMPLEMENTED in A6 */}
<Section2NetworksUtilities cad={cad} />
{/* Section 3 — IMPLEMENTED in A7 */}
<Section3SettingsAndCompetitors cad={cad} data={analysis} />
{/* Section 4 — IMPLEMENTED in A10 */}
<Section4Estimate cad={cad} />
{/* Section 5 — IMPLEMENTED in A11 */}
<Section5Atmosphere cad={cad} />
</div>
</div>
</main>
);
}
// ── 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 (
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
fontSize: 13,
color: "var(--fg-secondary, #5B6066)",
}}
>
<Link
href="/site-finder"
style={{
color: "var(--accent, #1D4ED8)",
textDecoration: "none",
fontWeight: 500,
}}
>
Site Finder
</Link>
<span style={{ color: "var(--fg-tertiary, #73767E)" }}>/</span>
{parts.map((part, i) => (
<span key={i}>
{i > 0 && (
<span
style={{
margin: "0 6px",
color: "var(--fg-tertiary, #73767E)",
}}
>
&middot;
</span>
)}
{part}
</span>
))}
<span style={{ margin: "0 4px", color: "var(--fg-tertiary, #73767E)" }}>
&middot;
</span>
<span style={{ color: "var(--fg-tertiary, #73767E)" }}>анализ</span>
</div>
);
}
// ── 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. Атмосфера" },
];
function AnalysisSidebarNav() {
function scrollTo(id: string) {
const el = document.getElementById(id);
if (el) el.scrollIntoView({ behavior: "smooth" });
}
return (
<nav
style={{
background: "var(--bg-card, #FFFFFF)",
border: "1px solid var(--border-card, #E6E8EC)",
borderRadius: 12,
padding: "12px 0",
display: "flex",
flexDirection: "column",
}}
>
{NAV_ITEMS.map((item) => (
<div key={item.id}>
<button
type="button"
onClick={() => scrollTo(item.id)}
style={{
display: "block",
width: "100%",
textAlign: "left",
padding: "8px 16px",
background: "none",
border: "none",
cursor: "pointer",
fontSize: 13,
fontWeight: 500,
color: "var(--fg-primary, #111111)",
transition: "background 0.1s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = "var(--bg-card-alt, #FAFBFC)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "none";
}}
>
{item.label}
</button>
{"children" in item &&
item.children?.map((child) => (
<button
key={child.id}
type="button"
onClick={() => scrollTo(child.id)}
style={{
display: "block",
width: "100%",
textAlign: "left",
padding: "6px 16px 6px 28px",
background: "none",
border: "none",
cursor: "pointer",
fontSize: 12,
color: "var(--fg-secondary, #5B6066)",
transition: "background 0.1s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.background =
"var(--bg-card-alt, #FAFBFC)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "none";
}}
>
{child.label}
</button>
))}
</div>
))}
</nav>
);
}
// ── Section placeholder ────────────────────────────────────────────────────────
function SectionPlaceholder({
number,
title,
note,
}: {
number: string;
title: string;
note: string;
}) {
return (
<>
<div
style={{
background: "var(--bg-headline, #0F172A)",
borderRadius: 12,
padding: "14px 18px",
marginBottom: 12,
}}
>
<div
style={{
fontSize: 15,
fontWeight: 600,
color: "var(--fg-on-dark, #E2E8F0)",
}}
>
{number}. {title}
</div>
</div>
<div
style={{
background: "var(--bg-card-alt, #FAFBFC)",
border: "1px dashed var(--border-strong, #D1D5DB)",
borderRadius: 12,
padding: "32px 24px",
textAlign: "center",
color: "var(--fg-tertiary, #73767E)",
fontSize: 13,
}}
>
{note}
</div>
</>
);
}