'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: 'от центра' },
]
const ROUTES: { num: string; color: string; bg: string }[] = [
{ num: '23', color: '#34d399', bg: 'linear-gradient(135deg, #10b981, #059669)' },
{ num: '27', color: '#60a5fa', bg: 'linear-gradient(135deg, #3b82f6, #2563eb)' },
{ num: '39', color: '#f87171', bg: 'linear-gradient(135deg, #ef4444, #dc2626)' },
]
function formatMinutes(m: number): string {
if (m <= 0) return 'сейчас'
return `${m} мин`
}
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 */}
{first.minutes <= 0 ? 'сейчас' : first.minutes}
{first.minutes > 0 && (
мин
)}
{/* Divider */}
{rest.length > 0 && (
)}
{/* Next arrivals */}
{rest.length > 0 && (
затем
{rest.map(r => formatMinutes(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 (
{/* Glow */}
{/* Header */}
Трамвай
Ул. Антонова-Овсеенко
{/* Column headers */}
{DIRECTIONS.map(d => (
))}
{/* Rows: one per route */}
{ROUTES.map(route => (
{/* Route badge */}
{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.2 }}
>
|
)
})}
))}
{loading && Object.keys(data).length === 0 && (
Загрузка расписания...
)}
)
}