'use client' import { useEffect, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { ArrowRight, Train } from 'lucide-react' interface Arrival { route: string minutes: number park: string wheelchair: boolean } interface Direction { stopId: string short: string sub: string } const DIRECTIONS: Direction[] = [ { stopId: '16226', short: 'Лента', sub: 'в центр' }, { stopId: '16354', short: 'Дыбенко', sub: 'от центра' }, ] // Маршрут-специфичные цвета — соответствуют реальной окраске маршрутов // (но в правильной семантике: good=23, info=27, danger=39) const ROUTES: { num: string; color: string; bg: string }[] = [ { num: '23', color: 'var(--data-good)', bg: 'linear-gradient(135deg, var(--data-good), color-mix(in srgb, var(--data-good) 75%, black))' }, { num: '27', color: 'var(--data-info)', bg: 'linear-gradient(135deg, var(--data-info), color-mix(in srgb, var(--data-info) 75%, black))' }, { num: '39', color: 'var(--data-danger)', bg: 'linear-gradient(135deg, var(--data-danger), color-mix(in srgb, var(--data-danger) 75%, black))' }, ] function Cell({ arrivals, color }: { arrivals: Arrival[]; color: string }) { const sorted = [...arrivals].sort((a, b) => a.minutes - b.minutes).slice(0, 3) if (sorted.length === 0) { return (
) } const [first, ...rest] = sorted const imminent = first.minutes <= 2 return (
{/* Primary time — big */}
{first.minutes <= 0 ? 'сейчас' : first.minutes}
{first.minutes > 0 && (
мин
)}
{rest.length > 0 && ( <>
затем
{rest.map(r => r.minutes <= 0 ? 'сейчас' : `${r.minutes}м`).join(' · ')}
)}
) } export default function TransportWidget() { const [data, setData] = useState>({}) const [loading, setLoading] = useState(true) useEffect(() => { let cancelled = false const load = async () => { try { const results = await Promise.all( DIRECTIONS.map(d => fetch(`/api/transport?stopId=${d.stopId}`) .then(r => r.json()) .then(j => ({ stopId: d.stopId, arrivals: (j.arrivals as Arrival[]) || [] })) .catch(() => ({ stopId: d.stopId, arrivals: [] as Arrival[] })) ) ) if (cancelled) return const map: Record = {} for (const r of results) map[r.stopId] = r.arrivals setData(map) } finally { if (!cancelled) setLoading(false) } } load() const t = setInterval(load, 30_000) return () => { cancelled = true; clearInterval(t) } }, []) return (
{/* Header */}
Трамвай
Ул. Антонова-Овсеенко
{/* Column headers */}
{DIRECTIONS.map(d => (
{d.short}
{d.sub}
))}
{/* Rows */}
{ROUTES.map(route => (
{route.num}
{DIRECTIONS.map(d => { const arrivals = (data[d.stopId] || []).filter(a => a.route === route.num) return ( a.minutes).join(',')}`} initial={{ opacity: 0, y: 4 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25 }} > ) })}
))}
{loading && Object.keys(data).length === 0 && (
Загрузка расписания...
)}
) }