aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/routes
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app/routes')
-rw-r--r--src/frontend/app/routes/about.tsx23
-rw-r--r--src/frontend/app/routes/home.tsx36
-rw-r--r--src/frontend/app/routes/lines.tsx57
-rw-r--r--src/frontend/app/routes/map.tsx62
-rw-r--r--src/frontend/app/routes/settings.tsx34
-rw-r--r--src/frontend/app/routes/stops-$id.tsx44
6 files changed, 164 insertions, 92 deletions
diff --git a/src/frontend/app/routes/about.tsx b/src/frontend/app/routes/about.tsx
index 1354e8f..5158330 100644
--- a/src/frontend/app/routes/about.tsx
+++ b/src/frontend/app/routes/about.tsx
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
import { usePageTitle } from "~/contexts/PageTitleContext";
-import '../tailwind-full.css';
+import "../tailwind-full.css";
export default function About() {
const { t } = useTranslation();
@@ -37,7 +37,9 @@ export default function About() {
<strong className="text-[--text-color] min-w-fit">
{t("about.data_realtime")}:
</strong>
- <span className="opacity-80">{t("about.data_realtime_source")}</span>
+ <span className="opacity-80">
+ {t("about.data_realtime_source")}
+ </span>
</li>
<li className="flex flex-col sm:flex-row sm:items-start gap-1">
<strong className="text-[--text-color] min-w-fit">
@@ -89,9 +91,7 @@ export default function About() {
</div>
<div className="mt-6 p-4 bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800 rounded-lg">
- <p className="text-sm leading-relaxed">
- {t("about.thanks_council")}
- </p>
+ <p className="text-sm leading-relaxed">{t("about.thanks_council")}</p>
</div>
</section>
@@ -119,8 +119,17 @@ export default function About() {
rel="nofollow noreferrer noopener"
target="_blank"
>
- <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
- <path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
+ <svg
+ className="w-5 h-5"
+ fill="currentColor"
+ viewBox="0 0 24 24"
+ aria-hidden="true"
+ >
+ <path
+ fillRule="evenodd"
+ d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
+ clipRule="evenodd"
+ />
</svg>
GitHub
</a>
diff --git a/src/frontend/app/routes/home.tsx b/src/frontend/app/routes/home.tsx
index f97fdf7..7c13da6 100644
--- a/src/frontend/app/routes/home.tsx
+++ b/src/frontend/app/routes/home.tsx
@@ -217,7 +217,9 @@ export default function StopList() {
if (isNumericSearch) {
// Direct match for stop codes
const stopId = searchQuery.trim();
- const exactMatch = data.filter((stop) => stop.stopId === stopId || stop.stopId.endsWith(`:${stopId}`));
+ const exactMatch = data.filter(
+ (stop) => stop.stopId === stopId || stop.stopId.endsWith(`:${stopId}`)
+ );
if (exactMatch.length > 0) {
items = exactMatch;
} else {
@@ -281,7 +283,9 @@ export default function StopList() {
{/* Favourites Gallery */}
{!loading && (
<StopGallery
- stops={favouriteStops.sort((a, b) => a.stopId.localeCompare(b.stopId))}
+ stops={favouriteStops.sort((a, b) =>
+ a.stopId.localeCompare(b.stopId)
+ )}
title={t("stoplist.favourites")}
emptyMessage={t("stoplist.no_favourites")}
/>
@@ -301,9 +305,24 @@ export default function StopList() {
<div className="w-full px-4 flex flex-col gap-2">
<div className="flex items-center gap-2">
{userLocation && (
- <svg className="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
+ <svg
+ className="w-5 h-5 text-blue-600 dark:text-blue-400"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth={2}
+ d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
+ />
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth={2}
+ d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
+ />
</svg>
)}
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
@@ -322,9 +341,10 @@ export default function StopList() {
</>
)}
{!loading && data
- ? (userLocation ? sortedAllStops.slice(0, 6) : sortedAllStops).map(
- (stop) => <StopItem key={stop.stopId} stop={stop} />
- )
+ ? (userLocation
+ ? sortedAllStops.slice(0, 6)
+ : sortedAllStops
+ ).map((stop) => <StopItem key={stop.stopId} stop={stop} />)
: null}
</ul>
</div>
diff --git a/src/frontend/app/routes/lines.tsx b/src/frontend/app/routes/lines.tsx
index 658716f..acf8a7f 100644
--- a/src/frontend/app/routes/lines.tsx
+++ b/src/frontend/app/routes/lines.tsx
@@ -2,36 +2,39 @@ import { useTranslation } from "react-i18next";
import LineIcon from "~/components/LineIcon";
import { usePageTitle } from "~/contexts/PageTitleContext";
import { VIGO_LINES } from "~/data/LinesData";
-import '../tailwind-full.css';
+import "../tailwind-full.css";
export default function LinesPage() {
- const { t } = useTranslation();
- usePageTitle(t("navbar.lines", "Líneas"));
+ const { t } = useTranslation();
+ usePageTitle(t("navbar.lines", "Líneas"));
- return (
- <div className="container mx-auto px-4 py-6">
- <p className="mb-6 text-gray-700 dark:text-gray-300">
- {t("lines.description", "A continuación se muestra una lista de las líneas de autobús urbano de Vigo con sus respectivas rutas y enlaces a los horarios oficiales.")}
- </p>
+ return (
+ <div className="container mx-auto px-4 py-6">
+ <p className="mb-6 text-gray-700 dark:text-gray-300">
+ {t(
+ "lines.description",
+ "A continuación se muestra una lista de las líneas de autobús urbano de Vigo con sus respectivas rutas y enlaces a los horarios oficiales."
+ )}
+ </p>
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
- {VIGO_LINES.map((line) => (
- <a
- key={line.lineNumber}
- href={line.scheduleUrl}
- target="_blank"
- rel="noopener noreferrer"
- className="flex items-center gap-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-lg transition-shadow border border-gray-200 dark:border-gray-700"
- >
- <LineIcon line={line.lineNumber} mode="rounded" />
- <div className="flex-1 min-w-0">
- <p className="text-sm md:text-md font-semibold text-gray-900 dark:text-gray-100">
- {line.routeName}
- </p>
- </div>
- </a>
- ))}
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
+ {VIGO_LINES.map((line) => (
+ <a
+ key={line.lineNumber}
+ href={line.scheduleUrl}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="flex items-center gap-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-lg transition-shadow border border-gray-200 dark:border-gray-700"
+ >
+ <LineIcon line={line.lineNumber} mode="rounded" />
+ <div className="flex-1 min-w-0">
+ <p className="text-sm md:text-md font-semibold text-gray-900 dark:text-gray-100">
+ {line.routeName}
+ </p>
</div>
- </div>
- );
+ </a>
+ ))}
+ </div>
+ </div>
+ );
}
diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx
index 402bf60..187e9f2 100644
--- a/src/frontend/app/routes/map.tsx
+++ b/src/frontend/app/routes/map.tsx
@@ -12,7 +12,7 @@ import Map, {
Source,
type MapLayerMouseEvent,
type MapRef,
- type StyleSpecification
+ type StyleSpecification,
} from "react-map-gl/maplibre";
import { StopSheet } from "~/components/StopSummarySheet";
import { REGION_DATA } from "~/config/RegionConfig";
@@ -35,7 +35,13 @@ export default function StopMap() {
const [stops, setStops] = useState<
GeoJsonFeature<
Point,
- { stopId: string; name: string; lines: string[]; cancelled?: boolean, prefix: string }
+ {
+ stopId: string;
+ name: string;
+ lines: string[];
+ cancelled?: boolean;
+ prefix: string;
+ }
>[]
>([]);
const [selectedStop, setSelectedStop] = useState<Stop | null>(null);
@@ -51,7 +57,12 @@ export default function StopMap() {
const onMapClick = (e: MapLayerMouseEvent) => {
const features = e.features;
if (!features || features.length === 0) {
- console.debug("No features found on map click. Position:", e.lngLat, "Point:", e.point);
+ console.debug(
+ "No features found on map click. Position:",
+ e.lngLat,
+ "Point:",
+ e.point
+ );
return;
}
const feature = features[0];
@@ -65,7 +76,13 @@ export default function StopMap() {
StopDataProvider.getStops().then((data) => {
const features: GeoJsonFeature<
Point,
- { stopId: string; name: string; lines: string[]; cancelled?: boolean, prefix: string }
+ {
+ stopId: string;
+ name: string;
+ lines: string[];
+ cancelled?: boolean;
+ prefix: string;
+ }
>[] = data.map((s) => ({
type: "Feature",
geometry: {
@@ -77,7 +94,11 @@ export default function StopMap() {
name: s.name.original,
lines: s.lines,
cancelled: s.cancelled ?? false,
- prefix: s.stopId.startsWith("renfe:") ? "stop-renfe" : (s.cancelled ? "stop-vitrasa-cancelled" : "stop-vitrasa"),
+ prefix: s.stopId.startsWith("renfe:")
+ ? "stop-renfe"
+ : s.cancelled
+ ? "stop-vitrasa-cancelled"
+ : "stop-vitrasa",
},
}));
setStops(features);
@@ -190,7 +211,11 @@ export default function StopMap() {
maxBounds={[REGION_DATA.bounds.sw, REGION_DATA.bounds.ne]}
>
<NavigationControl position="top-right" />
- <GeolocateControl position="top-right" trackUserLocation={true} positionOptions={{ enableHighAccuracy: false }} />
+ <GeolocateControl
+ position="top-right"
+ trackUserLocation={true}
+ positionOptions={{ enableHighAccuracy: false }}
+ />
<Source
id="stops-source"
@@ -204,10 +229,7 @@ export default function StopMap() {
minzoom={11}
source="stops-source"
layout={{
- "icon-image": [
- "get",
- "prefix"
- ],
+ "icon-image": ["get", "prefix"],
"icon-size": [
"interpolate",
["linear"],
@@ -242,22 +264,20 @@ export default function StopMap() {
"case",
["==", ["get", "prefix"], "stop-renfe"],
"#870164",
- "#e72b37"
+ "#e72b37",
],
"text-halo-color": "#FFF",
"text-halo-width": 1,
}}
/>
- {
- selectedStop && (
- <StopSheet
- isOpen={isSheetOpen}
- onClose={() => setIsSheetOpen(false)}
- stop={selectedStop}
- />
- )
- }
- </Map >
+ {selectedStop && (
+ <StopSheet
+ isOpen={isSheetOpen}
+ onClose={() => setIsSheetOpen(false)}
+ stop={selectedStop}
+ />
+ )}
+ </Map>
);
}
diff --git a/src/frontend/app/routes/settings.tsx b/src/frontend/app/routes/settings.tsx
index 9b4625f..56df777 100644
--- a/src/frontend/app/routes/settings.tsx
+++ b/src/frontend/app/routes/settings.tsx
@@ -2,22 +2,29 @@ import { Computer, Moon, Sun } from "lucide-react";
import { useTranslation } from "react-i18next";
import { usePageTitle } from "~/contexts/PageTitleContext";
import { useApp, type Theme } from "../AppContext";
-import '../tailwind-full.css';
+import "../tailwind-full.css";
export default function Settings() {
const { t, i18n } = useTranslation();
usePageTitle(t("navbar.settings", "Ajustes"));
- const {
- theme,
- setTheme,
- mapPositionMode,
- setMapPositionMode
- } = useApp();
+ const { theme, setTheme, mapPositionMode, setMapPositionMode } = useApp();
const THEMES = [
- { value: "light" as Theme, label: t("about.theme_light", "Claro"), icon: Sun },
- { value: "dark" as Theme, label: t("about.theme_dark", "Oscuro"), icon: Moon },
- { value: "system" as Theme, label: t("about.theme_system", "Sistema"), icon: Computer },
+ {
+ value: "light" as Theme,
+ label: t("about.theme_light", "Claro"),
+ icon: Sun,
+ },
+ {
+ value: "dark" as Theme,
+ label: t("about.theme_dark", "Oscuro"),
+ icon: Moon,
+ },
+ {
+ value: "system" as Theme,
+ label: t("about.theme_system", "Sistema"),
+ icon: Computer,
+ },
];
return (
@@ -37,9 +44,10 @@ export default function Settings() {
rounded-lg border-2 transition-all duration-200
hover:bg-gray-50 dark:hover:bg-gray-800
focus:outline-none focus:ring focus:ring-blue-500 dark:focus:ring-offset-gray-900
- ${value === theme
- ? "border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 font-semibold"
- : "border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300"
+ ${
+ value === theme
+ ? "border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 font-semibold"
+ : "border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300"
}
`}
>
diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx
index 553b8e7..6d06215 100644
--- a/src/frontend/app/routes/stops-$id.tsx
+++ b/src/frontend/app/routes/stops-$id.tsx
@@ -198,9 +198,7 @@ export default function Estimates() {
loadData();
StopDataProvider.pushRecent(stopId);
- setFavourited(
- StopDataProvider.isFavourite(stopId)
- );
+ setFavourited(StopDataProvider.isFavourite(stopId));
setDataLoading(false);
}, [stopId, loadData]);
@@ -246,34 +244,48 @@ export default function Estimates() {
<div className="flex items-center justify-between py-2">
<div className="flex items-center gap-8">
<Star
- className={`cursor-pointer transition-colors ${favourited
- ? "fill-[var(--star-color)] text-[var(--star-color)]"
- : "text-slate-500"
- }`}
+ className={`cursor-pointer transition-colors ${
+ favourited
+ ? "fill-[var(--star-color)] text-[var(--star-color)]"
+ : "text-slate-500"
+ }`}
onClick={toggleFavourite}
/>
- <CircleHelp className="text-slate-500 cursor-pointer" onClick={() => setIsHelpModalOpen(true)} />
+ <CircleHelp
+ className="text-slate-500 cursor-pointer"
+ onClick={() => setIsHelpModalOpen(true)}
+ />
</div>
<div className="consolidated-circulation-caption">
- {t("estimates.caption", "Estimaciones de llegadas a las {{time}}", {
- time: dataDate?.toLocaleTimeString(),
- })}
+ {t(
+ "estimates.caption",
+ "Estimaciones de llegadas a las {{time}}",
+ {
+ time: dataDate?.toLocaleTimeString(),
+ }
+ )}
</div>
<div>
{isReducedView ? (
- <EyeClosed className="text-slate-500" onClick={() => setIsReducedView(false)} />
+ <EyeClosed
+ className="text-slate-500"
+ onClick={() => setIsReducedView(false)}
+ />
) : (
- <Eye className="text-slate-500" onClick={() => setIsReducedView(true)} />
+ <Eye
+ className="text-slate-500"
+ onClick={() => setIsReducedView(true)}
+ />
)}
</div>
</div>
<ConsolidatedCirculationList
data={data}
reduced={isReducedView}
- driver={stopData?.stopId.split(':')[0]}
+ driver={stopData?.stopId.split(":")[0]}
onCirculationClick={(estimate, idx) => {
setSelectedCirculationId(getCirculationId(estimate));
setIsMapModalOpen(true);
@@ -295,8 +307,8 @@ export default function Estimates() {
previousTripShapeId: c.previousTripShapeId,
schedule: c.schedule
? {
- shapeId: c.schedule.shapeId,
- }
+ shapeId: c.schedule.shapeId,
+ }
: undefined,
}))}
isOpen={isMapModalOpen}