feat: implement weather radar component and update water resource data records. before river
This commit is contained in:
@@ -29,6 +29,7 @@ interface Lake {
|
||||
|
||||
interface Props {
|
||||
language: Language;
|
||||
windUnit?: 'kmh' | 'ms';
|
||||
}
|
||||
|
||||
const FavoritesOverview = ({ language }: Props) => {
|
||||
|
||||
@@ -28,6 +28,7 @@ interface Lake {
|
||||
|
||||
interface Props {
|
||||
language: Language;
|
||||
windUnit?: 'kmh' | 'ms';
|
||||
}
|
||||
|
||||
const LakeCard = ({ lake, language, isFav, onToggleFav }: { lake: Lake, language: Language, isFav: boolean, onToggleFav: (id: string) => void }) => {
|
||||
|
||||
+17
-10
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { FiDroplet, FiStar, FiMap, FiSettings, FiChevronLeft, FiChevronRight, FiDatabase } from 'react-icons/fi';
|
||||
import { FiDroplet, FiStar, FiMap, FiSettings, FiChevronLeft, FiChevronRight, FiDatabase, FiCloudRain } from 'react-icons/fi';
|
||||
import { type Language, t } from '../translations';
|
||||
import { useFavorites } from '../hooks/useFavorites';
|
||||
|
||||
@@ -21,6 +21,7 @@ const Sidebar = ({ language, onOpenSettings, isMobileMenuOpen, onCloseMobileMenu
|
||||
const isOverview = location.pathname === '/';
|
||||
const isFavoritesPage = location.pathname === '/favorites';
|
||||
const isMap = location.pathname === '/map';
|
||||
const isRadar = location.pathname === '/radar';
|
||||
|
||||
const handleNavigate = (path: string) => {
|
||||
navigate(path);
|
||||
@@ -29,17 +30,17 @@ const Sidebar = ({ language, onOpenSettings, isMobileMenuOpen, onCloseMobileMenu
|
||||
|
||||
return (
|
||||
<div className={`sidebar ${isCollapsed ? 'collapsed' : ''} ${isMobileMenuOpen ? 'mobile-open' : ''}`}>
|
||||
<div className="sidebar-logo">
|
||||
<FiDroplet size={28} color="var(--color-cyan)" />
|
||||
<div className="sidebar-text">
|
||||
<span style={{ fontWeight: 'bold', letterSpacing: '0.5px', fontSize: '1.1rem' }}>HLADINATOR</span>
|
||||
<small>v1.0</small>
|
||||
<div className="sidebar-logo" style={{ alignItems: 'center', gap: '0.4rem' }}>
|
||||
<FiDroplet size={34} color="var(--color-cyan)" style={{ marginLeft: '-4px', flexShrink: 0 }} />
|
||||
<div className="sidebar-text" style={{ position: 'relative', display: 'flex', alignItems: 'center' }}>
|
||||
<span style={{ fontWeight: 'bold', letterSpacing: '0.5px', fontSize: '1.15rem', lineHeight: 1 }}>HLADINATOR</span>
|
||||
<small style={{ position: 'absolute', top: '100%', left: '2px', marginTop: '6px', lineHeight: 1, fontSize: '0.75rem', fontWeight: 'bold', color: 'var(--text-muted)' }}>v1.0</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Toggle Button */}
|
||||
<div style={{ display: 'flex', justifyContent: isCollapsed ? 'center' : 'flex-end', marginBottom: '1.5rem', marginTop: isCollapsed ? '1rem' : '-0.5rem' }}>
|
||||
<button
|
||||
<div style={{ display: 'flex', justifyContent: isCollapsed ? 'center' : 'flex-end', marginBottom: '0.5rem', marginTop: isCollapsed ? '0.5rem' : '-1.5rem' }}>
|
||||
<button
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
style={{
|
||||
background: 'var(--bg-card)', border: '1px solid var(--border-color)', color: 'var(--text-main)',
|
||||
@@ -50,7 +51,7 @@ const Sidebar = ({ language, onOpenSettings, isMobileMenuOpen, onCloseMobileMenu
|
||||
{isCollapsed ? <FiChevronRight size={18} /> : <FiChevronLeft size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="nav-links">
|
||||
{/* Favourites */}
|
||||
<div className={`nav-item ${isFavoritesPage ? 'active' : ''}`} onClick={() => handleNavigate('/favorites')} style={{ position: 'relative' }}>
|
||||
@@ -92,6 +93,12 @@ const Sidebar = ({ language, onOpenSettings, isMobileMenuOpen, onCloseMobileMenu
|
||||
<FiMap />
|
||||
<span className="sidebar-text">{dict.map}</span>
|
||||
</div>
|
||||
|
||||
{/* Radar */}
|
||||
<div className={`nav-item ${isRadar ? 'active' : ''}`} onClick={() => handleNavigate('/radar')}>
|
||||
<FiCloudRain />
|
||||
<span className="sidebar-text">{dict.radar}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sidebar-footer">
|
||||
|
||||
+66
-20
@@ -8,6 +8,11 @@ interface Props {
|
||||
export const Tooltip = ({ content, children }: Props) => {
|
||||
const [show, setShow] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
const [positionStyle, setPositionStyle] = useState<React.CSSProperties>({
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
@@ -19,6 +24,45 @@ export const Tooltip = ({ content, children }: Props) => {
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!show) {
|
||||
setPositionStyle({
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Measure and adjust positioning to prevent overflow
|
||||
const adjustPosition = () => {
|
||||
if (tooltipRef.current) {
|
||||
const rect = tooltipRef.current.getBoundingClientRect();
|
||||
const windowWidth = window.innerWidth;
|
||||
const padding = 12; // safety margin from screen edges
|
||||
|
||||
if (rect.right > windowWidth - padding) {
|
||||
setPositionStyle({
|
||||
right: '0px',
|
||||
left: 'auto',
|
||||
transform: 'none',
|
||||
});
|
||||
} else if (rect.left < padding) {
|
||||
setPositionStyle({
|
||||
left: '0px',
|
||||
transform: 'none',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Run adjustment immediately
|
||||
adjustPosition();
|
||||
|
||||
// Also adjust on resize
|
||||
window.addEventListener('resize', adjustPosition);
|
||||
return () => window.removeEventListener('resize', adjustPosition);
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
@@ -32,26 +76,28 @@ export const Tooltip = ({ content, children }: Props) => {
|
||||
>
|
||||
{children}
|
||||
{show && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '100%',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
marginBottom: '8px',
|
||||
backgroundColor: 'var(--bg-card)',
|
||||
border: '1px solid var(--border-color)',
|
||||
padding: '0.5rem 0.75rem',
|
||||
borderRadius: '8px',
|
||||
width: 'max-content',
|
||||
maxWidth: '220px',
|
||||
zIndex: 9999,
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
|
||||
color: 'var(--text-main)',
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.4,
|
||||
textAlign: 'center',
|
||||
pointerEvents: 'none'
|
||||
}}>
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '100%',
|
||||
marginBottom: '8px',
|
||||
backgroundColor: 'var(--bg-card)',
|
||||
border: '1px solid var(--border-color)',
|
||||
padding: '0.5rem 0.75rem',
|
||||
borderRadius: '8px',
|
||||
width: 'max-content',
|
||||
maxWidth: '220px',
|
||||
zIndex: 9999,
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
|
||||
color: 'var(--text-main)',
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.4,
|
||||
textAlign: 'center',
|
||||
pointerEvents: 'none',
|
||||
...positionStyle
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { type Language, t } from '../translations';
|
||||
|
||||
interface Props {
|
||||
language: Language;
|
||||
}
|
||||
|
||||
const WeatherRadar = ({ language }: Props) => {
|
||||
return (
|
||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<Helmet>
|
||||
<title>{t[language].sidebar.radar} | Hladinátor</title>
|
||||
</Helmet>
|
||||
|
||||
<div style={{ padding: '0 1.5rem 1rem' }}>
|
||||
<h1 style={{ fontSize: '1.75rem', fontWeight: 'bold', margin: '0 0 0.5rem 0', color: 'var(--text-main)' }}>
|
||||
{t[language].sidebar.radar}
|
||||
</h1>
|
||||
<p style={{ margin: 0, color: 'var(--text-muted)' }}>
|
||||
{language === 'cs' ? 'Aktuální srážkový radar a předpověď počasí pro celou ČR.' : 'Current precipitation radar and weather forecast for the Czech Republic.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, minHeight: '600px', borderRadius: '12px', overflow: 'hidden', margin: '0 1.5rem 1.5rem 1.5rem', border: '1px solid var(--border-color)', backgroundColor: 'var(--bg-card)' }}>
|
||||
<iframe
|
||||
width="100%"
|
||||
height="100%"
|
||||
src={`https://embed.windy.com/embed.html?type=map&location=coordinates&metricRain=mm&metricTemp=%C2%B0C&metricWind=km/h&zoom=7&overlay=radar&product=radar&level=surface&lat=49.8&lon=15.5&message=true&lang=${language}`}
|
||||
frameBorder="0"
|
||||
title="Windy Weather Radar"
|
||||
style={{ display: 'block' }}
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WeatherRadar;
|
||||
Reference in New Issue
Block a user