aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs87
-rw-r--r--src/Enmarcha.Backend/Services/Processors/SantiagoRealTimeProcessor.cs19
-rw-r--r--src/Enmarcha.Backend/Types/Arrivals/Arrival.cs92
-rw-r--r--src/Enmarcha.Sources.TranviasCoruna/CorunaRealtimeEstimatesProvider.cs3
-rw-r--r--src/frontend/app/api/schema.ts9
-rw-r--r--src/frontend/app/components/arrivals/ArrivalCard.tsx42
-rw-r--r--src/frontend/app/components/arrivals/ReducedArrivalCard.tsx39
7 files changed, 196 insertions, 95 deletions
diff --git a/src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs
index ca3f91d..ad5465f 100644
--- a/src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs
@@ -37,31 +37,29 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
Epsg25829? stopLocation = null;
if (context.StopLocation != null)
{
- stopLocation = _shapeService.TransformToEpsg25829(context.StopLocation.Latitude, context.StopLocation.Longitude);
+ stopLocation =
+ _shapeService.TransformToEpsg25829(context.StopLocation.Latitude, context.StopLocation.Longitude);
}
var realtime = await _realtime.GetEstimatesForStop(numericStopId);
var usedTripIds = new HashSet<string>();
- var newArrivals = new List<Arrival>();
foreach (var estimate in realtime)
{
var bestMatch = context.Arrivals
.Where(a => !usedTripIds.Contains(a.TripId))
.Where(a => a.Route.RouteIdInGtfs.Trim() == estimate.RouteId.Trim())
- .Select(a =>
+ .Select(a => new
{
- return new
- {
- Arrival = a,
- TimeDiff = estimate.Minutes - a.Estimate.Minutes, // RealTime - Schedule
- RouteMatch = true
- };
+ Arrival = a,
+ TimeDiff = estimate.Minutes - a.Estimate.Minutes, // RealTime - Schedule
+ RouteMatch = true
})
.Where(x => x.RouteMatch) // Strict route matching
- .Where(x => x.TimeDiff >= -7 && x.TimeDiff <= 75) // Allow 7m early (RealTime < Schedule) or 75m late (RealTime > Schedule)
- .OrderBy(x => Math.Abs(x.TimeDiff)) // Best time fit
+ .Where(x => x.TimeDiff is >= -5
+ and <= 15) // Allow 5m early (RealTime < Schedule) or 15m late (RealTime > Schedule)
+ .OrderBy(x => x.TimeDiff < 0 ? Math.Abs(x.TimeDiff) * 2 : x.TimeDiff) // Best time fit
.FirstOrDefault();
if (bestMatch == null)
@@ -82,6 +80,17 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
arrival.Delay = new DelayBadge { Minutes = delayMinutes };
}
+ // Populate vehicle information
+ var busInfo = GetBusInfoByNumber(estimate.VehicleNumber);
+ arrival.VehicleInformation = new VehicleBadge
+ {
+ Identifier = estimate.VehicleNumber,
+ Make = busInfo?.Make,
+ Model = busInfo?.Model,
+ Kind = busInfo?.Kind,
+ Year = busInfo?.Year
+ };
+
// Calculate position
if (stopLocation != null)
{
@@ -106,7 +115,9 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
if (currentPosition != null)
{
- _logger.LogInformation("Calculated position from OTP geometry for trip {TripId}: {Lat}, {Lon}", arrival.TripId, currentPosition.Latitude, currentPosition.Longitude);
+ _logger.LogInformation(
+ "Calculated position from OTP geometry for trip {TripId}: {Lat}, {Lon}", arrival.TripId,
+ currentPosition.Latitude, currentPosition.Longitude);
}
// Populate Shape GeoJSON
@@ -149,7 +160,7 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
arrival.Shape = new
{
type = "FeatureCollection",
- features = features
+ features
};
}
}
@@ -162,14 +173,11 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
}
usedTripIds.Add(arrival.TripId);
-
}
-
- context.Arrivals.AddRange(newArrivals);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error fetching Vitrasa real-time data for stop {StopId}", context.StopId);
+ _logger.LogError(ex, "Error fetching Tranvías real-time data for stop {StopId}", context.StopId);
}
}
@@ -178,4 +186,49 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
return a == b || a.Contains(b) || b.Contains(a);
}
+ private (string Make, string Model, string Kind, string Year)? GetBusInfoByNumber(string identifier)
+ {
+ int number = int.Parse(identifier);
+
+ return number switch
+ {
+ // 2000
+ >= 326 and <= 336 => ("MB", "O405N2 Venus", "RIG", "2000"),
+ 337 => ("MB", "O405G Alce", "ART", "2000"),
+ // 2002-2003
+ >= 340 and <= 344 => ("MAN", "NG313F Delfos Venus", "ART", "2002"),
+ >= 345 and <= 347 => ("MAN", "NG313F Delfos Venus", "ART", "2003"),
+ // 2004
+ >= 348 and <= 349 => ("MAN", "NG313F Delfos Venus", "ART", "2004"),
+ >= 350 and <= 355 => ("MAN", "NL263F Luxor II", "RIG", "2004"),
+ // 2005
+ >= 356 and <= 359 => ("MAN", "NL263F Luxor II", "RIG", "2005"),
+ >= 360 and <= 362 => ("MAN", "NG313F Delfos", "ART", "2005"),
+ // 2007
+ >= 363 and <= 370 => ("MAN", "NL273F Luxor II", "RIG", "2007"),
+ // 2008
+ >= 371 and <= 377 => ("MAN", "NL273F Luxor II", "RIG", "2008"),
+ // 2009
+ >= 378 and <= 387 => ("MAN", "NL273F Luxor II", "RIG", "2009"),
+ // 2012
+ >= 388 and <= 392 => ("MAN", "NL283F Ceres", "RIG", "2012"),
+ >= 393 and <= 395 => ("MAN", "NG323F Ceres", "ART", "2012"),
+ // 2013
+ >= 396 and <= 403 => ("MAN", "NL283F Ceres", "RIG", "2013"),
+ // 2014
+ >= 404 and <= 407 => ("MB", "Citaro C2", "RIG", "2014"),
+ >= 408 and <= 411 => ("MAN", "NL283F Ceres", "RIG", "2014"),
+ // 2015
+ >= 412 and <= 414 => ("MB", "Citaro C2 G", "ART", "2015"),
+ >= 415 and <= 419 => ("MB", "Citaro C2", "RIG", "2015"),
+ // 2016
+ >= 420 and <= 427 => ("MB", "Citaro C2", "RIG", "2016"),
+ // 2024
+ 428 => ("MAN", "Lion's City 12 E", "RIG", "2024"),
+ // 2025
+ 429 => ("MAN", "Lion's City 18", "RIG", "2025"),
+ >= 430 and <= 432 => ("MAN", "Lion's City 12", "RIG", "2025"),
+ _ => null
+ };
+ }
}
diff --git a/src/Enmarcha.Backend/Services/Processors/SantiagoRealTimeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/SantiagoRealTimeProcessor.cs
index b941c6e..d14cfa0 100644
--- a/src/Enmarcha.Backend/Services/Processors/SantiagoRealTimeProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/SantiagoRealTimeProcessor.cs
@@ -36,7 +36,6 @@ public class SantiagoRealTimeProcessor : AbstractRealTimeProcessor
var realtime = await _realtime.GetEstimatesForStop(numericStopId);
var usedTripIds = new HashSet<string>();
- var newArrivals = new List<Arrival>();
foreach (var estimate in realtime)
{
@@ -50,11 +49,11 @@ public class SantiagoRealTimeProcessor : AbstractRealTimeProcessor
RouteMatch = true
})
.Where(x => x.RouteMatch) // Strict route matching
- .Where(x => x.TimeDiff >= -7 && x.TimeDiff <= 75) // Allow 7m early (RealTime < Schedule) or 75m late (RealTime > Schedule)
+ .Where(x => x.TimeDiff is >= -5 and <= 25) // Allow 2m early (RealTime < Schedule) or 25m late (RealTime > Schedule)
.OrderBy(x => Math.Abs(x.TimeDiff)) // Best time fit
.FirstOrDefault();
- if (bestMatch == null)
+ if (bestMatch is null)
{
context.Arrivals.Add(new Arrival
{
@@ -76,31 +75,21 @@ public class SantiagoRealTimeProcessor : AbstractRealTimeProcessor
Minutes = estimate.MinutesToArrive,
Precision = ArrivalPrecision.Confident
}
-
});
+ continue;
}
var arrival = bestMatch.Arrival;
- var scheduledMinutes = arrival.Estimate.Minutes;
arrival.Estimate.Minutes = estimate.MinutesToArrive;
arrival.Estimate.Precision = ArrivalPrecision.Confident;
- // Calculate delay badge
- var delayMinutes = estimate.MinutesToArrive - scheduledMinutes;
- if (delayMinutes != 0)
- {
- arrival.Delay = new DelayBadge { Minutes = delayMinutes };
- }
-
usedTripIds.Add(arrival.TripId);
}
-
- context.Arrivals.AddRange(newArrivals);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error fetching Vitrasa real-time data for stop {StopId}", context.StopId);
+ _logger.LogError(ex, "Error fetching Santiago real-time data for stop {StopId}", context.StopId);
}
}
diff --git a/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs b/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
index e99baa7..9d2ea1b 100644
--- a/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
+++ b/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
@@ -1,106 +1,100 @@
using System.Text.Json.Serialization;
using Enmarcha.Backend.Types;
+using Newtonsoft.Json;
namespace Enmarcha.Backend.Types.Arrivals;
public class Arrival
{
- [JsonPropertyName("tripId")]
- public required string TripId { get; set; }
+ [JsonPropertyName("tripId")] public required string TripId { get; set; }
- [JsonPropertyName("route")]
- public required RouteInfo Route { get; set; }
+ [JsonPropertyName("route")] public required RouteInfo Route { get; set; }
- [JsonPropertyName("headsign")]
- public required HeadsignInfo Headsign { get; set; }
+ [JsonPropertyName("headsign")] public required HeadsignInfo Headsign { get; set; }
- [JsonPropertyName("estimate")]
- public required ArrivalDetails Estimate { get; set; }
+ [JsonPropertyName("estimate")] public required ArrivalDetails Estimate { get; set; }
- [JsonPropertyName("delay")]
- public DelayBadge? Delay { get; set; }
+ [JsonPropertyName("delay")] public DelayBadge? Delay { get; set; }
- [JsonPropertyName("shift")]
- public ShiftBadge? Shift { get; set; }
+ [JsonPropertyName("shift")] public ShiftBadge? Shift { get; set; }
- [JsonPropertyName("shape")]
- public object? Shape { get; set; }
+ [JsonPropertyName("shape")] public object? Shape { get; set; }
- [JsonPropertyName("currentPosition")]
- public Position? CurrentPosition { get; set; }
+ [JsonPropertyName("currentPosition")] public Position? CurrentPosition { get; set; }
- [JsonPropertyName("stopShapeIndex")]
- public int? StopShapeIndex { get; set; }
+ [JsonPropertyName("stopShapeIndex")] public int? StopShapeIndex { get; set; }
- [JsonIgnore]
+ [JsonPropertyName("vehicleInformation")]
+ public VehicleBadge? VehicleInformation { get; set; }
+
+ [System.Text.Json.Serialization.JsonIgnore]
public List<string> NextStops { get; set; } = [];
- [JsonIgnore]
+ [System.Text.Json.Serialization.JsonIgnore]
public object? RawOtpTrip { get; set; }
}
public class RouteInfo
{
- [JsonPropertyName("gtfsId")]
- public required string GtfsId { get; set; }
+ [JsonPropertyName("gtfsId")] public required string GtfsId { get; set; }
public string RouteIdInGtfs => GtfsId.Split(':', 2)[1];
- [JsonPropertyName("shortName")]
- public required string ShortName { get; set; }
+ [JsonPropertyName("shortName")] public required string ShortName { get; set; }
- [JsonPropertyName("colour")]
- public required string Colour { get; set; }
+ [JsonPropertyName("colour")] public required string Colour { get; set; }
- [JsonPropertyName("textColour")]
- public required string TextColour { get; set; }
+ [JsonPropertyName("textColour")] public required string TextColour { get; set; }
}
public class HeadsignInfo
{
- [JsonPropertyName("badge")]
- public string? Badge { get; set; }
+ [JsonPropertyName("badge")] public string? Badge { get; set; }
- [JsonPropertyName("destination")]
- public required string Destination { get; set; }
+ [JsonPropertyName("destination")] public required string Destination { get; set; }
- [JsonPropertyName("marquee")]
- public string? Marquee { get; set; }
+ [JsonPropertyName("marquee")] public string? Marquee { get; set; }
}
public class ArrivalDetails
{
- [JsonPropertyName("minutes")]
- public required int Minutes { get; set; }
+ [JsonPropertyName("minutes")] public required int Minutes { get; set; }
- [JsonPropertyName("precision")]
- public ArrivalPrecision Precision { get; set; } = ArrivalPrecision.Scheduled;
+ [JsonPropertyName("precision")] public ArrivalPrecision Precision { get; set; } = ArrivalPrecision.Scheduled;
}
-[JsonConverter(typeof(JsonStringEnumConverter))]
+[System.Text.Json.Serialization.JsonConverter(typeof(JsonStringEnumConverter))]
public enum ArrivalPrecision
{
[JsonStringEnumMemberName("confident")]
Confident = 0,
- [JsonStringEnumMemberName("unsure")]
- Unsure = 1,
+ [JsonStringEnumMemberName("unsure")] Unsure = 1,
+
[JsonStringEnumMemberName("scheduled")]
Scheduled = 2,
- [JsonStringEnumMemberName("past")]
- Past = 3
+ [JsonStringEnumMemberName("past")] Past = 3
}
public class DelayBadge
{
- [JsonPropertyName("minutes")]
- public int Minutes { get; set; }
+ [JsonPropertyName("minutes")] public int Minutes { get; set; }
}
public class ShiftBadge
{
- [JsonPropertyName("shiftName")]
- public required string ShiftName { get; set; }
+ [JsonPropertyName("shiftName")] public required string ShiftName { get; set; }
+
+ [JsonPropertyName("shiftTrip")] public required string ShiftTrip { get; set; }
+}
+
+public class VehicleBadge
+{
+ [JsonPropertyName("identifier")] public required string Identifier { get; set; }
+
+ [JsonPropertyName("make")] public string? Make { get; set; }
+ [JsonPropertyName("model")] public string? Model { get; set; }
+ [JsonPropertyName("kind")] public string? Kind { get; set; }
+ [JsonPropertyName("year")] public string? Year { get; set; }
+
- [JsonPropertyName("shiftTrip")]
- public required string ShiftTrip { get; set; }
}
diff --git a/src/Enmarcha.Sources.TranviasCoruna/CorunaRealtimeEstimatesProvider.cs b/src/Enmarcha.Sources.TranviasCoruna/CorunaRealtimeEstimatesProvider.cs
index 70449ce..43debba 100644
--- a/src/Enmarcha.Sources.TranviasCoruna/CorunaRealtimeEstimatesProvider.cs
+++ b/src/Enmarcha.Sources.TranviasCoruna/CorunaRealtimeEstimatesProvider.cs
@@ -29,12 +29,13 @@ public class CorunaRealtimeEstimatesProvider
return r.Arrivals.Select(arrival =>
{
var minutes = arrival.Minutes == "<1" ? 0 : int.Parse(arrival.Minutes);
+ var metres = arrival.Metres == "--" ? 0 : int.Parse(arrival.Metres);
return new CorunaEstimate
(
r.RouteId.ToString(),
minutes,
- int.Parse(arrival.Metres),
+ metres,
arrival.VehicleNumber.ToString()
);
}).ToList();
diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts
index 95c7b6f..d9aec89 100644
--- a/src/frontend/app/api/schema.ts
+++ b/src/frontend/app/api/schema.ts
@@ -40,6 +40,14 @@ export const PositionSchema = z.object({
shapeIndex: z.number(),
});
+export const VehicleInformationSchema = z.object({
+ identifier: z.string(),
+ make: z.string().optional().nullable(),
+ model: z.string().optional().nullable(),
+ kind: z.string().optional().nullable(),
+ year: z.string().optional().nullable(),
+});
+
export const ArrivalSchema = z.object({
tripId: z.string(),
route: RouteInfoSchema,
@@ -50,6 +58,7 @@ export const ArrivalSchema = z.object({
shape: z.any().optional().nullable(),
currentPosition: PositionSchema.optional().nullable(),
stopShapeIndex: z.number().optional().nullable(),
+ vehicleInformation: VehicleInformationSchema.optional().nullable(),
});
export const StopArrivalsResponseSchema = z.object({
diff --git a/src/frontend/app/components/arrivals/ArrivalCard.tsx b/src/frontend/app/components/arrivals/ArrivalCard.tsx
index 6952f8f..f1fc1a5 100644
--- a/src/frontend/app/components/arrivals/ArrivalCard.tsx
+++ b/src/frontend/app/components/arrivals/ArrivalCard.tsx
@@ -1,4 +1,4 @@
-import { AlertTriangle, LocateIcon } from "lucide-react";
+import { AlertTriangle, BusFront, LocateIcon } from "lucide-react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import Marquee from "react-fast-marquee";
import { useTranslation } from "react-i18next";
@@ -59,7 +59,8 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
onClick,
}) => {
const { t } = useTranslation();
- const { route, headsign, estimate, delay, shift } = arrival;
+ const { route, headsign, estimate, delay, shift, vehicleInformation } =
+ arrival;
const etaValue = estimate.minutes.toString();
const etaUnit = t("estimates.minutes", "min");
@@ -81,7 +82,7 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
const chips: Array<{
label: string;
tone?: string;
- kind?: "regular" | "gps" | "delay" | "warning";
+ kind?: "regular" | "gps" | "delay" | "warning" | "vehicle";
}> = [];
// Badge/Shift info as a chip
@@ -140,7 +141,10 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
tone: "warning",
kind: "warning",
});
- } else if (estimate.precision === "confident") {
+ } else if (
+ estimate.precision === "confident" &&
+ arrival.currentPosition !== null
+ ) {
chips.push({
label: t("estimates.bus_gps_position"),
kind: "gps",
@@ -155,8 +159,27 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
});
}
+ // Vehicle information if available
+ if (vehicleInformation) {
+ let label = vehicleInformation.identifier;
+ if (vehicleInformation.make) {
+ label += ` (${vehicleInformation.make}`;
+ if (vehicleInformation.model) {
+ label += ` ${vehicleInformation.model}`;
+ }
+ if (vehicleInformation.year) {
+ label += ` - ${vehicleInformation.year}`;
+ }
+ label += `)`;
+ }
+ chips.push({
+ label,
+ kind: "vehicle",
+ });
+ }
+
return chips;
- }, [delay, shift, estimate.precision, t, headsign.badge]);
+ }, [delay, shift, estimate.precision, t, headsign.badge, vehicleInformation]);
const isClickable = !!onClick && estimate.precision !== "past";
const Tag = isClickable ? "button" : "div";
@@ -208,7 +231,7 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
</div>
</div>
- <div className="flex items-center gap-0.5 flex-wrap">
+ <div className="flex items-center gap-2 flex-wrap">
{metaChips.map((chip, idx) => {
let chipColourClasses = "";
switch (chip.tone) {
@@ -243,10 +266,13 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
className={`text-xs px-2.5 py-0.5 rounded-full flex items-center justify-center gap-1 shrink-0 font-medium tracking-wide ${chipColourClasses}`}
>
{chip.kind === "gps" && (
- <LocateIcon className="w-2.5 h-2.5 inline-block" />
+ <LocateIcon className="w-3 h-3 inline-block" />
)}
{chip.kind === "warning" && (
- <AlertTriangle className="w-2.5 h-2.5 inline-block" />
+ <AlertTriangle className="w-3 h-3 inline-block" />
+ )}
+ {chip.kind === "vehicle" && (
+ <BusFront className="w-3 h-3 inline-block" />
)}
{chip.label}
</span>
diff --git a/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx b/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx
index 2c1ea20..44c8eda 100644
--- a/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx
+++ b/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx
@@ -1,4 +1,4 @@
-import { AlertTriangle, LocateIcon } from "lucide-react";
+import { AlertTriangle, BusFront, LocateIcon } from "lucide-react";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import LineIcon from "~/components/LineIcon";
@@ -15,7 +15,8 @@ export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({
onClick,
}) => {
const { t } = useTranslation();
- const { route, headsign, estimate, delay, shift } = arrival;
+ const { route, headsign, estimate, delay, shift, vehicleInformation } =
+ arrival;
const etaValue = estimate.minutes.toString();
const etaUnit = t("estimates.minutes", "min");
@@ -37,7 +38,7 @@ export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({
const chips: Array<{
label: string;
tone?: string;
- kind?: "regular" | "gps" | "delay" | "warning";
+ kind?: "regular" | "gps" | "delay" | "warning" | "vehicle";
}> = [];
// Badge/Shift info as a chip
@@ -96,15 +97,40 @@ export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({
tone: "warning",
kind: "warning",
});
- } else if (estimate.precision === "confident") {
+ } else if (
+ estimate.precision === "confident" &&
+ arrival.currentPosition !== null
+ ) {
chips.push({
label: "", // Just the icon for reduced
kind: "gps",
});
}
+ // Vehicle information if available
+ if (vehicleInformation) {
+ let label = vehicleInformation.identifier;
+ if (vehicleInformation.make) {
+ label += ` (${vehicleInformation.make}`;
+ if (vehicleInformation.kind) {
+ const kindLabel =
+ vehicleInformation.kind.charAt(0).toUpperCase() +
+ vehicleInformation.kind.slice(1).toLowerCase();
+ label += ` ${kindLabel}.`;
+ }
+ if (vehicleInformation.year) {
+ label += ` ${vehicleInformation.year}`;
+ }
+ label += `)`;
+ }
+ chips.push({
+ label,
+ kind: "vehicle",
+ });
+ }
+
return chips;
- }, [delay, shift, estimate.precision, headsign.badge]);
+ }, [delay, shift, estimate.precision, headsign.badge, vehicleInformation]);
const isClickable = !!onClick && estimate.precision !== "past";
const Tag = isClickable ? "button" : "div";
@@ -173,6 +199,9 @@ export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({
{chip.kind === "warning" && (
<AlertTriangle className="w-3 h-3 my-0.5 inline-block" />
)}
+ {chip.kind === "vehicle" && (
+ <BusFront className="w-3 h-3 my-0.5 inline-block" />
+ )}
{chip.label}
</span>
);