Files
smart-home-tablet/components/TopBar.tsx
Cosmo 311ae1dc4b
All checks were successful
Deploy to Coolify / deploy (push) Successful in 3s
feat: full redesign - sidebar layout, room tabs, device cards
2026-04-22 11:05:41 +00:00

230 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect } from 'react'
import { Cloud, Droplets, Wind } from 'lucide-react'
interface WeatherData {
temp: string
desc: string
humidity: string
windSpeed: string
feelsLike: string
forecast?: { date: string; maxTemp: string; minTemp: string; desc: string }[]
}
interface SensorData {
temperature: number
humidity: number
pm25: number
}
interface TopBarProps {
weather: WeatherData | null
sensors: SensorData | null
}
function getWeatherEmoji(desc: string): string {
const d = desc?.toLowerCase() || ''
if (d.includes('ясно') || d.includes('солнеч')) return '☀️'
if (d.includes('облач')) return '⛅'
if (d.includes('пасмурн')) return '☁️'
if (d.includes('дождь') || d.includes('морос')) return '🌧️'
if (d.includes('снег')) return '❄️'
if (d.includes('гроз')) return '⛈️'
return '🌤️'
}
function formatTime(date: Date): string {
return date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
}
function formatDate(date: Date): string {
return date.toLocaleDateString('ru-RU', { weekday: 'short', day: 'numeric', month: 'short' })
}
export default function TopBar({ weather, sensors }: TopBarProps) {
const [time, setTime] = useState(() => new Date())
const [showModal, setShowModal] = useState(false)
useEffect(() => {
const t = setInterval(() => setTime(new Date()), 1000)
return () => clearInterval(t)
}, [])
return (
<>
<header
style={{
height: 64,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 20px',
borderBottom: '1px solid rgba(255,255,255,0.06)',
background: 'rgba(255,255,255,0.02)',
backdropFilter: 'blur(10px)',
flexShrink: 0,
}}
>
{/* Left: time + date */}
<div style={{ display: 'flex', alignItems: 'baseline', gap: 10 }}>
<span style={{ fontSize: 22, fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.5px' }}>
{formatTime(time)}
</span>
<span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
{formatDate(time)}
</span>
</div>
{/* Right: sensors + weather */}
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
{/* Room sensors */}
{sensors && (
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<span style={{ fontSize: 12, color: 'var(--text-secondary)' }}>🌡</span>
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text-primary)' }}>
{sensors.temperature}°
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<Droplets size={13} color="rgba(255,255,255,0.4)" />
<span style={{ fontSize: 14, fontWeight: 500, color: 'var(--text-primary)' }}>
{sensors.humidity}%
</span>
</div>
{sensors.pm25 !== undefined && (
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<Wind size={13} color="rgba(255,255,255,0.4)" />
<span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
PM2.5: {sensors.pm25}
</span>
</div>
)}
</div>
)}
{/* Divider */}
{sensors && weather && (
<div style={{ width: 1, height: 24, background: 'rgba(255,255,255,0.08)' }} />
)}
{/* Weather widget */}
{weather && (
<button
onClick={() => setShowModal(true)}
style={{
display: 'flex',
alignItems: 'center',
gap: 6,
padding: '6px 12px',
borderRadius: 10,
background: 'rgba(255,255,255,0.05)',
border: '1px solid rgba(255,255,255,0.08)',
color: 'var(--text-primary)',
touchAction: 'manipulation',
WebkitTapHighlightColor: 'transparent',
}}
>
<span style={{ fontSize: 16 }}>{getWeatherEmoji(weather.desc)}</span>
<span style={{ fontSize: 16, fontWeight: 600 }}>{weather.temp}°</span>
<span style={{ fontSize: 12, color: 'var(--text-secondary)' }}>{weather.desc}</span>
</button>
)}
</div>
</header>
{/* Weather Modal */}
{showModal && weather && (
<div
onClick={() => setShowModal(false)}
style={{
position: 'fixed',
inset: 0,
background: 'rgba(0,0,0,0.7)',
backdropFilter: 'blur(8px)',
zIndex: 100,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div
onClick={e => e.stopPropagation()}
style={{
background: '#13131f',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 20,
padding: 28,
minWidth: 320,
maxWidth: 400,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
<span style={{ fontSize: 40 }}>{getWeatherEmoji(weather.desc)}</span>
<div>
<div style={{ fontSize: 36, fontWeight: 700, lineHeight: 1 }}>{weather.temp}°C</div>
<div style={{ fontSize: 15, color: 'var(--text-secondary)', marginTop: 2 }}>{weather.desc}</div>
</div>
</div>
<div style={{ display: 'flex', gap: 16, marginBottom: 20 }}>
<div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
<Droplets size={13} style={{ marginRight: 4 }} />
Влажность: {weather.humidity}%
</div>
<div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
<Wind size={13} style={{ marginRight: 4 }} />
Ветер: {weather.windSpeed} км/ч
</div>
<div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
Ощущается: {weather.feelsLike}°
</div>
</div>
{weather.forecast && weather.forecast.length > 0 && (
<div>
<div style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 12 }}>
Прогноз
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{weather.forecast.map(day => {
const d = new Date(day.date)
const label = d.toLocaleDateString('ru-RU', { weekday: 'short', day: 'numeric', month: 'short' })
return (
<div key={day.date} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span style={{ fontSize: 13, color: 'var(--text-secondary)', minWidth: 80 }}>{label}</span>
<span style={{ fontSize: 16 }}>{getWeatherEmoji(day.desc)}</span>
<span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>{day.desc}</span>
<span style={{ fontSize: 13, color: 'var(--text-primary)', fontWeight: 500 }}>
{day.maxTemp}° / {day.minTemp}°
</span>
</div>
)
})}
</div>
</div>
)}
<button
onClick={() => setShowModal(false)}
style={{
marginTop: 20,
width: '100%',
padding: '10px',
borderRadius: 10,
background: 'rgba(255,255,255,0.05)',
color: 'var(--text-secondary)',
fontSize: 14,
touchAction: 'manipulation',
}}
>
Закрыть
</button>
</div>
</div>
)}
</>
)
}