aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/routes/stoplist.tsx
diff options
context:
space:
mode:
authorCopilot <198982749+Copilot@users.noreply.github.com>2025-06-26 23:44:25 +0200
committerGitHub <noreply@github.com>2025-06-26 23:44:25 +0200
commit7b8594debceb93a1fa400d48fe1dcff943bd5af6 (patch)
tree73e68c7238a91d8931d669364d395ce2994164f4 /src/frontend/app/routes/stoplist.tsx
parent3dac17a9fb54c977c97280ed4c482e9d4266b7de (diff)
Implement stop sheet modal for map stop interactions (#27)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: arielcostas <94913521+arielcostas@users.noreply.github.com> Co-authored-by: Ariel Costas Guerrero <ariel@costas.dev>
Diffstat (limited to 'src/frontend/app/routes/stoplist.tsx')
-rw-r--r--src/frontend/app/routes/stoplist.tsx214
1 files changed, 116 insertions, 98 deletions
diff --git a/src/frontend/app/routes/stoplist.tsx b/src/frontend/app/routes/stoplist.tsx
index 9404b39..58cdab4 100644
--- a/src/frontend/app/routes/stoplist.tsx
+++ b/src/frontend/app/routes/stoplist.tsx
@@ -2,125 +2,143 @@ import { useEffect, useMemo, useRef, useState } from "react";
import StopDataProvider, { type Stop } from "../data/StopDataProvider";
import StopItem from "../components/StopItem";
import Fuse from "fuse.js";
-import './stoplist.css';
+import "./stoplist.css";
import { useTranslation } from "react-i18next";
export default function StopList() {
- const { t } = useTranslation();
- const [data, setData] = useState<Stop[] | null>(null)
- const [searchResults, setSearchResults] = useState<Stop[] | null>(null);
- const searchTimeout = useRef<NodeJS.Timeout | null>(null);
+ const { t } = useTranslation();
+ const [data, setData] = useState<Stop[] | null>(null);
+ const [searchResults, setSearchResults] = useState<Stop[] | null>(null);
+ const searchTimeout = useRef<NodeJS.Timeout | null>(null);
- const randomPlaceholder = useMemo(() => t('stoplist.search_placeholder'), [t]);
- const fuse = useMemo(() => new Fuse(data || [], { threshold: 0.3, keys: ['name.original'] }), [data]);
+ const randomPlaceholder = useMemo(
+ () => t("stoplist.search_placeholder"),
+ [t],
+ );
+ const fuse = useMemo(
+ () => new Fuse(data || [], { threshold: 0.3, keys: ["name.original"] }),
+ [data],
+ );
- useEffect(() => {
- StopDataProvider.getStops().then((stops: Stop[]) => setData(stops))
- }, []);
+ useEffect(() => {
+ StopDataProvider.getStops().then((stops: Stop[]) => setData(stops));
+ }, []);
- const handleStopSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
- const stopName = event.target.value || "";
+ const handleStopSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
+ const stopName = event.target.value || "";
- if (searchTimeout.current) {
- clearTimeout(searchTimeout.current);
- }
+ if (searchTimeout.current) {
+ clearTimeout(searchTimeout.current);
+ }
- searchTimeout.current = setTimeout(() => {
- if (stopName.length === 0) {
- setSearchResults(null);
- return;
- }
+ searchTimeout.current = setTimeout(() => {
+ if (stopName.length === 0) {
+ setSearchResults(null);
+ return;
+ }
- if (!data) {
- console.error("No data available for search");
- 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 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 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]);
+ 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">{t('common.loading')}</h1>
+ if (data === null)
+ return <h1 className="page-title">{t("common.loading")}</h1>;
- return (
- <div className="page-container">
- <h1 className="page-title">UrbanoVigo Web</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">
- {t('stoplist.search_label', 'Buscar paradas')}
- </label>
- <input className="form-input" type="text" placeholder={randomPlaceholder} id="stopName" onChange={handleStopSearch} />
- </div>
- </form>
+ <form className="search-form">
+ <div className="form-group">
+ <label className="form-label" htmlFor="stopName">
+ {t("stoplist.search_label", "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">{t('stoplist.search_results', 'Resultados de la búsqueda')}</h2>
- <ul className="list">
- {searchResults.map((stop: Stop) => (
- <StopItem key={stop.stopId} stop={stop} />
- ))}
- </ul>
- </div>
- )}
+ {searchResults && searchResults.length > 0 && (
+ <div className="list-container">
+ <h2 className="page-subtitle">
+ {t("stoplist.search_results", "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">{t('stoplist.favourites')}</h2>
+ <div className="list-container">
+ <h2 className="page-subtitle">{t("stoplist.favourites")}</h2>
- {favouritedStops?.length === 0 && (
- <p className="message">
- {t('stoplist.no_favourites', 'Accede a una parada y márcala como favorita para verla aquí.')}
- </p>
- )}
+ {favouritedStops?.length === 0 && (
+ <p className="message">
+ {t(
+ "stoplist.no_favourites",
+ "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>
+ <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">{t('stoplist.recents')}</h2>
+ {recentStops && recentStops.length > 0 && (
+ <div className="list-container">
+ <h2 className="page-subtitle">{t("stoplist.recents")}</h2>
- <ul className="list">
- {recentStops.map((stop: Stop) => (
- <StopItem key={stop.stopId} stop={stop} />
- ))}
- </ul>
- </div>
- )}
+ <ul className="list">
+ {recentStops.map((stop: Stop) => (
+ <StopItem key={stop.stopId} stop={stop} />
+ ))}
+ </ul>
+ </div>
+ )}
- <div className="list-container">
- <h2 className="page-subtitle">{t('stoplist.all_stops', 'Paradas')}</h2>
+ <div className="list-container">
+ <h2 className="page-subtitle">{t("stoplist.all_stops", "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>
- )
+ <ul className="list">
+ {data
+ ?.sort((a, b) => a.stopId - b.stopId)
+ .map((stop: Stop) => <StopItem key={stop.stopId} stop={stop} />)}
+ </ul>
+ </div>
+ </div>
+ );
}