Add Wiener Linien API integration and departures monitoring

This commit is contained in:
2026-05-10 17:19:24 +02:00
parent c0f34b9e2a
commit 7901368971
20 changed files with 1746 additions and 197 deletions
+57 -57
View File
@@ -1,75 +1,75 @@
'use client';
"use client";
import React, { useState, useCallback } from 'react';
import { Event, Station } from '@/types';
import TrainSection from './TrainSection';
import BikeSection from './BikeSection';
import LeaveByBadge from './LeaveByBadge';
import { calculateCountdown } from '@/lib/countdown-utils';
import { useDestinationStation } from '@/hooks/useDestinationStation';
import { useGeocode } from '@/hooks/useGeocode';
import { useJourneys } from '@/hooks/useJourneys';
import { useBikeRoute } from '@/hooks/useBikeRoute';
import { useGeolocation } from '@/hooks/useGeolocation';
import { format } from "date-fns";
import { useJourneys } from "@/hooks/useJourneys";
import { useBikeRoute } from "@/hooks/useBikeRoute";
import { useGeocode } from "@/hooks/useGeocode";
import { useClock } from "@/hooks/useClock";
import { useWienerLinien } from "@/hooks/useWienerLinien";
import type { Event, Station } from "@/types";
import TrainSection from "./TrainSection";
import BikeSection from "./BikeSection";
import WienerLinienSection from "./WienerLinienSection";
import CountdownBadge from "@/app/ui/CountdownBadge";
type EventCardProps = {
interface EventCardProps {
event: Event;
originStation: Station | null;
className?: string;
};
}
const EventCard: React.FC<EventCardProps> = ({ event, originStation, className = '' }) => {
const [refreshKey, setRefreshKey] = useState(0);
const handleRefresh = useCallback(() => setRefreshKey((k) => k + 1), []);
export default function EventCard({ event, originStation }: EventCardProps) {
const {
journeys,
loading: journeysLoading,
error: journeysError,
} = useJourneys(originStation?.extId ?? null, null, event.eventTime, 0);
const { location } = useGeolocation();
const { station: destStation } = useDestinationStation(event.destination);
const { coords: destCoords } = useGeocode(event.destination);
const destCoords = useGeocode(event.destination);
const { journeys, loading, error } = useJourneys(
originStation?.extId ?? null,
destStation?.extId ?? null,
event.eventTime,
refreshKey,
);
const {
bikeRoute,
loading: bikeLoading,
error: bikeError,
} = useBikeRoute(originStation?.lat, originStation?.lng, destCoords.coords?.lat, destCoords.coords?.lng);
const { bikeRoute, loading: bikeLoading, error: bikeError } = useBikeRoute(
location?.coords.latitude,
location?.coords.longitude,
destCoords?.lat,
destCoords?.lng,
);
const {
stops,
departures,
loading: wlLoading,
error: wlError,
} = useWienerLinien(destCoords.coords?.lat, destCoords.coords?.lng);
const { countdown, status } = useClock(event.eventTime);
return (
<div className={`bg-white dark:bg-gray-800 rounded-lg shadow-sm overflow-hidden ${className}`}>
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-start justify-between">
<div className="flex-1">
<h3 className="font-semibold text-gray-900 dark:text-white">{event.title}</h3>
<p className="text-sm text-gray-600 dark:text-gray-300">{event.destination}</p>
</div>
<div className="ml-4 flex items-center">
<LeaveByBadge countdown={calculateCountdown(event.eventTime)} />
</div>
</div>
<div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{event.title}</h3>
<CountdownBadge countdown={countdown} status={status} />
</div>
<div className="p-4">
<div className="mb-2 text-sm text-gray-600 dark:text-gray-300">
<span className="font-medium">Destination:</span> {event.destination}
</div>
<div className="mb-4 text-sm text-gray-600 dark:text-gray-300">
<span className="font-medium">Time:</span> {format(event.eventTime, "EEE dd MMM yyyy HH:mm")}
</div>
<div className="space-y-4">
<TrainSection
journeys={journeys}
eventTime={event.eventTime}
destName={destStation?.name ?? event.destination}
loading={loading}
error={error}
onRefresh={handleRefresh}
/>
<BikeSection
bikeRoute={bikeRoute}
bikeLoading={bikeLoading}
bikeError={bikeError}
destName={event.destination}
loading={journeysLoading}
error={journeysError}
/>
<BikeSection bikeRoute={bikeRoute} bikeLoading={bikeLoading} bikeError={bikeError} />
{stops.length > 0 && (
<WienerLinienSection stops={stops} departures={departures} loading={wlLoading} error={wlError} />
)}
</div>
</div>
);
};
export default EventCard;
}