From 5cc27f852b02446659e0ab85305916c9f5e5a5f0 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Wed, 6 Aug 2025 00:12:19 +0200 Subject: feat: Implement pull-to-refresh functionality across various components - Added `PullToRefresh` component to enable pull-to-refresh behavior in `StopList` and `Estimates` pages. - Integrated `usePullToRefresh` hook to manage pull-to-refresh state and actions. - Created `UpdateNotification` component to inform users of available updates from the service worker. - Enhanced service worker management with `ServiceWorkerManager` class for better update handling and caching strategies. - Updated CSS styles for new components and improved layout for better user experience. - Refactored API caching logic in service worker to handle multiple endpoints and dynamic cache expiration. - Added auto-refresh functionality for estimates data to keep information up-to-date. --- src/frontend/app/routes/estimates-$id.tsx | 141 +++++++++++++++++++----------- 1 file changed, 89 insertions(+), 52 deletions(-) (limited to 'src/frontend/app/routes/estimates-$id.tsx') diff --git a/src/frontend/app/routes/estimates-$id.tsx b/src/frontend/app/routes/estimates-$id.tsx index b5ae91a..d9b9b47 100644 --- a/src/frontend/app/routes/estimates-$id.tsx +++ b/src/frontend/app/routes/estimates-$id.tsx @@ -1,4 +1,4 @@ -import { type JSX, useEffect, useState } from "react"; +import { type JSX, useEffect, useState, useCallback } from "react"; import { useParams, Link } from "react-router"; import StopDataProvider from "../data/StopDataProvider"; import { Star, Edit2, ExternalLink } from "lucide-react"; @@ -8,6 +8,9 @@ import { useApp } from "../AppContext"; import { GroupedTable } from "../components/GroupedTable"; import { useTranslation } from "react-i18next"; import { TimetableTable, type TimetableEntry } from "../components/TimetableTable"; +import { usePullToRefresh } from "../hooks/usePullToRefresh"; +import { PullToRefreshIndicator } from "../components/PullToRefresh"; +import { useAutoRefresh } from "../hooks/useAutoRefresh"; export interface StopDetails { stop: { @@ -62,23 +65,51 @@ export default function Estimates() { const [timetableData, setTimetableData] = useState([]); const { tableStyle } = useApp(); - useEffect(() => { - // Load real-time estimates - loadData(params.id!).then((body: StopDetails) => { - setData(body); - setDataDate(new Date()); - setCustomName(StopDataProvider.getCustomName(stopIdNum)); - }); + const loadEstimatesData = useCallback(async () => { + const body: StopDetails = await loadData(params.id!); + setData(body); + setDataDate(new Date()); + setCustomName(StopDataProvider.getCustomName(stopIdNum)); + }, [params.id, stopIdNum]); - // Load timetable data - loadTimetableData(params.id!).then((timetableBody: TimetableEntry[]) => { - setTimetableData(timetableBody); - }); + const loadTimetableDataAsync = useCallback(async () => { + const timetableBody: TimetableEntry[] = await loadTimetableData(params.id!); + setTimetableData(timetableBody); + }, [params.id]); - StopDataProvider.pushRecent(parseInt(params.id ?? "")); + const refreshData = useCallback(async () => { + await Promise.all([ + loadEstimatesData(), + loadTimetableDataAsync() + ]); + }, [loadEstimatesData, loadTimetableDataAsync]); + + const { + containerRef, + isRefreshing, + pullDistance, + canRefresh, + } = usePullToRefresh({ + onRefresh: refreshData, + threshold: 80, + enabled: true, + }); + + // Auto-refresh estimates data every 30 seconds + useAutoRefresh({ + onRefresh: loadEstimatesData, + interval: 30000, + enabled: true, + }); + + useEffect(() => { + // Initial load + loadEstimatesData(); + loadTimetableDataAsync(); + StopDataProvider.pushRecent(parseInt(params.id ?? "")); setFavourited(StopDataProvider.isFavourite(parseInt(params.id ?? ""))); - }, [params.id]); + }, [params.id, loadEstimatesData, loadTimetableDataAsync]); const toggleFavourite = () => { if (favourited) { @@ -108,45 +139,51 @@ export default function Estimates() { return

{t("common.loading")}

; return ( -
-
-

- + +
+

+ + + {customName ?? data.stop.name}{" "} + ({data.stop.id}) +

+
+ +
+ {tableStyle === "grouped" ? ( + + ) : ( + + )} +
+ +
+ - - {customName ?? data.stop.name}{" "} - ({data.stop.id}) -

-
- -
- {tableStyle === "grouped" ? ( - - ) : ( - - )} -
- -
- - - {timetableData.length > 0 && ( -
- - - {t("timetable.viewAll", "Ver todos los horarios")} - -
- )} -
+ + {timetableData.length > 0 && ( +
+ + + {t("timetable.viewAll", "Ver todos los horarios")} + +
+ )} +
+ ); } -- cgit v1.3