From d71f0ed16d175285f2e8cbde6091994c2aa1d962 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Mon, 9 Mar 2026 00:00:39 +0100 Subject: Enhance route details handling and add favorites functionality; improve error logging and response structure --- src/frontend/app/routes/routes.tsx | 222 ++++++++++++++++++++++++++++--------- 1 file changed, 168 insertions(+), 54 deletions(-) (limited to 'src/frontend/app/routes/routes.tsx') diff --git a/src/frontend/app/routes/routes.tsx b/src/frontend/app/routes/routes.tsx index b33fe58..128bbc4 100644 --- a/src/frontend/app/routes/routes.tsx +++ b/src/frontend/app/routes/routes.tsx @@ -1,16 +1,30 @@ import { useQuery } from "@tanstack/react-query"; -import { useState } from "react"; +import { ChevronDown, ChevronRight, Star } from "lucide-react"; +import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router"; import { fetchRoutes } from "~/api/transit"; import RouteIcon from "~/components/RouteIcon"; import { usePageTitle } from "~/contexts/PageTitleContext"; +import { useFavorites } from "~/hooks/useFavorites"; import "../tailwind-full.css"; export default function RoutesPage() { const { t } = useTranslation(); usePageTitle(t("navbar.routes", "Rutas")); const [searchQuery, setSearchQuery] = useState(""); + const { toggleFavorite: toggleFavoriteRoute, isFavorite: isFavoriteRoute } = + useFavorites("favouriteRoutes"); + const { toggleFavorite: toggleFavoriteAgency, isFavorite: isFavoriteAgency } = + useFavorites("favouriteAgencies"); + + const [expandedAgencies, setExpandedAgencies] = useState< + Record + >({}); + + const toggleAgencyExpanded = (agency: string) => { + setExpandedAgencies((prev) => ({ ...prev, [agency]: !prev[agency] })); + }; const orderedAgencies = [ "vitrasa", @@ -26,21 +40,50 @@ export default function RoutesPage() { queryFn: () => fetchRoutes(orderedAgencies), }); - const filteredRoutes = routes?.filter( - (route) => - route.shortName?.toLowerCase().includes(searchQuery.toLowerCase()) || - route.longName?.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const filteredRoutes = useMemo(() => { + return routes?.filter( + (route) => + route.shortName?.toLowerCase().includes(searchQuery.toLowerCase()) || + route.longName?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [routes, searchQuery]); - const routesByAgency = filteredRoutes?.reduce( - (acc, route) => { - const agency = route.agencyName || t("routes.unknown_agency", "Otros"); - if (!acc[agency]) acc[agency] = []; - acc[agency].push(route); - return acc; - }, - {} as Record - ); + const routesByAgency = useMemo(() => { + return filteredRoutes?.reduce( + (acc, route) => { + const agency = route.agencyName || t("routes.unknown_agency", "Otros"); + if (!acc[agency]) acc[agency] = []; + acc[agency].push(route); + return acc; + }, + {} as Record + ); + }, [filteredRoutes, t]); + + const sortedAgencyEntries = useMemo(() => { + if (!routesByAgency) return []; + return Object.entries(routesByAgency).sort(([a], [b]) => { + // First, sort by favorite status + const isFavA = isFavoriteAgency(a); + const isFavB = isFavoriteAgency(b); + if (isFavA && !isFavB) return -1; + if (!isFavA && isFavB) return 1; + + // Then by fixed order + const indexA = orderedAgencies.indexOf(a.toLowerCase()); + const indexB = orderedAgencies.indexOf(b.toLowerCase()); + if (indexA === -1 && indexB === -1) { + return a.localeCompare(b); + } + if (indexA === -1) return 1; + if (indexB === -1) return -1; + return indexA - indexB; + }); + }, [routesByAgency, orderedAgencies, isFavoriteAgency]); + + const favoriteRoutes = useMemo(() => { + return filteredRoutes?.filter((route) => isFavoriteRoute(route.id)) || []; + }, [filteredRoutes, isFavoriteRoute]); return (
@@ -48,7 +91,7 @@ export default function RoutesPage() { setSearchQuery(e.target.value)} /> @@ -60,47 +103,118 @@ export default function RoutesPage() {
)} -
- {routesByAgency && - Object.entries(routesByAgency) - .sort(([a], [b]) => { - const indexA = orderedAgencies.indexOf(a.toLowerCase()); - const indexB = orderedAgencies.indexOf(b.toLowerCase()); - if (indexA === -1 && indexB === -1) { - return a.localeCompare(b); - } - if (indexA === -1) return 1; - if (indexB === -1) return -1; - return indexA - indexB; - }) - .map(([agency, agencyRoutes]) => ( -
-

- {agency} -

-
+
+ {favoriteRoutes.length > 0 && !searchQuery && ( +
+

+ + {t("routes.favorites", "Favoritas")} +

+
+ {favoriteRoutes.map((route) => ( +
+ + +
+

+ {route.longName} +

+
+ +
+ ))} +
+
+ )} + + {sortedAgencyEntries.map(([agency, agencyRoutes]) => { + const isFav = isFavoriteAgency(agency); + const isExpanded = searchQuery + ? true + : (expandedAgencies[agency] ?? false); + + return ( +
+
+ + +
+ + {isExpanded && ( +
{agencyRoutes.map((route) => ( - - -
-

- {route.longName} -

-
- +
+ + +
+

+ {route.longName} +

+
+ +
))}
-
- ))} + )} +
+ ); + })}
); -- cgit v1.3