feat: update water level metrics and optimize sidebar UI layout
This commit is contained in:
@@ -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 (
|
||||
<div
|
||||
@@ -94,13 +102,13 @@ const LakeCard = ({ lake, language, isFav, onToggleFav }: { lake: Lake, language
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={chartData}>
|
||||
<defs>
|
||||
<linearGradient id="colorSpark" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="var(--color-cyan)" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="var(--color-cyan)" stopOpacity={0} />
|
||||
<linearGradient id={`colorSpark-${lake.id}`} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={trendColor} stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor={trendColor} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<YAxis domain={yDomain} hide />
|
||||
<Area type="monotone" dataKey="value" stroke="var(--color-cyan)" fillOpacity={1} fill="url(#colorSpark)" baseValue={yDomain[0]} />
|
||||
<Area type="monotone" dataKey="value" stroke={trendColor} fillOpacity={1} fill={`url(#colorSpark-${lake.id})`} baseValue={yDomain[0]} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
@@ -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 (
|
||||
<div
|
||||
className="kpi-card"
|
||||
onClick={() => navigate(`/${slugify(lake.name)}`)}
|
||||
style={{ cursor: 'pointer', padding: '1rem', display: 'flex', flexDirection: 'column', gap: '0.5rem', position: 'relative' }}
|
||||
>
|
||||
{/* Star button */}
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onToggleFav(lake.id); }}
|
||||
title={isFav ? (language === 'cs' ? 'Odepnout' : 'Unpin') : (language === 'cs' ? 'Připnout jako oblíbené' : 'Pin to favorites')}
|
||||
style={{
|
||||
position: 'absolute', top: '0.6rem', right: '0.6rem',
|
||||
background: 'none', border: 'none', cursor: 'pointer',
|
||||
color: isFav ? '#f59e0b' : 'var(--text-muted)',
|
||||
opacity: isFav ? 1 : 0.4,
|
||||
transition: 'color 0.2s, opacity 0.2s, transform 0.15s',
|
||||
padding: '2px',
|
||||
display: 'flex', alignItems: 'center',
|
||||
zIndex: 2,
|
||||
}}
|
||||
onMouseOver={(e) => { e.currentTarget.style.opacity = '1'; e.currentTarget.style.transform = 'scale(1.2)'; }}
|
||||
onMouseOut={(e) => { e.currentTarget.style.opacity = isFav ? '1' : '0.4'; e.currentTarget.style.transform = 'scale(1)'; }}
|
||||
>
|
||||
<FiStar size={14} fill={isFav ? '#f59e0b' : 'none'} />
|
||||
</button>
|
||||
|
||||
<div style={{ fontSize: '0.85rem', fontWeight: 'bold', paddingRight: '1.5rem', lineHeight: 1.2 }}>{lake.name}</div>
|
||||
<div style={{ fontSize: '1.1rem', fontWeight: 'bold', color: 'var(--color-cyan)' }}>{lake.level} <span style={{ fontSize: '0.7rem', color: 'var(--text-muted)', fontWeight: 'normal' }}>m n.m.</span></div>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>
|
||||
<span style={{ color: lake.capacity >= 80 ? 'var(--color-green)' : lake.capacity < 40 ? 'var(--color-red)' : 'var(--text-muted)', fontWeight: 600 }}>
|
||||
{lake.capacity > 0 ? `${lake.capacity}%` : 'N/A'}
|
||||
</span>
|
||||
{lake.storageDiff !== undefined && (
|
||||
<span style={{ color: lake.storageDiff >= 0 ? 'var(--color-green)' : 'var(--color-red)', marginLeft: '4px' }}>
|
||||
({lake.storageDiff > 0 ? '+' : ''}{lake.storageDiff.toFixed(2)} m)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LakesOverview = ({ language }: Props) => {
|
||||
const [lakes, setLakes] = useState<Lake[]>([]);
|
||||
const { isFavorite, toggleFavorite, favorites } = useFavorites();
|
||||
const { isFavorite, toggleFavorite } = useFavorites();
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = () => {
|
||||
@@ -234,6 +197,19 @@ const LakesOverview = ({ language }: Props) => {
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{otherLakes.length > 0 && (
|
||||
<section>
|
||||
<h2 style={{ fontSize: '1.1rem', fontWeight: 'bold', marginBottom: '1rem', marginTop: '1rem' }}>{language === 'cs' ? 'Ostatní' : 'Other'}</h2>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
|
||||
gap: '1.5rem'
|
||||
}}>
|
||||
{otherLakes.map(lake => <LakeCard key={lake.id} lake={lake} language={language} isFav={false} onToggleFav={toggleFavorite} />)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user