aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/routes/routes-$id.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app/routes/routes-$id.tsx')
-rw-r--r--src/frontend/app/routes/routes-$id.tsx187
1 files changed, 99 insertions, 88 deletions
diff --git a/src/frontend/app/routes/routes-$id.tsx b/src/frontend/app/routes/routes-$id.tsx
index 2174244..bccaf56 100644
--- a/src/frontend/app/routes/routes-$id.tsx
+++ b/src/frontend/app/routes/routes-$id.tsx
@@ -19,7 +19,7 @@ import {
Source,
type MapRef,
} from "react-map-gl/maplibre";
-import { Link, useParams } from "react-router";
+import { Link, useLocation, useNavigate, useParams } from "react-router";
import { fetchRouteDetails } from "~/api/transit";
import { AppMap } from "~/components/shared/AppMap";
import {
@@ -28,7 +28,7 @@ import {
usePageTitle,
usePageTitleNode,
} from "~/contexts/PageTitleContext";
-import { useStopArrivals } from "~/hooks/useArrivals";
+import { useStopEstimates } from "~/hooks/useArrivals";
import { useFavorites } from "~/hooks/useFavorites";
import { formatHex } from "~/utils/colours";
import "../tailwind-full.css";
@@ -59,9 +59,9 @@ function FavoriteStar({ id }: { id?: string }) {
export default function RouteDetailsPage() {
const { id } = useParams();
const { t, i18n } = useTranslation();
- const [selectedPatternId, setSelectedPatternId] = useState<string | null>(
- null
- );
+ const navigate = useNavigate();
+ const location = useLocation();
+ const selectedPatternId = location.hash ? location.hash.slice(1) : null;
const [selectedStopId, setSelectedStopId] = useState<string | null>(null);
const [layoutMode, setLayoutMode] = useState<"balanced" | "map" | "list">(
"balanced"
@@ -103,34 +103,13 @@ export default function RouteDetailsPage() {
queryFn: () => fetchRouteDetails(id!, selectedDateKey),
enabled: !!id,
});
- const { data: selectedStopRealtime, isLoading: isRealtimeLoading } =
- useStopArrivals(
+ const { data: selectedStopEstimates, isLoading: isRealtimeLoading } =
+ useStopEstimates(
selectedStopId ?? "",
- true,
- Boolean(selectedStopId) && isTodaySelectedDate
+ id ?? "",
+ undefined,
+ Boolean(selectedStopId) && Boolean(id) && isTodaySelectedDate
);
- const filteredRealtimeArrivals = useMemo(() => {
- const arrivals = selectedStopRealtime?.arrivals ?? [];
- if (arrivals.length === 0) {
- return [];
- }
-
- const routeId = id?.trim();
- const routeShortName = route?.shortName?.trim().toLowerCase();
-
- return arrivals.filter((arrival) => {
- const arrivalGtfsId = arrival.route.gtfsId?.trim();
- if (routeId && arrivalGtfsId) {
- return arrivalGtfsId === routeId;
- }
-
- if (routeShortName) {
- return arrival.route.shortName.trim().toLowerCase() === routeShortName;
- }
-
- return true;
- });
- }, [selectedStopRealtime?.arrivals, id, route?.shortName]);
usePageTitle(
route?.shortName
@@ -589,7 +568,10 @@ export default function RouteDetailsPage() {
key={pattern.id}
type="button"
onClick={() => {
- setSelectedPatternId(pattern.id);
+ navigate(
+ { hash: "#" + pattern.id },
+ { replace: true }
+ );
setSelectedStopId(null);
setIsPatternPickerOpen(false);
}}
@@ -748,33 +730,31 @@ export default function RouteDetailsPage() {
{selectedStopId === stop.id &&
(departuresByStop.get(stop.id)?.length ?? 0) > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
- {(
- departuresByStop
- .get(stop.id)
- ?.filter((item) =>
- isTodaySelectedDate
- ? item.departure >=
- nowSeconds - ONE_HOUR_SECONDS
- : true
- ) ?? []
- ).map((item, i) => (
- <span
- key={`${item.patternId}-${item.departure}-${i}`}
- className={`text-[11px] px-2 py-0.5 rounded ${
- item.patternId === selectedPattern?.id
- ? "bg-gray-100 dark:bg-gray-900"
- : "bg-gray-50 dark:bg-gray-900 text-gray-400 font-light"
- }`}
- >
- {Math.floor(item.departure / 3600)
- .toString()
- .padStart(2, "0")}
- :
- {Math.floor((item.departure % 3600) / 60)
- .toString()
- .padStart(2, "0")}
- </span>
- ))}
+ {(departuresByStop.get(stop.id) ?? []).map(
+ (item, i) => {
+ const isPast =
+ isTodaySelectedDate &&
+ item.departure < nowSeconds;
+ return (
+ <span
+ key={`${item.patternId}-${item.departure}-${i}`}
+ className={`text-[11px] px-2 py-0.5 rounded ${
+ item.patternId === selectedPattern?.id
+ ? "bg-gray-100 dark:bg-gray-900"
+ : "bg-gray-50 dark:bg-gray-900 text-gray-400 font-light"
+ } ${isPast ? "line-through opacity-50" : ""}`}
+ >
+ {Math.floor(item.departure / 3600)
+ .toString()
+ .padStart(2, "0")}
+ :
+ {Math.floor((item.departure % 3600) / 60)
+ .toString()
+ .padStart(2, "0")}
+ </span>
+ );
+ }
+ )}
</div>
)}
@@ -787,7 +767,8 @@ export default function RouteDetailsPage() {
<div className="text-[11px] text-muted">
{t("routes.loading_realtime", "Cargando...")}
</div>
- ) : filteredRealtimeArrivals.length === 0 ? (
+ ) : (selectedStopEstimates?.arrivals.length ?? 0) ===
+ 0 ? (
<div className="text-[11px] text-muted">
{t(
"routes.realtime_no_route_estimates",
@@ -796,37 +777,67 @@ export default function RouteDetailsPage() {
</div>
) : (
<>
- <div className="flex items-center justify-between gap-2 rounded-lg border border-green-500/30 bg-green-500/10 px-2.5 py-2">
- <span className="text-[11px] font-semibold uppercase tracking-wide text-green-700 dark:text-green-300">
- {t("routes.next_arrival", "Próximo")}
- </span>
- <span className="inline-flex min-w-16 items-center justify-center rounded-xl bg-green-600 px-3 py-1.5 text-base font-bold leading-none text-white">
- {filteredRealtimeArrivals[0].estimate.minutes}′
- {filteredRealtimeArrivals[0].delay?.minutes
- ? formatDelayMinutes(
- filteredRealtimeArrivals[0].delay.minutes
- )
- : ""}
- </span>
- </div>
+ {(() => {
+ const firstArrival =
+ selectedStopEstimates!.arrivals[0];
+ const isFirstSelectedPattern =
+ firstArrival.patternId === selectedPattern?.id;
+ return (
+ <div
+ className={`flex items-center justify-between gap-2 rounded-lg border px-2.5 py-2 ${isFirstSelectedPattern ? "border-green-500/30 bg-green-500/10" : "border-emerald-500/20 bg-emerald-500/5 opacity-50"}`}
+ >
+ <span
+ className={`text-[11px] font-semibold uppercase tracking-wide ${isFirstSelectedPattern ? "text-green-700 dark:text-green-300" : "text-emerald-700 dark:text-emerald-400"}`}
+ >
+ {t("routes.next_arrival", "Próximo")}
+ </span>
+ <span
+ className={`inline-flex min-w-16 items-center justify-center rounded-xl px-3 py-1.5 text-base font-bold leading-none text-white ${isFirstSelectedPattern ? "bg-green-600" : "bg-emerald-600"}`}
+ >
+ {firstArrival.estimate.minutes}′
+ {firstArrival.delay?.minutes
+ ? formatDelayMinutes(
+ firstArrival.delay.minutes
+ )
+ : ""}
+ </span>
+ </div>
+ );
+ })()}
- {filteredRealtimeArrivals.length > 1 && (
+ {selectedStopEstimates!.arrivals.length > 1 && (
<div className="mt-2 flex flex-wrap justify-end gap-1">
- {filteredRealtimeArrivals
+ {selectedStopEstimates!.arrivals
.slice(1)
- .map((arrival, i) => (
- <span
- key={`${arrival.tripId}-${i}`}
- className="text-[11px] px-2 py-0.5 bg-primary/10 text-primary rounded"
- >
- {arrival.estimate.minutes}′
- {arrival.delay?.minutes
- ? formatDelayMinutes(
- arrival.delay.minutes
- )
- : ""}
- </span>
- ))}
+ .map((arrival, i) => {
+ const isSelectedPattern =
+ arrival.patternId === selectedPattern?.id;
+ return (
+ <span
+ key={`${arrival.tripId}-${i}`}
+ className={`text-[11px] px-2 py-0.5 rounded ${
+ isSelectedPattern
+ ? "bg-gray-100 dark:bg-gray-900"
+ : "bg-gray-50 dark:bg-gray-900 text-gray-400 font-light"
+ }`}
+ title={
+ isSelectedPattern
+ ? undefined
+ : t(
+ "routes.other_pattern",
+ "Otro trayecto"
+ )
+ }
+ >
+ {arrival.estimate.minutes}′
+ {arrival.delay?.minutes
+ ? formatDelayMinutes(
+ arrival.delay.minutes
+ )
+ : ""}
+ </span>
+ );
+ })}
</div>
)}
</>