diff --git a/public/data/MARI.json b/public/data/MARI.json
index 58d1d96..f2b4b10 100644
--- a/public/data/MARI.json
+++ b/public/data/MARI.json
@@ -465,10 +465,37 @@
{
"timestamp": "2026-06-06T15:10:00.000Z",
"level": 467.75,
+ "flow": 0.7,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.2,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 467.75,
+ "flow": 0.7,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.2,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 467.75,
+ "flow": 0.7,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.2,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 467.75,
"flow": 0,
"inflow": 2.24,
"volume": 26.54,
- "temperature": 22,
+ "temperature": 21.8,
"precipitation": 0
}
]
\ No newline at end of file
diff --git a/public/data/MZHR.json b/public/data/MZHR.json
index 4673920..9781ba7 100644
--- a/public/data/MZHR.json
+++ b/public/data/MZHR.json
@@ -475,9 +475,36 @@
"timestamp": "2026-06-06T15:10:00.000Z",
"level": 352.83,
"flow": 2.52,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.9,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 352.83,
+ "flow": 2.52,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.9,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 352.84,
+ "flow": 2.52,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.9,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 352.84,
+ "flow": 0,
"inflow": 1.47,
"volume": 32.31,
- "temperature": 22.7,
+ "temperature": 22.5,
"precipitation": 0
}
]
\ No newline at end of file
diff --git a/public/data/VLHN.json b/public/data/VLHN.json
index b197b0b..2bd7e1a 100644
--- a/public/data/VLHN.json
+++ b/public/data/VLHN.json
@@ -476,6 +476,33 @@
"level": 369.83,
"flow": 14.23,
"inflow": 0,
+ "volume": 0,
+ "temperature": 22.5,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 369.83,
+ "flow": 14.23,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.5,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 369.83,
+ "flow": 14.23,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.5,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 369.83,
+ "flow": 14.23,
+ "inflow": 0,
"volume": 20.37,
"temperature": 22.6,
"precipitation": 0
diff --git a/public/data/VLKO.json b/public/data/VLKO.json
index 38aa456..c571598 100644
--- a/public/data/VLKO.json
+++ b/public/data/VLKO.json
@@ -475,9 +475,36 @@
"timestamp": "2026-06-06T15:10:00.000Z",
"level": 352.43,
"flow": 19.05,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 21.9,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 352.43,
+ "flow": 19.05,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 21.9,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 352.43,
+ "flow": 19.05,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 21.9,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 352.43,
+ "flow": 19.05,
"inflow": 13.43,
"volume": 2.74,
- "temperature": 21.9,
+ "temperature": 22.1,
"precipitation": 0
}
]
\ No newline at end of file
diff --git a/public/data/VLL1.json b/public/data/VLL1.json
index f35ac81..5b8bcb6 100644
--- a/public/data/VLL1.json
+++ b/public/data/VLL1.json
@@ -474,10 +474,37 @@
{
"timestamp": "2026-06-06T15:10:00.000Z",
"level": 723.09,
+ "flow": 1.51,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 20.8,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 723.09,
+ "flow": 1.51,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 20.8,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 723.09,
+ "flow": 1.51,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 20.8,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 723.09,
"flow": 0,
"inflow": 9.25,
"volume": 199.67,
- "temperature": 20.7,
+ "temperature": 20.6,
"precipitation": 0
}
]
\ No newline at end of file
diff --git a/public/data/VLL2.json b/public/data/VLL2.json
index 7be54d7..0d99a8b 100644
--- a/public/data/VLL2.json
+++ b/public/data/VLL2.json
@@ -465,7 +465,7 @@
{
"timestamp": "2026-06-06T15:00:00.000Z",
"level": 558.44,
- "flow": 0,
+ "flow": 7.51,
"inflow": 0,
"volume": 0,
"temperature": 21.8,
@@ -474,10 +474,37 @@
{
"timestamp": "2026-06-06T15:10:00.000Z",
"level": 558.43,
+ "flow": 7.51,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 21.8,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 558.41,
+ "flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 21.8,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 558.38,
+ "flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 21.8,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 558.37,
"flow": 0,
"inflow": 5.37,
"volume": 0.35,
- "temperature": 21.7,
+ "temperature": 21.6,
"precipitation": 0
}
]
\ No newline at end of file
diff --git a/public/data/VLOR.json b/public/data/VLOR.json
index 4857002..f77d133 100644
--- a/public/data/VLOR.json
+++ b/public/data/VLOR.json
@@ -475,9 +475,36 @@
"timestamp": "2026-06-06T15:10:00.000Z",
"level": 345.29,
"flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.6,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 345.29,
+ "flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.6,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 345.29,
+ "flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 22.6,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 345.29,
+ "flow": 0,
"inflow": 24.39,
"volume": 522.72,
- "temperature": 22.6,
+ "temperature": 22.3,
"precipitation": 0
}
]
\ No newline at end of file
diff --git a/public/data/VLSL.json b/public/data/VLSL.json
index f80c887..e847fd3 100644
--- a/public/data/VLSL.json
+++ b/public/data/VLSL.json
@@ -475,9 +475,36 @@
"timestamp": "2026-06-06T15:10:00.000Z",
"level": 269.88,
"flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 23.5,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 269.88,
+ "flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 23.5,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 269.88,
+ "flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 23.5,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 269.89,
+ "flow": 0,
"inflow": 81.06,
"volume": 261.1,
- "temperature": 23.5,
+ "temperature": 23.3,
"precipitation": 0
}
]
\ No newline at end of file
diff --git a/public/data/VLST.json b/public/data/VLST.json
index cbb28e3..5bbf55b 100644
--- a/public/data/VLST.json
+++ b/public/data/VLST.json
@@ -475,9 +475,36 @@
"timestamp": "2026-06-06T15:10:00.000Z",
"level": 216.98,
"flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 23.2,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:20:00.000Z",
+ "level": 216.96,
+ "flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 23.2,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:30:00.000Z",
+ "level": 216.94,
+ "flow": 0,
+ "inflow": 0,
+ "volume": 0,
+ "temperature": 23.2,
+ "precipitation": 0
+ },
+ {
+ "timestamp": "2026-06-06T15:40:00.000Z",
+ "level": 216.95,
+ "flow": 0,
"inflow": 48.25,
"volume": 8.14,
- "temperature": 23.2,
+ "temperature": 22.9,
"precipitation": 0
}
]
\ No newline at end of file
diff --git a/public/data/lakes_index.json b/public/data/lakes_index.json
index 413904f..9eee985 100644
--- a/public/data/lakes_index.json
+++ b/public/data/lakes_index.json
@@ -33,9 +33,9 @@
"name": "Lipno II",
"river": "Vltava",
"priority": true,
- "level": "558.43",
+ "level": "558.37",
"capacity": 23.3,
- "storageDiff": -2.07,
+ "storageDiff": -2.13,
"inflow": "5.4",
"outflow": "0.0",
"volume": 0.35,
@@ -43,9 +43,6 @@
"lat": 48.625,
"lng": 14.318,
"sparkline": [
- 558.63,
- 558.62,
- 558.6,
558.58,
558.56,
558.54,
@@ -54,7 +51,10 @@
558.49,
558.47,
558.44,
- 558.43
+ 558.43,
+ 558.41,
+ 558.38,
+ 558.37
]
},
{
@@ -72,9 +72,9 @@
"lat": 49.183,
"lng": 14.444,
"sparkline": [
- 369.84,
- 369.84,
- 369.84,
+ 369.83,
+ 369.83,
+ 369.83,
369.83,
369.83,
369.83,
@@ -101,9 +101,6 @@
"lat": 49.255,
"lng": 14.398,
"sparkline": [
- 352.44,
- 352.43,
- 352.43,
352.44,
352.44,
352.44,
@@ -112,6 +109,9 @@
352.43,
352.44,
352.44,
+ 352.43,
+ 352.43,
+ 352.43,
352.43
]
},
@@ -149,9 +149,9 @@
"name": "Slapy",
"river": "Vltava",
"priority": true,
- "level": "269.88",
+ "level": "269.89",
"capacity": 97,
- "storageDiff": -0.72,
+ "storageDiff": -0.71,
"inflow": "81.1",
"outflow": "0.0",
"volume": 261.1,
@@ -159,9 +159,6 @@
"lat": 49.822,
"lng": 14.436,
"sparkline": [
- 269.88,
- 269.89,
- 269.89,
269.89,
269.88,
269.88,
@@ -170,7 +167,10 @@
269.88,
269.88,
269.88,
- 269.88
+ 269.88,
+ 269.88,
+ 269.88,
+ 269.89
]
},
{
@@ -178,9 +178,9 @@
"name": "Štěchovice",
"river": "Vltava",
"priority": true,
- "level": "216.98",
+ "level": "216.95",
"capacity": 72.7,
- "storageDiff": -2.42,
+ "storageDiff": -2.45,
"inflow": "48.3",
"outflow": "0.0",
"volume": 8.14,
@@ -188,9 +188,6 @@
"lat": 49.845,
"lng": 14.412,
"sparkline": [
- 217,
- 216.97,
- 216.99,
216.98,
216.95,
216.98,
@@ -199,7 +196,10 @@
216.98,
216.97,
216.95,
- 216.98
+ 216.98,
+ 216.96,
+ 216.94,
+ 216.95
]
},
{
@@ -236,19 +236,16 @@
"name": "Hracholusky",
"river": "Mže",
"priority": true,
- "level": "352.83",
+ "level": "352.84",
"capacity": 57,
- "storageDiff": -16.67,
+ "storageDiff": -16.66,
"inflow": "1.5",
- "outflow": "2.5",
+ "outflow": "0.0",
"volume": 32.31,
"maxVolume": 56.7,
"lat": 49.789,
"lng": 13.155,
"sparkline": [
- 352.84,
- 352.84,
- 352.83,
352.84,
352.84,
352.84,
@@ -257,7 +254,10 @@
352.84,
352.84,
352.84,
- 352.83
+ 352.83,
+ 352.83,
+ 352.84,
+ 352.84
]
}
]
\ No newline at end of file
diff --git a/scripts/watchData.ts b/scripts/watchData.ts
index a2f98c3..7bac7a2 100644
--- a/scripts/watchData.ts
+++ b/scripts/watchData.ts
@@ -1,25 +1,52 @@
import { execSync } from 'child_process';
-const args = process.argv.slice(2);
-const minutes = parseInt(args[0], 10) || 10;
-const intervalMs = minutes * 60 * 1000;
+// How many minutes after the 10-minute mark should we run the scraper?
+// The basin authority (PVL) generates data at HH:00, HH:10, HH:20... but it takes time to publish.
+// 5 minutes (HH:05, HH:15...) is a safe buffer to avoid fetching outdated data.
+const offsetMinutes = 5;
console.log(`\n⏱️ HLADINATOR Watcher spuštěn!`);
-console.log(`Budu automaticky stahovat nová data každých ${minutes} minut.\n`);
+console.log(`Budu automaticky stahovat nová data vždy v časech končících na ${offsetMinutes} (např. 10:05, 10:15, 10:25...).\nTo zajistí, že má Povodí dostatek času data vygenerovat a nahrát.\n`);
function runUpdate() {
const now = new Date().toLocaleTimeString('cs-CZ');
console.log(`[${now}] 🔄 Spouštím npm run data:update...`);
try {
execSync('npm run data:update', { stdio: 'inherit' });
- console.log(`[${new Date().toLocaleTimeString('cs-CZ')}] ✅ Úspěšně hotovo. Další kontrola za ${minutes} minut...\n`);
+ console.log(`[${new Date().toLocaleTimeString('cs-CZ')}] ✅ Úspěšně hotovo.\n`);
} catch (error: any) {
console.error(`[${new Date().toLocaleTimeString('cs-CZ')}] ❌ Chyba při aktualizaci:`, error.message);
}
+ scheduleNextRun();
}
-// Spustit ihned po zapnutí
-runUpdate();
+function scheduleNextRun() {
+ const now = new Date();
+ const currentMinute = now.getMinutes();
+
+ // Find the next target minute (ending in 5)
+ // E.g. if it's 12, next will be 15. If it's 26, next will be 35.
+ let nextMinute = Math.floor(currentMinute / 10) * 10 + offsetMinutes;
+ if (nextMinute <= currentMinute) {
+ nextMinute += 10;
+ }
+
+ const targetTime = new Date(now);
+ if (nextMinute >= 60) {
+ targetTime.setHours(targetTime.getHours() + 1);
+ targetTime.setMinutes(nextMinute % 60);
+ } else {
+ targetTime.setMinutes(nextMinute);
+ }
+ targetTime.setSeconds(0);
+ targetTime.setMilliseconds(0);
+
+ const waitMs = targetTime.getTime() - now.getTime();
+
+ console.log(`[${new Date().toLocaleTimeString('cs-CZ')}] ⏳ Další stahování naplánováno na: ${targetTime.toLocaleTimeString('cs-CZ')} (za ${(waitMs / 60000).toFixed(1)} minut)\n`);
+
+ setTimeout(runUpdate, waitMs);
+}
-// A pak periodicky v zadaném intervalu
-setInterval(runUpdate, intervalMs);
+// Run update immediately on first launch and then set the timer
+runUpdate();
diff --git a/src/App.css b/src/App.css
index 4fc42ae..98d74a9 100644
--- a/src/App.css
+++ b/src/App.css
@@ -7,12 +7,12 @@
}
.sidebar {
- width: 250px;
+ width: 190px;
background-color: var(--bg-card);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
- padding: 1.5rem 1rem;
+ padding: 1.5rem 0.75rem;
transition: width 0.3s ease;
overflow: hidden;
}
@@ -75,8 +75,8 @@
.nav-item {
display: flex;
align-items: center;
- gap: 1rem;
- padding: 0.75rem 1rem;
+ gap: 0.75rem;
+ padding: 0.75rem 0.5rem;
border-radius: 0.5rem;
color: var(--text-muted);
font-size: 0.95rem;
@@ -86,6 +86,11 @@
white-space: nowrap;
}
+.nav-item svg {
+ flex-shrink: 0;
+ min-width: 20px;
+}
+
.nav-item:hover {
background-color: rgba(255, 255, 255, 0.03);
color: var(--text-main);
diff --git a/src/App.tsx b/src/App.tsx
index d18b697..2516c81 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
-import { Routes, Route, useParams, useLocation, useNavigate, Navigate } from 'react-router-dom';
+import { Routes, Route, useParams, Navigate } from 'react-router-dom';
import LakeDetail from './components/LakeDetail';
import LakesOverview from './components/LakesOverview';
import LakeMap from './components/LakeMap';
diff --git a/src/components/FavoritesOverview.tsx b/src/components/FavoritesOverview.tsx
index 8143c12..44de3f9 100644
--- a/src/components/FavoritesOverview.tsx
+++ b/src/components/FavoritesOverview.tsx
@@ -6,7 +6,7 @@ import { Helmet } from 'react-helmet-async';
import { CircularProgress } from './CircularProgress';
import { useNavigate } from 'react-router-dom';
import { slugify } from '../utils/slugify';
-import { AreaChart, Area, ResponsiveContainer } from 'recharts';
+import { AreaChart, Area, ResponsiveContainer, YAxis } from 'recharts';
import { FiTrendingUp, FiTrendingDown } from 'react-icons/fi';
interface Lake {
@@ -80,7 +80,21 @@ const FavoritesOverview = ({ language }: Props) => {
{favoriteLakes.map(lake => {
const chartData = lake.sparkline.map((val, i) => ({ name: i, value: val }));
- const isFav = isFavorite(lake.id);
+
+ const minVal = Math.min(...lake.sparkline);
+ const maxVal = Math.max(...lake.sparkline);
+ const diff = maxVal - minVal;
+ const padding = diff === 0 ? 0.1 : diff * 0.1;
+ const yDomain = [minVal - padding, maxVal + padding];
+
+ const firstVal = lake.sparkline[0] || 0;
+ const lastVal = lake.sparkline[lake.sparkline.length - 1] || 0;
+ const trendDiff = lastVal - firstVal;
+
+ let trendColor = 'var(--color-cyan)';
+ if (trendDiff > 0.01) trendColor = 'var(--color-green)';
+ else if (trendDiff < -0.01) trendColor = 'var(--color-red)';
+
return (
{
-
-
-
-
{t[language].kpi.inflow} {lake.inflow} m³/s
+
+
-
-
-
{t[language].kpi.outflow} {lake.outflow} m³/s
+
+
+
+
+ {t[language].kpi.inflow} {lake.inflow} m³/s
+
+
+
+ {t[language].kpi.outflow} {lake.outflow} m³/s
+
diff --git a/src/components/KpiCards.tsx b/src/components/KpiCards.tsx
index 4e096da..0b442dc 100644
--- a/src/components/KpiCards.tsx
+++ b/src/components/KpiCards.tsx
@@ -13,6 +13,9 @@ interface KpiData {
volume: number;
fullness: number;
storageDiff?: number;
+ minDiff?: number;
+ avgInflow24h?: number;
+ avgOutflow24h?: number;
}
interface Props {
@@ -37,43 +40,62 @@ const KpiCards = ({ data, language, lakeName = 'Lipno 1' }: Props) => {
return (
<>
- {/* CARD 1: HLADINA */}
+ {/* CARD 1: WATER LEVEL */}
{dict.level} {lakeName}
-
+
{data.level.toFixed(2)} m n. m.
-
-
= 0 ? 'var(--color-green)' : 'var(--color-red)' }}>
- ({(data.levelDiff24h ?? 0) > 0 ? '+' : ''}{((data.levelDiff24h ?? 0) * 100).toFixed(1)} cm / 24h)
+
+
+ 1D
+ = 0 ? 'var(--color-green)' : 'var(--color-red)' }}>
+ {(data.levelDiff24h ?? 0) > 0 ? '+' : ''}{((data.levelDiff24h ?? 0) * 100).toFixed(1)} cm
+
-
= 0 ? 'var(--color-green)' : 'var(--color-red)' }}>
- ({(data.levelDiff7d ?? 0) > 0 ? '+' : ''}{((data.levelDiff7d ?? 0) * 100).toFixed(1)} cm / 7d)
+
+ 7D
+ = 0 ? 'var(--color-green)' : 'var(--color-red)' }}>
+ {(data.levelDiff7d ?? 0) > 0 ? '+' : ''}{((data.levelDiff7d ?? 0) * 100).toFixed(1)} cm
+
-
= 0 ? 'var(--color-green)' : 'var(--color-red)' }}>
- ({(data.levelDiff30d ?? 0) > 0 ? '+' : ''}{((data.levelDiff30d ?? 0) * 100).toFixed(1)} cm / 30d)
+
+ 30D
+ = 0 ? 'var(--color-green)' : 'var(--color-red)' }}>
+ {(data.levelDiff30d ?? 0) > 0 ? '+' : ''}{((data.levelDiff30d ?? 0) * 100).toFixed(1)} cm
+
- {/* CARD 2: PRŮTOK */}
+ {/* CARD 2: FLOW */}
{dict.flow}
-
-
+
+
{dict.inflow}: {data.inflow.toFixed(1)} m³/s
-
-
+ {data.avgInflow24h !== undefined && (
+
+ Ø 24h: {data.avgInflow24h.toFixed(1)} m³/s
+
+ )}
+
+
{dict.outflow}: {data.outflow.toFixed(1)} m³/s
{flowDiff > 0 ? : flowDiff < 0 ? : null}
+ {data.avgOutflow24h !== undefined && (
+
+ Ø 24h: {data.avgOutflow24h.toFixed(1)} m³/s
+
+ )}
{/* Flow Circle */}
@@ -98,7 +120,7 @@ const KpiCards = ({ data, language, lakeName = 'Lipno 1' }: Props) => {
- {/* CARD 3: NAPLNĚNOST */}
+ {/* CARD 3: CAPACITY */}
{dict.fullness}
@@ -133,16 +155,23 @@ const KpiCards = ({ data, language, lakeName = 'Lipno 1' }: Props) => {
)}
-
-
-
-
-
+
+
+
{data.storageDiff !== undefined && data.storageDiff !== 0 ? (data.storageDiff > 0 ? `+${data.storageDiff.toFixed(2)} m` : `${data.storageDiff.toFixed(2)} m`) : (data.fullness > 0 ? `${data.fullness.toFixed(1)}%` : 'N/A')}
-
- {dict.volume}: {data.volume.toFixed(1)} mil. m³
+
+ {dict.volume}: {data.volume.toFixed(1)} mil. m³
+ {data.minDiff !== undefined && (
+
+ {language === 'cs' ? 'K minimu:' : 'To min:'} {data.minDiff.toFixed(2)} m
+
+ )}
+
+
+
+
diff --git a/src/components/LakeDetail.tsx b/src/components/LakeDetail.tsx
index 9c92144..605fe17 100644
--- a/src/components/LakeDetail.tsx
+++ b/src/components/LakeDetail.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
-import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Line, ComposedChart, ReferenceLine, Bar } from 'recharts';
+import { ComposedChart, Area, Line, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine } from 'recharts';
import { Helmet } from 'react-helmet-async';
import { type Language, t } from '../translations';
import KpiCards from './KpiCards';
@@ -30,7 +30,7 @@ interface Props {
const CustomTooltip = ({ active, payload, label, language, isWeather }: any) => {
if (active && payload && payload.length) {
- const dict = t[language].chart;
+ const dict = t[language as Language].chart;
if (isWeather) {
return (
@@ -118,7 +118,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
volume: item.volume || 0,
fullness: 0,
temperature: item.temperature,
- precipitation: item.precipitation
+ precipitation: item.precipitation === null ? undefined : item.precipitation
};
});
setData(formattedData);
@@ -190,6 +190,10 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
let minDiff7d = Infinity;
let minDiff30d = Infinity;
+ let inflowSum24h = 0;
+ let outflowSum24h = 0;
+ let flowCount24h = 0;
+
for (const d of data) {
const t = new Date(d.timestamp).getTime();
@@ -210,11 +214,23 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
minDiff30d = diff30d;
level30dAgo = d.level;
}
+
+ if (t >= targetMs24h && d.inflow !== undefined && d.outflow !== undefined) {
+ inflowSum24h += d.inflow;
+ outflowSum24h += d.outflow;
+ flowCount24h++;
+ }
}
const levelDiff24h = latestData.level - level24hAgo;
const levelDiff7d = latestData.level - level7dAgo;
const levelDiff30d = latestData.level - level30dAgo;
+
+ const avgInflow24h = flowCount24h > 0 ? inflowSum24h / flowCount24h : undefined;
+ const avgOutflow24h = flowCount24h > 0 ? outflowSum24h / flowCount24h : undefined;
+
+ const limits = lakeInfo ? NAVIGATION_LIMITS[lakeInfo.id] : undefined;
+ const staticConfig = lakeInfo ? lakesConfig.find(l => l.id === lakeInfo.id) : undefined;
const kpiData = {
level: latestData.level,
@@ -225,12 +241,12 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
outflow: lastValidFlowData.outflow,
volume: lakeInfo?.volume || 0,
fullness: lakeInfo?.capacity || 0,
- storageDiff: lakeInfo?.storageDiff
+ storageDiff: lakeInfo?.storageDiff,
+ minDiff: staticConfig?.minLevel ? latestData.level - staticConfig.minLevel : undefined,
+ avgInflow24h,
+ avgOutflow24h
};
- const limits = lakeInfo ? NAVIGATION_LIMITS[lakeInfo.id] : undefined;
- const staticConfig = lakeInfo ? lakesConfig.find(l => l.id === lakeInfo.id) : undefined;
-
const leftYAxisDomain = [
(dataMin: number) => {
let min = dataMin;
@@ -284,7 +300,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
{lakeInfo && lakeInfo.lat && lakeInfo.lng && (
-
+
)}
@@ -343,14 +359,14 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
))}
{staticConfig && staticConfig.maxLevel && (
-
+
)}
{staticConfig && staticConfig.storageLevel && (
-
+
)}
-
-
+
+
@@ -358,8 +374,8 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
{/* Chart Legend */}
{dict.level}
-
{dict.outflow}
-
{dict.inflow}
+
{dict.outflow}
+
{dict.inflow}
{/* WEATHER CHART SECTION */}
diff --git a/src/components/LakeMap.tsx b/src/components/LakeMap.tsx
index 280a54e..2f4e473 100644
--- a/src/components/LakeMap.tsx
+++ b/src/components/LakeMap.tsx
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
-import { FiX, FiSearch, FiDroplet } from 'react-icons/fi';
+import { FiX, FiSearch } from 'react-icons/fi';
import { type Language, t } from '../translations';
import { slugify } from '../utils/slugify';
import { useNavigate } from 'react-router-dom';
diff --git a/src/components/LakesOverview.tsx b/src/components/LakesOverview.tsx
index 3356480..1d995a5 100644
--- a/src/components/LakesOverview.tsx
+++ b/src/components/LakesOverview.tsx
@@ -33,9 +33,17 @@ const LakeCard = ({ lake, language, isFav, onToggleFav }: { lake: Lake, language
const minVal = Math.min(...lake.sparkline);
const maxVal = Math.max(...lake.sparkline);
const diff = maxVal - minVal;
- // Enforce a minimum visual span of 0.5 meters so tiny fluctuations don't look like mountains
- const padding = diff < 0.5 ? (0.5 - diff) / 2 : 0;
+ const padding = diff === 0 ? 0.1 : diff * 0.1; // dynamic 10% padding
const yDomain = [minVal - padding, maxVal + padding];
+
+ const firstVal = lake.sparkline[0] || 0;
+ const lastVal = lake.sparkline[lake.sparkline.length - 1] || 0;
+ const trendDiff = lastVal - firstVal;
+
+ // Dynamic color based on trend direction: stable=cyan, rising=green, falling=red
+ let trendColor = 'var(--color-cyan)';
+ if (trendDiff > 0.01) trendColor = 'var(--color-green)';
+ else if (trendDiff < -0.01) trendColor = 'var(--color-red)';
return (
@@ -120,54 +128,9 @@ const LakeCard = ({ lake, language, isFav, onToggleFav }: { lake: Lake, language
);
};
-const SmallLakeCard = ({ lake, isFav, onToggleFav }: { lake: Lake, isFav: boolean, onToggleFav: (id: string) => void }) => {
- const navigate = useNavigate();
-
- return (
-
navigate(`/${slugify(lake.name)}`)}
- style={{ cursor: 'pointer', padding: '1rem', display: 'flex', flexDirection: 'column', gap: '0.5rem', position: 'relative' }}
- >
- {/* Star button */}
-
-
-
{lake.name}
-
{lake.level} m n.m.
-
- = 80 ? 'var(--color-green)' : lake.capacity < 40 ? 'var(--color-red)' : 'var(--text-muted)', fontWeight: 600 }}>
- {lake.capacity > 0 ? `${lake.capacity}%` : 'N/A'}
-
- {lake.storageDiff !== undefined && (
- = 0 ? 'var(--color-green)' : 'var(--color-red)', marginLeft: '4px' }}>
- ({lake.storageDiff > 0 ? '+' : ''}{lake.storageDiff.toFixed(2)} m)
-
- )}
-
-
- );
-};
-
const LakesOverview = ({ language }: Props) => {
const [lakes, setLakes] = useState
([]);
- const { isFavorite, toggleFavorite, favorites } = useFavorites();
+ const { isFavorite, toggleFavorite } = useFavorites();
useEffect(() => {
const loadData = () => {
@@ -234,6 +197,19 @@ const LakesOverview = ({ language }: Props) => {
)}
+
+ {otherLakes.length > 0 && (
+
+ {language === 'cs' ? 'Ostatní' : 'Other'}
+
+ {otherLakes.map(lake => )}
+
+
+ )}
);
};
diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx
index bf26f87..1cc11d2 100644
--- a/src/components/SettingsModal.tsx
+++ b/src/components/SettingsModal.tsx
@@ -1,4 +1,4 @@
-import { FiX, FiMoon, FiSun, FiGlobe, FiCoffee, FiWind } from 'react-icons/fi';
+import { FiX, FiMoon, FiSun, FiCoffee, FiWind } from 'react-icons/fi';
import { type Language, t } from '../translations';
interface Props {
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index ccb6783..61661ff 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -29,18 +29,19 @@ const Sidebar = ({ language, onOpenSettings, isMobileMenuOpen, onCloseMobileMenu
return (
-
-
+
+
- HLADINATOR
+ HLADINATOR
v1.0
-
- {/* Toggle Button */}
+
+
+ {/* Toggle Button */}
+