aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/routes/routes.tsx
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-12-28 22:24:26 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2025-12-28 22:25:01 +0100
commit48ec0aae80a200d7eb50639ff4c4ca8ae564f29b (patch)
tree8cf2a2a02a49d8295985d90679c33c5bc8375818 /src/frontend/app/routes/routes.tsx
parentb2ddc0ef449ccbe7f0d33e539ccdfc1baef04e2c (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.tsx76
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>
+ );
+}