- 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.
31 lines
3.7 KiB
TypeScript
31 lines
3.7 KiB
TypeScript
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></>;
|
||
}
|