{label}
- {payload.map((entry: any, index: number) => {
+ {payload.map((entry: TooltipPayloadItem, index: number) => {
const isTemp = entry.name === 'temperature' || entry.dataKey === 'temperature';
return (
@@ -54,12 +79,12 @@ const CustomTooltip = ({ active, payload, label, language, isWeather, isRiver, c
return (
{label}
- {[...payload].sort((a: any, b: any) => {
+ {[...payload].sort((a: TooltipPayloadItem, b: TooltipPayloadItem) => {
const order = ['level', 'inflow', 'outflow', 'temperature', 'precipitation'];
const indexA = order.indexOf(a.dataKey);
const indexB = order.indexOf(b.dataKey);
return (indexA === -1 ? 99 : indexA) - (indexB === -1 ? 99 : indexB);
- }).map((entry: any, index: number) => {
+ }).map((entry: TooltipPayloadItem, index: number) => {
let labelStr = '';
let unit = '';
let color = '';
@@ -116,9 +141,9 @@ const CustomTooltip = ({ active, payload, label, language, isWeather, isRiver, c
const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
const [data, setData] = useState
([]);
const [loading, setLoading] = useState(true);
- const [lakeInfo, setLakeInfo] = useState(null);
+ const [lakeInfo, setLakeInfo] = useState(null);
const [isSmoothed, setIsSmoothed] = useState(true);
- const [timeRange, setTimeRange] = useState<'24h' | '7d' | '30d' | '1y' | 'all'>('7d');
+ const [timeRange, setTimeRange] = useState<'24h' | '7d' | '30d' | '1y' | 'all'>('24h');
const [visibleSeries, setVisibleSeries] = useState({
level: true,
outflow: true,
@@ -133,6 +158,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
const [rightCustomDomain, setRightCustomDomain] = useState<[number, number] | null>(null);
const [tooltipY, setTooltipY] = useState(undefined);
const [weatherTooltipY, setWeatherTooltipY] = useState(undefined);
+ const { isFavorite, toggleFavorite } = useFavorites();
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth <= 768);
@@ -141,10 +167,12 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
return () => window.removeEventListener('resize', handleResize);
}, []);
- useEffect(() => {
+ const [prevDeps, setPrevDeps] = useState({ timeRange, lakeId });
+ if (prevDeps.timeRange !== timeRange || prevDeps.lakeId !== lakeId) {
+ setPrevDeps({ timeRange, lakeId });
setLeftCustomDomain(null);
setRightCustomDomain(null);
- }, [timeRange, lakeId]);
+ }
const dict = t[language].chart;
const topbarDict = t[language].topbar;
@@ -154,7 +182,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
fetch(`/data/lakes_index.json?t=${Date.now()}`)
.then(res => res.json())
.then(indexData => {
- const found = indexData.find((l: any) => l.id === lakeId);
+ const found = indexData.find((l: LakeInfo) => l.id === lakeId);
setLakeInfo(found || { name: 'Lipno 1', river: 'Vltava' });
})
.catch(err => console.error(err));
@@ -166,7 +194,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
.then(res => res.json())
.then(json => {
let lastValidLevel: number | null = null;
- const formattedData = json.map((item: any) => {
+ const formattedData = json.map((item: { timestamp: string, level?: number, flow?: number, inflow?: number, volume?: number, temperature?: number, precipitation?: number, qn?: string }) => {
const outflow = item.flow === null || isNaN(item.flow) ? 0 : item.flow;
let level = item.level === null || isNaN(item.level) ? 0 : item.level;
@@ -263,7 +291,6 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
const targetMs7d = nowMs - 7 * 24 * 60 * 60 * 1000;
const targetMs30d = nowMs - 30 * 24 * 60 * 60 * 1000;
- const { isFavorite, toggleFavorite } = useFavorites();
const isFav = lakeId ? isFavorite(lakeId) : false;
let level24hAgo = latestData.level;
@@ -493,11 +520,11 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
-
-
+
+
-
-
+
+
@@ -616,7 +643,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
{
+ onMouseMove={(state: { chartY?: number } | null | undefined) => {
if (state && state.chartY !== undefined) {
const isBottomHalf = state.chartY > 150;
const targetY = isBottomHalf ? 5 : 180;
@@ -632,7 +659,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
- v.toFixed(isRiver ? 0 : 2)} />
+ v.toFixed(isRiver ? 0 : 2)} />
Math.max(dataMax, 1)]} stroke={(visibleSeries.outflow || visibleSeries.inflow) ? "var(--text-muted)" : "transparent"} tick={{fill: (visibleSeries.outflow || visibleSeries.inflow) ? 'var(--text-muted)' : 'transparent', fontSize: isMobile ? 10 : 12}} tickLine={(visibleSeries.outflow || visibleSeries.inflow) ? { stroke: 'var(--text-muted)' } : { stroke: 'transparent' }} axisLine={(visibleSeries.outflow || visibleSeries.inflow) ? { stroke: 'var(--text-muted)' } : { stroke: 'transparent' }} width={isMobile ? 35 : 60} tickFormatter={(v) => v.toFixed(1)} />
@@ -773,7 +800,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
{
+ onMouseMove={(state: { chartY?: number } | null | undefined) => {
if (state && state.chartY !== undefined) {
const isBottomHalf = state.chartY > 100;
const targetY = isBottomHalf ? 5 : 110;
@@ -793,7 +820,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
/>
-
+
diff --git a/src/components/LakeMap.tsx b/src/components/LakeMap.tsx
index c7443f1..e7a5f3a 100644
--- a/src/components/LakeMap.tsx
+++ b/src/components/LakeMap.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo } from 'react';
import { MapContainer, TileLayer, Marker, Popup, Tooltip } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
@@ -71,6 +71,27 @@ const LakeMap = ({ language }: Props) => {
.catch(err => console.error('Error fetching map lakes:', err));
}, []);
+ const randomStats = useMemo(() => {
+ const stats: Record = {};
+ const simpleHash = (str: string) => {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = (hash << 5) - hash + str.charCodeAt(i);
+ hash = hash & hash;
+ }
+ return Math.abs(hash);
+ };
+
+ lakes.forEach(l => {
+ const hash = simpleHash(l.id);
+ stats[l.id] = {
+ area: (((hash % 500) / 10) + 10).toFixed(1),
+ depth: (((hash % 300) / 10) + 5).toFixed(1)
+ };
+ });
+ return stats;
+ }, [lakes]);
+
const filteredLakes = lakes.filter(lake =>
lake.name.toLowerCase().includes(searchTerm.toLowerCase())
);
@@ -148,11 +169,11 @@ const LakeMap = ({ language }: Props) => {
{language === 'cs' ? 'Rozloha' : 'Area'}
- {(Math.random() * 50 + 10).toFixed(1)} km²
+ {randomStats[lake.id]?.area || '0.0'} km²
{language === 'cs' ? 'Hloubka' : 'Depth'}
- {(Math.random() * 30 + 5).toFixed(1)}m
+ {randomStats[lake.id]?.depth || '0.0'}m
diff --git a/src/components/RiversOverview.tsx b/src/components/RiversOverview.tsx
index 5de1efb..23d18fb 100644
--- a/src/components/RiversOverview.tsx
+++ b/src/components/RiversOverview.tsx
@@ -167,7 +167,7 @@ export const RiversOverview = ({ language }: Props) => {
.then(res => res.json())
.then(data => {
// Filter only rivers
- const filtered = data.filter((item: any) => item.type === 'river');
+ const filtered = data.filter((item: Partial) => item.type === 'river');
setRivers(filtered);
})
.catch(err => console.error(err));
diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx
index 8da3b2e..791b4a7 100644
--- a/src/components/Tooltip.tsx
+++ b/src/components/Tooltip.tsx
@@ -26,10 +26,6 @@ export const Tooltip = ({ content, children }: Props) => {
useEffect(() => {
if (!show) {
- setPositionStyle({
- left: '50%',
- transform: 'translateX(-50%)',
- });
return;
}
@@ -67,10 +63,16 @@ export const Tooltip = ({ content, children }: Props) => {
setShow(true)}
+ onMouseEnter={() => {
+ setPositionStyle({ left: '50%', transform: 'translateX(-50%)' });
+ setShow(true);
+ }}
onMouseLeave={() => setShow(false)}
onClick={(e) => {
e.stopPropagation();
+ if (!show) {
+ setPositionStyle({ left: '50%', transform: 'translateX(-50%)' });
+ }
setShow(!show);
}}
>
diff --git a/src/components/WeatherWidget.tsx b/src/components/WeatherWidget.tsx
index c103c64..524102d 100644
--- a/src/components/WeatherWidget.tsx
+++ b/src/components/WeatherWidget.tsx
@@ -73,7 +73,7 @@ export const WeatherWidget = ({ lat, lng, language, sensorTemp, windUnit = 'kmh'
// Refresh weather every 15 minutes
const interval = setInterval(fetchWeather, 15 * 60 * 1000);
return () => clearInterval(interval);
- }, [lat, lng]);
+ }, [lat, lng, windUnit]);
const dict = {
cs: { title: 'Počasí a Vítr (Aktuálně)', error: 'Data nedostupná', wind: 'Vítr', gusts: 'Nárazy', temp: 'Teplota' },
@@ -113,8 +113,18 @@ export const WeatherWidget = ({ lat, lng, language, sensorTemp, windUnit = 'kmh'
-
- {data.windSpeed.toFixed(1)}
{windUnit === 'kmh' ? 'km/h' : 'm/s'} • {getCompassDirection(data.windDir, language)}
+
+ {data.windSpeed.toFixed(1)}
+
+ {windUnit === 'kmh' ? 'km/h' : 'm/s'} • {getCompassDirection(data.windDir, language)}
+
+
{dict.gusts}: (windUnit === 'kmh' ? 50 : 13.8) ? 'var(--color-red)' : 'var(--text-main)' }}>{data.windGusts.toFixed(1)} {windUnit === 'kmh' ? 'km/h' : 'm/s'}
diff --git a/src/components/WindChart.tsx b/src/components/WindChart.tsx
index b76d1fb..85721a9 100644
--- a/src/components/WindChart.tsx
+++ b/src/components/WindChart.tsx
@@ -27,7 +27,17 @@ const getCompassDirection = (degrees: number, language: 'cs' | 'en') => {
return directions[index];
};
-const CustomWindTooltip = ({ active, payload, label, language, windUnit = 'kmh', coordinate, viewBox }: any) => {
+interface CustomWindTooltipProps {
+ active?: boolean;
+ payload?: { payload: WindDataPoint, value: number, name: string }[];
+ label?: string;
+ language: 'cs' | 'en';
+ windUnit?: 'kmh' | 'ms';
+ coordinate?: { x: number; y: number };
+ viewBox?: { width: number; height: number };
+}
+
+const CustomWindTooltip = ({ active, payload, label, language, windUnit = 'kmh', coordinate, viewBox }: CustomWindTooltipProps) => {
if (active && payload && payload.length) {
const isLeft = coordinate && viewBox && coordinate.x > viewBox.width / 2;
const tooltipClass = `chart-tooltip ${isLeft ? 'tooltip-left' : 'tooltip-right'}`;
@@ -52,7 +62,16 @@ const CustomWindTooltip = ({ active, payload, label, language, windUnit = 'kmh',
-
{language === 'cs' ? 'Směr' : 'Direction'}: {data.dirStr} ({data.dir}°)
+
+ {language === 'cs' ? 'Směr' : 'Direction'}: {data.dirStr} ({data.dir}°)
+
+
@@ -61,7 +80,7 @@ const CustomWindTooltip = ({ active, payload, label, language, windUnit = 'kmh',
return null;
};
-const CustomWindDot = (props: any) => {
+const CustomWindDot = (props: { cx?: number; cy?: number; payload?: WindDataPoint }) => {
const { cx, cy, payload } = props;
if (!cx || !cy || payload.dir === undefined) return null;
@@ -78,10 +97,11 @@ const CustomWindDot = (props: any) => {
);
};
-export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'kmh' }: WindChartProps) => {
+export const WindChart = ({ lat, lng, language, timeRange = '24h', windUnit = 'kmh' }: WindChartProps) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [currentSpeed, setCurrentSpeed] = useState(0);
+ const [currentDir, setCurrentDir] = useState(0);
const [maxGust, setMaxGust] = useState(0);
const [isMobile, setIsMobile] = useState(false);
const [tooltipY, setTooltipY] = useState(undefined);
@@ -169,6 +189,7 @@ export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'km
setData(downsampled);
setMaxGust(maxG);
setCurrentSpeed(speeds[closestIdx] || speeds[speeds.length - 1] || 0);
+ setCurrentDir(dirs[closestIdx] || dirs[dirs.length - 1] || 0);
} catch (err) {
console.error(err);
@@ -180,7 +201,7 @@ export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'km
if (lat && lng) {
fetchWind();
}
- }, [lat, lng, language, timeRange]);
+ }, [lat, lng, language, timeRange, windUnit]);
if (loading) {
return (
@@ -209,9 +230,16 @@ export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'km
}}>
{language === 'cs' ? 'Aktuální rychlost' : 'Current Speed'}
-
+
{currentSpeed.toFixed(1)}
{windUnit === 'kmh' ? 'km/h' : 'm/s'}
+
@@ -230,7 +258,7 @@ export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'km
{
+ onMouseMove={(state: { chartY?: number } | null | undefined) => {
if (state && state.chartY !== undefined) {
const isBottomHalf = state.chartY > 140;
const targetY = isBottomHalf ? 5 : 160;
@@ -275,7 +303,11 @@ export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'km
fillOpacity={1}
fill="url(#colorWind)"
isAnimationActive={true}
- dot={}
+ dot={(props: any) => {
+ const step = Math.max(1, Math.floor(data.length / (isMobile ? 15 : 30)));
+ if (props.index % step !== 0 && props.index !== data.length - 1) return null;
+ return ;
+ }}
activeDot={{ r: 6, fill: 'var(--color-cyan)', stroke: '#1e293b', strokeWidth: 2 }}
/>