aboutsummaryrefslogtreecommitdiff
path: root/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/Estimates.tsx99
-rw-r--r--src/pages/Map.tsx75
-rw-r--r--src/pages/Settings.tsx65
-rw-r--r--src/pages/StopList.tsx135
4 files changed, 0 insertions, 374 deletions
diff --git a/src/pages/Estimates.tsx b/src/pages/Estimates.tsx
deleted file mode 100644
index 7cf941a..0000000
--- a/src/pages/Estimates.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { JSX, useEffect, useState } from "react";
-import { useParams } from "react-router";
-import StopDataProvider from "../data/StopDataProvider";
-import { Star, Edit2 } from 'lucide-react';
-import "../styles/Estimates.css";
-import { RegularTable } from "../components/RegularTable";
-import { useApp } from "../AppContext";
-import { GroupedTable } from "../components/GroupedTable";
-
-export interface StopDetails {
- stop: {
- id: number;
- name: string;
- latitude: number;
- longitude: number;
- }
- estimates: {
- line: string;
- route: string;
- minutes: number;
- meters: number;
- }[]
-}
-
-const loadData = async (stopId: string) => {
- const resp = await fetch(`/api/GetStopEstimates?id=${stopId}`);
- return await resp.json();
-};
-
-export function Estimates(): JSX.Element {
- const params = useParams();
- const stopIdNum = parseInt(params.stopId ?? "");
- const [customName, setCustomName] = useState<string | undefined>(undefined);
- const [data, setData] = useState<StopDetails | null>(null);
- const [dataDate, setDataDate] = useState<Date | null>(null);
- const [favourited, setFavourited] = useState(false);
- const { tableStyle } = useApp();
-
- useEffect(() => {
- loadData(params.stopId!)
- .then((body: StopDetails) => {
- setData(body);
- setDataDate(new Date());
- setCustomName(StopDataProvider.getCustomName(stopIdNum));
- })
-
-
- StopDataProvider.pushRecent(parseInt(params.stopId ?? ""));
-
- setFavourited(
- StopDataProvider.isFavourite(parseInt(params.stopId ?? ""))
- );
- }, [params.stopId]);
-
-
- const toggleFavourite = () => {
- if (favourited) {
- StopDataProvider.removeFavourite(stopIdNum);
- setFavourited(false);
- } else {
- StopDataProvider.addFavourite(stopIdNum);
- setFavourited(true);
- }
- }
-
- const handleRename = () => {
- const current = customName ?? data?.stop.name;
- const input = window.prompt('Custom name for this stop:', current);
- if (input === null) return; // cancelled
- const trimmed = input.trim();
- if (trimmed === '') {
- StopDataProvider.removeCustomName(stopIdNum);
- setCustomName(undefined);
- } else {
- StopDataProvider.setCustomName(stopIdNum, trimmed);
- setCustomName(trimmed);
- }
- };
-
- if (data === null) return <h1 className="page-title">Cargando datos en tiempo real...</h1>
-
- return (
- <div className="page-container">
- <div className="estimates-header">
- <h1 className="page-title">
- <Star className={`star-icon ${favourited ? 'active' : ''}`} onClick={toggleFavourite} />
- <Edit2 className="edit-icon" onClick={handleRename} />
- {(customName ?? data.stop.name)} <span className="estimates-stop-id">({data.stop.id})</span>
- </h1>
- </div>
-
- <div className="table-responsive">
- {tableStyle === 'grouped' ?
- <GroupedTable data={data} dataDate={dataDate} /> :
- <RegularTable data={data} dataDate={dataDate} />}
- </div>
- </div>
- )
-}
diff --git a/src/pages/Map.tsx b/src/pages/Map.tsx
deleted file mode 100644
index 1f0a9e0..0000000
--- a/src/pages/Map.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import StopDataProvider, { Stop } from "../data/StopDataProvider";
-
-import 'leaflet/dist/leaflet.css'
-import 'react-leaflet-markercluster/styles'
-
-import { useEffect, useState } from 'react';
-import LineIcon from '../components/LineIcon';
-import { Link } from 'react-router';
-import { MapContainer, TileLayer, Marker, Popup, useMapEvents } from "react-leaflet";
-import MarkerClusterGroup from "react-leaflet-markercluster";
-import { Icon, LatLngTuple } from "leaflet";
-import { EnhancedLocateControl } from "../controls/LocateControl";
-import { useApp } from "../AppContext";
-
-const icon = new Icon({
- iconUrl: '/map-pin-icon.png',
- iconSize: [25, 41],
- iconAnchor: [12, 41],
- popupAnchor: [1, -34],
- shadowSize: [41, 41]
-});
-
-// Componente auxiliar para detectar cambios en el mapa
-const MapEventHandler = () => {
- const { updateMapState } = useApp();
-
- const map = useMapEvents({
- moveend: () => {
- const center = map.getCenter();
- const zoom = map.getZoom();
- updateMapState([center.lat, center.lng], zoom);
- }
- });
-
- return null;
-};
-
-// Componente principal del mapa
-export function StopMap() {
- const [stops, setStops] = useState<Stop[]>([]);
- const { mapState } = useApp();
-
- useEffect(() => {
- StopDataProvider.getStops().then(setStops);
- }, []);
-
- return (
- <MapContainer
- center={mapState.center}
- zoom={mapState.zoom}
- scrollWheelZoom={true}
- style={{ height: '100%' }}
- >
- <TileLayer
- attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attributions">CARTO</a>'
- url="https://d.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png"
- />
- <EnhancedLocateControl />
- <MapEventHandler />
- <MarkerClusterGroup>
- {stops.map(stop => (
- <Marker key={stop.stopId} position={[stop.latitude, stop.longitude] as LatLngTuple} icon={icon}>
- <Popup>
- <Link to={`/estimates/${stop.stopId}`}>{StopDataProvider.getDisplayName(stop)}</Link>
- <br />
- {stop.lines.map((line) => (
- <LineIcon key={line} line={line} />
- ))}
- </Popup>
- </Marker>
- ))}
- </MarkerClusterGroup>
- </MapContainer>
- );
-}
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
deleted file mode 100644
index 1ad15ab..0000000
--- a/src/pages/Settings.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { useApp } from "../AppContext";
-import "../styles/Settings.css";
-
-export function Settings() {
- const { theme, setTheme, tableStyle, setTableStyle, mapPositionMode, setMapPositionMode } = useApp();
-
- return (
- <div className="about-page">
- <h1 className="page-title">Sobre UrbanoVigo Web</h1>
- <p className="about-description">
- Aplicación web para encontrar paradas y tiempos de llegada de los autobuses
- urbanos de Vigo, España.
- </p>
- <section className="settings-section">
- <h2>Ajustes</h2>
- <div className="settings-content-inline">
- <label htmlFor="theme" className="form-label-inline">Modo:</label>
- <select id="theme" className="form-select-inline" value={theme} onChange={(e) => setTheme(e.target.value as "light" | "dark")}>
- <option value="light">Claro</option>
- <option value="dark">Oscuro</option>
- </select>
- </div>
- <div className="settings-content-inline">
- <label htmlFor="tableStyle" className="form-label-inline">Estilo de tabla:</label>
- <select id="tableStyle" className="form-select-inline" value={tableStyle} onChange={(e) => setTableStyle(e.target.value as "regular" | "grouped")}>
- <option value="regular">Mostrar por orden</option>
- <option value="grouped">Agrupar por línea</option>
- </select>
- </div>
- <div className="settings-content-inline">
- <label htmlFor="mapPositionMode" className="form-label-inline">Posición del mapa:</label>
- <select id="mapPositionMode" className="form-select-inline" value={mapPositionMode} onChange={e => setMapPositionMode(e.target.value as 'gps' | 'last')}>
- <option value="gps">Posición GPS</option>
- <option value="last">Donde lo dejé</option>
- </select>
- </div>
- <details className="form-details">
- <summary>¿Qué significa esto?</summary>
- <p>
- La tabla de horarios puede mostrarse de dos formas:
- </p>
- <dl>
- <dt>Mostrar por orden</dt>
- <dd>Las paradas se muestran en el orden en que se visitan. Aplicaciones como Infobus (Vitrasa) usan este estilo.</dd>
- <dt>Agrupar por línea</dt>
- <dd>Las paradas se agrupan por la línea de autobús. Aplicaciones como iTranvias (A Coruña) o Moovit (más o menos) usan este estilo.</dd>
- </dl>
- </details>
- </section>
- <h2>Créditos</h2>
- <p>
- <a href="https://github.com/arielcostas/urbanovigo-web" className="about-link" rel="nofollow noreferrer noopener">
- Código en GitHub
- </a> -
- Desarrollado por <a href="https://www.costas.dev" className="about-link" rel="nofollow noreferrer noopener">
- Ariel Costas
- </a>
- </p>
- <p>
- Datos obtenidos de <a href="https://datos.vigo.org" className="about-link" rel="nofollow noreferrer noopener">datos.vigo.org</a> bajo
- licencia <a href="https://opendefinition.org/licenses/odc-by/" className="about-link" rel="nofollow noreferrer noopener">Open Data Commons Attribution License</a>
- </p>
- </div>
- )
-} \ No newline at end of file
diff --git a/src/pages/StopList.tsx b/src/pages/StopList.tsx
deleted file mode 100644
index b965456..0000000
--- a/src/pages/StopList.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { useEffect, useMemo, useRef, useState } from "react";
-import StopDataProvider, { Stop } from "../data/StopDataProvider";
-import StopItem from "../components/StopItem";
-import Fuse from "fuse.js";
-
-const placeholders = [
- "Urzaiz",
- "Gran Vía",
- "Castelao",
- "García Barbón",
- "Valladares",
- "Florida",
- "Pizarro",
- "Estrada Madrid",
- "Sanjurjo Badía"
-];
-
-export function StopList() {
- const [data, setData] = useState<Stop[] | null>(null)
- const [searchResults, setSearchResults] = useState<Stop[] | null>(null);
- const searchTimeout = useRef<NodeJS.Timeout | null>(null);
-
- const randomPlaceholder = useMemo(() => placeholders[Math.floor(Math.random() * placeholders.length)], []);
- const fuse = useMemo(() => new Fuse(data || [], { threshold: 0.3, keys: ['name.original'] }), [data]);
-
- useEffect(() => {
- StopDataProvider.getStops().then((stops: Stop[]) => setData(stops))
- }, []);
-
- const handleStopSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
- const stopName = event.target.value || "";
-
- if (searchTimeout.current) {
- clearTimeout(searchTimeout.current);
- }
-
- searchTimeout.current = setTimeout(() => {
- if (stopName.length === 0) {
- setSearchResults(null);
- return;
- }
-
- if (!data) {
- console.error("No data available for search");
- return;
- }
-
- const results = fuse.search(stopName);
- const items = results.map(result => result.item);
- setSearchResults(items);
- }, 300);
- }
-
- const favouritedStops = useMemo(() => {
- return data?.filter(stop => stop.favourite) ?? []
- }, [data])
-
- const recentStops = useMemo(() => {
- // no recent items if data not loaded
- if (!data) return null;
- const recentIds = StopDataProvider.getRecent();
- if (recentIds.length === 0) return null;
- // map and filter out missing entries
- const stopsList = recentIds
- .map(id => data.find(stop => stop.stopId === id))
- .filter((s): s is Stop => Boolean(s));
- return stopsList.reverse();
- }, [data]);
-
- if (data === null) return <h1 className="page-title">Loading...</h1>
-
- return (
- <div className="page-container">
- <h1 className="page-title">UrbanoVigo Web</h1>
-
- <form className="search-form">
- <div className="form-group">
- <label className="form-label" htmlFor="stopName">
- Buscar paradas
- </label>
- <input className="form-input" type="text" placeholder={randomPlaceholder} id="stopName" onChange={handleStopSearch} />
- </div>
- </form>
-
- {searchResults && searchResults.length > 0 && (
- <div className="list-container">
- <h2 className="page-subtitle">Resultados de la búsqueda</h2>
- <ul className="list">
- {searchResults.map((stop: Stop) => (
- <StopItem key={stop.stopId} stop={stop} />
- ))}
- </ul>
- </div>
- )}
-
- <div className="list-container">
- <h2 className="page-subtitle">Paradas favoritas</h2>
-
- {favouritedStops?.length === 0 && (
- <p className="message">
- Accede a una parada y márcala como favorita para verla aquí.
- </p>
- )}
-
- <ul className="list">
- {favouritedStops?.sort((a, b) => a.stopId - b.stopId).map((stop: Stop) => (
- <StopItem key={stop.stopId} stop={stop} />
- ))}
- </ul>
- </div>
-
- {recentStops && recentStops.length > 0 && (
- <div className="list-container">
- <h2 className="page-subtitle">Recientes</h2>
-
- <ul className="list">
- {recentStops.map((stop: Stop) => (
- <StopItem key={stop.stopId} stop={stop} />
- ))}
- </ul>
- </div>
- )}
-
- <div className="list-container">
- <h2 className="page-subtitle">Paradas</h2>
-
- <ul className="list">
- {data?.sort((a, b) => a.stopId - b.stopId).map((stop: Stop) => (
- <StopItem key={stop.stopId} stop={stop} />
- ))}
- </ul>
- </div>
- </div>
- )
-}