From 7b8594debceb93a1fa400d48fe1dcff943bd5af6 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Jun 2025 23:44:25 +0200 Subject: 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 --- src/frontend/app/routes/stoplist.tsx | 254 +++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 118 deletions(-) (limited to 'src/frontend/app/routes/stoplist.tsx') 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(null) - const [searchResults, setSearchResults] = useState(null); - const searchTimeout = useRef(null); - - 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)) - }, []); - - const handleStopSearch = (event: React.ChangeEvent) => { - 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

{t('common.loading')}

- - return ( -
-

UrbanoVigo Web

- -
-
- - -
-
- - {searchResults && searchResults.length > 0 && ( -
-

{t('stoplist.search_results', 'Resultados de la búsqueda')}

-
    - {searchResults.map((stop: Stop) => ( - - ))} -
-
- )} - -
-

{t('stoplist.favourites')}

- - {favouritedStops?.length === 0 && ( -

- {t('stoplist.no_favourites', 'Accede a una parada y márcala como favorita para verla aquí.')} -

- )} - -
    - {favouritedStops?.sort((a, b) => a.stopId - b.stopId).map((stop: Stop) => ( - - ))} -
-
- - {recentStops && recentStops.length > 0 && ( -
-

{t('stoplist.recents')}

- -
    - {recentStops.map((stop: Stop) => ( - - ))} -
-
- )} - -
-

{t('stoplist.all_stops', 'Paradas')}

- -
    - {data?.sort((a, b) => a.stopId - b.stopId).map((stop: Stop) => ( - - ))} -
-
-
- ) + const { t } = useTranslation(); + const [data, setData] = useState(null); + const [searchResults, setSearchResults] = useState(null); + const searchTimeout = useRef(null); + + 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)); + }, []); + + const handleStopSearch = (event: React.ChangeEvent) => { + 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

{t("common.loading")}

; + + return ( +
+

UrbanoVigo Web

+ +
+
+ + +
+
+ + {searchResults && searchResults.length > 0 && ( +
+

+ {t("stoplist.search_results", "Resultados de la búsqueda")} +

+
    + {searchResults.map((stop: Stop) => ( + + ))} +
+
+ )} + +
+

{t("stoplist.favourites")}

+ + {favouritedStops?.length === 0 && ( +

+ {t( + "stoplist.no_favourites", + "Accede a una parada y márcala como favorita para verla aquí.", + )} +

+ )} + +
    + {favouritedStops + ?.sort((a, b) => a.stopId - b.stopId) + .map((stop: Stop) => )} +
+
+ + {recentStops && recentStops.length > 0 && ( +
+

{t("stoplist.recents")}

+ +
    + {recentStops.map((stop: Stop) => ( + + ))} +
+
+ )} + +
+

{t("stoplist.all_stops", "Paradas")}

+ +
    + {data + ?.sort((a, b) => a.stopId - b.stopId) + .map((stop: Stop) => )} +
+
+
+ ); } -- cgit v1.3