diff --git a/frontend/src/app/analytics/page.tsx b/frontend/src/app/analytics/page.tsx index 1df9aa92..bd76d6f6 100644 --- a/frontend/src/app/analytics/page.tsx +++ b/frontend/src/app/analytics/page.tsx @@ -118,6 +118,7 @@ export default function SverdlMarketPage() {
+
+ {/* Header */} +
+ + ← GenDesign + +

+ Источники данных и атрибуция +

+

+ GenDesign объединяет открытые и лицензированные источники + пространственных и рыночных данных. Ниже — перечень источников, + режим их использования и требуемая атрибуция. Картографическая + подложка предоставлена участниками OpenStreetMap по лицензии ODbL. +

+
+ + {/* Sources table */} +
+
+
+ + {/* На узких экранах таблица скроллится горизонтально (issue #66). */} +
+ + + + + + + + + + + {SOURCES.map((s) => ( + + + + + + + ))} + +
ИсточникЧто используетсяЛицензия / режимАтрибуция
+ + {s.name} + + + + {s.hrefLabel} + + {s.usage} + {s.license} + + {s.attribution} +
+
+
+ + {/* OSM attribution note */} +
+
+ + {/* Legal caveat */} +

+ Перечень носит информационный характер и отражает текущий состав + используемых источников. По вопросам условий использования и + лицензирования данных — свяжитесь с командой GenDesign. +

+
+ + ); +} diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 13046cd8..53bbea46 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -88,3 +88,114 @@ body { a { color: inherit; } + +/* + * ── Responsive (issue #66) ─────────────────────────────────────────────────── + * 3 контекста (НЕ Tailwind sm/md/lg): phone <768, tablet 768–1280, desktop >1280. + * Утилиты ниже — единственный источник правды для брейкпоинтов. Компоненты + * подключаются через className; инлайн-стили остаются desktop-видом по умолчанию. + */ + +/* Никакого горизонтального скролла на узких экранах (acceptance #66). */ +html, +body { + max-width: 100%; + overflow-x: hidden; +} + +/* Sidebar-grid (напр. "240px 1fr" / "minmax(300px,360px) 1fr"): на планшете + * и ниже схлопывается в одну колонку, сайдбар уезжает над контентом. */ +@media (max-width: 1024px) { + .gd-grid-sidebar { + grid-template-columns: 1fr !important; + } + /* sticky-сайдбар внутри схлопнутого грида не должен «прилипать». */ + .gd-grid-sidebar > .gd-sidebar { + position: static !important; + top: auto !important; + height: auto !important; + } +} + +/* Карта/контент рядом с фиксированным сайдбаром: на планшете в колонку. */ +@media (max-width: 1024px) { + .gd-split { + flex-direction: column !important; + overflow: visible !important; + } + .gd-split > .gd-split-aside { + width: 100% !important; + flex-shrink: 1 !important; + /* в desktop у сайдбара не было левого паддинга (он стоял справа от карты); + * в колонке возвращаем симметрию. */ + padding-left: 16px !important; + padding-top: 0 !important; + } +} + +/* Nav-tabs: на телефоне — горизонтальный скролл со снапом, НЕ hamburger. */ +@media (max-width: 767px) { + .gd-nav-scroll { + overflow-x: auto; + flex-wrap: nowrap; + scroll-snap-type: x proximity; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + .gd-nav-scroll::-webkit-scrollbar { + display: none; + } + .gd-nav-scroll > * { + flex: 0 0 auto; + scroll-snap-align: start; + } +} + +/* Таблицы с фиксированной шириной колонок — оборачивать в .gd-table-scroll. */ +.gd-table-scroll { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +/* KPI-grid: на телефоне одна колонка во всю ширину (ui-conventions). */ +@media (max-width: 767px) { + .gd-kpi-grid { + grid-template-columns: 1fr !important; + } +} + +/* Карта Leaflet на узких экранах — гарантированная высота, чтобы не + * схлопнулась в 0 когда flex-родитель встаёт в колонку. */ +@media (max-width: 1024px) { + .gd-map-shell { + min-height: 420px; + } +} + +/* Section-nav сайдбара анализа: на планшете/телефоне (когда грид схлопнут) + * вертикальный список превращается в горизонтальную скролл-полоску со снапом — + * чтобы не съедать пол-экрана высоты над контентом (issue #66). */ +@media (max-width: 1024px) { + .gd-section-nav { + flex-direction: row !important; + overflow-x: auto; + padding: 8px 4px !important; + scroll-snap-type: x proximity; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + .gd-section-nav::-webkit-scrollbar { + display: none; + } + .gd-section-nav > * { + display: flex; + flex: 0 0 auto; + scroll-snap-align: start; + } + .gd-section-nav button { + width: auto !important; + white-space: nowrap; + padding-left: 12px !important; + padding-right: 12px !important; + } +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 066457b1..a2f43082 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -699,6 +699,12 @@ export default function HomePage() { > ПКК Росреестра + + Источники данных + GenDesign {new Date().getFullYear()} diff --git a/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx b/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx index 68ca08ea..f80f6349 100644 --- a/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx +++ b/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx @@ -134,8 +134,10 @@ export function AnalysisPageContent({ cad }: Props) { />
- {/* Page layout: sidebar (240px) + main scroll */} + {/* Page layout: sidebar (240px) + main scroll. На планшете/телефоне + .gd-grid-sidebar схлопывает грид в одну колонку (issue #66). */}
{/* ── Sidebar nav ───────────────────────────────────────────────── */}
- {/* Main layout */} + {/* Main layout. На планшете/телефоне .gd-split встаёт в колонку + (карта над сайдбаром), .gd-map-shell держит высоту карты (issue #66). */}
{/* Map area — position: relative so ParcelDrawer can anchor absolutely here */}
@@ -30,12 +31,13 @@ export function AnalyticsNav() { style={{ padding: "10px 14px", borderBottom: active - ? "2px solid #1d4ed8" + ? "2px solid var(--accent)" : "2px solid transparent", - color: active ? "#1d4ed8" : "#374151", + color: active ? "var(--accent)" : "var(--fg-secondary)", fontWeight: active ? 600 : 500, textDecoration: "none", fontSize: 14, + whiteSpace: "nowrap", }} > {tab.label} diff --git a/frontend/src/components/analytics/ChartShell.tsx b/frontend/src/components/analytics/ChartShell.tsx index b20d5fc3..bd4be921 100644 --- a/frontend/src/components/analytics/ChartShell.tsx +++ b/frontend/src/components/analytics/ChartShell.tsx @@ -1,7 +1,9 @@ "use client"; import dynamic from "next/dynamic"; +import { useEffect, useRef } from "react"; import type { CSSProperties } from "react"; +import type { EChartsInstance } from "echarts-for-react"; const ReactECharts = dynamic(() => import("echarts-for-react"), { ssr: false }); @@ -20,13 +22,37 @@ export function ChartShell({ loading, notMerge, }: Props) { + const wrapRef = useRef(null); + const instanceRef = useRef(null); + + // ECharts canvas only reflows on window.resize by default. On mobile/tablet + // the chart container width changes via CSS (sidebar grid collapse, flex + // column) WITHOUT a window resize, so the canvas keeps its desktop width and + // overflows. A ResizeObserver on the wrapper resizes the instance whenever its + // box changes — covers every chart that goes through ChartShell (issue #66). + // The instance is captured via onChartReady (a plain prop) rather than a ref, + // because next/dynamic does not forward refs to the wrapped component. + useEffect(() => { + const el = wrapRef.current; + if (el == null || typeof ResizeObserver === "undefined") return; + + const ro = new ResizeObserver(() => { + instanceRef.current?.resize(); + }); + ro.observe(el); + return () => ro.disconnect(); + }, []); + return ( -
+
{ + instanceRef.current = instance; + }} />
);