aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/api
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-12-22 18:16:57 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2025-12-22 18:16:57 +0100
commit4b7eaa318f22d7cc768491c421cb7aeac477f95d (patch)
tree0b39abce444679396475e4f48885479e2ae0650f /src/frontend/app/api
parent91f7d7dd5a4ca8453cfdbc9a3beeb216b6638ef7 (diff)
Implement retrieving next arrivals for a stop (scheduled only)
Diffstat (limited to 'src/frontend/app/api')
-rw-r--r--src/frontend/app/api/arrivals.ts31
-rw-r--r--src/frontend/app/api/schema.ts96
2 files changed, 127 insertions, 0 deletions
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<StopArrivalsResponse> => {
+ 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<typeof RouteInfoSchema>;
+export type HeadsignInfo = z.infer<typeof HeadsignInfoSchema>;
+export type ArrivalPrecission = z.infer<typeof ArrivalPrecissionSchema>;
+export type ArrivalDetails = z.infer<typeof ArrivalDetailsSchema>;
+export type DelayBadge = z.infer<typeof DelayBadgeSchema>;
+export type ShiftBadge = z.infer<typeof ShiftBadgeSchema>;
+export type Arrival = z.infer<typeof ArrivalSchema>;
+export type StopArrivalsResponse = z.infer<typeof StopArrivalsResponseSchema>;
+
+// 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
+>;