weather&logout
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
interface CurrencyRate {
|
||||
currency: string;
|
||||
@@ -13,7 +13,20 @@ interface MetalRate {
|
||||
change: number;
|
||||
}
|
||||
|
||||
const BottomBar: React.FC = () => {
|
||||
interface WeatherDay {
|
||||
date: string;
|
||||
dayOfWeek: string;
|
||||
tempMin: number;
|
||||
tempMax: number;
|
||||
icon: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface BottomBarProps {
|
||||
onLogout?: () => void;
|
||||
}
|
||||
|
||||
const BottomBar: React.FC<BottomBarProps> = ({ onLogout }) => {
|
||||
const [currencyRates, setCurrencyRates] = useState<CurrencyRate[]>([
|
||||
{ currency: 'USD', rate: 3.22, change: 0.5 },
|
||||
{ currency: 'EUR', rate: 3.45, change: -0.2 },
|
||||
@@ -30,43 +43,87 @@ const BottomBar: React.FC = () => {
|
||||
|
||||
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [currentTime, setCurrentTime] = useState(new Date()); // ✅ Добавлено
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
const [weatherData, setWeatherData] = useState<WeatherDay[]>([]);
|
||||
const [weatherLoading, setWeatherLoading] = useState(false);
|
||||
const [weatherError, setWeatherError] = useState(false);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
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];
|
||||
|
||||
// ✅ Обновление времени каждую секунду
|
||||
// ============================================
|
||||
// 🌤️ ЗАГРУЗКА ПОГОДЫ
|
||||
// ============================================
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
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);
|
||||
@@ -80,6 +137,19 @@ const BottomBar: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ============================================
|
||||
// ⏰ ВРЕМЯ
|
||||
// ============================================
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// ============================================
|
||||
// 📊 ЗАГРУЗКА КУРСОВ
|
||||
// ============================================
|
||||
useEffect(() => {
|
||||
const loadRates = async () => {
|
||||
try {
|
||||
@@ -105,206 +175,424 @@ const BottomBar: React.FC = () => {
|
||||
|
||||
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<string, string> = {
|
||||
'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 (
|
||||
<motion.div
|
||||
className="bottom-bar"
|
||||
initial={{ y: 100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
background: 'rgba(44, 62, 80, 0.95)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
color: '#fff',
|
||||
padding: '8px 20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '16px',
|
||||
overflow: 'hidden',
|
||||
zIndex: 1000,
|
||||
height: '44px',
|
||||
borderTop: '1px solid rgba(255,255,255,0.1)',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'monospace',
|
||||
}}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
>
|
||||
{/* Статус */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
flexShrink: 0,
|
||||
zIndex: 2,
|
||||
background: 'rgba(44, 62, 80, 0.95)',
|
||||
paddingRight: '12px',
|
||||
}}>
|
||||
<span style={{
|
||||
color: isOnline ? '#2ecc71' : '#e74c3c',
|
||||
fontSize: '12px',
|
||||
}}>
|
||||
{isOnline ? '●' : '○'}
|
||||
</span>
|
||||
<span style={{ fontWeight: 600, opacity: 0.7, fontSize: '11px' }}>КУРСЫ</span>
|
||||
<div style={{
|
||||
width: '1px',
|
||||
height: '24px',
|
||||
background: 'rgba(255,255,255,0.15)',
|
||||
marginLeft: '4px',
|
||||
}} />
|
||||
</div>
|
||||
<>
|
||||
{/* 🌤️ ПОЛОСКА ПОГОДЫ — показываем только если есть интернет и данные */}
|
||||
<AnimatePresence>
|
||||
{isOnline && weatherData.length > 0 && !weatherLoading && !weatherError && (
|
||||
<motion.div
|
||||
initial={{ y: -30, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: -30, opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: '44px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 999,
|
||||
background: 'rgba(44, 62, 80, 0.85)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
color: '#fff',
|
||||
padding: '4px 20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
overflow: 'hidden',
|
||||
height: '32px',
|
||||
borderTop: '1px solid rgba(255,255,255,0.05)',
|
||||
borderBottom: '1px solid rgba(255,255,255,0.05)',
|
||||
fontSize: '12px',
|
||||
fontFamily: 'monospace',
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
flexShrink: 0,
|
||||
background: 'rgba(44, 62, 80, 0.85)',
|
||||
paddingRight: '12px',
|
||||
}}>
|
||||
<span style={{ fontSize: '16px' }}>🌤️</span>
|
||||
<span style={{ fontWeight: 600, opacity: 0.7, fontSize: '11px' }}>
|
||||
ГРОДНО
|
||||
</span>
|
||||
<div style={{
|
||||
width: '1px',
|
||||
height: '20px',
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
marginLeft: '4px',
|
||||
}} />
|
||||
</div>
|
||||
|
||||
{/* Бегущая строка */}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
maskImage: 'linear-gradient(to right, transparent, black 3%, black 97%, transparent)',
|
||||
WebkitMaskImage: 'linear-gradient(to right, transparent, black 3%, black 97%, transparent)',
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
animate={{
|
||||
x: isPaused ? 0 : ['0%', '-33.33%'],
|
||||
}}
|
||||
transition={{
|
||||
duration: 35,
|
||||
repeat: Infinity,
|
||||
ease: 'linear',
|
||||
repeatType: 'loop',
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '40px',
|
||||
whiteSpace: 'nowrap',
|
||||
willChange: 'transform',
|
||||
transform: 'translateZ(0)',
|
||||
backfaceVisibility: 'hidden',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
MozOsxFontSmoothing: 'grayscale',
|
||||
textRendering: 'geometricPrecision',
|
||||
paddingRight: '40px',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{doubledItems.map((item, index) => (
|
||||
<div
|
||||
key={`${item.id}-${index}`}
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
flexShrink: 0,
|
||||
fontSmooth: 'always',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
textRendering: 'geometricPrecision',
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
fontWeight: 600,
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
fontSize: '13px',
|
||||
letterSpacing: '0.3px',
|
||||
}}>
|
||||
{item.label}
|
||||
</span>
|
||||
<span style={{
|
||||
color: 'rgba(255,255,255,0.8)',
|
||||
fontSize: '13px',
|
||||
letterSpacing: '0.2px',
|
||||
}}>
|
||||
{item.displayValue}
|
||||
</span>
|
||||
<span style={{
|
||||
color: item.change >= 0 ? '#2ecc71' : '#e74c3c',
|
||||
fontSize: '11px',
|
||||
<motion.div
|
||||
animate={{
|
||||
x: isPaused ? 0 : ['0%', '-50%'],
|
||||
}}
|
||||
transition={{
|
||||
duration: 20,
|
||||
repeat: Infinity,
|
||||
ease: 'linear',
|
||||
repeatType: 'loop',
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '30px',
|
||||
whiteSpace: 'nowrap',
|
||||
willChange: 'transform',
|
||||
transform: 'translateZ(0)',
|
||||
paddingRight: '30px',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{[...weatherData, ...weatherData].map((day, index) => (
|
||||
<div
|
||||
key={`${day.date}-${index}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '13px', fontWeight: 500 }}>
|
||||
{day.dayOfWeek}
|
||||
</span>
|
||||
<span style={{ fontSize: '16px' }}>
|
||||
{getWeatherIcon(day.icon)}
|
||||
</span>
|
||||
<span style={{ color: 'rgba(255,255,255,0.7)' }}>
|
||||
{Math.round(day.tempMin)}°…{Math.round(day.tempMax)}°
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* 📊 ОСНОВНАЯ ПОЛОСКА (курсы валют) */}
|
||||
<motion.div
|
||||
className="bottom-bar"
|
||||
initial={{ y: 100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
background: 'rgba(44, 62, 80, 0.95)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
color: '#fff',
|
||||
padding: '8px 20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '16px',
|
||||
overflow: 'hidden',
|
||||
zIndex: 1000,
|
||||
height: '44px',
|
||||
borderTop: '1px solid rgba(255,255,255,0.1)',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'monospace',
|
||||
}}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
>
|
||||
{/* Статус */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
flexShrink: 0,
|
||||
zIndex: 2,
|
||||
background: 'rgba(44, 62, 80, 0.95)',
|
||||
paddingRight: '12px',
|
||||
}}>
|
||||
<span style={{
|
||||
color: isOnline ? '#2ecc71' : '#e74c3c',
|
||||
fontSize: '12px',
|
||||
}}>
|
||||
{isOnline ? '●' : '○'}
|
||||
</span>
|
||||
<span style={{ fontWeight: 600, opacity: 0.7, fontSize: '11px' }}>КУРСЫ</span>
|
||||
<div style={{
|
||||
width: '1px',
|
||||
height: '24px',
|
||||
background: 'rgba(255,255,255,0.15)',
|
||||
marginLeft: '4px',
|
||||
}} />
|
||||
</div>
|
||||
|
||||
{/* Бегущая строка с курсами */}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
maskImage: 'linear-gradient(to right, transparent, black 3%, black 97%, transparent)',
|
||||
WebkitMaskImage: 'linear-gradient(to right, transparent, black 3%, black 97%, transparent)',
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
animate={{
|
||||
x: isPaused ? 0 : ['0%', '-33.33%'],
|
||||
}}
|
||||
transition={{
|
||||
duration: 35,
|
||||
repeat: Infinity,
|
||||
ease: 'linear',
|
||||
repeatType: 'loop',
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '40px',
|
||||
whiteSpace: 'nowrap',
|
||||
willChange: 'transform',
|
||||
transform: 'translateZ(0)',
|
||||
backfaceVisibility: 'hidden',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
MozOsxFontSmoothing: 'grayscale',
|
||||
textRendering: 'geometricPrecision',
|
||||
paddingRight: '40px',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{doubledItems.map((item, index) => (
|
||||
<div
|
||||
key={`${item.id}-${index}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
flexShrink: 0,
|
||||
fontSmooth: 'always',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
textRendering: 'geometricPrecision',
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
fontWeight: 600,
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
fontSize: '13px',
|
||||
letterSpacing: '0.3px',
|
||||
}}>
|
||||
{item.label}
|
||||
</span>
|
||||
<span style={{
|
||||
color: 'rgba(255,255,255,0.8)',
|
||||
fontSize: '13px',
|
||||
letterSpacing: '0.2px',
|
||||
}}>
|
||||
{item.displayValue}
|
||||
</span>
|
||||
<span style={{
|
||||
color: item.change >= 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)}%
|
||||
</span>
|
||||
{item.type === 'metal' && (
|
||||
<span style={{
|
||||
fontSize: '9px',
|
||||
opacity: 0.3,
|
||||
marginLeft: '-2px',
|
||||
}}>
|
||||
$
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Время + Кнопка выхода */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
flexShrink: 0,
|
||||
paddingLeft: '12px',
|
||||
background: 'rgba(44, 62, 80, 0.95)',
|
||||
zIndex: 2,
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: '11px',
|
||||
opacity: 0.4,
|
||||
whiteSpace: 'nowrap',
|
||||
fontSmooth: 'always',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
}}>
|
||||
{currentTime.toLocaleString('ru-RU', {
|
||||
timeZone: 'Europe/Minsk',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{onLogout && (
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={{
|
||||
padding: '4px 14px',
|
||||
background: 'rgba(231, 76, 60, 0.8)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.3s ease',
|
||||
whiteSpace: 'nowrap',
|
||||
minHeight: '28px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '2px',
|
||||
letterSpacing: '0.2px',
|
||||
}}>
|
||||
{item.change >= 0 ? '▲' : '▼'}
|
||||
{Math.abs(item.change).toFixed(1)}%
|
||||
</span>
|
||||
{item.type === 'metal' && (
|
||||
<span style={{
|
||||
fontSize: '9px',
|
||||
opacity: 0.3,
|
||||
marginLeft: '-2px',
|
||||
}}>
|
||||
$
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* ✅ Время с СЕКУНДАМИ и обновлением */}
|
||||
<div style={{
|
||||
marginLeft: 'auto',
|
||||
fontSize: '11px',
|
||||
opacity: 0.4,
|
||||
whiteSpace: 'nowrap',
|
||||
flexShrink: 0,
|
||||
paddingLeft: '12px',
|
||||
background: 'rgba(44, 62, 80, 0.95)',
|
||||
zIndex: 2,
|
||||
fontSmooth: 'always',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
}}>
|
||||
{currentTime.toLocaleString('ru-RU', {
|
||||
timeZone: 'Europe/Minsk',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Пауза */}
|
||||
{isPaused && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontSize: '11px',
|
||||
color: 'rgba(255,255,255,0.3)',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 3,
|
||||
background: 'rgba(0,0,0,0.5)',
|
||||
padding: '2px 12px',
|
||||
borderRadius: '4px',
|
||||
backdropFilter: 'blur(5px)',
|
||||
}}>
|
||||
⏸ Пауза
|
||||
gap: '4px',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(231, 76, 60, 1)';
|
||||
e.currentTarget.style.transform = 'scale(1.05)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(231, 76, 60, 0.8)';
|
||||
e.currentTarget.style.transform = 'scale(1)';
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '14px' }}>⏻</span>
|
||||
Выйти
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{/* Пауза */}
|
||||
{isPaused && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontSize: '11px',
|
||||
color: 'rgba(255,255,255,0.3)',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 3,
|
||||
background: 'rgba(0,0,0,0.5)',
|
||||
padding: '2px 12px',
|
||||
borderRadius: '4px',
|
||||
backdropFilter: 'blur(5px)',
|
||||
}}>
|
||||
⏸ Пауза
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user