Files
ridebus-board/src/WeatherBlock.tsx
Zakhar Timoshenko f4ea47f2ae feat: add verification layout themes documentation and new components
- Created `verification-layout-themes.md` to document layout and theme verification results.
- Introduced `AlertCard` component for displaying alerts with appropriate icons and metadata.
- Added `RouteCloud` component to manage and display route information dynamically.
- Implemented tests for QR code generation and decoding to ensure functionality across multiple city and platform links.
2026-10-09 02:43:42 +03:00

31 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
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.
import { Cloud, Moon, Rain, Snow, Storm, Sun, Wind } from './Icons';
import { NumericText } from './NumericText';
import { weatherAvailable, weatherCondition } from './lib/weather.mjs';
import type { Weather } from './types';
const icons = { cloud: Cloud, moon: Moon, rain: Rain, snow: Snow, storm: Storm, sun: Sun };
const hourFormat = new Intl.DateTimeFormat('ru', { timeZone: 'Europe/Minsk', hour: '2-digit', minute: '2-digit' });
const degrees = (value: number) => `${Math.round(value) > 0 ? '+' : ''}${Math.round(value)}°`;
function WeatherIcon({ code, isDay = true, size = 32 }: { code: number; isDay?: boolean; size?: number }) {
const condition = weatherCondition(code, isDay);
const Icon = icons[condition.icon as keyof typeof icons];
return <Icon size={size} />;
}
export function WeatherSummary({ data, now, failed }: { data: Weather | null; now: number; failed: boolean }) {
if (!weatherAvailable(data, now) || !data) return null;
return <div className="weather-summary" aria-label={`Погода: ${degrees(data.temperature)}, ${weatherCondition(data.code, data.isDay).label}${failed ? ', сохранённые данные' : ''}`}>
<WeatherIcon code={data.code} isDay={data.isDay} size={25} /><strong><NumericText value={degrees(data.temperature)} /></strong><span>{weatherCondition(data.code, data.isDay).label}</span>{failed && <small>сохранено</small>}
</div>;
}
export function WeatherCard({ data, now, loading, failed, demo }: { data: Weather | null; now: number; loading: boolean; failed: boolean; demo: boolean }) {
const available = weatherAvailable(data, now);
if (!available || !data) return <><div className="card-icon"><Cloud /></div><h3>Погода</h3><p>{demo ? 'Погода доступна с реальными данными остановки.' : loading ? 'Получаем прогноз…' : 'Прогноз сейчас недоступен.'}</p><div className="info-caption">{data ? 'Сохранённый прогноз устарел' : 'Погода обновится при подключении'}</div></>;
const hours = data.hourly.filter(hour => Date.parse(hour.at) > now).slice(0, 3);
const probability = hours.length ? Math.max(...hours.map(hour => hour.precipitationProbability)) : null;
return <><h3>Погода</h3><div className="weather-body"><div className="weather-now"><span className="weather-now-label">СЕЙЧАС</span><div className="weather-current"><WeatherIcon code={data.code} isDay={data.isDay} size={60} /><strong><NumericText value={degrees(data.temperature)} /></strong></div>
<p className="weather-description">{weatherCondition(data.code, data.isDay).label}</p><div className="weather-details"><span>Ощущается как <NumericText value={degrees(data.apparentTemperature)} /></span><span><Wind size={20} /><NumericText value={Math.round(data.windSpeed)} /> м/с</span></div></div><div className="weather-future">
{!!hours.length && <div className="weather-hours">{hours.map(hour => <div key={hour.at}><time dateTime={hour.at}><NumericText value={hourFormat.format(Date.parse(hour.at))} /></time><WeatherIcon code={hour.code} size={40} /><strong><NumericText value={degrees(hour.temperature)} /></strong><small className="weather-hour-rain"><NumericText value={hour.precipitationProbability} />%<span>осадки</span></small></div>)}</div>}
{probability !== null && <div className="precipitation-chance">Осадки в ближайшие 3 часа: <NumericText value={probability} />%</div>}</div></div>
<div className="weather-attribution"><span>{failed ? 'Сохранено' : 'Обновлено'} в {hourFormat.format(Date.parse(data.updatedAt))}</span><a href="https://open-meteo.com/" target="_blank" rel="noreferrer">Open-Meteo · CC BY 4.0</a></div></>;
}