diff --git a/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx b/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx index 8d4cc818..db414820 100644 --- a/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx +++ b/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx @@ -119,6 +119,18 @@ export function AnalysisPageContent({ cad }: Props) { {/* Breadcrumb */} +

+ Анализ участка {cad} + {districtName && districtName !== "—" ? ` · ${districtName}` : ""} +

+ {/* Controls row — horizon selector drives the analyze + forecast queries */}
; iconBg: string; } const COLOR_BY_LABEL: Record = { - Можно: { bg: "#ecfdf5", border: "#34d399", icon: "✅", iconBg: "#10b981" }, - Нельзя: { bg: "#fef2f2", border: "#f87171", icon: "🚫", iconBg: "#ef4444" }, + Можно: { + bg: "#ecfdf5", + border: "#34d399", + Icon: CheckCircle2, + iconBg: "#10b981", + }, + Нельзя: { bg: "#fef2f2", border: "#f87171", Icon: XCircle, iconBg: "#ef4444" }, "С ограничениями": { bg: "#fffbeb", border: "#fbbf24", - icon: "⚠️", + Icon: AlertTriangle, iconBg: "#f59e0b", }, "Нужна проверка": { bg: "#f9fafb", border: "#9ca3af", - icon: "❓", + Icon: HelpCircle, iconBg: "#6b7280", }, }; @@ -55,14 +70,32 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) { if (!verdict) return null; const color = COLOR_BY_LABEL[verdict.verdict_label]; + const Icon = color.Icon; const hasDetails = verdict.blockers.length > 0 || verdict.warnings.length > 0; + const iconSx = { flexShrink: 0 } as const; const sourceHint = - verdict.source === "nspd_dump_partial" - ? "⏱ устарели" - : verdict.source === "no_data" - ? "❓ нет данных" - : "🟢 свежие"; + verdict.source === "nspd_dump_partial" ? ( + <> + + устарели + + ) : verdict.source === "no_data" ? ( + <> + + нет данных + + ) : ( + <> + + свежие + + ); const summaryParts: string[] = []; if (verdict.blockers.length > 0) @@ -94,11 +127,10 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) { display: "flex", alignItems: "center", justifyContent: "center", - fontSize: 20, flexShrink: 0, }} > - {color.icon} +
@@ -107,7 +139,15 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
{summaryText} {" · "} - + {sourceHint}
@@ -142,7 +182,23 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) { > {verdict.blockers.length > 0 && (
- 🚫 Блокеры + + + + Блокеры + +
    0 && (
    - ⚠️ Предупреждения + + + + Предупреждения + +
      - 🟡 + Вечная мерзлота — особые фундаменты
    )} @@ -114,7 +120,12 @@ export function GeotechRiskBlock({ risk }: Props) { gap: 6, }} > - + {risk.industrial_within_500m} промзон(ы) в <500м (риск загрязнения почв) diff --git a/frontend/src/components/site-finder/HydrologyBlock.tsx b/frontend/src/components/site-finder/HydrologyBlock.tsx index ed32f9d4..7f555320 100644 --- a/frontend/src/components/site-finder/HydrologyBlock.tsx +++ b/frontend/src/components/site-finder/HydrologyBlock.tsx @@ -1,16 +1,21 @@ "use client"; +import type { ComponentType } from "react"; +import { AlertTriangle, Droplet, Waves, type LucideProps } from "lucide-react"; import type { Hydrology, HydrologyNearest } from "@/types/site-finder"; interface Props { hydrology: Hydrology; } -const SUBTYPE_ICON: Record = { - river: "🏞", - stream: "💦", - lake_or_pond: "🪷", - canal: "🚣", +// Decorative water glyph per subtype — the text label already names the body +// (река / ручей / озеро / канал), so the icon is aria-hidden. Lucide has no +// distinct lake/canal glyph; flowing bodies use Waves, the rest use Droplet. +const SUBTYPE_ICON: Record> = { + river: Waves, + stream: Droplet, + lake_or_pond: Waves, + canal: Waves, }; function subtypeLabel(subtype: HydrologyNearest["subtype"]): string { @@ -63,7 +68,12 @@ export function HydrologyBlock({ hydrology }: Props) { gap: 6, }} > - + Возможная пойма — река/канал в <200м
)} @@ -76,7 +86,7 @@ export function HydrologyBlock({ hydrology }: Props) { ) : (
{top5.map((item, i) => { - const icon = SUBTYPE_ICON[item.subtype ?? ""] ?? "💧"; + const Icon = SUBTYPE_ICON[item.subtype ?? ""] ?? Droplet; const label = subtypeLabel(item.subtype); const name = item.name ?? "б/н"; return ( @@ -90,7 +100,12 @@ export function HydrologyBlock({ hydrology }: Props) { color: "#374151", }} > - {icon} + {name} {label} — {Math.round(item.distance_m)} м diff --git a/frontend/src/components/site-finder/OverviewTab.tsx b/frontend/src/components/site-finder/OverviewTab.tsx index 595a6e51..ab0f9030 100644 --- a/frontend/src/components/site-finder/OverviewTab.tsx +++ b/frontend/src/components/site-finder/OverviewTab.tsx @@ -1,5 +1,6 @@ "use client"; +import { AlertTriangle } from "lucide-react"; import type { FeatureCollection } from "geojson"; import type { ParcelAnalysis } from "@/types/site-finder"; import { SectionLabel } from "@/components/ui/SectionLabel"; @@ -185,13 +186,24 @@ export function OverviewTab({ data, onIsochronesResult }: Props) { {data.utilities.power_line_охранная_зона_flag && (
- ⚠ Охранная зона ЛЭП — капитальное строительство запрещено + + + Охранная зона ЛЭП — капитальное строительство запрещено +
)}
@@ -212,7 +224,12 @@ export function OverviewTab({ data, onIsochronesResult }: Props) { gap: 8, }} > - + Трамвай в {nearestTram} м — возможный источник шума
)} diff --git a/frontend/src/components/site-finder/analysis/ForecastChart.tsx b/frontend/src/components/site-finder/analysis/ForecastChart.tsx index 469d77cf..ab811474 100644 --- a/frontend/src/components/site-finder/analysis/ForecastChart.tsx +++ b/frontend/src/components/site-finder/analysis/ForecastChart.tsx @@ -61,11 +61,15 @@ const VIZ: Record = { // Single-line fallback colour (ui-tokens: одиночная линия — #374151). const SINGLE_LINE = "#374151"; -// Prediction / forecast (dashed) — --prediction-line. -const PREDICTION_LINE = "#0EA5E9"; -// Axis chrome. -const FG_SECONDARY = "#5B6066"; -const BORDER_STRONG = "#D1D5DB"; +// Chrome colours below are intentionally raw hex, NOT var(--token), for the same +// reason as VIZ above: ChartShell renders via echarts-for-react with the default +// CANVAS renderer (no opts.renderer:"svg"), and the canvas 2D context does not +// resolve CSS custom properties — `var(--x)` would paint as transparent/black. +// Values mirror ui-tokens.md exactly: --prediction-line, --fg-secondary, +// --border-strong. Keep in sync with ui-tokens.md by hand. +const PREDICTION_LINE = "#0EA5E9"; // --prediction-line +const FG_SECONDARY = "#5B6066"; // --fg-secondary +const BORDER_STRONG = "#D1D5DB"; // --border-strong // ── Tooltip row type (echarts-for-react gives loose params; narrow ourselves) ── interface TooltipParam { diff --git a/frontend/src/components/site-finder/analysis/Section2NetworksUtilities.tsx b/frontend/src/components/site-finder/analysis/Section2NetworksUtilities.tsx index 77209674..460a47de 100644 --- a/frontend/src/components/site-finder/analysis/Section2NetworksUtilities.tsx +++ b/frontend/src/components/site-finder/analysis/Section2NetworksUtilities.tsx @@ -804,7 +804,8 @@ export function Section2NetworksUtilities({ cad }: Props) { /> Участок находится в охранной зоне ЛЭП ≥35 кВ. Капитальное - строительство в ОЗ запрещено (СП 36.13330, ЗОУИТ тип 5). + строительство в охранной зоне (ОЗ) запрещено (СП 36.13330). Участок + в зоне с особыми условиями использования территорий (ЗОУИТ), тип 5.
)} diff --git a/frontend/src/components/site-finder/analysis/Section4Estimate.tsx b/frontend/src/components/site-finder/analysis/Section4Estimate.tsx index 118dea0a..8cc5be9d 100644 --- a/frontend/src/components/site-finder/analysis/Section4Estimate.tsx +++ b/frontend/src/components/site-finder/analysis/Section4Estimate.tsx @@ -219,6 +219,7 @@ export function Section4Estimate({ cad }: Props) { }} >