Two prod issues:
1. Analysis page B5 endpoint returned HTTP 400 'Неверный формат
кадастрового номера' for any cad. Next.js dynamic route delivers
`params.cad` URL-encoded (':' -> '%3A'). Hooks then ran
encodeURIComponent on the already-encoded string, producing
/api/v1/parcels/66%253A41%253A0204016%253A10/analyze; FastAPI
decoded one layer to '66%3A41%3A0204016%3A10', which the regex
rejected. Fix: decodeURIComponent the param once at the page
boundary so downstream consumers (hooks + breadcrumb + Section1)
work with the canonical '66:41:0204016:10' and encode exactly
once when building URLs. Also resolves the breadcrumb double-
encode noted in PR #346 QA.
2. Entry page right-sidebar (CadInput + RecentParcels + ParcelLegend)
visually overlapped the ParcelDrawer slide-in: both were anchored
to the right edge with the drawer rendered above as an overlay
without an opaque backdrop on the sidebar area. Conditional render:
sidebar only mounts when no parcel is selected, so opening a parcel
gives the drawer a clean right column.
184 lines
5.8 KiB
TypeScript
184 lines
5.8 KiB
TypeScript
"use client";
|
||
|
||
import { use } from "react";
|
||
import { LayoutDashboard } from "lucide-react";
|
||
|
||
import { AnalysisSidebar } from "@/components/site-finder/analysis/AnalysisSidebar";
|
||
import { AnalysisBreadcrumb } from "@/components/site-finder/analysis/AnalysisBreadcrumb";
|
||
import { UserAvatar } from "@/components/site-finder/analysis/UserAvatar";
|
||
import { Section1ParcelInfo } from "@/components/site-finder/analysis/Section1ParcelInfo";
|
||
|
||
// ── Section placeholder (Wave 3–4 refactor will replace these) ─────────────────
|
||
|
||
interface SectionPlaceholderProps {
|
||
id: string;
|
||
title: string;
|
||
note?: string;
|
||
}
|
||
|
||
function SectionPlaceholder({ id, title, note }: SectionPlaceholderProps) {
|
||
return (
|
||
<section
|
||
id={id}
|
||
style={{
|
||
background: "var(--bg-card)",
|
||
border: "1px solid var(--border-card)",
|
||
borderRadius: 12,
|
||
padding: 20,
|
||
marginBottom: 24,
|
||
}}
|
||
>
|
||
<h2
|
||
style={{
|
||
margin: "0 0 8px",
|
||
fontSize: 18,
|
||
fontWeight: 600,
|
||
color: "var(--fg-primary)",
|
||
}}
|
||
>
|
||
{title}
|
||
</h2>
|
||
{note && (
|
||
<p style={{ margin: 0, fontSize: 13, color: "var(--fg-tertiary)" }}>
|
||
{note}
|
||
</p>
|
||
)}
|
||
<div
|
||
style={{
|
||
marginTop: 16,
|
||
padding: "24px 16px",
|
||
background: "var(--bg-card-alt)",
|
||
border: "1px dashed var(--border-strong)",
|
||
borderRadius: 8,
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
color: "var(--fg-tertiary)",
|
||
fontSize: 13,
|
||
}}
|
||
>
|
||
<LayoutDashboard
|
||
size={16}
|
||
strokeWidth={1.5}
|
||
style={{ marginRight: 8, flexShrink: 0 }}
|
||
/>
|
||
Заполняется в Wave 2–4 (A5–A11)
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ── Page ──────────────────────────────────────────────────────────────────────
|
||
|
||
interface PageProps {
|
||
params: Promise<{ cad: string }>;
|
||
}
|
||
|
||
export default function AnalysisPage({ params }: PageProps) {
|
||
// React 19: use() unwraps Promise params (app router pattern).
|
||
// Next.js delivers `cad` URL-encoded (":" -> "%3A"); decode once here so
|
||
// downstream hooks/components see the canonical "66:41:0204016:10" and can
|
||
// re-encode exactly once when building API URLs / hrefs (prevents double-
|
||
// encode that made backend regex reject the cad with HTTP 400).
|
||
const { cad: cadRaw } = use(params);
|
||
const cad = decodeURIComponent(cadRaw);
|
||
|
||
return (
|
||
<main
|
||
style={{
|
||
minHeight: "100vh",
|
||
background: "var(--bg-app)",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
}}
|
||
>
|
||
{/* ── Header ─────────────────────────────────────────────────────────── */}
|
||
<header
|
||
style={{
|
||
background: "var(--bg-card)",
|
||
borderBottom: "1px solid var(--border-card)",
|
||
padding: "12px 24px",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 12,
|
||
flexWrap: "wrap",
|
||
position: "sticky",
|
||
top: 0,
|
||
zIndex: 10,
|
||
minHeight: 56,
|
||
boxSizing: "border-box",
|
||
}}
|
||
>
|
||
<AnalysisBreadcrumb cadNum={cad} />
|
||
|
||
{/* Push avatar to the right */}
|
||
<div style={{ marginLeft: "auto" }}>
|
||
<UserAvatar />
|
||
</div>
|
||
</header>
|
||
|
||
{/* ── Body: sidebar + scrollable sections ───────────────────────────── */}
|
||
<div style={{ display: "flex", flex: 1 }}>
|
||
<AnalysisSidebar />
|
||
|
||
{/* Main scrollable content */}
|
||
<article
|
||
style={{
|
||
flex: 1,
|
||
padding: "24px 32px",
|
||
overflowY: "auto",
|
||
maxWidth: 1000,
|
||
}}
|
||
>
|
||
{/* Section 1 — A5: Инфо об участке */}
|
||
<Section1ParcelInfo cad={cad} />
|
||
|
||
{/* Section 2 — TODO A6 */}
|
||
<SectionPlaceholder
|
||
id="section-2"
|
||
title="2. Земля и риски"
|
||
note="TODO A6: reuse NspdEngineeringNearbyBlock в новом layout headline+map"
|
||
/>
|
||
|
||
{/* Section 3 — TODO A7/A8/A9 */}
|
||
<SectionPlaceholder
|
||
id="section-3"
|
||
title="3. Рынок"
|
||
note="TODO A7: WeightProfilePanel (настройки выборки) · A8: BestLayoutsBlock / Pipeline24moBlock / VelocityBlock · A9: CompetitorTable с drawer drill-in"
|
||
/>
|
||
|
||
{/* Sub-section anchor targets (invisible) for scrollspy / direct links */}
|
||
<div
|
||
id="section-3-1"
|
||
style={{ height: 0, overflow: "hidden" }}
|
||
aria-hidden="true"
|
||
/>
|
||
<div
|
||
id="section-3-2"
|
||
style={{ height: 0, overflow: "hidden" }}
|
||
aria-hidden="true"
|
||
/>
|
||
<div
|
||
id="section-3-3"
|
||
style={{ height: 0, overflow: "hidden" }}
|
||
aria-hidden="true"
|
||
/>
|
||
|
||
{/* Section 4 — TODO A10 */}
|
||
<SectionPlaceholder
|
||
id="section-4"
|
||
title="4. Инфраструктура"
|
||
note="TODO A10: ScoreBreakdownPanel + ScoreBreakdownStackedBar + GateVerdictBanner + GeologyBlock / HydrologyBlock / GeotechRiskBlock"
|
||
/>
|
||
|
||
{/* Section 5 — TODO A11 */}
|
||
<SectionPlaceholder
|
||
id="section-5"
|
||
title="5. Свежесть"
|
||
note="TODO A11: SeasonalWeatherBlock + air quality (reuse EnvironmentTab logic split на блоки)"
|
||
/>
|
||
</article>
|
||
</div>
|
||
</main>
|
||
);
|
||
}
|