aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs51
-rw-r--r--src/Enmarcha.Backend/Types/Arrivals/Arrival.cs5
-rw-r--r--src/frontend/app/api/schema.ts1
-rw-r--r--src/frontend/app/components/arrivals/ArrivalCard.tsx16
4 files changed, 71 insertions, 2 deletions
diff --git a/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs b/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs
index 4c0b8ac..0f86be5 100644
--- a/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs
@@ -43,7 +43,31 @@ public class NextStopsProcessor : IArrivalsProcessor
.ToList();
}
+ // Remove last stop since it doesn't make sense to show "via" for the terminus
+ arrival.NextStops = arrival.NextStops.Take(arrival.NextStops.Count - 1).ToList();
+
+ if (feedId == "xunta")
+ {
+ arrival.OriginStops = otpArrival.Trip.Stoptimes
+ .Where(s => s.ScheduledDeparture < currentStopDeparture)
+ .OrderBy(s => s.ScheduledDeparture)
+ .Take(1)
+ .Select(s => $"{s.Stop.Name} -- {s.Stop.Description}")
+ .Distinct()
+ .ToList();
+ }
+ else if (feedId == "renfe")
+ {
+ arrival.OriginStops = otpArrival.Trip.Stoptimes
+ .Where(s => s.ScheduledDeparture < currentStopDeparture)
+ .OrderBy(s => s.ScheduledDeparture)
+ .Take(1)
+ .Select(s => FeedService.NormalizeStopName(feedId, s.Stop.Name))
+ .ToList();
+ }
+
arrival.Headsign.Marquee = GenerateMarquee(feedId, arrival.NextStops);
+ arrival.Headsign.Origin = GenerateOrigin(feedId, arrival.OriginStops);
}
return Task.CompletedTask;
@@ -122,6 +146,33 @@ public class NextStopsProcessor : IArrivalsProcessor
};
}
+ private static string? GenerateOrigin(string feedId, List<string> originStops)
+ {
+ if (originStops.Count == 0) return null;
+
+ if (feedId == "xunta")
+ {
+ var points = originStops.Select(SplitXuntaStopDescription).ToList();
+ var concellos = points
+ .Select(p => p.concello)
+ .Where(c => !string.IsNullOrWhiteSpace(c))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ return concellos.Count > 0 ? string.Join(" > ", concellos) : null;
+ }
+
+ if (feedId == "renfe")
+ {
+ // For trains just show the origin terminus
+ return originStops.First();
+ }
+
+ // For local bus feeds, origin is generally not very informative,
+ // but return the first stop for completeness
+ return originStops.First();
+ }
+
private static (string nombre, string parroquia, string concello) SplitXuntaStopDescription(string stopName)
{
var parts = stopName.Split(" -- ", 3);
diff --git a/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs b/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
index 0e74a44..3272a05 100644
--- a/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
+++ b/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
@@ -34,6 +34,9 @@ public class Arrival
public List<string> NextStops { get; set; } = [];
[JsonIgnore]
+ public List<string> OriginStops { get; set; } = [];
+
+ [JsonIgnore]
public ArrivalsAtStopResponse.Arrival? RawOtpTrip { get; set; }
[JsonIgnore] public bool Delete { get; set; }
@@ -73,6 +76,8 @@ public class HeadsignInfo
[JsonPropertyName("destination")] public required string Destination { get; set; }
[JsonPropertyName("marquee")] public string? Marquee { get; set; }
+
+ [JsonPropertyName("origin")] public string? Origin { get; set; }
}
public class ArrivalDetails
diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts
index 864ea57..d900055 100644
--- a/src/frontend/app/api/schema.ts
+++ b/src/frontend/app/api/schema.ts
@@ -11,6 +11,7 @@ export const HeadsignInfoSchema = z.object({
badge: z.string().optional().nullable(),
destination: z.string().nullable(),
marquee: z.string().optional().nullable(),
+ origin: z.string().optional().nullable(),
});
export const ArrivalPrecisionSchema = z.enum([
diff --git a/src/frontend/app/components/arrivals/ArrivalCard.tsx b/src/frontend/app/components/arrivals/ArrivalCard.tsx
index 51e0803..3aae65e 100644
--- a/src/frontend/app/components/arrivals/ArrivalCard.tsx
+++ b/src/frontend/app/components/arrivals/ArrivalCard.tsx
@@ -34,7 +34,7 @@ const AutoMarquee = ({ text }: { text: string }) => {
if (!el) return;
const checkScroll = () => {
- const charWidth = 8;
+ const charWidth = 12;
const availableWidth = el.offsetWidth;
const textWidth = text.length * charWidth;
setShouldScroll(textWidth > availableWidth);
@@ -252,10 +252,22 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
{operator && (
<span className="text-xs font-mono text-slate-700 dark:text-slate-200 font-medium shrink-0">
{operator}
+ {headsign.destination || headsign.origin ? (
+ <>&nbsp;·&nbsp;</>
+ ) : (
+ ""
+ )}
+ </span>
+ )}
+ {headsign.origin && (
+ <span className="text-xs font-mono text-slate-500 dark:text-slate-400 shrink-0">
+ Proc: {headsign.origin}{" "}
{headsign.marquee && <>&nbsp;·&nbsp;</>}
</span>
)}
- {headsign.marquee && <AutoMarquee text={headsign.marquee} />}
+ {headsign.marquee && (
+ <AutoMarquee text={"Via: " + headsign.marquee} />
+ )}
</div>
</div>
</div>