diff --git a/src/components/alerts/TtcAlertList.tsx b/src/components/alerts/TtcAlertList.tsx new file mode 100644 index 0000000..9d5418a --- /dev/null +++ b/src/components/alerts/TtcAlertList.tsx @@ -0,0 +1,28 @@ +import { useQuery } from "@tanstack/react-query"; + +import { ttcAlerts } from "../fetch/queries.js"; +import { SkeetList } from "./SkeetList.js"; + +export function TtcAlertList({ lineNum }: { lineNum: number[] }) { + const bskyAlerts = useQuery(ttcAlerts); + + const filteredBskyAlerts = + bskyAlerts.data?.feed.filter( + (skeet: { post: { record: { text: string } } }) => + lineNum.some((line) => { + return line < 6 + ? skeet.post.record.text.match(`Line ${line}`) + : skeet.post.record.text.startsWith(`${line}`); + }) + ) ?? []; + return ( + <> + {filteredBskyAlerts.length > 0 && ( + 1 ? "all" : `${lineNum[0]}`} + /> + )} + + ); +} diff --git a/src/components/fetch/FetchStop.tsx b/src/components/fetch/FetchStop.tsx index 399d12b..cfd8f1c 100644 --- a/src/components/fetch/FetchStop.tsx +++ b/src/components/fetch/FetchStop.tsx @@ -1,5 +1,6 @@ import { Button, Title1 } from "@fluentui/react-components"; import { ArrowClockwise24Regular } from "@fluentui/react-icons"; +import { useQuery } from "@tanstack/react-query"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -7,6 +8,7 @@ import { EtaPredictionJson } from "../../models/etaJson.js"; import { EtaBusWithID, LineStopEta } from "../../models/etaObjects.js"; import { store } from "../../store/index.js"; import { settingsSelectors } from "../../store/settings/slice.js"; +import { TtcAlertList } from "../alerts/TtcAlertList.js"; import { DirectionBadge } from "../badges.js"; import { BookmarkButton } from "../bookmarks/BookmarkButton.js"; import CountdownGroup from "../countdown/CountdownGroup.js"; @@ -15,15 +17,32 @@ import SMSButton from "../eta/SMSButton.js"; import { etaParser } from "../parser/etaParser.js"; import RawDisplay from "../rawDisplay/RawDisplay.js"; import style from "./FetchStop.module.css"; -import { getStopPredictions } from "./fetchUtils.js"; +import { ttcStops } from "./queries.js"; +function RefreshButton({ + handleRefreshClick, +}: { + handleRefreshClick: () => void; +}) { + const { t } = useTranslation(); + + return ( + + ); +} function StopPredictionInfo(props: { stopId: number }): JSX.Element { - const [data, setData] = useState(); - const [stopId] = useState(props.stopId); + const stopId = props.stopId; const [etaDb, setEtaDb] = useState([]); const [lastUpdatedAt, setLastUpdatedAt] = useState(Date.now()); const { t } = useTranslation(); const [unifiedEta, setUnifiedEta] = useState([]); + const [lineList, setLineList] = useState([]); + const ttcStopPrediction = useQuery({ + ...ttcStops(stopId), + queryKey: [`ttc-stop-${stopId}`, lastUpdatedAt.toString()], + }); const handleRefreshClick = useCallback(() => { setLastUpdatedAt(Date.now()); @@ -33,40 +52,28 @@ function StopPredictionInfo(props: { stopId: number }): JSX.Element { settingsSelectors.selectById(store.getState().settings, "unifiedEta") ?.value === "true"; - function RefreshButton() { - return ( - - ); - } - useEffect(() => { - const controller = new AbortController(); + if (ttcStopPrediction.data) { + setEtaDb(etaParser(ttcStopPrediction.data)); + } + }, [ttcStopPrediction]); - getStopPredictions(stopId, { signal: controller.signal }).then((data) => { - if (data) { - setData(data); - setEtaDb(etaParser(data)); + useEffect(() => { + setLineList([ + ...new Set(etaDb.map((item) => parseInt(item.line.toString()))), + ]); + if (unifiedEtaValue) { + let templist: EtaBusWithID[] = []; + for (const list of etaDb) { + templist = templist.concat(list.etas ?? []); } - }); - // when useEffect is called, the following clean-up fn will run first - return () => { - controller.abort(); - }; - }, [lastUpdatedAt]); - - useEffect(() => { - let templist: EtaBusWithID[] = []; - for (const list of etaDb) { - templist = templist.concat(list.etas ?? []); + setUnifiedEta(templist.sort((a, b) => a.epochTime - b.epochTime)); } - setUnifiedEta(templist.sort((a, b) => a.epochTime - b.epochTime)); - }, [etaDb]); + }, [etaDb, unifiedEtaValue]); - if (data) { - if (data.Error === undefined) { + if (ttcStopPrediction.data) { + if (ttcStopPrediction.data.Error === undefined) { let listContent: JSX.Element[] = []; if (unifiedEtaValue) { listContent = unifiedEta.map((element, index) => { @@ -96,6 +103,7 @@ function StopPredictionInfo(props: { stopId: number }): JSX.Element { }); return (
+ {etaDb[0] && ( <> @@ -114,7 +122,7 @@ function StopPredictionInfo(props: { stopId: number }): JSX.Element { )}
- + {etaDb[0] && ( )} - +
); } else { @@ -146,18 +154,18 @@ function StopPredictionInfo(props: { stopId: number }): JSX.Element {
{t("reminder.failToLocate")}
- +
- {data.Error["#text"]} - + {ttcStopPrediction.data.Error["#text"]} +
); } } else { return (
- {navigator.onLine ? ( + {ttcStopPrediction.status === "pending" ? ( {t("reminder.loading")} ) : ( <> @@ -167,7 +175,7 @@ function StopPredictionInfo(props: { stopId: number }): JSX.Element { )}
- +
diff --git a/src/components/fetch/FetchSubwayStop.tsx b/src/components/fetch/FetchSubwayStop.tsx index 348d978..d0c37b9 100644 --- a/src/components/fetch/FetchSubwayStop.tsx +++ b/src/components/fetch/FetchSubwayStop.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"; import { SubwayStop } from "../../models/ttc.js"; import { store } from "../../store/index.js"; import { subwayDbSelectors } from "../../store/suwbayDb/slice.js"; +import { TtcAlertList } from "../alerts/TtcAlertList.js"; import { BookmarkButton } from "../bookmarks/BookmarkButton.js"; import { CountdownSec } from "../countdown/CountdownSec.js"; import RawDisplay from "../rawDisplay/RawDisplay.js"; @@ -75,6 +76,7 @@ function SubwayStopPredictionInfo(props: { )} {data.directionText} +
{ const response = await fetch( @@ -14,9 +15,26 @@ export const ttcAlerts = { }, staleTime: 60 * 1000, refetchInterval: 60 * 1000, +}); + +export const ttcStops = (stopId: number) => { + return { + queryKey: [`ttc-stop-${stopId}`], + queryFn: async () => { + const response = await fetch( + `https://webservices.umoiq.com/service/publicJSONFeed?command=predictions&a=ttc&stopId=${stopId}` + ); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + return response.json(); + }, + placeholderData: (prev: any) => prev, + }; }; -export const ttcLines = { +export const ttcLines = queryOptions({ queryKey: ["ttc-lines"], queryFn: async () => { const response = await fetch( @@ -30,7 +48,7 @@ export const ttcLines = { }, staleTime: 24 * 60 * 60 * 1000, refetchInterval: 60 * 1000, -}; +}); // inaccessible; CORS Missing Allow Origin // export const ttcGtfsAlerts = { diff --git a/src/routes/Line.tsx b/src/routes/Line.tsx index 914e4a4..114dc49 100644 --- a/src/routes/Line.tsx +++ b/src/routes/Line.tsx @@ -1,13 +1,11 @@ import { Accordion, Title1 } from "@fluentui/react-components"; -import { useQuery } from "@tanstack/react-query"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; -import { SkeetList } from "../components/alerts/SkeetList.js"; +import { TtcAlertList } from "../components/alerts/TtcAlertList.js"; import RouteInfo from "../components/fetch/FetchRoute.js"; import SubwayRouteInfo from "../components/fetch/FetchSubwayRoute.js"; -import { ttcAlerts } from "../components/fetch/queries.js"; export default function Line() { const params = useParams(); @@ -15,25 +13,13 @@ export default function Line() { const lineNum = parseInt(`${params.lineId}`); - const bskyAlerts = useQuery(ttcAlerts); - - const filteredBskyAlerts = - bskyAlerts.data?.feed.filter( - (skeet: { post: { record: { text: string } } }) => - lineNum < 6 - ? skeet.post.record.text.match(`Line ${lineNum}`) - : skeet.post.record.text.startsWith(`${lineNum}`) - ) ?? []; - useEffect(() => { document.title = t("lines.browserTitle", { lineNum }); }); return (
- {filteredBskyAlerts.length > 0 && ( - - )} + {t("lines.number", { lineNum })} {lineNum < 6 && }