From 48ec0aae80a200d7eb50639ff4c4ca8ae564f29b Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Sun, 28 Dec 2025 22:24:26 +0100 Subject: Implement displaying routes with dynamic data from OTP --- src/frontend/app/api/schema.ts | 46 +++++++++++++++++++++++++++++++++++++++++ src/frontend/app/api/transit.ts | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/frontend/app/api/transit.ts (limited to 'src/frontend/app/api') diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts index 63f4368..f7f0a39 100644 --- a/src/frontend/app/api/schema.ts +++ b/src/frontend/app/api/schema.ts @@ -70,6 +70,52 @@ export type Position = z.infer; export type Arrival = z.infer; export type StopArrivalsResponse = z.infer; +// Transit Routes +export const RouteSchema = z.object({ + id: z.string(), + shortName: z.string().nullable(), + longName: z.string().nullable(), + color: z.string().nullable(), + textColor: z.string().nullable(), + sortOrder: z.number().nullable(), + agencyName: z.string().nullable().optional(), + tripCount: z.number(), +}); + +export const PatternStopSchema = z.object({ + id: z.string(), + code: z.string().nullable(), + name: z.string(), + lat: z.number(), + lon: z.number(), + scheduledDepartures: z.array(z.number()), +}); + +export const PatternSchema = z.object({ + id: z.string(), + name: z.string().nullable(), + headsign: z.string().nullable(), + directionId: z.number(), + code: z.string().nullable(), + semanticHash: z.string().nullable(), + tripCount: z.number(), + geometry: z.array(z.array(z.number())).nullable(), + stops: z.array(PatternStopSchema), +}); + +export const RouteDetailsSchema = z.object({ + shortName: z.string().nullable(), + longName: z.string().nullable(), + color: z.string().nullable(), + textColor: z.string().nullable(), + patterns: z.array(PatternSchema), +}); + +export type Route = z.infer; +export type PatternStop = z.infer; +export type Pattern = z.infer; +export type RouteDetails = z.infer; + // Consolidated Circulation (Legacy/Alternative API) export const ConsolidatedCirculationSchema = z.object({ line: z.string(), diff --git a/src/frontend/app/api/transit.ts b/src/frontend/app/api/transit.ts new file mode 100644 index 0000000..317271a --- /dev/null +++ b/src/frontend/app/api/transit.ts @@ -0,0 +1,39 @@ +import { + RouteDetailsSchema, + RouteSchema, + type Route, + type RouteDetails, +} from "./schema"; + +export const fetchRoutes = async (feeds: string[] = []): Promise => { + const params = new URLSearchParams(); + feeds.forEach((f) => params.append("feeds", f)); + + const resp = await fetch(`/api/transit/routes?${params.toString()}`, { + headers: { + Accept: "application/json", + }, + }); + + if (!resp.ok) { + throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); + } + + const data = await resp.json(); + return RouteSchema.array().parse(data); +}; + +export const fetchRouteDetails = async (id: string): Promise => { + const resp = await fetch(`/api/transit/routes/${encodeURIComponent(id)}`, { + headers: { + Accept: "application/json", + }, + }); + + if (!resp.ok) { + throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); + } + + const data = await resp.json(); + return RouteDetailsSchema.parse(data); +}; -- cgit v1.3