import React, { useState, useEffect, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; interface CurrencyRate { currency: string; rate: number; change: number; } interface MetalRate { metal: string; price: number; change: number; } interface WeatherDay { date: string; dayOfWeek: string; tempMin: number; tempMax: number; icon: string; description: string; } interface BottomBarProps { onLogout?: () => void; } const BottomBar: React.FC = ({ onLogout }) => { const [currencyRates, setCurrencyRates] = useState([ { currency: 'USD', rate: 3.22, change: 0.5 }, { currency: 'EUR', rate: 3.45, change: -0.2 }, { currency: 'CNY', rate: 0.44, change: 0.1 }, { currency: 'RUB', rate: 0.035, change: -0.3 }, ]); const [metalRates, setMetalRates] = useState([ { metal: 'Золото', price: 1950, change: 0.8 }, { metal: 'Серебро', price: 23.5, change: -0.5 }, { metal: 'Платина', price: 920, change: 0.3 }, { metal: 'Палладий', price: 1450, change: -0.7 }, ]); const [isOnline, setIsOnline] = useState(navigator.onLine); const [isPaused, setIsPaused] = useState(false); const [currentTime, setCurrentTime] = useState(new Date()); const [weatherData, setWeatherData] = useState([]); const [weatherLoading, setWeatherLoading] = useState(false); const [weatherError, setWeatherError] = useState(false); const scrollRef = useRef(null); // ============================================ // 🌤️ ЗАГРУЗКА ПОГОДЫ // ============================================ useEffect(() => { const fetchWeather = async () => { if (!isOnline) { setWeatherData([]); return; } try { setWeatherLoading(true); setWeatherError(false); // API OpenWeatherMap (бесплатный, нужно зарегистрироваться и получить ключ) // Замени YOUR_API_KEY на свой ключ const apiKey = '9edc3b8058098d110db0aa1894a08c97'; // ← Получить на https://openweathermap.org/api const city = 'Grodno'; const country = 'BY'; const url = `https://api.openweathermap.org/data/2.5/forecast?q=${city},${country}&units=metric&lang=ru&appid=${apiKey}`; const response = await fetch(url); if (!response.ok) { throw new Error(`Ошибка: ${response.status}`); } const data = await response.json(); // Группируем прогноз по дням (берем по 1 прогнозу на день) const dailyForecast: WeatherDay[] = []; const daysMap = new Map(); data.list.forEach((item: any) => { const date = new Date(item.dt * 1000); const dateStr = date.toISOString().split('T')[0]; const dayOfWeek = date.toLocaleString('ru-RU', { weekday: 'short' }); if (!daysMap.has(dateStr)) { daysMap.set(dateStr, { date: dateStr, dayOfWeek: dayOfWeek.charAt(0).toUpperCase() + dayOfWeek.slice(1), tempMin: item.main.temp_min, tempMax: item.main.temp_max, icon: item.weather[0].icon, description: item.weather[0].description, }); } else { const existing = daysMap.get(dateStr); existing.tempMin = Math.min(existing.tempMin, item.main.temp_min); existing.tempMax = Math.max(existing.tempMax, item.main.temp_max); } }); // Берем первые 7 дней const sevenDays = Array.from(daysMap.values()).slice(0, 7); setWeatherData(sevenDays); setWeatherLoading(false); } catch (error) { console.warn('Ошибка загрузки погоды:', error); setWeatherError(true); setWeatherLoading(false); setWeatherData([]); } }; fetchWeather(); const interval = setInterval(fetchWeather, 30 * 60 * 1000); // Обновление каждые 30 минут return () => clearInterval(interval); }, [isOnline]); // ============================================ // 🌐 СТАТУС ИНТЕРНЕТА // ============================================ useEffect(() => { const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []); // ============================================ // ⏰ ВРЕМЯ // ============================================ useEffect(() => { const interval = setInterval(() => { setCurrentTime(new Date()); }, 1000); return () => clearInterval(interval); }, []); // ============================================ // 📊 ЗАГРУЗКА КУРСОВ // ============================================ useEffect(() => { const loadRates = async () => { try { if (isOnline) { const response = await fetch('http://localhost:3001/api/rates'); if (response.ok) { const data = await response.json(); setCurrencyRates(data.currency || currencyRates); setMetalRates(data.metals || metalRates); } } else { const stored = localStorage.getItem('currencyRates'); if (stored) { const data = JSON.parse(stored); setCurrencyRates(data.currency || currencyRates); setMetalRates(data.metals || metalRates); } } } catch (error) { console.warn('Ошибка загрузки курсов:', error); } }; loadRates(); const interval = setInterval(loadRates, 60000); return () => clearInterval(interval); }, [isOnline]); // ============================================ // 🎮 УПРАВЛЕНИЕ ПАУЗОЙ // ============================================ const handleMouseEnter = () => setIsPaused(true); const handleMouseLeave = () => setIsPaused(false); const handleTouchStart = () => setIsPaused(true); const handleTouchEnd = () => setIsPaused(false); // ============================================ // 🚪 ВЫХОД // ============================================ const handleLogout = () => { if (onLogout) { onLogout(); } }; // ============================================ // 🎨 ИКОНКА ПОГОДЫ // ============================================ const getWeatherIcon = (iconCode: string) => { const icons: Record = { '01d': '☀️', '01n': '🌙', '02d': '⛅', '02n': '☁️', '03d': '☁️', '03n': '☁️', '04d': '☁️', '04n': '☁️', '09d': '🌧️', '09n': '🌧️', '10d': '🌦️', '10n': '🌧️', '11d': '⛈️', '11n': '⛈️', '13d': '❄️', '13n': '❄️', '50d': '🌫️', '50n': '🌫️', }; return icons[iconCode] || '🌡️'; }; // ============================================ // 📦 ВСЕ ЭЛЕМЕНТЫ ДЛЯ БЕГУЩЕЙ СТРОКИ // ============================================ const getAllItems = () => { const currencyItems = currencyRates.map(item => ({ id: `currency-${item.currency}`, type: 'currency' as const, label: item.currency, value: item.rate, change: item.change, displayValue: item.rate.toFixed(item.rate < 1 ? 3 : 2), })); const metalItems = metalRates.map(item => ({ id: `metal-${item.metal}`, type: 'metal' as const, label: item.metal, value: item.price, change: item.change, displayValue: `$${item.price.toFixed(item.price < 10 ? 2 : 0)}`, })); return [...currencyItems, ...metalItems]; }; const items = getAllItems(); const doubledItems = [...items, ...items, ...items]; // ============================================ // 🎨 РЕНДЕР // ============================================ return ( <> {/* 🌤️ ПОЛОСКА ПОГОДЫ — показываем только если есть интернет и данные */} {isOnline && weatherData.length > 0 && !weatherLoading && !weatherError && (
🌤️ ГРОДНО
{[...weatherData, ...weatherData].map((day, index) => (
{day.dayOfWeek} {getWeatherIcon(day.icon)} {Math.round(day.tempMin)}°…{Math.round(day.tempMax)}°
))}
)} {/* 📊 ОСНОВНАЯ ПОЛОСКА (курсы валют) */} {/* Статус */}
{isOnline ? '●' : '○'} КУРСЫ
{/* Бегущая строка с курсами */}
{doubledItems.map((item, index) => (
{item.label} {item.displayValue} = 0 ? '#2ecc71' : '#e74c3c', fontSize: '11px', fontWeight: 500, display: 'flex', alignItems: 'center', gap: '2px', letterSpacing: '0.2px', }}> {item.change >= 0 ? '▲' : '▼'} {Math.abs(item.change).toFixed(1)}% {item.type === 'metal' && ( $ )}
))}
{/* Время + Кнопка выхода */}
{currentTime.toLocaleString('ru-RU', { timeZone: 'Europe/Minsk', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, })}
{onLogout && ( )}
{/* Пауза */} {isPaused && (
⏸ Пауза
)} ); }; export default BottomBar;