gendesign/frontend/src/app/site-finder/analysis/[cad]/page.tsx
lekss361 5b215def23 fix(sf-fe): decode cad URL param + hide entry sidebar when drawer open
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.
2026-05-18 02:30:45 +03:00

184 lines
5.8 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 { 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 34 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 24 (A5A11)
</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>
);
}