M2: frontend walkthrough fixes + explicit dev compose stack
frontend / frontend (push) Successful in 2m0s
pytest / test (push) Successful in 1m32s

Post-M2 self-walkthrough polish, batched into one commit.

Map / heat:
- fix heat-layer white-screen crash after login (add layer to map before
  setLatLngs; an off-map leaflet.heat layer has a null _map and throws)
- normalize each heat layer to the densest pixel cell visible in the CURRENT
  viewport (maxZoom:0 so intensity factor f=1) and recompute on moveend/zoomend,
  so sparse poo data reaches red and stays normalized at any zoom level
- dark CARTO basemap tiles when the color scheme is dark

UI:
- dark-mode toggle in the top-right, beside the settings gear
- switch top-right nav (records / theme / settings / logout) to Feather icons
  with hover tooltips
- home: Grafana-style quick time-range presets + back/forward shift buttons,
  placed between the From/To pickers and Apply; fix Select/tooltip z-index
  (Leaflet stacking) and the shift-button height alignment

API client:
- stop flooding GET /api/session with 401s: the session probe and the login
  endpoint own their 401s (no global redirect), which fixes the logout hang and
  the spinning login page

Compose:
- rename docker-compose.override.yml -> docker-compose.dev.yml as an explicit,
  non-auto-layered dev stack (8001, -dev container names, prod-copy ./data DB);
  update tests/test_deployment.py (read dev.yml, tolerate the !override tag) and
  the README "Docker Compose" section

Tests:
- pixel-grid peak counter, time-range presets, heat-layer ordering regression,
  and 401-redirect regression
This commit is contained in:
2026-06-13 15:20:50 +02:00
parent bd09523e94
commit da236643f2
16 changed files with 722 additions and 66 deletions
+70 -23
View File
@@ -13,10 +13,17 @@
* AppLayout renders a nav with a gear-icon entry for /config and a logout button (T07).
*/
import { MantineProvider } from '@mantine/core'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { BrowserRouter, Routes, Route, Link, Outlet, useNavigate } from 'react-router-dom'
import { Button, Group } from '@mantine/core'
import {
MantineProvider,
Group,
ActionIcon,
Tooltip,
useMantineColorScheme,
useComputedColorScheme,
} from '@mantine/core'
import { List, Settings, Sun, Moon, LogOut } from 'react-feather'
// Mantine requires its CSS to be imported once.
import '@mantine/core/styles.css'
@@ -70,9 +77,40 @@ function LogoutButton() {
}
return (
<Button variant="subtle" size="xs" onClick={handleLogout} data-testid="logout-button">
Log out
</Button>
<Tooltip label="Log out">
<ActionIcon
variant="default"
size="lg"
onClick={handleLogout}
aria-label="Log out"
data-testid="logout-button"
>
<LogOut size={18} />
</ActionIcon>
</Tooltip>
)
}
// ---------------------------------------------------------------------------
// Dark-mode toggle (sits next to the gear / settings icon)
// ---------------------------------------------------------------------------
function ColorSchemeToggle() {
const { setColorScheme } = useMantineColorScheme()
const computed = useComputedColorScheme('light', { getInitialValueInEffect: true })
const isDark = computed === 'dark'
return (
<Tooltip label={isDark ? 'Light mode' : 'Dark mode'}>
<ActionIcon
variant="default"
size="lg"
aria-label="Toggle color scheme"
onClick={() => setColorScheme(isDark ? 'light' : 'dark')}
data-testid="color-scheme-toggle"
>
{isDark ? <Sun size={18} /> : <Moon size={18} />}
</ActionIcon>
</Tooltip>
)
}
@@ -90,7 +128,7 @@ function AppLayout() {
alignItems: 'center',
justifyContent: 'space-between',
padding: '0.5rem 1rem',
borderBottom: '1px solid #eee',
borderBottom: '1px solid var(--mantine-color-default-border)',
}}
>
<Link to="/" style={{ fontWeight: 600, textDecoration: 'none' }}>
@@ -99,22 +137,31 @@ function AppLayout() {
<Group gap="xs">
{/* Records nav link */}
<Link
to="/records"
style={{ textDecoration: 'none', fontSize: '0.9rem' }}
title="Records"
>
Records
</Link>
{/* Gear icon nav slot — links to config page (§5#10) */}
<Link
to="/config"
aria-label="Configuration"
style={{ fontSize: '1.25rem', textDecoration: 'none' }}
title="Configuration"
>
</Link>
<Tooltip label="Records">
<ActionIcon
component={Link}
to="/records"
variant="default"
size="lg"
aria-label="Records"
>
<List size={18} />
</ActionIcon>
</Tooltip>
{/* Dark-mode toggle — directly beside the settings gear */}
<ColorSchemeToggle />
{/* Settings — links to config page (§5#10) */}
<Tooltip label="Settings">
<ActionIcon
component={Link}
to="/config"
variant="default"
size="lg"
aria-label="Settings"
>
<Settings size={18} />
</ActionIcon>
</Tooltip>
<LogoutButton />
</Group>
</nav>
@@ -133,7 +180,7 @@ function AppLayout() {
export default function App() {
return (
<MantineProvider>
<MantineProvider defaultColorScheme="auto">
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<SessionProvider>