import { useQuery } from "@tanstack/react-query"; import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { AttributionControl, Layer, Source, type MapRef, } from "react-map-gl/maplibre"; import { useParams } from "react-router"; import { fetchRouteDetails } from "~/api/transit"; import { AppMap } from "~/components/shared/AppMap"; import { usePageTitle } from "~/contexts/PageTitleContext"; import "../tailwind-full.css"; export default function RouteDetailsPage() { const { id } = useParams(); const { t } = useTranslation(); const [selectedPatternId, setSelectedPatternId] = useState( null ); const [selectedStopId, setSelectedStopId] = useState(null); const mapRef = useRef(null); const stopRefs = useRef>({}); const { data: route, isLoading } = useQuery({ queryKey: ["route", id], queryFn: () => fetchRouteDetails(id!), enabled: !!id, }); usePageTitle( route?.shortName ? `${route.shortName} - ${route.longName}` : t("routes.details", "Detalles de ruta") ); if (isLoading) { return (
); } if (!route) { return (
{t("routes.not_found", "LĂ­nea no encontrada")}
); } const activePatterns = route.patterns.filter((p) => p.tripCount > 0); const patternsByDirection = activePatterns.reduce( (acc, pattern) => { const dir = pattern.directionId; if (!acc[dir]) acc[dir] = []; acc[dir].push(pattern); return acc; }, {} as Record ); const selectedPattern = activePatterns.find((p) => p.id === selectedPatternId) || activePatterns[0]; const handleStopClick = ( stopId: string, lat: number, lon: number, scroll = true ) => { setSelectedStopId(stopId); mapRef.current?.flyTo({ center: [lon, lat], zoom: 16, duration: 1000, }); if (scroll) { stopRefs.current[stopId]?.scrollIntoView({ behavior: "smooth", block: "center", }); } }; const geojson: GeoJSON.FeatureCollection = { type: "FeatureCollection", features: selectedPattern?.geometry ? [ { type: "Feature", geometry: { type: "LineString", coordinates: selectedPattern.geometry, }, properties: {}, }, ] : [], }; const stopsGeojson: GeoJSON.FeatureCollection = { type: "FeatureCollection", features: selectedPattern?.stops.map((stop) => ({ type: "Feature", geometry: { type: "Point", coordinates: [stop.lon, stop.lat], }, properties: { id: stop.id, name: stop.name, code: stop.code, lat: stop.lat, lon: stop.lon, }, })) || [], }; return (
{ const feature = e.features?.[0]; if (feature && feature.layer.id === "stop-circles") { const { id, lat, lon } = feature.properties; handleStopClick(id, lat, lon, true); } }} showTraffic={false} attributionControl={false} > {selectedPattern?.geometry && ( )}

{t("routes.stops", "Paradas")}

{selectedPattern?.stops.map((stop, idx) => (
{ stopRefs.current[stop.id] = el; }} onClick={() => handleStopClick(stop.id, stop.lat, stop.lon, false) } className={`flex items-start gap-4 p-3 rounded-lg border transition-colors cursor-pointer ${ selectedStopId === stop.id ? "bg-primary/5 border-primary" : "bg-surface border-border hover:border-primary/50" }`} >
{idx < selectedPattern.stops.length - 1 && (
)}

{stop.name} {stop.code && ( {stop.code} )}

{selectedStopId === stop.id && stop.scheduledDepartures.length > 0 && (
{stop.scheduledDepartures.map((dep, i) => ( {Math.floor(dep / 3600) .toString() .padStart(2, "0")} : {Math.floor((dep % 3600) / 60) .toString() .padStart(2, "0")} ))}
)}
))}
); }