diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-28 22:24:26 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-28 22:25:01 +0100 |
| commit | 48ec0aae80a200d7eb50639ff4c4ca8ae564f29b (patch) | |
| tree | 8cf2a2a02a49d8295985d90679c33c5bc8375818 /src/frontend/app/routes/routes.tsx | |
| parent | b2ddc0ef449ccbe7f0d33e539ccdfc1baef04e2c (diff) | |
Implement displaying routes with dynamic data from OTP
Diffstat (limited to 'src/frontend/app/routes/routes.tsx')
| -rw-r--r-- | src/frontend/app/routes/routes.tsx | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/src/frontend/app/routes/routes.tsx b/src/frontend/app/routes/routes.tsx new file mode 100644 index 0000000..2c11168 --- /dev/null +++ b/src/frontend/app/routes/routes.tsx @@ -0,0 +1,76 @@ +import { useQuery } from "@tanstack/react-query"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router"; +import { fetchRoutes } from "~/api/transit"; +import LineIcon from "~/components/LineIcon"; +import { usePageTitle } from "~/contexts/PageTitleContext"; +import "../tailwind-full.css"; + +export default function RoutesPage() { + const { t } = useTranslation(); + usePageTitle(t("navbar.routes", "Rutas")); + + const { data: routes, isLoading } = useQuery({ + queryKey: ["routes"], + queryFn: () => fetchRoutes(["santiago", "vitrasa", "coruna", "feve"]), + }); + + const routesByAgency = routes?.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<string, typeof routes> + ); + + return ( + <div className="container mx-auto px-4 py-6"> + <p className="mb-6 text-gray-700 dark:text-gray-300"> + {t( + "routes.description", + "A continuación se muestra una lista de las rutas de autobús urbano con sus respectivos trayectos." + )} + </p> + + {isLoading && ( + <div className="flex justify-center py-12"> + <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div> + </div> + )} + + <div className="space-y-8"> + {routesByAgency && + Object.entries(routesByAgency).map(([agency, agencyRoutes]) => ( + <div key={agency}> + <h2 className="text-xl font-bold text-text mb-4 border-b border-border pb-2"> + {agency} + </h2> + <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> + {agencyRoutes.map((route) => ( + <Link + key={route.id} + to={`/routes/${route.id}`} + className="flex items-center gap-3 p-4 bg-surface rounded-lg shadow hover:shadow-lg transition-shadow border border-border" + > + <LineIcon + line={route.shortName ?? "?"} + mode="pill" + colour={route.color ?? undefined} + textColour={route.textColor ?? undefined} + /> + <div className="flex-1 min-w-0"> + <p className="text-sm md:text-md font-semibold text-text"> + {route.longName} + </p> + </div> + </Link> + ))} + </div> + </div> + ))} + </div> + </div> + ); +} |
