From 4b7eaa318f22d7cc768491c421cb7aeac477f95d Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Mon, 22 Dec 2025 18:16:57 +0100 Subject: Implement retrieving next arrivals for a stop (scheduled only) --- src/frontend/app/api/arrivals.ts | 31 +++++++++++++ src/frontend/app/api/schema.ts | 96 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/frontend/app/api/arrivals.ts create mode 100644 src/frontend/app/api/schema.ts (limited to 'src/frontend/app/api') diff --git a/src/frontend/app/api/arrivals.ts b/src/frontend/app/api/arrivals.ts new file mode 100644 index 0000000..8ae6e78 --- /dev/null +++ b/src/frontend/app/api/arrivals.ts @@ -0,0 +1,31 @@ +import { + StopArrivalsResponseSchema, + type StopArrivalsResponse, +} from "./schema"; + +export const fetchArrivals = async ( + stopId: string, + reduced: boolean = false +): Promise => { + const resp = await fetch( + `/api/stops/arrivals?id=${stopId}&reduced=${reduced}`, + { + headers: { + Accept: "application/json", + }, + } + ); + + if (!resp.ok) { + throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); + } + + const data = await resp.json(); + try { + return StopArrivalsResponseSchema.parse(data); + } catch (e) { + console.error("Zod parsing failed for arrivals:", e); + console.log("Received data:", data); + throw e; + } +}; diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts new file mode 100644 index 0000000..60e2d97 --- /dev/null +++ b/src/frontend/app/api/schema.ts @@ -0,0 +1,96 @@ +import { z } from "zod"; + +export const RouteInfoSchema = z.object({ + shortName: z.string(), + colour: z.string(), + textColour: z.string(), +}); + +export const HeadsignInfoSchema = z.object({ + badge: z.string().optional().nullable(), + destination: z.string(), + marquee: z.string().optional().nullable(), +}); + +export const ArrivalPrecissionSchema = z.enum([ + "confident", + "unsure", + "scheduled", + "past", +]); + +export const ArrivalDetailsSchema = z.object({ + minutes: z.number(), + precission: ArrivalPrecissionSchema, +}); + +export const DelayBadgeSchema = z.object({ + minutes: z.number(), +}); + +export const ShiftBadgeSchema = z.object({ + shiftName: z.string(), + shiftTrip: z.string(), +}); + +export const ArrivalSchema = z.object({ + route: RouteInfoSchema, + headsign: HeadsignInfoSchema, + estimate: ArrivalDetailsSchema, + delay: DelayBadgeSchema.optional().nullable(), + shift: ShiftBadgeSchema.optional().nullable(), +}); + +export const StopArrivalsResponseSchema = z.object({ + stopCode: z.string(), + stopName: z.string(), + arrivals: z.array(ArrivalSchema), +}); + +export type RouteInfo = z.infer; +export type HeadsignInfo = z.infer; +export type ArrivalPrecission = z.infer; +export type ArrivalDetails = z.infer; +export type DelayBadge = z.infer; +export type ShiftBadge = z.infer; +export type Arrival = z.infer; +export type StopArrivalsResponse = z.infer; + +// Consolidated Circulation (Legacy/Alternative API) +export const ConsolidatedCirculationSchema = z.object({ + line: z.string(), + route: z.string(), + schedule: z + .object({ + running: z.boolean(), + minutes: z.number(), + serviceId: z.string(), + tripId: z.string(), + shapeId: z.string().optional().nullable(), + }) + .optional() + .nullable(), + realTime: z + .object({ + minutes: z.number(), + distance: z.number(), + }) + .optional() + .nullable(), + currentPosition: z + .object({ + latitude: z.number(), + longitude: z.number(), + orientationDegrees: z.number(), + shapeIndex: z.number().optional().nullable(), + }) + .optional() + .nullable(), + isPreviousTrip: z.boolean().optional().nullable(), + previousTripShapeId: z.string().optional().nullable(), + nextStreets: z.array(z.string()).optional().nullable(), +}); + +export type ConsolidatedCirculation = z.infer< + typeof ConsolidatedCirculationSchema +>; -- cgit v1.3