diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2026-04-20 13:41:35 +0200 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2026-04-20 13:41:35 +0200 |
| commit | f2a37bc6366beccce247f834adee752b8e6323ae (patch) | |
| tree | bb893100ebb8a9ca6f057410adf83e293cfa1123 | |
| parent | 23b6fb227a1b840a36864bdebc4de89c19fd6463 (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.cs | 19 | ||||
| -rw-r--r-- | src/Enmarcha.Backend/Types/Arrivals/Arrival.cs | 6 | ||||
| -rw-r--r-- | src/frontend/app/api/schema.ts | 8 | ||||
| -rw-r--r-- | src/frontend/app/components/arrivals/ArrivalCard.tsx | 43 | ||||
| -rw-r--r-- | src/frontend/app/components/arrivals/ReducedArrivalCard.tsx | 30 |
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; |
