Newer
Older
import React, {useEffect, useState} from 'react';
import PanelWrapper from "../../meta/PanelWrapper";
import PlanElement from "./components/PlanElement";
import PanelTitle from "../../meta/PanelTitle";
import PanelContent from "../../meta/PanelContent";
import {StationResponse} from "./types/vrrfAPI";
import {Warning} from "@phosphor-icons/react";
export type FahrplanPanelDefinition = {
stops: string[],
filter: {
types?: string[],
destinations?: string[]
}
}
type Route = {
uid: string,
identifier: string,
heading: string,
stops: {
name: string,
arrival: Date,
delay?: number,
countdown: number
}[],
countdown: number
}
const FahrplanPanel = (props: {definition: FahrplanPanelDefinition}) => {
const [routes, setRoutes] = useState<Route[]>([]);
useEffect(() => {
const update = async () => {
const departures = (await Promise.all(props.definition.stops.map(getStopData)))
.map(d => d.raw)
.flat();
let newRoutes: Route[] = [];
// Determine stop data from the departures
for(let departure of departures) {
// First throw away all data that belongs to a filtered category
if((props.definition.filter.types ?? []).includes(departure.type)) {
continue;
}
// Find existing route with same uid
const existing_ind = newRoutes.findIndex(r => r.uid === departure.key)
// Pre-compute values that will be needed regardless
const delay = stringToDelay(departure.delay);
const arrival = processArrival(departure.sched_date, departure.time);
if(existing_ind === -1) {
// If it does not exist, create a new route
newRoutes.push({
uid: departure.key,
heading: departure.lineref.direction,
identifier: departure.line,
stops: [
{
name: departure.internal.stop,
arrival,
delay,
countdown: parseInt(departure.countdown)
],
countdown: parseInt(departure.countdown)
})
} else {
// If it doesn't, just add a stop to the existing route
newRoutes[existing_ind].stops.push({
name: departure.internal.stop,
arrival,
delay: stringToDelay(departure.delay),
countdown: parseInt(departure.countdown)
newRoutes[existing_ind].stops = newRoutes[existing_ind].stops.sort((a, b) => a.countdown - b.countdown)
}
}
// Sort the output
newRoutes = newRoutes.sort((a, b) => a.countdown - b.countdown)
// Write to the display
setRoutes(newRoutes);
}
update();
const interval = setInterval(update, 2 * 60 * 1000);
return () => {
clearInterval(interval);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
return (
<PanelWrapper>
<PanelTitle title={"ÖPNV Monitor"}/>
<PanelContent className={"flex flex-col"}>
<div className={"flex-1 flex flex-col gap-3"}>
{routes.map((route) => (
<PlanElement
key={route.uid}
tripId={route.uid}
trainIdentifier={route.identifier}
trainHeading={route.heading}
stops={route.stops}
/>
))}
{routes.length === 0 && (
<div className={"flex-1 flex justify-center items-center"}>
<div className={"mb-10 flex flex-col items-center"}>
<Warning size={48} className={"mb-3"}/>
<p className={"max-w-xs text-center text-zinc-400"}>
Aktuell sind keine Abfahrtsdaten verfügbar.
</p>
</div>
</div>
)}
</div>
</PanelContent>
</PanelWrapper>
);
};
export default FahrplanPanel;
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
async function getStopData(stop: string): Promise<StationResponse> {
const request = await fetch(`https://vrrf.finalrewind.org/${encodeURIComponent(stop)}.json`);
if(!request.ok) {
throw new Error("Converting stop did not work");
}
const data = await request.json();
if(data.error) {
console.warn("Stop data for", stop, "could not be fetched");
}
// Add internal reference data
data.raw = data.raw.map((r: any) => ({
...r,
internal: {
stop
}
}))
console.log(data);
return data as StationResponse;
}
function stringToDelay(input: string): number | undefined {
const delay = parseInt(input);
if(delay === 0) {
return undefined;
}
if(delay === Number.NaN) {
console.warn("While parsing delay, the string was not interpretable as number", input);
return delay;
}
function processArrival(date: string, time: string): Date {
const d_parts = date.split(".");
return new Date(`${d_parts[2]}-${d_parts[1]}-${d_parts[0]} ${time}`);
}