diff options
Diffstat (limited to 'src/frontend/app/routes')
| -rw-r--r-- | src/frontend/app/routes/about.tsx | 23 | ||||
| -rw-r--r-- | src/frontend/app/routes/home.tsx | 36 | ||||
| -rw-r--r-- | src/frontend/app/routes/lines.tsx | 57 | ||||
| -rw-r--r-- | src/frontend/app/routes/map.tsx | 62 | ||||
| -rw-r--r-- | src/frontend/app/routes/settings.tsx | 34 | ||||
| -rw-r--r-- | src/frontend/app/routes/stops-$id.tsx | 44 |
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} |
