feat: add circular progress component and update historical lake data indices

This commit is contained in:
David Fencl
2026-06-06 10:38:43 +02:00
parent 27551f9183
commit a3b3d40769
15 changed files with 2896 additions and 243 deletions
+13 -44
View File
@@ -1,10 +1,11 @@
import { useState, useEffect } from 'react';
import { FiTrendingUp, FiTrendingDown, FiStar } from 'react-icons/fi';
import { type Language, t } from '../translations';
import { AreaChart, Area, ResponsiveContainer } from 'recharts';
import { AreaChart, Area, ResponsiveContainer, YAxis } from 'recharts';
import { useNavigate } from 'react-router-dom';
import { slugify } from '../utils/slugify';
import { useFavorites } from '../hooks/useFavorites';
import { CircularProgress } from './CircularProgress';
interface Lake {
id: string;
@@ -24,44 +25,16 @@ interface Props {
language: Language;
}
const CircularProgress = ({ value, size = 60, strokeWidth = 6 }: { value: number, size?: number, strokeWidth?: number }) => {
const radius = (size - strokeWidth) / 2;
const circumference = radius * 2 * Math.PI;
const offset = circumference - (value / 100) * circumference;
return (
<div style={{ position: 'relative', width: size, height: size }}>
<svg width={size} height={size}>
<circle
stroke="rgba(255,255,255,0.1)"
fill="transparent"
strokeWidth={strokeWidth}
r={radius}
cx={size / 2}
cy={size / 2}
/>
<circle
stroke="var(--color-cyan)"
fill="transparent"
strokeWidth={strokeWidth}
strokeLinecap="round"
style={{ strokeDasharray: circumference, strokeDashoffset: offset, transition: 'stroke-dashoffset 0.5s ease-in-out' }}
r={radius}
cx={size / 2}
cy={size / 2}
transform={`rotate(-90 ${size / 2} ${size / 2})`}
/>
</svg>
<div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: size * 0.25, fontWeight: 'bold' }}>
{value > 0 ? `${value}%` : 'N/A'}
</div>
</div>
);
};
const LakeCard = ({ lake, language, isFav, onToggleFav }: { lake: Lake, language: Language, isFav: boolean, onToggleFav: (id: string) => void }) => {
const navigate = useNavigate();
const chartData = lake.sparkline.map((val, i) => ({ name: i, value: val }));
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 yDomain = [minVal - padding, maxVal + padding];
return (
<div
@@ -106,13 +79,8 @@ const LakeCard = ({ lake, language, isFav, onToggleFav }: { lake: Lake, language
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<CircularProgress value={lake.capacity} size={70} strokeWidth={6} />
<div>
<div style={{ fontSize: '1.25rem', fontWeight: 'bold' }}>
<span style={{ color: lake.capacity >= 80 ? 'var(--color-green)' : lake.capacity < 40 ? 'var(--color-red)' : 'var(--text-main)' }}>
{lake.capacity > 0 ? `${lake.capacity}%` : 'N/A'}
</span>
</div>
{lake.storageDiff !== undefined && (
<div style={{ fontSize: '0.8rem', color: lake.storageDiff >= 0 ? 'var(--color-green)' : 'var(--color-red)', fontWeight: 500 }}>
<div style={{ fontSize: '1.25rem', color: lake.storageDiff >= 0 ? 'var(--color-green)' : 'var(--color-red)', fontWeight: 'bold' }}>
{lake.storageDiff > 0 ? '+' : ''}{lake.storageDiff.toFixed(2)} m
</div>
)}
@@ -130,7 +98,8 @@ const LakeCard = ({ lake, language, isFav, onToggleFav }: { lake: Lake, language
<stop offset="95%" stopColor="var(--color-cyan)" stopOpacity={0} />
</linearGradient>
</defs>
<Area type="monotone" dataKey="value" stroke="var(--color-cyan)" fillOpacity={1} fill="url(#colorSpark)" />
<YAxis domain={yDomain} hide />
<Area type="monotone" dataKey="value" stroke="var(--color-cyan)" fillOpacity={1} fill="url(#colorSpark)" baseValue={yDomain[0]} />
</AreaChart>
</ResponsiveContainer>
</div>
@@ -201,7 +170,7 @@ const LakesOverview = ({ language }: Props) => {
const { isFavorite, toggleFavorite, favorites } = useFavorites();
useEffect(() => {
fetch('/data/lakes_index.json')
fetch(`/data/lakes_index.json?t=${Date.now()}`)
.then(res => res.json())
.then(data => setLakes(data))
.catch(err => console.error(err));