diff --git a/frontend/src/App.css b/frontend/src/App.css index 68e9169..697fb92 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -61,6 +61,7 @@ body, #root { .layout { min-height: 100vh; + height: 100vh; background: var(--bg); color: var(--text); display: flex; @@ -70,94 +71,51 @@ body, #root { .content { width: 100%; - max-width: 1200px; - margin: 0 auto; + max-width: none; + margin: 0; display: flex; flex-direction: column; - overflow-y: hidden; + overflow: hidden; + flex: 1 1 auto; + min-height: 0; } -.topbar { - display: flex; - justify-content: space-between; - border-radius: 0 0 14px 14px; - align-items: center; - padding: 18px 22px; - border-bottom: 1px solid var(--glass-border); - backdrop-filter: blur(10px); - background: var(--glass); -} - -.brand { - display: flex; - gap: 12px; - align-items: center; -} - -.pulse { - width: 12px; - height: 12px; - border-radius: 50%; - background: #f59e0b; - animation: pulse 2.4s infinite; -} - -.pulse.ok { - background: #34d399; -} - -@keyframes pulse { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.2); - } - 100% { - transform: scale(1); - } -} - -.title { - font-weight: 700; - letter-spacing: 0.4px; -} - -.subtitle { - color: var(--muted); - font-size: 13px; -} - -.top-actions { - display: flex; - gap: 10px; - align-items: center; +.status-strip { + flex: 0 0 auto; + padding: 6px 10px; + background: var(--surface); + border-bottom: 1px solid var(--border); + font-size: 12px; } .mini-info { display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - padding: 0 12px; + grid-template-columns: 1fr 1fr 1fr; + gap: 8px; + padding: 0; +} + +.mini-info.compact { + align-items: stretch; } .chip { - padding: 10px 12px; - border-radius: 12px; + padding: 4px 10px; + border-radius: 10px; background: var(--surface-strong); - border: 1px solid var(--border); display: flex; flex-direction: column; - gap: 4px; + gap: 2px; +} +.leaflet-container{ + height: 100% !important; +} +.chip.small span { + font-size: 12px; } -.chip span { - font-size: 13px; - color: var(--muted); -} - -.chip strong { - font-size: 18px; +.chip.small strong { + font-size: 16px; } .pill { @@ -190,15 +148,21 @@ body, #root { .info-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-template-columns: repeat(3, 1fr); gap: 12px; + padding-bottom: 32px; } .card { - padding: 12px; + width: 141px; + height: 141px; border-radius: 12px; background: var(--surface-strong); border: 1px solid var(--border); + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; } .card-label { @@ -213,26 +177,38 @@ body, #root { color: var(--text); } +.map-shell { + flex: 1 1 auto; + min-height: 0; + padding: 0; +} + .map-panel { - padding: 12px; + padding: 0; display: flex; flex-direction: column; - gap: 12px; + gap: 0; + flex: 1 1 auto; + min-height: 0; + background: transparent; + border: none; } .map { - height: 70vh; - border-radius: 12px; + height: 100%; + min-height: 0; + border-radius: 0; overflow: hidden; - border: 1px solid var(--border); + border: none; width: 100%; } .maplibre { - height: 70vh; - border-radius: 12px; + height: 100%; + min-height: 0; + border-radius: 0; overflow: hidden; - border: 1px solid var(--border); + border: none; width: 100%; } @@ -256,7 +232,8 @@ body, #root { display: flex; justify-content: space-between; align-items: center; - padding-bottom: 14px; + padding: 32px 0; + border-top: 1px solid var(--border); } .panel-title { @@ -293,6 +270,7 @@ body, #root { display: flex; flex-direction: column; gap: 12px; + padding-bottom: 80px; } .field { @@ -369,10 +347,11 @@ body, #root { gap: 10px; flex-wrap: wrap; align-items: center; + justify-content: end; } .field.actions { - grid-template-columns: 180px 1fr; + grid-template-columns: 1fr 1fr; background: var(--surface); } @@ -429,6 +408,44 @@ body, #root { border-color: var(--btn-ghost-border); } +.bottom-actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; + width: 100%; + height: 10vh; + min-height: 64px; + max-height: 120px; +} +.bottom-settings{ + display: grid; + grid-template-columns: 1fr; + gap: 0; + width: 100%; + height: 10vh; + min-height: 64px; + max-height: 120px; + position: absolute; + bottom: 0; + left: 0; +} +.bottom-settings .btn.block { + width: 100%; + height: 100%; + border-radius: 0; + font-size: 16px; +} +.bottom-actions .btn.block { + width: 100%; + height: 100%; + border-radius: 0; + font-size: 16px; +} + +.bottom-actions .btn.ghost { + border-right: 1px solid var(--border); +} + .settings-backdrop { position: fixed; inset: 0; @@ -476,7 +493,7 @@ body, #root { .settings-content { overflow: auto; - padding: 0 18px 18px; + padding: 0; display: flex; flex-direction: column; gap: 12px; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 74ff06b..d389fdb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,7 +5,6 @@ import "maplibre-gl/dist/maplibre-gl.css"; import { MapPanel } from "./components/MapPanel"; import { SettingsPanel } from "./components/SettingsPanel"; -import { Topbar } from "./components/Topbar"; import { TrackSummary } from "./components/TrackSummary"; import { useVectorMap } from "./hooks/useVectorMap"; import { getInitialTheme } from "./utils/theme"; @@ -309,11 +308,12 @@ export default function App() { return (
- setSettingsOpen(true)} /> +
+ +
-
-
- +
+
+
+ + +
+ setSettingsOpen(false)} @@ -332,8 +345,6 @@ export default function App() { status={status} theme={theme} onToggleTheme={() => setTheme((t) => (t === "dark" ? "light" : "dark"))} - mapMode={mapMode} - onMapModeChange={setMapMode} tilesets={tilesets} activeTileset={activeTileset} onActivateTileset={(name) => activateTileset(name).catch((err) => sendFrontLog(`${err}`, "error"))} diff --git a/frontend/src/components/SettingsPanel.tsx b/frontend/src/components/SettingsPanel.tsx index 09e1890..24852df 100644 --- a/frontend/src/components/SettingsPanel.tsx +++ b/frontend/src/components/SettingsPanel.tsx @@ -1,6 +1,6 @@ import type { MutableRefObject, RefObject } from "react"; import { FormEvent } from "react"; -import type { AppConfig, MapMode, SliceParams } from "../types"; +import type { AppConfig, SliceParams } from "../types"; type SettingsPanelProps = { open: boolean; @@ -9,8 +9,6 @@ type SettingsPanelProps = { status: string; theme: "dark" | "light"; onToggleTheme: () => void; - mapMode: MapMode; - onMapModeChange: (mode: MapMode) => void; tilesets: string[]; activeTileset: string; onActivateTileset: (name: string) => void; @@ -34,8 +32,6 @@ export const SettingsPanel = ({ status, theme, onToggleTheme, - mapMode, - onMapModeChange, tilesets, activeTileset, onActivateTileset, @@ -59,9 +55,6 @@ export const SettingsPanel = ({
Настройки
Управление источниками и нарезкой
-
@@ -80,213 +73,205 @@ export const SettingsPanel = ({
{status}
+ +
+
+
Тема
+
Светлая или тёмная
+
+ +
+ +
+
+
Наборы тайлов
+
Активация и обновление
+
+
+
+ + +
+ +
+
+
Нарезка тайлов
+
Запускает gdal2tiles на бэкенде
+
+
+ {sliceStatus} +
+
+
{ + event.preventDefault(); + onSliceSubmit(event); + }} + > + + + + + + + + + + + + + + + + + +
+ + +
+
+
-
-
-
-
Тема
-
Светлая или тёмная
-
- +
+
- -
- -
-
-
-
Наборы тайлов
-
Активация и обновление
-
-
-
- - -
-
- -
-
-
-
Нарезка тайлов
-
Запускает gdal2tiles на бэкенде
-
-
- {sliceStatus} -
-
-
{ - event.preventDefault(); - onSliceSubmit(event); - }} - > - - - - - - - - - - - - - - - - -
- Действия -
- - -
-
-
-
diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx deleted file mode 100644 index 8a79920..0000000 --- a/frontend/src/components/Topbar.tsx +++ /dev/null @@ -1,21 +0,0 @@ -type TopbarProps = { - status: string; - onOpenSettings: () => void; -}; - -export const Topbar = ({ status, onOpenSettings }: TopbarProps) => ( -
-
-
-
-
GoAgro
-
Тестовая версия
-
-
-
- -
-
-); diff --git a/frontend/src/components/TrackSummary.tsx b/frontend/src/components/TrackSummary.tsx index 24a417c..a2f1e85 100644 --- a/frontend/src/components/TrackSummary.tsx +++ b/frontend/src/components/TrackSummary.tsx @@ -4,17 +4,22 @@ import type { TrackPoint } from "../types"; type TrackSummaryProps = { lastPoint: TrackPoint | null; pointsCount: number; + compact?: boolean; }; -export const TrackSummary = ({ lastPoint, pointsCount }: TrackSummaryProps) => ( -
-
+export const TrackSummary = ({ lastPoint, pointsCount, compact }: TrackSummaryProps) => ( +
+
Последняя фиксация {formatTime(lastPoint?.timestamp)}
-
+
Точек трека {pointsCount}
+
+ Статус + ок +
);