aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/routes/stops-$id.tsx
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2026-03-08 23:42:39 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2026-03-08 23:42:39 +0100
commit5288cfbed34f94c4321b8d9dc497cfd0da3ffd26 (patch)
treefc4038fa829a07e58944d32b4b34f626b935e686 /src/frontend/app/routes/stops-$id.tsx
parent4056bc1b66db722bfcffaa960f8ff89150971a4d (diff)
Refactor VigoUsageProcessor to remove CSV whitelist loading; add StopUsageChart component for usage visualization in Stops page
Diffstat (limited to 'src/frontend/app/routes/stops-$id.tsx')
-rw-r--r--src/frontend/app/routes/stops-$id.tsx147
1 files changed, 68 insertions, 79 deletions
diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx
index 3198fca..a61d019 100644
--- a/src/frontend/app/routes/stops-$id.tsx
+++ b/src/frontend/app/routes/stops-$id.tsx
@@ -1,10 +1,4 @@
-import {
- ChartNoAxesColumn,
- CircleHelp,
- Eye,
- EyeClosed,
- Star,
-} from "lucide-react";
+import { CircleHelp, Eye, EyeClosed, Star } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router";
@@ -21,8 +15,8 @@ import { PullToRefresh } from "~/components/PullToRefresh";
import RouteIcon from "~/components/RouteIcon";
import { StopHelpModal } from "~/components/stop/StopHelpModal";
import { StopMapModal } from "~/components/stop/StopMapModal";
-import { StopUsageModal } from "~/components/stop/StopUsageModal";
-import { usePageTitle } from "~/contexts/PageTitleContext";
+import { StopUsageChart } from "~/components/stop/StopUsageChart";
+import { usePageRightNode, usePageTitle } from "~/contexts/PageTitleContext";
import { formatHex } from "~/utils/colours";
import StopDataProvider from "../data/StopDataProvider";
import "../tailwind-full.css";
@@ -58,7 +52,6 @@ export default function Estimates() {
const [isManualRefreshing, setIsManualRefreshing] = useState(false);
const [isMapModalOpen, setIsMapModalOpen] = useState(false);
const [isHelpModalOpen, setIsHelpModalOpen] = useState(false);
- const [isUsageVisible, setIsUsageVisible] = useState(false);
const [isReducedView, setIsReducedView] = useState(false);
const [selectedArrivalId, setSelectedArrivalId] = useState<
string | undefined
@@ -72,6 +65,30 @@ export default function Estimates() {
usePageTitle(getStopDisplayName());
+ const toggleFavourite = useCallback(() => {
+ if (favourited) {
+ StopDataProvider.removeFavourite(stopId);
+ setFavourited(false);
+ } else {
+ StopDataProvider.addFavourite(stopId);
+ setFavourited(true);
+ }
+ }, [favourited, stopId]);
+
+ usePageRightNode(
+ <button
+ onClick={toggleFavourite}
+ className={`app-header__menu-btn p-2 rounded-full transition-colors ${
+ favourited
+ ? "text-[var(--star-color)]"
+ : "text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
+ }`}
+ aria-label={t("stop.toggle_favourite", "Alternar favorito")}
+ >
+ <Star className={favourited ? "fill-current" : ""} size={24} />
+ </button>
+ );
+
const parseError = (error: any): ErrorInfo => {
if (!navigator.onLine) {
return { type: "network", message: "No internet connection" };
@@ -138,19 +155,9 @@ export default function Estimates() {
setDataLoading(false);
}, [stopId, loadData]);
- const toggleFavourite = () => {
- if (favourited) {
- StopDataProvider.removeFavourite(stopId);
- setFavourited(false);
- } else {
- StopDataProvider.addFavourite(stopId);
- setFavourited(true);
- }
- };
-
return (
<PullToRefresh onRefresh={handleManualRefresh}>
- <div className="page-container stops-page">
+ <div className="page-container stops-page flex-1">
{apiRoutes.length > 0 && (
<div className={`estimates-lines-container scrollable`}>
{apiRoutes.map((line) => (
@@ -166,9 +173,7 @@ export default function Estimates() {
</div>
)}
- {/*{stopData && <StopAlert stop={stopData} />}*/}
-
- <div className="estimates-list-container">
+ <div className="estimates-list-container flex-1">
{dataLoading ? (
<>{/*TODO: New loading skeleton*/}</>
) : dataError ? (
@@ -182,54 +187,40 @@ export default function Estimates() {
/>
) : data ? (
<>
- <div className="flex items-center justify-between py-2">
- <div className="flex items-center gap-4">
- <Star
- className={`cursor-pointer transition-colors ${
- favourited
- ? "fill-[var(--star-color)] text-[var(--star-color)]"
- : "text-muted"
- }`}
- onClick={toggleFavourite}
- />
-
- {data.usage && data.usage.length > 0 && (
- <ChartNoAxesColumn
- className={`cursor-pointer transition-colors ${
- isUsageVisible ? "text-primary" : "text-muted"
- }`}
- onClick={() => setIsUsageVisible(!isUsageVisible)}
- />
- )}
-
- <CircleHelp
- className="text-muted cursor-pointer"
- onClick={() => setIsHelpModalOpen(true)}
- />
- </div>
+ <div className="flex flex-col gap-3">
+ <div className="flex items-center justify-between">
+ <div className="consolidated-circulation-caption text-xs font-bold uppercase tracking-wider text-muted m-0">
+ {t("estimates.caption", "Estimaciones a las {{time}}", {
+ time: dataDate?.toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ }),
+ })}
+ </div>
- <div className="consolidated-circulation-caption">
- {t(
- "estimates.caption",
- "Estimaciones de llegadas a las {{time}}",
- {
- time: dataDate?.toLocaleTimeString(),
- }
- )}
- </div>
-
- <div>
- {isReducedView ? (
- <EyeClosed
- className="text-muted"
- onClick={() => setIsReducedView(false)}
- />
- ) : (
- <Eye
- className="text-muted"
- onClick={() => setIsReducedView(true)}
- />
- )}
+ <div className="flex items-center gap-2">
+ <button
+ className="p-1.5 rounded-md hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-muted"
+ onClick={() => setIsHelpModalOpen(true)}
+ >
+ <CircleHelp className="w-5 h-5" />
+ </button>
+ {isReducedView ? (
+ <button
+ className="p-1.5 rounded-md hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-muted"
+ onClick={() => setIsReducedView(false)}
+ >
+ <EyeClosed className="w-5 h-5" />
+ </button>
+ ) : (
+ <button
+ className="p-1.5 rounded-md hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-muted"
+ onClick={() => setIsReducedView(true)}
+ >
+ <Eye className="w-5 h-5" />
+ </button>
+ )}
+ </div>
</div>
</div>
<ArrivalList
@@ -240,6 +231,12 @@ export default function Estimates() {
setIsMapModalOpen(true);
}}
/>
+
+ {data.usage && data.usage.length > 0 && (
+ <div className="mt-8">
+ <StopUsageChart usage={data.usage} />
+ </div>
+ )}
</>
) : null}
</div>
@@ -271,14 +268,6 @@ export default function Estimates() {
isOpen={isHelpModalOpen}
onClose={() => setIsHelpModalOpen(false)}
/>
-
- {data?.usage && (
- <StopUsageModal
- isOpen={isUsageVisible}
- onClose={() => setIsUsageVisible(false)}
- usage={data.usage}
- />
- )}
</div>
</PullToRefresh>
);