aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/components/StopSheet.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app/components/StopSheet.tsx')
-rw-r--r--src/frontend/app/components/StopSheet.tsx154
1 files changed, 154 insertions, 0 deletions
diff --git a/src/frontend/app/components/StopSheet.tsx b/src/frontend/app/components/StopSheet.tsx
new file mode 100644
index 0000000..8075e9d
--- /dev/null
+++ b/src/frontend/app/components/StopSheet.tsx
@@ -0,0 +1,154 @@
+import React, { useEffect, useState } from "react";
+import { Sheet } from "react-modal-sheet";
+import { Link } from "react-router";
+import { useTranslation } from "react-i18next";
+import LineIcon from "./LineIcon";
+import { type StopDetails } from "../routes/estimates-$id";
+import "./StopSheet.css";
+
+interface StopSheetProps {
+ isOpen: boolean;
+ onClose: () => void;
+ stopId: number;
+ stopName: string;
+}
+
+const loadStopData = async (stopId: number): Promise<StopDetails> => {
+ const resp = await fetch(`/api/GetStopEstimates?id=${stopId}`, {
+ headers: {
+ Accept: "application/json",
+ },
+ });
+ return await resp.json();
+};
+
+export const StopSheet: React.FC<StopSheetProps> = ({
+ isOpen,
+ onClose,
+ stopId,
+ stopName,
+}) => {
+ const { t } = useTranslation();
+ const [data, setData] = useState<StopDetails | null>(null);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (isOpen && stopId) {
+ setLoading(true);
+ setData(null);
+ loadStopData(stopId)
+ .then((stopData) => {
+ setData(stopData);
+ })
+ .catch((error) => {
+ console.error("Failed to load stop data:", error);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+ }, [isOpen, stopId]);
+
+ const formatTime = (minutes: number) => {
+ if (minutes > 15) {
+ const now = new Date();
+ const arrival = new Date(now.getTime() + minutes * 60000);
+ return Intl.DateTimeFormat(
+ typeof navigator !== "undefined" ? navigator.language : "en",
+ {
+ hour: "2-digit",
+ minute: "2-digit",
+ }
+ ).format(arrival);
+ } else {
+ return `${minutes} ${t("estimates.minutes", "min")}`;
+ }
+ };
+
+ const formatDistance = (meters: number) => {
+ if (meters > 1024) {
+ return `${(meters / 1000).toFixed(1)} km`;
+ } else {
+ return `${meters} ${t("estimates.meters", "m")}`;
+ }
+ };
+
+ // Show only the next 4 arrivals
+ const limitedEstimates =
+ data?.estimates.sort((a, b) => a.minutes - b.minutes).slice(0, 4) || [];
+
+ return (
+ <Sheet
+ isOpen={isOpen}
+ onClose={onClose}
+ detent="content-height"
+ >
+ <Sheet.Container>
+ <Sheet.Header />
+ <Sheet.Content>
+ <div className="stop-sheet-content">
+ <div className="stop-sheet-header">
+ <h2 className="stop-sheet-title">{stopName}</h2>
+ <span className="stop-sheet-id">({stopId})</span>
+ </div>
+
+ {loading && (
+ <div className="stop-sheet-loading">
+ {t("common.loading", "Loading...")}
+ </div>
+ )}
+
+ {data && !loading && (
+ <>
+ <div className="stop-sheet-estimates">
+ <h3 className="stop-sheet-subtitle">
+ {t("estimates.next_arrivals", "Next arrivals")}
+ </h3>
+
+ {limitedEstimates.length === 0 ? (
+ <div className="stop-sheet-no-estimates">
+ {t("estimates.none", "No hay estimaciones disponibles")}
+ </div>
+ ) : (
+ <div className="stop-sheet-estimates-list">
+ {limitedEstimates.map((estimate, idx) => (
+ <div key={idx} className="stop-sheet-estimate-item">
+ <div className="stop-sheet-estimate-line">
+ <LineIcon line={estimate.line} />
+ </div>
+ <div className="stop-sheet-estimate-details">
+ <div className="stop-sheet-estimate-route">
+ {estimate.route}
+ </div>
+ <div className="stop-sheet-estimate-time">
+ {formatTime(estimate.minutes)}
+ {estimate.meters > -1 && (
+ <span className="stop-sheet-estimate-distance">
+ {" • "}
+ {formatDistance(estimate.meters)}
+ </span>
+ )}
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+
+ <Link
+ to={`/estimates/${stopId}`}
+ className="stop-sheet-view-all"
+ onClick={onClose}
+ >
+ {t("map.view_all_estimates", "Ver todas las estimaciones")}
+ </Link>
+ </>
+ )}
+ </div>
+ </Sheet.Content>
+ </Sheet.Container>
+ <Sheet.Backdrop />
+ </Sheet>
+ );
+};