From e3a631664eda8a6393f87907d8ff1ceab7dc17c6 Mon Sep 17 00:00:00 2001 From: Thibaud Moustier Date: Wed, 25 Feb 2026 15:54:11 +0100 Subject: [PATCH] Mobile : Changement Ui Dashboard --- Wallette/mobile/App.tsx | 55 ++- Wallette/mobile/package-lock.json | 51 +-- Wallette/mobile/package.json | 1 + .../mobile/src/screens/DashboardScreen.tsx | 362 ++++++++++++------ 4 files changed, 317 insertions(+), 152 deletions(-) diff --git a/Wallette/mobile/App.tsx b/Wallette/mobile/App.tsx index 4540509..c1f7b6f 100644 --- a/Wallette/mobile/App.tsx +++ b/Wallette/mobile/App.tsx @@ -1,5 +1,7 @@ import { NavigationContainer } from "@react-navigation/native"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; +import { TouchableOpacity, View } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; import DashboardScreen from "./src/screens/DashboardScreen"; import SettingsScreen from "./src/screens/SettingsScreen"; @@ -7,8 +9,6 @@ import HistoryScreen from "./src/screens/HistoryScreen"; import AlertsScreen from "./src/screens/AlertsScreen"; import StrategyScreen from "./src/screens/StrategyScreen"; - -// Types des routes (pour éviter les erreurs de navigation) export type RootStackParamList = { Dashboard: undefined; Settings: undefined; @@ -26,7 +26,25 @@ export default function App() { ({ + title: "Dashboard", + // ✅ icônes header à droite + headerRight: () => ( + + navigation.navigate("Alerts")}> + + + + navigation.navigate("History")}> + + + + navigation.navigate("Settings")}> + + + + ), + })} /> - - - - - + + + ); diff --git a/Wallette/mobile/package-lock.json b/Wallette/mobile/package-lock.json index 212ee14..fd361d2 100644 --- a/Wallette/mobile/package-lock.json +++ b/Wallette/mobile/package-lock.json @@ -8,6 +8,7 @@ "name": "wall-e-tte", "version": "1.0.0", "dependencies": { + "@expo/vector-icons": "^15.0.3", "@react-native-async-storage/async-storage": "2.2.0", "@react-navigation/native": "^7.1.28", "@react-navigation/native-stack": "^7.13.0", @@ -2197,6 +2198,17 @@ "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", "license": "MIT" }, + "node_modules/@expo/vector-icons": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz", + "integrity": "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw==", + "license": "MIT", + "peerDependencies": { + "expo-font": ">=14.0.4", + "react": "*", + "react-native": "*" + } + }, "node_modules/@expo/ws-tunnel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-1.0.6.tgz", @@ -4733,6 +4745,20 @@ "react-native": "*" } }, + "node_modules/expo-font": { + "version": "14.0.11", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", + "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", + "license": "MIT", + "dependencies": { + "fontfaceobserver": "^2.1.0" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "3.0.24", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz", @@ -5033,17 +5059,6 @@ } } }, - "node_modules/expo/node_modules/@expo/vector-icons": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.3.tgz", - "integrity": "sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA==", - "license": "MIT", - "peerDependencies": { - "expo-font": ">=14.0.4", - "react": "*", - "react-native": "*" - } - }, "node_modules/expo/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -5176,20 +5191,6 @@ "react-native": "*" } }, - "node_modules/expo/node_modules/expo-font": { - "version": "14.0.11", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", - "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", - "license": "MIT", - "dependencies": { - "fontfaceobserver": "^2.1.0" - }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, "node_modules/expo/node_modules/expo-keep-awake": { "version": "15.0.8", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-15.0.8.tgz", diff --git a/Wallette/mobile/package.json b/Wallette/mobile/package.json index 3500e3a..0db1739 100644 --- a/Wallette/mobile/package.json +++ b/Wallette/mobile/package.json @@ -9,6 +9,7 @@ "web": "expo start --web" }, "dependencies": { + "@expo/vector-icons": "^15.0.3", "@react-native-async-storage/async-storage": "2.2.0", "@react-navigation/native": "^7.1.28", "@react-navigation/native-stack": "^7.13.0", diff --git a/Wallette/mobile/src/screens/DashboardScreen.tsx b/Wallette/mobile/src/screens/DashboardScreen.tsx index 7064954..61905dd 100644 --- a/Wallette/mobile/src/screens/DashboardScreen.tsx +++ b/Wallette/mobile/src/screens/DashboardScreen.tsx @@ -1,5 +1,12 @@ -import { View, Text, StyleSheet, ScrollView } from "react-native"; -import { useState, useCallback, useEffect } from "react"; +import { + View, + Text, + StyleSheet, + ScrollView, + TouchableOpacity, + useWindowDimensions, +} from "react-native"; +import { useState, useCallback, useEffect, useMemo } from "react"; import { SafeAreaView } from "react-native-safe-area-context"; import { useFocusEffect, useNavigation } from "@react-navigation/native"; @@ -8,56 +15,41 @@ import { fetchDashboardSummary } from "../services/dashboardService"; import { loadSettings } from "../utils/settingsStorage"; import type { UserSettings } from "../models/UserSettings"; -import MarketCard from "../components/MarketCard"; -import StrategyCard from "../components/StrategyCard"; -import WalletCard from "../components/WalletCard"; -import ActionsCard from "../components/ActionsCard"; - import { ui } from "../components/ui/uiStyles"; -// ✅ Socket.IO import { socketService } from "../services/socketService"; import { SERVER_URL } from "../config/env"; import type { Alert } from "../types/Alert"; - -// ✅ Store global alertes import { alertStore } from "../services/alertStore"; - -// ✅ Notifications locales Expo import { showAlertNotification } from "../services/notificationService"; /** - * DashboardScreen - * ---------------- - * Écran principal mobile. - * - Charge Dashboard + Settings - * - Refresh auto si activé - * - Socket.IO non bloquant pour alertes live - * - Dashboard = résumé : on affiche UNE seule alerte (la dernière) - * - * Notifications : - * - Si settings.notificationsEnabled = true -> notification locale à chaque alerte reçue + * DashboardScreen (WF-01) — Responsive + * ----------------------------------- + * Objectif : coller au mockup, éviter le scroll. + * - On enlève le gros bloc "Actions" (navigation via header icons) + * - Certaines cartes deviennent cliquables (tap-to-open) */ export default function DashboardScreen() { + const { height } = useWindowDimensions(); + const compact = height < 760; + const [summary, setSummary] = useState(null); const [settings, setSettings] = useState(null); - const [loading, setLoading] = useState(true); - // erreurs REST/refresh + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - // Socket state (non bloquant) const [socketConnected, setSocketConnected] = useState(false); const [socketError, setSocketError] = useState(null); - // Résumé dashboard (dernière alerte reçue affichée) const [liveAlerts, setLiveAlerts] = useState([]); + const [lastRefreshMs, setLastRefreshMs] = useState(null); + const [refreshing, setRefreshing] = useState(false); + const navigation = useNavigation(); - /** - * Chargement initial + rechargement quand on revient sur l'écran - */ useFocusEffect( useCallback(() => { let isActive = true; @@ -72,10 +64,11 @@ export default function DashboardScreen() { loadSettings(), ]); - if (isActive) { - setSummary(dashboardData); - setSettings(userSettings); - } + if (!isActive) return; + + setSummary(dashboardData); + setSettings(userSettings); + setLastRefreshMs(Date.now()); } catch { if (isActive) setError("Impossible de charger le dashboard."); } finally { @@ -91,9 +84,6 @@ export default function DashboardScreen() { }, []) ); - /** - * Refresh auto (sans clignotement global) - */ useEffect(() => { if (!settings) return; if (settings.refreshMode !== "auto") return; @@ -103,7 +93,10 @@ export default function DashboardScreen() { const intervalId = setInterval(async () => { try { const data = await fetchDashboardSummary(); - if (!cancelled) setSummary(data); + if (!cancelled) { + setSummary(data); + setLastRefreshMs(Date.now()); + } } catch { if (!cancelled) setError("Erreur lors du rafraîchissement automatique."); } @@ -115,17 +108,25 @@ export default function DashboardScreen() { }; }, [settings]); - /** - * Socket.IO (non bloquant) - * - En Step 1/2/3 on utilise un userId de test (auth pas encore là) - * - Si serveur KO, on n'empêche pas l'app de fonctionner - */ + const handleManualRefresh = async () => { + try { + setRefreshing(true); + const data = await fetchDashboardSummary(); + setSummary(data); + setLastRefreshMs(Date.now()); + setError(null); + } catch { + setError("Erreur lors de l'actualisation."); + } finally { + setRefreshing(false); + } + }; + useEffect(() => { if (!settings) return; setSocketError(null); - // ✅ userId de test pour avancer maintenant const userId = "test-user"; try { @@ -138,15 +139,10 @@ export default function DashboardScreen() { } const unsubscribeAlert = socketService.onAlert((alert) => { - // 1) Stock global (écran Alertes) alertStore.add(alert); + setLiveAlerts((prev) => [alert, ...prev].slice(0, 100)); - // 2) Résumé dashboard (dernière alerte) - setLiveAlerts((prev) => [alert, ...prev].slice(0, 50)); - - // 3) Notification locale (si activée) if (settings.notificationsEnabled) { - // On encapsule pour ne jamais casser le thread UI void (async () => { try { await showAlertNotification(alert); @@ -166,7 +162,18 @@ export default function DashboardScreen() { }; }, [settings]); - // Chargement + const urgentAlert: Alert | null = useMemo(() => { + if (liveAlerts.length === 0) return null; + + const critical = liveAlerts.filter((a) => a.alertLevel === "CRITICAL"); + if (critical.length > 0) return critical[0]; + + const warning = liveAlerts.filter((a) => a.alertLevel === "WARNING"); + if (warning.length > 0) return warning[0]; + + return liveAlerts[0]; + }, [liveAlerts]); + if (loading) { return ( @@ -175,7 +182,6 @@ export default function DashboardScreen() { ); } - // Erreur bloquante (si rien à afficher) if (error && (!summary || !settings)) { return ( @@ -184,7 +190,6 @@ export default function DashboardScreen() { ); } - // Sécurité if (!summary || !settings) { return ( @@ -195,58 +200,130 @@ export default function DashboardScreen() { return ( - - {/* Bannière warning REST/refresh (non bloquant) */} + {error && ( - - {error} + + + {error} + )} - {/* Bannière Socket (non bloquant) */} - - - Socket.IO : {socketConnected ? "connecté ✅" : "déconnecté ⚠️"} + {/* 1) CONSEILLER */} + + Conseiller + + {summary.decision} + + + Pourquoi ? {summary.reason} - {!!socketError && {socketError}} - - Alertes reçues : {liveAlerts.length} + + Stratégie : {settings.selectedStrategyKey} + + navigation.navigate("Strategy" as never)} + > + Sélectionner stratégie + - {/* Dashboard : afficher uniquement la DERNIÈRE alerte */} - {liveAlerts.length > 0 && ( - - - {liveAlerts[0].alertLevel} — {liveAlerts[0].action}{" "} - {liveAlerts[0].pair} + {/* 2) PORTEFEUILLE (pas encore cliquable car pas d’écran Wallet dédié) */} + + Portefeuille + + + Valeur Totale : + 10 000 {settings.currency} + + + {!compact && ( + + Step 1 : mono-utilisateur / mono-crypto - - {(liveAlerts[0].confidence * 100).toFixed(0)}% —{" "} - {liveAlerts[0].reason} + )} + + + {/* 3) URGENCE — cliquable => Alertes */} + navigation.navigate("Alerts" as never)} + > + + Urgence + + {urgentAlert ? ( + + + {urgentAlert.alertLevel} : {urgentAlert.reason} + + + {urgentAlert.action} {urgentAlert.pair} —{" "} + {(urgentAlert.confidence * 100).toFixed(0)}% + + + ) : ( + Aucune alerte pour le moment. + )} + + + Socket : {socketConnected ? "connecté ✅" : "déconnecté ⚠️"} + {socketError ? ` — ${socketError}` : ""} - - Voir toutes les alertes dans “Alertes”. + + + Appuie pour ouvrir “Alertes” - )} + + + {/* 4) PRIX BTC — cliquable => Historique */} + navigation.navigate("History" as never)} + > + + + Prix BTC + + + {refreshing ? "…" : "Actualiser"} + + + + + Dernière maj :{" "} + {lastRefreshMs ? new Date(lastRefreshMs).toLocaleTimeString() : "—"} + - - - navigation.navigate("Strategy" as never)} - /> - - - - navigation.navigate("Settings" as never)} - onGoHistory={() => navigation.navigate("History" as never)} - onGoAlerts={() => navigation.navigate("Alerts" as never)} - /> + + + Prix BTC + + {summary.price.toFixed(2)} {settings.currency} + + + + + + Appuie pour ouvrir “Historique” + + + ); @@ -258,9 +335,22 @@ const styles = StyleSheet.create({ backgroundColor: ui.screen.backgroundColor, }, - errorText: { - color: "#dc2626", - fontWeight: "900", + containerCompact: { + padding: 12, + }, + + cardCompact: { + padding: 12, + marginBottom: 10, + }, + + titleCompact: { + marginBottom: 6, + }, + + bigCompact: { + fontSize: 24, + marginVertical: 4, }, bannerWarning: { @@ -269,34 +359,90 @@ const styles = StyleSheet.create({ bannerWarningText: { color: "#ca8a04", }, + bannerCompact: { + padding: 10, + marginBottom: 10, + }, - bannerSocket: { - borderColor: "#cbd5e1", + errorText: { + color: "#dc2626", + fontWeight: "900", }, - socketTitle: { + + centerText: { + textAlign: "center", + }, + + boldInline: { + fontWeight: "900", color: "#0f172a", }, - // Dernière alerte affichée (résumé) - alertItem: { + fullButton: { + flexGrow: 0, + flexBasis: "auto", + width: "100%", + marginTop: 12, + }, + buttonCompact: { + paddingVertical: 10, + marginTop: 10, + }, + + urgentBox: { borderWidth: 1, borderColor: "#e5e7eb", - padding: 10, borderRadius: 10, - marginBottom: 12, + padding: 10, backgroundColor: "#ffffff", }, - alertHeader: { + urgentBoxCompact: { + padding: 8, + }, + urgentTitle: { fontWeight: "900", color: "#0f172a", }, - alertBody: { - marginTop: 4, - color: "#334155", + + priceHeaderRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: 8, + }, + refreshBtn: { + paddingHorizontal: 12, + paddingVertical: 8, + borderRadius: 10, + borderWidth: 1, + borderColor: "#e5e7eb", + backgroundColor: "#fff", + }, + refreshBtnCompact: { + paddingVertical: 6, + }, + refreshBtnDisabled: { + opacity: 0.6, + }, + refreshBtnText: { + fontWeight: "900", + color: "#0f172a", }, - alertHint: { - marginTop: 6, - fontSize: 12, - opacity: 0.7, + + priceCard: { + marginTop: 10, + borderWidth: 1, + borderColor: "#e5e7eb", + borderRadius: 10, + padding: 12, + backgroundColor: "#ffffff", + }, + priceCardCompact: { + paddingVertical: 10, + }, + priceBig: { + fontSize: 22, + fontWeight: "900", + color: "#0f172a", }, }); \ No newline at end of file -- 2.50.1