From 80bcf4a5f29ab926c2208d5efb4c19087c600323 Mon Sep 17 00:00:00 2001
From: Ariel Costas Guerrero <ariel@costas.dev>
Date: Sun, 7 Sep 2025 19:22:28 +0200
Subject: feat: Enhance StopSheet component with error handling and loading
 states

- Added skeleton loading state to StopSheet for better UX during data fetch.
- Implemented error handling with descriptive messages for network and server errors.
- Introduced manual refresh functionality to reload stop estimates.
- Updated styles for loading and error states.
- Created StopSheetSkeleton and TimetableSkeleton components for consistent loading indicators.

feat: Improve StopList component with loading indicators and network data fetching

- Integrated loading state for StopList while fetching stops from the network.
- Added skeleton loading indicators for favourite and recent stops.
- Refactored data fetching logic to include favourite and recent stops with full data.
- Enhanced user experience with better loading and error handling.

feat: Update Timetable component with loading and error handling

- Added loading skeletons to Timetable for improved user experience.
- Implemented error handling for timetable data fetching.
- Refactored data loading logic to handle errors gracefully and provide retry options.

chore: Update package dependencies

- Upgraded react-router, lucide-react, and other dependencies to their latest versions.
- Updated types for TypeScript compatibility.
---
 src/frontend/app/components/ErrorDisplay.tsx | 84 ++++++++++++++++++++++++++++
 1 file changed, 84 insertions(+)
 create mode 100644 src/frontend/app/components/ErrorDisplay.tsx

(limited to 'src/frontend/app/components/ErrorDisplay.tsx')

diff --git a/src/frontend/app/components/ErrorDisplay.tsx b/src/frontend/app/components/ErrorDisplay.tsx
new file mode 100644
index 0000000..3c91db6
--- /dev/null
+++ b/src/frontend/app/components/ErrorDisplay.tsx
@@ -0,0 +1,84 @@
+import React from "react";
+import { AlertTriangle, RefreshCw, Wifi, WifiOff } from "lucide-react";
+import { useTranslation } from "react-i18next";
+import "./ErrorDisplay.css";
+
+interface ErrorDisplayProps {
+  error: {
+    type: 'network' | 'server' | 'unknown';
+    status?: number;
+    message?: string;
+  };
+  onRetry?: () => void;
+  title?: string;
+  className?: string;
+}
+
+export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({
+  error,
+  onRetry,
+  title,
+  className = ""
+}) => {
+  const { t } = useTranslation();
+
+  const getErrorIcon = () => {
+    switch (error.type) {
+      case 'network':
+        return <WifiOff className="error-icon" />;
+      case 'server':
+        return <AlertTriangle className="error-icon" />;
+      default:
+        return <AlertTriangle className="error-icon" />;
+    }
+  };
+
+  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':
+        if (error.status === 404) {
+          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.");
+        }
+        if (error.status && error.status >= 400) {
+          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' });
+      default:
+        return error.message || t("errors.unknown", "Ha ocurrido un error inesperado.");
+    }
+  };
+
+  const getErrorTitle = () => {
+    if (title) return title;
+
+    switch (error.type) {
+      case 'network':
+        return t("errors.network_title", "Sin conexión");
+      case 'server':
+        return t("errors.server_title", "Error del servidor");
+      default:
+        return t("errors.unknown_title", "Error");
+    }
+  };
+
+  return (
+    <div className={`error-display ${className}`}>
+      <div className="error-content">
+        {getErrorIcon()}
+        <h3 className="error-title">{getErrorTitle()}</h3>
+        <p className="error-message">{getErrorMessage()}</p>
+        {onRetry && (
+          <button className="error-retry-button" onClick={onRetry}>
+            <RefreshCw className="retry-icon" />
+            {t("errors.retry", "Reintentar")}
+          </button>
+        )}
+      </div>
+    </div>
+  );
+};
-- 
cgit v1.3

