aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2026-04-20 13:41:35 +0200
committerAriel Costas Guerrero <ariel@costas.dev>2026-04-20 13:41:35 +0200
commitf2a37bc6366beccce247f834adee752b8e6323ae (patch)
treebb893100ebb8a9ca6f057410adf83e293cfa1123
parent23b6fb227a1b840a36864bdebc4de89c19fd6463 (diff)
Distinguir pasos de subida y salida (inicio trayecto), y pasos de bajada de llegada (final trayecto)
Puede ser importante distinguir entre un viaje que solo permite bajar en un punto, y un viaje que finaliza ahí, ya que puede tener sentido ocultar los viajes que terminan en algunos feeds. Por otra parte, es útil saber que ciertos viajes salen de ese sitio, frente a ser pasantes de subida, porque el vehículo suele estar con antelación
-rw-r--r--src/Enmarcha.Backend/Controllers/ArrivalsController.cs19
-rw-r--r--src/Enmarcha.Backend/Types/Arrivals/Arrival.cs6
-rw-r--r--src/frontend/app/api/schema.ts8
-rw-r--r--src/frontend/app/components/arrivals/ArrivalCard.tsx43
-rw-r--r--src/frontend/app/components/arrivals/ReducedArrivalCard.tsx30
5 files changed, 85 insertions, 21 deletions
diff --git a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs
index 7feeee0..5608723 100644
--- a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs
+++ b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs
@@ -140,8 +140,23 @@ public partial class ArrivalsController : ControllerBase
return Ok(new StopEstimatesResponse { Arrivals = estimates });
}
- private static VehicleOperation GetVehicleOperation(ArrivalsAtStopResponse.PickupType pickup, ArrivalsAtStopResponse.PickupType dropoff)
+ private static VehicleOperation GetVehicleOperation(
+ ArrivalsAtStopResponse.Arrival item
+ )
{
+ var pickup = item.PickupTypeParsed;
+ var dropoff = item.DropoffTypeParsed;
+
+ if (item.StopPosition == 0)
+ {
+ return VehicleOperation.Departure;
+ }
+
+ if (item.StopPosition == item.Trip.Stoptimes.Count - 1)
+ {
+ return VehicleOperation.Arrival;
+ }
+
if (pickup == ArrivalsAtStopResponse.PickupType.None && dropoff == ArrivalsAtStopResponse.PickupType.None) return VehicleOperation.PickupDropoff;
if (pickup != ArrivalsAtStopResponse.PickupType.None && dropoff != ArrivalsAtStopResponse.PickupType.None) return VehicleOperation.PickupDropoff;
if (pickup != ArrivalsAtStopResponse.PickupType.None) return VehicleOperation.PickupOnly;
@@ -215,7 +230,7 @@ public partial class ArrivalsController : ControllerBase
},
Operator = feedId == "xunta" ? item.Trip.Route.Agency?.Name : null,
RawOtpTrip = item,
- Operation = GetVehicleOperation(item.PickupTypeParsed, item.DropoffTypeParsed)
+ Operation = GetVehicleOperation(item)
});
}
diff --git a/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs b/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
index 81811c2..0e74a44 100644
--- a/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
+++ b/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
@@ -46,7 +46,11 @@ public enum VehicleOperation
[JsonStringEnumMemberName("pickup_only")]
PickupOnly = 1,
[JsonStringEnumMemberName("dropoff_only")]
- DropoffOnly = 2
+ DropoffOnly = 2,
+ [JsonStringEnumMemberName("departure")]
+ Departure = 3,
+ [JsonStringEnumMemberName("arrival")]
+ Arrival = 4
}
public class RouteInfo
diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts
index 64a9e94..71eeae9 100644
--- a/src/frontend/app/api/schema.ts
+++ b/src/frontend/app/api/schema.ts
@@ -66,7 +66,13 @@ export const ArrivalSchema = z.object({
currentPosition: PositionSchema.optional().nullable(),
vehicleInformation: VehicleInformationSchema.optional().nullable(),
operator: z.string().nullable(),
- operation: z.enum(["pickup_dropoff", "pickup_only", "dropoff_only"]),
+ operation: z.enum([
+ "pickup_dropoff",
+ "pickup_only",
+ "dropoff_only",
+ "departure",
+ "arrival",
+ ]),
});
export const ArrivalEstimateSchema = z.object({
diff --git a/src/frontend/app/components/arrivals/ArrivalCard.tsx b/src/frontend/app/components/arrivals/ArrivalCard.tsx
index 827599e..51e0803 100644
--- a/src/frontend/app/components/arrivals/ArrivalCard.tsx
+++ b/src/frontend/app/components/arrivals/ArrivalCard.tsx
@@ -5,13 +5,18 @@ import {
BusFront,
LocateIcon,
Navigation,
+ SquareArrowRightEnter,
+ SquareArrowRightExit,
} from "lucide-react";
import React, { useEffect, useMemo, useRef, useState } from "react";
-import Marquee from "react-fast-marquee";
+import _MarqueeImport from "react-fast-marquee";
import { useTranslation } from "react-i18next";
import RouteIcon from "~/components/RouteIcon";
import { type Arrival } from "../../api/schema";
import "./ArrivalCard.css";
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const Marquee: typeof _MarqueeImport =
+ (_MarqueeImport as any).default ?? _MarqueeImport;
interface ArrivalCardProps {
arrival: Arrival;
@@ -96,6 +101,22 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
}
}, [estimate.precision]);
+ const OPERATION_LABELS: Record<string, string> = {
+ pickup_only: t("journey.pickup_only", "Solo subida"),
+ dropoff_only: t("journey.dropoff_only", "Solo bajada"),
+ departure: t("journey.departure", "Salida"),
+ arrival: t("journey.arrival", "Llegada"),
+ };
+
+ type OperationKind = "pickup" | "dropoff" | "departure" | "arrival";
+
+ const OPERATION_KINDS: Record<string, OperationKind> = {
+ pickup_only: "pickup",
+ dropoff_only: "dropoff",
+ departure: "departure",
+ arrival: "arrival",
+ };
+
const metaChips = useMemo(() => {
const chips: Array<{
label: string;
@@ -106,18 +127,14 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
| "delay"
| "warning"
| "vehicle"
- | "pickup"
- | "dropoff";
+ | OperationKind;
}> = [];
if (operation !== "pickup_dropoff") {
chips.push({
- label:
- operation === "pickup_only"
- ? t("journey.pickup_only", "Solo subida")
- : t("journey.dropoff_only", "Solo bajada"),
- tone: operation === "pickup_only" ? "pickup" : "dropoff",
- kind: operation === "pickup_only" ? "pickup" : "dropoff",
+ label: OPERATION_LABELS[operation] || operation,
+ tone: OPERATION_KINDS[operation] || "regular",
+ kind: OPERATION_KINDS[operation] || "regular",
});
}
@@ -258,10 +275,12 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
let chipColourClasses = "";
switch (chip.tone) {
case "pickup":
+ case "departure":
chipColourClasses =
"bg-green-600/10 dark:bg-green-600/20 text-green-700 dark:text-green-300";
break;
case "dropoff":
+ case "arrival":
chipColourClasses =
"bg-orange-400/10 dark:bg-orange-600/20 text-orange-700 dark:text-orange-300";
break;
@@ -310,6 +329,12 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
{chip.kind === "dropoff" && (
<ArrowDownRightSquare className="w-3 h-3 inline-block" />
)}
+ {chip.kind === "departure" && (
+ <SquareArrowRightExit className="w-3 h-3 inline-block" />
+ )}
+ {chip.kind === "arrival" && (
+ <SquareArrowRightEnter 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 6046ffc..6d6bf85 100644
--- a/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx
+++ b/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx
@@ -47,6 +47,22 @@ export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({
}
}, [estimate.precision]);
+ const OPERATION_LABELS: Record<string, string> = {
+ pickup_only: t("journey.pickup_only", "Solo subida"),
+ dropoff_only: t("journey.dropoff_only", "Solo bajada"),
+ departure: t("journey.departure", "Salida"),
+ arrival: t("journey.arrival", "Llegada"),
+ };
+
+ type OperationKind = "pickup" | "dropoff" | "departure" | "arrival";
+
+ const OPERATION_KINDS: Record<string, OperationKind> = {
+ pickup_only: "pickup",
+ dropoff_only: "dropoff",
+ departure: "departure",
+ arrival: "arrival",
+ };
+
const metaChips = useMemo(() => {
const chips: Array<{
label: string;
@@ -57,18 +73,14 @@ export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({
| "delay"
| "warning"
| "vehicle"
- | "pickup"
- | "dropoff";
+ | OperationKind;
}> = [];
if (operation !== "pickup_dropoff") {
chips.push({
- label:
- operation === "pickup_only"
- ? t("journey.pickup_only", "Solo subida")
- : t("journey.dropoff_only", "Solo bajada"),
- tone: operation === "pickup_only" ? "pickup" : "dropoff",
- kind: operation === "pickup_only" ? "pickup" : "dropoff",
+ label: OPERATION_LABELS[operation] || operation,
+ tone: OPERATION_KINDS[operation] || "regular",
+ kind: OPERATION_KINDS[operation] || "regular",
});
}
@@ -210,10 +222,12 @@ export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({
let chipColourClasses = "";
switch (chip.tone) {
case "pickup":
+ case "departure":
chipColourClasses =
"bg-green-600/10 dark:bg-green-600/20 text-green-700 dark:text-green-300";
break;
case "dropoff":
+ case "arrival":
chipColourClasses =
"bg-orange-400/10 dark:bg-orange-600/20 text-orange-700 dark:text-orange-300";
break;