From ee77f38cdb324cbcf12518490df77fc9e6b89282 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:52:02 +0000 Subject: Improve gallery scroll indicators and format code Co-authored-by: arielcostas <94913521+arielcostas@users.noreply.github.com> --- src/frontend/app/components/ErrorDisplay.tsx | 45 ++++-- src/frontend/app/components/GroupedTable.tsx | 6 +- src/frontend/app/components/LineIcon.css | 4 +- src/frontend/app/components/LineIcon.tsx | 15 +- src/frontend/app/components/PullToRefresh.tsx | 169 ++++++++++++--------- src/frontend/app/components/RegularTable.tsx | 2 +- src/frontend/app/components/SchedulesTable.css | 4 +- src/frontend/app/components/SchedulesTable.tsx | 85 +++++++---- .../app/components/SchedulesTableSkeleton.tsx | 42 +++-- src/frontend/app/components/ServiceAlerts.tsx | 4 +- src/frontend/app/components/StopAlert.tsx | 24 ++- src/frontend/app/components/StopGallery.css | 7 +- src/frontend/app/components/StopGallery.tsx | 51 +++++-- src/frontend/app/components/StopItem.tsx | 4 +- src/frontend/app/components/StopItemSkeleton.tsx | 33 +++- src/frontend/app/components/StopSheet.tsx | 52 ++++--- src/frontend/app/components/StopSheetSkeleton.tsx | 30 +++- src/frontend/app/components/TimetableSkeleton.tsx | 10 +- 18 files changed, 398 insertions(+), 189 deletions(-) (limited to 'src/frontend/app/components') diff --git a/src/frontend/app/components/ErrorDisplay.tsx b/src/frontend/app/components/ErrorDisplay.tsx index 3c91db6..f63c995 100644 --- a/src/frontend/app/components/ErrorDisplay.tsx +++ b/src/frontend/app/components/ErrorDisplay.tsx @@ -5,7 +5,7 @@ import "./ErrorDisplay.css"; interface ErrorDisplayProps { error: { - type: 'network' | 'server' | 'unknown'; + type: "network" | "server" | "unknown"; status?: number; message?: string; }; @@ -18,15 +18,15 @@ export const ErrorDisplay: React.FC = ({ error, onRetry, title, - className = "" + className = "", }) => { const { t } = useTranslation(); const getErrorIcon = () => { switch (error.type) { - case 'network': + case "network": return ; - case 'server': + case "server": return ; default: return ; @@ -35,21 +35,38 @@ export const ErrorDisplay: React.FC = ({ const getErrorMessage = () => { switch (error.type) { - case 'network': - return t("errors.network", "No hay conexión a internet. Comprueba tu conexión y vuelve a intentarlo."); - case 'server': + case "network": + return t( + "errors.network", + "No hay conexión a internet. Comprueba tu conexión y vuelve a intentarlo.", + ); + case "server": if (error.status === 404) { - return t("errors.not_found", "No se encontraron datos para esta parada."); + return t( + "errors.not_found", + "No se encontraron datos para esta parada.", + ); } if (error.status === 500) { - return t("errors.server_error", "Error del servidor. Inténtalo de nuevo más tarde."); + return t( + "errors.server_error", + "Error del servidor. Inténtalo de nuevo más tarde.", + ); } if (error.status && error.status >= 400) { - return t("errors.client_error", "Error en la solicitud. Verifica que la parada existe."); + return t( + "errors.client_error", + "Error en la solicitud. Verifica que la parada existe.", + ); } - return t("errors.server_generic", "Error del servidor ({{status}})", { status: error.status || 'desconocido' }); + return t("errors.server_generic", "Error del servidor ({{status}})", { + status: error.status || "desconocido", + }); default: - return error.message || t("errors.unknown", "Ha ocurrido un error inesperado."); + return ( + error.message || + t("errors.unknown", "Ha ocurrido un error inesperado.") + ); } }; @@ -57,9 +74,9 @@ export const ErrorDisplay: React.FC = ({ if (title) return title; switch (error.type) { - case 'network': + case "network": return t("errors.network_title", "Sin conexión"); - case 'server': + case "server": return t("errors.server_title", "Error del servidor"); default: return t("errors.unknown_title", "Error"); diff --git a/src/frontend/app/components/GroupedTable.tsx b/src/frontend/app/components/GroupedTable.tsx index 9bdd5a0..f116537 100644 --- a/src/frontend/app/components/GroupedTable.tsx +++ b/src/frontend/app/components/GroupedTable.tsx @@ -8,7 +8,11 @@ interface GroupedTable { regionConfig: RegionConfig; } -export const GroupedTable: React.FC = ({ data, dataDate, regionConfig }) => { +export const GroupedTable: React.FC = ({ + data, + dataDate, + regionConfig, +}) => { const formatDistance = (meters: number) => { if (meters > 1024) { return `${(meters / 1000).toFixed(1)} km`; diff --git a/src/frontend/app/components/LineIcon.css b/src/frontend/app/components/LineIcon.css index 94d5848..fdfdd06 100644 --- a/src/frontend/app/components/LineIcon.css +++ b/src/frontend/app/components/LineIcon.css @@ -87,7 +87,6 @@ --line-santiago-p6: #999999; --line-santiago-p7: #d2438c; --line-santiago-p8: #e28c3a; - } .line-icon { @@ -118,6 +117,5 @@ border-radius: 50%; font: 600 14px / 1 monospace; - letter-spacing: .05em; + letter-spacing: 0.05em; } - diff --git a/src/frontend/app/components/LineIcon.tsx b/src/frontend/app/components/LineIcon.tsx index ef05987..8e9a4bd 100644 --- a/src/frontend/app/components/LineIcon.tsx +++ b/src/frontend/app/components/LineIcon.tsx @@ -8,7 +8,11 @@ interface LineIconProps { rounded?: boolean; } -const LineIcon: React.FC = ({ line, region = "vigo", rounded = false }) => { +const LineIcon: React.FC = ({ + line, + region = "vigo", + rounded = false, +}) => { const formattedLine = useMemo(() => { return /^[a-zA-Z]/.test(line) ? line : `L${line}`; }, [line]); @@ -17,8 +21,13 @@ const LineIcon: React.FC = ({ line, region = "vigo", rounded = fa return ( {line} diff --git a/src/frontend/app/components/PullToRefresh.tsx b/src/frontend/app/components/PullToRefresh.tsx index 9def8f5..d5ea51b 100644 --- a/src/frontend/app/components/PullToRefresh.tsx +++ b/src/frontend/app/components/PullToRefresh.tsx @@ -27,75 +27,97 @@ export const PullToRefresh: React.FC = ({ const rotate = useTransform(y, [0, threshold], [0, 180]); const isAtPageTop = useCallback(() => { - const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + const scrollTop = + window.pageYOffset || + document.documentElement.scrollTop || + document.body.scrollTop || + 0; return scrollTop <= 10; // Increased tolerance to 10px }, []); - const handleTouchStart = useCallback((e: TouchEvent) => { - // Very strict check - must be at absolute top - const windowScroll = window.pageYOffset || window.scrollY || 0; - const htmlScroll = document.documentElement.scrollTop; - const bodyScroll = document.body.scrollTop; - const containerScroll = containerRef.current?.scrollTop || 0; - const parentScroll = containerRef.current?.parentElement?.scrollTop || 0; - const maxScroll = Math.max(windowScroll, htmlScroll, bodyScroll, containerScroll, parentScroll); - - if (maxScroll > 0 || isRefreshing) { - setIsPulling(false); - setIsActive(false); - return; - } - - startY.current = e.touches[0].clientY; - setIsPulling(true); - }, [isRefreshing]); - - const handleTouchMove = useCallback((e: TouchEvent) => { - if (!isPulling) return; - - // Continuously check if we're still at the top during the gesture - const windowScroll = window.pageYOffset || window.scrollY || 0; - const htmlScroll = document.documentElement.scrollTop; - const bodyScroll = document.body.scrollTop; - const containerScroll = containerRef.current?.scrollTop || 0; - const parentScroll = containerRef.current?.parentElement?.scrollTop || 0; - const maxScroll = Math.max(windowScroll, htmlScroll, bodyScroll, containerScroll, parentScroll); - - if (maxScroll > 10) { - // Cancel pull-to-refresh if we've scrolled away from top - setIsPulling(false); - setIsActive(false); - y.set(0); - return; - } + const handleTouchStart = useCallback( + (e: TouchEvent) => { + // Very strict check - must be at absolute top + const windowScroll = window.pageYOffset || window.scrollY || 0; + const htmlScroll = document.documentElement.scrollTop; + const bodyScroll = document.body.scrollTop; + const containerScroll = containerRef.current?.scrollTop || 0; + const parentScroll = containerRef.current?.parentElement?.scrollTop || 0; + const maxScroll = Math.max( + windowScroll, + htmlScroll, + bodyScroll, + containerScroll, + parentScroll, + ); + + if (maxScroll > 0 || isRefreshing) { + setIsPulling(false); + setIsActive(false); + return; + } - const currentY = e.touches[0].clientY; - const pullDistance = currentY - startY.current; + startY.current = e.touches[0].clientY; + setIsPulling(true); + }, + [isRefreshing], + ); - if (pullDistance > 0) { - // Only prevent default when the event is cancelable - if (e.cancelable) { - e.preventDefault(); + const handleTouchMove = useCallback( + (e: TouchEvent) => { + if (!isPulling) return; + + // Continuously check if we're still at the top during the gesture + const windowScroll = window.pageYOffset || window.scrollY || 0; + const htmlScroll = document.documentElement.scrollTop; + const bodyScroll = document.body.scrollTop; + const containerScroll = containerRef.current?.scrollTop || 0; + const parentScroll = containerRef.current?.parentElement?.scrollTop || 0; + const maxScroll = Math.max( + windowScroll, + htmlScroll, + bodyScroll, + containerScroll, + parentScroll, + ); + + if (maxScroll > 10) { + // Cancel pull-to-refresh if we've scrolled away from top + setIsPulling(false); + setIsActive(false); + y.set(0); + return; } - const dampedDistance = Math.min(pullDistance * 0.5, threshold * 1.2); - y.set(dampedDistance); + const currentY = e.touches[0].clientY; + const pullDistance = currentY - startY.current; - if (dampedDistance >= threshold && !isActive) { - setIsActive(true); - // Only vibrate if user activation is available and vibrate is supported - if (navigator.vibrate && navigator.userActivation?.hasBeenActive) { - navigator.vibrate(50); + if (pullDistance > 0) { + // Only prevent default when the event is cancelable + if (e.cancelable) { + e.preventDefault(); } - } else if (dampedDistance < threshold && isActive) { + + const dampedDistance = Math.min(pullDistance * 0.5, threshold * 1.2); + y.set(dampedDistance); + + if (dampedDistance >= threshold && !isActive) { + setIsActive(true); + // Only vibrate if user activation is available and vibrate is supported + if (navigator.vibrate && navigator.userActivation?.hasBeenActive) { + navigator.vibrate(50); + } + } else if (dampedDistance < threshold && isActive) { + setIsActive(false); + } + } else { + // Reset if pulling up + y.set(0); setIsActive(false); } - } else { - // Reset if pulling up - y.set(0); - setIsActive(false); - } - }, [isPulling, threshold, isActive, y]); + }, + [isPulling, threshold, isActive, y], + ); const handleTouchEnd = useCallback(async () => { if (!isPulling) return; @@ -106,7 +128,7 @@ export const PullToRefresh: React.FC = ({ try { await onRefresh(); } catch (error) { - console.error('Refresh failed:', error); + console.error("Refresh failed:", error); } } @@ -121,14 +143,18 @@ export const PullToRefresh: React.FC = ({ if (!container) return; // Use passive: false for touchmove to allow preventDefault - container.addEventListener('touchstart', handleTouchStart, { passive: true }); - container.addEventListener('touchmove', handleTouchMove, { passive: false }); - container.addEventListener('touchend', handleTouchEnd, { passive: true }); + container.addEventListener("touchstart", handleTouchStart, { + passive: true, + }); + container.addEventListener("touchmove", handleTouchMove, { + passive: false, + }); + container.addEventListener("touchend", handleTouchEnd, { passive: true }); return () => { - container.removeEventListener('touchstart', handleTouchStart); - container.removeEventListener('touchmove', handleTouchMove); - container.removeEventListener('touchend', handleTouchEnd); + container.removeEventListener("touchstart", handleTouchStart); + container.removeEventListener("touchmove", handleTouchMove); + container.removeEventListener("touchend", handleTouchEnd); }; }, [handleTouchStart, handleTouchMove, handleTouchEnd]); @@ -136,25 +162,20 @@ export const PullToRefresh: React.FC = ({
{/* Simple indicator */} {isPulling && ( - + )} {/* Normal content - no transform interference */} -
- {children} -
+
{children}
); }; diff --git a/src/frontend/app/components/RegularTable.tsx b/src/frontend/app/components/RegularTable.tsx index 868332f..baa3804 100644 --- a/src/frontend/app/components/RegularTable.tsx +++ b/src/frontend/app/components/RegularTable.tsx @@ -24,7 +24,7 @@ export const RegularTable: React.FC = ({ { hour: "2-digit", minute: "2-digit", - } + }, ).format(arrival); }; diff --git a/src/frontend/app/components/SchedulesTable.css b/src/frontend/app/components/SchedulesTable.css index 6d2f201..74d7569 100644 --- a/src/frontend/app/components/SchedulesTable.css +++ b/src/frontend/app/components/SchedulesTable.css @@ -22,7 +22,9 @@ border: 1px solid var(--card-border); border-radius: 10px; padding: 1.25rem; - transition: background-color 0.2s ease, border 0.2s ease; + transition: + background-color 0.2s ease, + border 0.2s ease; } /* Next upcoming service: slight emphasis */ diff --git a/src/frontend/app/components/SchedulesTable.tsx b/src/frontend/app/components/SchedulesTable.tsx index a3bbd9f..9f3f062 100644 --- a/src/frontend/app/components/SchedulesTable.tsx +++ b/src/frontend/app/components/SchedulesTable.tsx @@ -24,7 +24,7 @@ export type ScheduledTable = { terminus_code: string; terminus_name: string; terminus_time: string; -} +}; interface TimetableTableProps { data: ScheduledTable[]; @@ -34,11 +34,11 @@ interface TimetableTableProps { // Utility function to parse service ID and get the turn number const parseServiceId = (serviceId: string): string => { - const parts = serviceId.split('_'); - if (parts.length === 0) return ''; + const parts = serviceId.split("_"); + if (parts.length === 0) return ""; const lastPart = parts[parts.length - 1]; - if (lastPart.length < 6) return ''; + if (lastPart.length < 6) return ""; const last6 = lastPart.slice(-6); const lineCode = last6.slice(0, 3); @@ -52,15 +52,32 @@ const parseServiceId = (serviceId: string): string => { let displayLine: string; switch (lineNumber) { - case 1: displayLine = "C1"; break; - case 3: displayLine = "C3"; break; - case 30: displayLine = "N1"; break; - case 33: displayLine = "N4"; break; - case 8: displayLine = "A"; break; - case 101: displayLine = "H"; break; - case 150: displayLine = "REF"; break; - case 500: displayLine = "TUR"; break; - default: displayLine = `L${lineNumber}`; + case 1: + displayLine = "C1"; + break; + case 3: + displayLine = "C3"; + break; + case 30: + displayLine = "N1"; + break; + case 33: + displayLine = "N4"; + break; + case 8: + displayLine = "A"; + break; + case 101: + displayLine = "H"; + break; + case 150: + displayLine = "REF"; + break; + case 500: + displayLine = "TUR"; + break; + default: + displayLine = `L${lineNumber}`; } return `${displayLine}-${turnNumber}`; @@ -68,21 +85,26 @@ const parseServiceId = (serviceId: string): string => { // Utility function to compare times const timeToMinutes = (time: string): number => { - const [hours, minutes] = time.split(':').map(Number); + const [hours, minutes] = time.split(":").map(Number); return hours * 60 + minutes; }; // Utility function to find nearby entries -const findNearbyEntries = (entries: ScheduledTable[], currentTime: string, before: number = 4, after: number = 4): ScheduledTable[] => { +const findNearbyEntries = ( + entries: ScheduledTable[], + currentTime: string, + before: number = 4, + after: number = 4, +): ScheduledTable[] => { if (!currentTime) return entries.slice(0, before + after); const currentMinutes = timeToMinutes(currentTime); - const sortedEntries = [...entries].sort((a, b) => - timeToMinutes(a.calling_time) - timeToMinutes(b.calling_time) + const sortedEntries = [...entries].sort( + (a, b) => timeToMinutes(a.calling_time) - timeToMinutes(b.calling_time), ); - let currentIndex = sortedEntries.findIndex(entry => - timeToMinutes(entry.calling_time) >= currentMinutes + let currentIndex = sortedEntries.findIndex( + (entry) => timeToMinutes(entry.calling_time) >= currentMinutes, ); if (currentIndex === -1) { @@ -99,21 +121,24 @@ const findNearbyEntries = (entries: ScheduledTable[], currentTime: string, befor export const SchedulesTable: React.FC = ({ data, showAll = false, - currentTime + currentTime, }) => { const { t } = useTranslation(); const { region } = useApp(); - const displayData = showAll ? data : findNearbyEntries(data, currentTime || ''); - const nowMinutes = currentTime ? timeToMinutes(currentTime) : timeToMinutes(new Date().toTimeString().slice(0, 8)); + const displayData = showAll + ? data + : findNearbyEntries(data, currentTime || ""); + const nowMinutes = currentTime + ? timeToMinutes(currentTime) + : timeToMinutes(new Date().toTimeString().slice(0, 8)); return (
{showAll ? t("timetable.fullCaption", "Horarios teóricos de la parada") - : t("timetable.nearbyCaption", "Próximos horarios teóricos") - } + : t("timetable.nearbyCaption", "Próximos horarios teóricos")}
@@ -127,7 +152,7 @@ export const SchedulesTable: React.FC = ({ style={{ background: isPast ? "var(--surface-past, #f3f3f3)" - : "var(--surface-future, #fff)" + : "var(--surface-future, #fff)", }} >
@@ -139,7 +164,9 @@ export const SchedulesTable: React.FC = ({ {entry.route && entry.route.trim() ? ( {entry.route} ) : ( - {t("timetable.noDestination", "Línea")} {entry.line} + + {t("timetable.noDestination", "Línea")} {entry.line} + )}
@@ -155,7 +182,7 @@ export const SchedulesTable: React.FC = ({ {parseServiceId(entry.service_id)} {entry.next_streets.length > 0 && ( - — {entry.next_streets.join(' — ')} + — {entry.next_streets.join(" — ")} )}
@@ -164,7 +191,9 @@ export const SchedulesTable: React.FC = ({ })} {displayData.length === 0 && ( -

{t("timetable.noData", "No hay datos de horarios disponibles")}

+

+ {t("timetable.noData", "No hay datos de horarios disponibles")} +

)} ); diff --git a/src/frontend/app/components/SchedulesTableSkeleton.tsx b/src/frontend/app/components/SchedulesTableSkeleton.tsx index 50ba94d..3ae9729 100644 --- a/src/frontend/app/components/SchedulesTableSkeleton.tsx +++ b/src/frontend/app/components/SchedulesTableSkeleton.tsx @@ -8,7 +8,7 @@ interface EstimatesTableSkeletonProps { } export const SchedulesTableSkeleton: React.FC = ({ - rows = 3 + rows = 3, }) => { const { t } = useTranslation(); @@ -32,13 +32,23 @@ export const SchedulesTableSkeleton: React.FC = ({ {Array.from({ length: rows }, (_, index) => ( - + -
+
@@ -59,10 +69,9 @@ interface EstimatesGroupedSkeletonProps { rowsPerGroup?: number; } -export const EstimatesGroupedSkeleton: React.FC = ({ - groups = 3, - rowsPerGroup = 2 -}) => { +export const EstimatesGroupedSkeleton: React.FC< + EstimatesGroupedSkeletonProps +> = ({ groups = 3, rowsPerGroup = 2 }) => { const { t } = useTranslation(); return ( @@ -85,17 +94,30 @@ export const EstimatesGroupedSkeleton: React.FC = {Array.from({ length: groups }, (_, groupIndex) => ( {Array.from({ length: rowsPerGroup }, (_, rowIndex) => ( - + {rowIndex === 0 && ( - + )} -
+
diff --git a/src/frontend/app/components/ServiceAlerts.tsx b/src/frontend/app/components/ServiceAlerts.tsx index 7966f2a..eba8a92 100644 --- a/src/frontend/app/components/ServiceAlerts.tsx +++ b/src/frontend/app/components/ServiceAlerts.tsx @@ -12,7 +12,9 @@ const ServiceAlerts: React.FC = () => {
ℹ️
{t("stoplist.alerts_coming_soon")}
-
{t("stoplist.alerts_description")}
+
+ {t("stoplist.alerts_description")} +
diff --git a/src/frontend/app/components/StopAlert.tsx b/src/frontend/app/components/StopAlert.tsx index d969108..f1356e6 100644 --- a/src/frontend/app/components/StopAlert.tsx +++ b/src/frontend/app/components/StopAlert.tsx @@ -8,7 +8,10 @@ interface StopAlertProps { compact?: boolean; } -export const StopAlert: React.FC = ({ stop, compact = false }) => { +export const StopAlert: React.FC = ({ + stop, + compact = false, +}) => { // Don't render anything if there's no alert content const hasContent = stop.title || stop.message; if (!hasContent) { @@ -28,11 +31,30 @@ export const StopAlert: React.FC = ({ stop, compact = false }) = }, [stop.alert]); return ( +<<<<<<< HEAD
{alertIcon}
{stop.title &&
{stop.title}
} {stop.message &&
{stop.message}
} +======= +
+
+ {isError ? : } +
+
+ {stop.title &&
{stop.title}
} + {stop.message && ( +
{stop.message}
+ )} + {stop.alternateCodes && stop.alternateCodes.length > 0 && ( +
+ Alternative stops: {stop.alternateCodes.join(", ")} +
+ )} +>>>>>>> 88e0621 (Improve gallery scroll indicators and format code)
); diff --git a/src/frontend/app/components/StopGallery.css b/src/frontend/app/components/StopGallery.css index f53f2a5..adba001 100644 --- a/src/frontend/app/components/StopGallery.css +++ b/src/frontend/app/components/StopGallery.css @@ -57,7 +57,10 @@ .gallery-item-link { display: block; padding: 1rem; - background-color: var(--card-background-color, var(--message-background-color)); + background-color: var( + --card-background-color, + var(--message-background-color) + ); border: 1px solid var(--border-color); border-radius: 12px; text-decoration: none; @@ -146,7 +149,7 @@ .gallery-item { flex: 0 0 320px; } - + .gallery-scroll-container { margin: 0; padding: 0; diff --git a/src/frontend/app/components/StopGallery.tsx b/src/frontend/app/components/StopGallery.tsx index 7dda637..3646fdd 100644 --- a/src/frontend/app/components/StopGallery.tsx +++ b/src/frontend/app/components/StopGallery.tsx @@ -1,5 +1,4 @@ -import React, { useRef } from "react"; -import { motion, useMotionValue } from "framer-motion"; +import React, { useRef, useState, useEffect } from "react"; import { type Stop } from "../data/StopDataProvider"; import StopGalleryItem from "./StopGalleryItem"; import "./StopGallery.css"; @@ -10,9 +9,28 @@ interface StopGalleryProps { emptyMessage?: string; } -const StopGallery: React.FC = ({ stops, title, emptyMessage }) => { +const StopGallery: React.FC = ({ + stops, + title, + emptyMessage, +}) => { const scrollRef = useRef(null); - const x = useMotionValue(0); + const [activeIndex, setActiveIndex] = useState(0); + + useEffect(() => { + const element = scrollRef.current; + if (!element) return; + + const handleScroll = () => { + const scrollLeft = element.scrollLeft; + const itemWidth = element.scrollWidth / stops.length; + const index = Math.round(scrollLeft / itemWidth); + setActiveIndex(index); + }; + + element.addEventListener("scroll", handleScroll); + return () => element.removeEventListener("scroll", handleScroll); + }, [stops.length]); if (stops.length === 0 && emptyMessage) { return ( @@ -33,24 +51,25 @@ const StopGallery: React.FC = ({ stops, title, emptyMessage })

{title}

{stops.length}
- - + +
{stops.map((stop) => ( ))}
- - -
- {stops.map((_, index) => ( -
- ))}
+ + {stops.length > 1 && ( +
+ {stops.map((_, index) => ( +
+ ))} +
+ )}
); }; diff --git a/src/frontend/app/components/StopItem.tsx b/src/frontend/app/components/StopItem.tsx index 7d89d7d..ae51df8 100644 --- a/src/frontend/app/components/StopItem.tsx +++ b/src/frontend/app/components/StopItem.tsx @@ -17,7 +17,9 @@ const StopItem: React.FC = ({ stop }) => { {stop.favourite && } ( {stop.stopId}) {StopDataProvider.getDisplayName(region, stop)}
- {stop.lines?.map((line) => )} + {stop.lines?.map((line) => ( + + ))}
diff --git a/src/frontend/app/components/StopItemSkeleton.tsx b/src/frontend/app/components/StopItemSkeleton.tsx index 9133393..76559de 100644 --- a/src/frontend/app/components/StopItemSkeleton.tsx +++ b/src/frontend/app/components/StopItemSkeleton.tsx @@ -3,14 +3,15 @@ import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; import "react-loading-skeleton/dist/skeleton.css"; interface StopItemSkeletonProps { - showId?: boolean; - stopId?: number; + showId?: boolean; + stopId?: number; } const StopItemSkeleton: React.FC = ({ - showId = false, - stopId + showId = false, + stopId, }) => { +<<<<<<< HEAD return (
  • @@ -37,6 +38,30 @@ const StopItemSkeleton: React.FC = ({
  • ); +======= + return ( + +
  • +
    + {showId && stopId && <>({stopId}) } + +
    + +
    +
    +
  • +
    + ); +>>>>>>> 88e0621 (Improve gallery scroll indicators and format code) }; export default StopItemSkeleton; diff --git a/src/frontend/app/components/StopSheet.tsx b/src/frontend/app/components/StopSheet.tsx index 695b18e..422558b 100644 --- a/src/frontend/app/components/StopSheet.tsx +++ b/src/frontend/app/components/StopSheet.tsx @@ -20,12 +20,15 @@ interface StopSheetProps { } interface ErrorInfo { - type: 'network' | 'server' | 'unknown'; + type: "network" | "server" | "unknown"; status?: number; message?: string; } -const loadStopData = async (region: RegionId, stopId: number): Promise => { +const loadStopData = async ( + region: RegionId, + stopId: number, +): Promise => { const regionConfig = getRegionConfig(region); const resp = await fetch(`${regionConfig.estimatesEndpoint}?id=${stopId}`, { headers: { @@ -43,7 +46,7 @@ const loadStopData = async (region: RegionId, stopId: number): Promise = ({ isOpen, onClose, - stop + stop, }) => { const { t } = useTranslation(); const { region } = useApp(); @@ -55,20 +58,23 @@ export const StopSheet: React.FC = ({ const parseError = (error: any): ErrorInfo => { if (!navigator.onLine) { - return { type: 'network', message: 'No internet connection' }; + return { type: "network", message: "No internet connection" }; } - if (error.message?.includes('Failed to fetch') || error.message?.includes('NetworkError')) { - return { type: 'network' }; + if ( + error.message?.includes("Failed to fetch") || + error.message?.includes("NetworkError") + ) { + return { type: "network" }; } - if (error.message?.includes('HTTP')) { + if (error.message?.includes("HTTP")) { const statusMatch = error.message.match(/HTTP (\d+):/); const status = statusMatch ? parseInt(statusMatch[1]) : undefined; - return { type: 'server', status }; + return { type: "server", status }; } - return { type: 'unknown', message: error.message }; + return { type: "unknown", message: error.message }; }; const loadData = async () => { @@ -103,7 +109,7 @@ export const StopSheet: React.FC = ({ { hour: "2-digit", minute: "2-digit", - } + }, ).format(arrival); } else { return `${minutes} ${t("estimates.minutes", "min")}`; @@ -123,11 +129,7 @@ export const StopSheet: React.FC = ({ data?.sort((a, b) => a.minutes - b.minutes).slice(0, 4) || []; return ( - + @@ -153,7 +155,10 @@ export const StopSheet: React.FC = ({ ) : data ? ( @@ -180,7 +185,9 @@ export const StopSheet: React.FC = ({
    -
    +
    {formatTime(estimate.minutes)}
    @@ -203,7 +210,7 @@ export const StopSheet: React.FC = ({ {lastUpdated.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", - second: "2-digit" + second: "2-digit", })}
    )} @@ -215,7 +222,9 @@ export const StopSheet: React.FC = ({ disabled={loading} title={t("estimates.reload", "Recargar estimaciones")} > - + {t("estimates.reload", "Recargar")} @@ -224,7 +233,10 @@ export const StopSheet: React.FC = ({ className="stop-sheet-view-all" onClick={onClose} > - {t("map.view_all_estimates", "Ver todas las estimaciones")} + {t( + "map.view_all_estimates", + "Ver todas las estimaciones", + )}
    diff --git a/src/frontend/app/components/StopSheetSkeleton.tsx b/src/frontend/app/components/StopSheetSkeleton.tsx index 6870af2..36ce546 100644 --- a/src/frontend/app/components/StopSheetSkeleton.tsx +++ b/src/frontend/app/components/StopSheetSkeleton.tsx @@ -8,7 +8,7 @@ interface StopSheetSkeletonProps { } export const StopSheetSkeleton: React.FC = ({ - rows = 4 + rows = 4, }) => { const { t } = useTranslation(); @@ -23,7 +23,11 @@ export const StopSheetSkeleton: React.FC = ({ {Array.from({ length: rows }, (_, index) => (
    - +
    @@ -45,18 +49,32 @@ export const StopSheetSkeleton: React.FC = ({
    -
    +
    +<<<<<<< HEAD
    +======= +
    +>>>>>>> 88e0621 (Improve gallery scroll indicators and format code)
    diff --git a/src/frontend/app/components/TimetableSkeleton.tsx b/src/frontend/app/components/TimetableSkeleton.tsx index 79c7725..cd5bc81 100644 --- a/src/frontend/app/components/TimetableSkeleton.tsx +++ b/src/frontend/app/components/TimetableSkeleton.tsx @@ -8,7 +8,7 @@ interface TimetableSkeletonProps { } export const TimetableSkeleton: React.FC = ({ - rows = 4 + rows = 4, }) => { const { t } = useTranslation(); @@ -24,7 +24,11 @@ export const TimetableSkeleton: React.FC = ({
    - +
    @@ -48,7 +52,7 @@ export const TimetableSkeleton: React.FC = ({ style={{ display: "inline-block", borderRadius: "3px", - marginRight: "0.5rem" + marginRight: "0.5rem", }} />