Compare commits
No commits in common. "f13eac3b97d92e4b4e612145b124920929584d44" and "bfa95e4aeebfdb450e38befd5224d25f876196f0" have entirely different histories.
f13eac3b97
...
bfa95e4aee
8 changed files with 33 additions and 166 deletions
|
|
@ -119,18 +119,6 @@ export function AnalysisPageContent({ cad }: Props) {
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb */}
|
||||||
<Breadcrumb cad={cad} districtName={districtName} areaStr={areaStr} />
|
<Breadcrumb cad={cad} districtName={districtName} areaStr={areaStr} />
|
||||||
|
|
||||||
<h1
|
|
||||||
style={{
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: 600,
|
|
||||||
margin: "0 0 12px",
|
|
||||||
color: "var(--fg-primary)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Анализ участка {cad}
|
|
||||||
{districtName && districtName !== "—" ? ` · ${districtName}` : ""}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{/* Controls row — horizon selector drives the analyze + forecast queries */}
|
{/* Controls row — horizon selector drives the analyze + forecast queries */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { ComponentType } from "react";
|
|
||||||
import {
|
|
||||||
AlertOctagon,
|
|
||||||
AlertTriangle,
|
|
||||||
CheckCircle2,
|
|
||||||
Clock,
|
|
||||||
HelpCircle,
|
|
||||||
XCircle,
|
|
||||||
type LucideProps,
|
|
||||||
} from "lucide-react";
|
|
||||||
import type { GateVerdict, GateVerdictLabel } from "@/types/site-finder";
|
import type { GateVerdict, GateVerdictLabel } from "@/types/site-finder";
|
||||||
import { SectionLabel } from "@/components/ui/SectionLabel";
|
import { SectionLabel } from "@/components/ui/SectionLabel";
|
||||||
|
|
||||||
|
|
@ -20,28 +10,23 @@ interface GateVerdictBannerProps {
|
||||||
interface VerdictColor {
|
interface VerdictColor {
|
||||||
bg: string;
|
bg: string;
|
||||||
border: string;
|
border: string;
|
||||||
Icon: ComponentType<LucideProps>;
|
icon: string;
|
||||||
iconBg: string;
|
iconBg: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLOR_BY_LABEL: Record<GateVerdictLabel, VerdictColor> = {
|
const COLOR_BY_LABEL: Record<GateVerdictLabel, VerdictColor> = {
|
||||||
Можно: {
|
Можно: { bg: "#ecfdf5", border: "#34d399", icon: "✅", iconBg: "#10b981" },
|
||||||
bg: "#ecfdf5",
|
Нельзя: { bg: "#fef2f2", border: "#f87171", icon: "🚫", iconBg: "#ef4444" },
|
||||||
border: "#34d399",
|
|
||||||
Icon: CheckCircle2,
|
|
||||||
iconBg: "#10b981",
|
|
||||||
},
|
|
||||||
Нельзя: { bg: "#fef2f2", border: "#f87171", Icon: XCircle, iconBg: "#ef4444" },
|
|
||||||
"С ограничениями": {
|
"С ограничениями": {
|
||||||
bg: "#fffbeb",
|
bg: "#fffbeb",
|
||||||
border: "#fbbf24",
|
border: "#fbbf24",
|
||||||
Icon: AlertTriangle,
|
icon: "⚠️",
|
||||||
iconBg: "#f59e0b",
|
iconBg: "#f59e0b",
|
||||||
},
|
},
|
||||||
"Нужна проверка": {
|
"Нужна проверка": {
|
||||||
bg: "#f9fafb",
|
bg: "#f9fafb",
|
||||||
border: "#9ca3af",
|
border: "#9ca3af",
|
||||||
Icon: HelpCircle,
|
icon: "❓",
|
||||||
iconBg: "#6b7280",
|
iconBg: "#6b7280",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -70,32 +55,14 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
|
||||||
if (!verdict) return null;
|
if (!verdict) return null;
|
||||||
|
|
||||||
const color = COLOR_BY_LABEL[verdict.verdict_label];
|
const color = COLOR_BY_LABEL[verdict.verdict_label];
|
||||||
const Icon = color.Icon;
|
|
||||||
const hasDetails = verdict.blockers.length > 0 || verdict.warnings.length > 0;
|
const hasDetails = verdict.blockers.length > 0 || verdict.warnings.length > 0;
|
||||||
|
|
||||||
const iconSx = { flexShrink: 0 } as const;
|
|
||||||
const sourceHint =
|
const sourceHint =
|
||||||
verdict.source === "nspd_dump_partial" ? (
|
verdict.source === "nspd_dump_partial"
|
||||||
<>
|
? "⏱ устарели"
|
||||||
<Clock size={14} strokeWidth={1.5} aria-hidden style={iconSx} />
|
: verdict.source === "no_data"
|
||||||
устарели
|
? "❓ нет данных"
|
||||||
</>
|
: "🟢 свежие";
|
||||||
) : verdict.source === "no_data" ? (
|
|
||||||
<>
|
|
||||||
<HelpCircle size={14} strokeWidth={1.5} aria-hidden style={iconSx} />
|
|
||||||
нет данных
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CheckCircle2
|
|
||||||
size={14}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ ...iconSx, color: "var(--success, #0A7A3A)" }}
|
|
||||||
/>
|
|
||||||
свежие
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const summaryParts: string[] = [];
|
const summaryParts: string[] = [];
|
||||||
if (verdict.blockers.length > 0)
|
if (verdict.blockers.length > 0)
|
||||||
|
|
@ -127,10 +94,11 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
fontSize: 20,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon size={20} strokeWidth={1.5} aria-hidden />
|
{color.icon}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ fontSize: 18, fontWeight: 700, color: "#111827" }}>
|
<div style={{ fontSize: 18, fontWeight: 700, color: "#111827" }}>
|
||||||
|
|
@ -139,15 +107,7 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
|
||||||
<div style={{ fontSize: 12, color: "#6b7280", marginTop: 2 }}>
|
<div style={{ fontSize: 12, color: "#6b7280", marginTop: 2 }}>
|
||||||
{summaryText}
|
{summaryText}
|
||||||
{" · "}
|
{" · "}
|
||||||
<span
|
<span title={SOURCE_LABEL[verdict.source] ?? verdict.source}>
|
||||||
title={SOURCE_LABEL[verdict.source] ?? verdict.source}
|
|
||||||
style={{
|
|
||||||
display: "inline-flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 4,
|
|
||||||
verticalAlign: "middle",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{sourceHint}
|
{sourceHint}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -182,23 +142,7 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
|
||||||
>
|
>
|
||||||
{verdict.blockers.length > 0 && (
|
{verdict.blockers.length > 0 && (
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<SectionLabel>
|
<SectionLabel>🚫 Блокеры</SectionLabel>
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: "inline-flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 6,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AlertOctagon
|
|
||||||
size={16}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ flexShrink: 0, color: "var(--danger, #B3261E)" }}
|
|
||||||
/>
|
|
||||||
Блокеры
|
|
||||||
</span>
|
|
||||||
</SectionLabel>
|
|
||||||
<ul
|
<ul
|
||||||
style={{
|
style={{
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
|
|
@ -217,23 +161,7 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
|
||||||
)}
|
)}
|
||||||
{verdict.warnings.length > 0 && (
|
{verdict.warnings.length > 0 && (
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<SectionLabel>
|
<SectionLabel>⚠️ Предупреждения</SectionLabel>
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: "inline-flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 6,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AlertTriangle
|
|
||||||
size={16}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ flexShrink: 0, color: "var(--warn, #9A6700)" }}
|
|
||||||
/>
|
|
||||||
Предупреждения
|
|
||||||
</span>
|
|
||||||
</SectionLabel>
|
|
||||||
<ul
|
<ul
|
||||||
style={{
|
style={{
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AlertTriangle, Snowflake } from "lucide-react";
|
|
||||||
import type { GeotechRisk } from "@/types/site-finder";
|
import type { GeotechRisk } from "@/types/site-finder";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -94,12 +93,7 @@ export function GeotechRiskBlock({ risk }: Props) {
|
||||||
color: "#374151",
|
color: "#374151",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Snowflake
|
<span>🟡</span>
|
||||||
size={16}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ flexShrink: 0, color: "#92400e" }}
|
|
||||||
/>
|
|
||||||
<span>Вечная мерзлота — особые фундаменты</span>
|
<span>Вечная мерзлота — особые фундаменты</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -120,12 +114,7 @@ export function GeotechRiskBlock({ risk }: Props) {
|
||||||
gap: 6,
|
gap: 6,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AlertTriangle
|
<span>⚠</span>
|
||||||
size={16}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ flexShrink: 0 }}
|
|
||||||
/>
|
|
||||||
<span>
|
<span>
|
||||||
{risk.industrial_within_500m} промзон(ы) в <500м (риск
|
{risk.industrial_within_500m} промзон(ы) в <500м (риск
|
||||||
загрязнения почв)
|
загрязнения почв)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,16 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { ComponentType } from "react";
|
|
||||||
import { AlertTriangle, Droplet, Waves, type LucideProps } from "lucide-react";
|
|
||||||
import type { Hydrology, HydrologyNearest } from "@/types/site-finder";
|
import type { Hydrology, HydrologyNearest } from "@/types/site-finder";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
hydrology: Hydrology;
|
hydrology: Hydrology;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decorative water glyph per subtype — the text label already names the body
|
const SUBTYPE_ICON: Record<string, string> = {
|
||||||
// (река / ручей / озеро / канал), so the icon is aria-hidden. Lucide has no
|
river: "🏞",
|
||||||
// distinct lake/canal glyph; flowing bodies use Waves, the rest use Droplet.
|
stream: "💦",
|
||||||
const SUBTYPE_ICON: Record<string, ComponentType<LucideProps>> = {
|
lake_or_pond: "🪷",
|
||||||
river: Waves,
|
canal: "🚣",
|
||||||
stream: Droplet,
|
|
||||||
lake_or_pond: Waves,
|
|
||||||
canal: Waves,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function subtypeLabel(subtype: HydrologyNearest["subtype"]): string {
|
function subtypeLabel(subtype: HydrologyNearest["subtype"]): string {
|
||||||
|
|
@ -68,12 +63,7 @@ export function HydrologyBlock({ hydrology }: Props) {
|
||||||
gap: 6,
|
gap: 6,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AlertTriangle
|
<span>⚠</span>
|
||||||
size={16}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ flexShrink: 0 }}
|
|
||||||
/>
|
|
||||||
<span>Возможная пойма — река/канал в <200м</span>
|
<span>Возможная пойма — река/канал в <200м</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -86,7 +76,7 @@ export function HydrologyBlock({ hydrology }: Props) {
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||||
{top5.map((item, i) => {
|
{top5.map((item, i) => {
|
||||||
const Icon = SUBTYPE_ICON[item.subtype ?? ""] ?? Droplet;
|
const icon = SUBTYPE_ICON[item.subtype ?? ""] ?? "💧";
|
||||||
const label = subtypeLabel(item.subtype);
|
const label = subtypeLabel(item.subtype);
|
||||||
const name = item.name ?? "б/н";
|
const name = item.name ?? "б/н";
|
||||||
return (
|
return (
|
||||||
|
|
@ -100,12 +90,7 @@ export function HydrologyBlock({ hydrology }: Props) {
|
||||||
color: "#374151",
|
color: "#374151",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<span>{icon}</span>
|
||||||
size={16}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ flexShrink: 0, color: "#0369a1" }}
|
|
||||||
/>
|
|
||||||
<span>
|
<span>
|
||||||
{name} {label} — {Math.round(item.distance_m)} м
|
{name} {label} — {Math.round(item.distance_m)} м
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AlertTriangle } from "lucide-react";
|
|
||||||
import type { FeatureCollection } from "geojson";
|
import type { FeatureCollection } from "geojson";
|
||||||
import type { ParcelAnalysis } from "@/types/site-finder";
|
import type { ParcelAnalysis } from "@/types/site-finder";
|
||||||
import { SectionLabel } from "@/components/ui/SectionLabel";
|
import { SectionLabel } from "@/components/ui/SectionLabel";
|
||||||
|
|
@ -186,24 +185,13 @@ export function OverviewTab({ data, onIsochronesResult }: Props) {
|
||||||
{data.utilities.power_line_охранная_зона_flag && (
|
{data.utilities.power_line_охранная_зона_flag && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 6,
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: "#b3261e",
|
color: "#b3261e",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AlertTriangle
|
⚠ Охранная зона ЛЭП — капитальное строительство запрещено
|
||||||
size={16}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ flexShrink: 0 }}
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
Охранная зона ЛЭП — капитальное строительство запрещено
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -224,12 +212,7 @@ export function OverviewTab({ data, onIsochronesResult }: Props) {
|
||||||
gap: 8,
|
gap: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AlertTriangle
|
<span style={{ fontSize: 16 }}>⚠</span>
|
||||||
size={16}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden
|
|
||||||
style={{ flexShrink: 0 }}
|
|
||||||
/>
|
|
||||||
<span>Трамвай в {nearestTram} м — возможный источник шума</span>
|
<span>Трамвай в {nearestTram} м — возможный источник шума</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -61,15 +61,11 @@ const VIZ: Record<ScenarioKey, string> = {
|
||||||
|
|
||||||
// Single-line fallback colour (ui-tokens: одиночная линия — #374151).
|
// Single-line fallback colour (ui-tokens: одиночная линия — #374151).
|
||||||
const SINGLE_LINE = "#374151";
|
const SINGLE_LINE = "#374151";
|
||||||
// Chrome colours below are intentionally raw hex, NOT var(--token), for the same
|
// Prediction / forecast (dashed) — --prediction-line.
|
||||||
// reason as VIZ above: ChartShell renders via echarts-for-react with the default
|
const PREDICTION_LINE = "#0EA5E9";
|
||||||
// CANVAS renderer (no opts.renderer:"svg"), and the canvas 2D context does not
|
// Axis chrome.
|
||||||
// resolve CSS custom properties — `var(--x)` would paint as transparent/black.
|
const FG_SECONDARY = "#5B6066";
|
||||||
// Values mirror ui-tokens.md exactly: --prediction-line, --fg-secondary,
|
const BORDER_STRONG = "#D1D5DB";
|
||||||
// --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) ──
|
// ── Tooltip row type (echarts-for-react gives loose params; narrow ourselves) ──
|
||||||
interface TooltipParam {
|
interface TooltipParam {
|
||||||
|
|
|
||||||
|
|
@ -804,8 +804,7 @@ export function Section2NetworksUtilities({ cad }: Props) {
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
Участок находится в охранной зоне ЛЭП ≥35 кВ. Капитальное
|
Участок находится в охранной зоне ЛЭП ≥35 кВ. Капитальное
|
||||||
строительство в охранной зоне (ОЗ) запрещено (СП 36.13330). Участок
|
строительство в ОЗ запрещено (СП 36.13330, ЗОУИТ тип 5).
|
||||||
в зоне с особыми условиями использования территорий (ЗОУИТ), тип 5.
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,6 @@ export function Section4Estimate({ cad }: Props) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
title="Зоны с особыми условиями использования территорий — охранные зоны инженерной инфраструктуры (ЛЭП, газопровод и т.д.)"
|
|
||||||
style={{
|
style={{
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue