From: Thibaud Moustier Date: Mon, 23 Feb 2026 12:27:01 +0000 (+0100) Subject: Mobile : navigation + setting screen + auto refresh X-Git-Url: https://git.digitality.be/?a=commitdiff_plain;h=cbc0271179e259b40a7a6a4a02358459060f529a;p=pdw25-26 Mobile : navigation + setting screen + auto refresh --- diff --git a/mobile-ui/App.tsx b/mobile-ui/App.tsx index a4eeb87..c94646d 100644 --- a/mobile-ui/App.tsx +++ b/mobile-ui/App.tsx @@ -1,6 +1,32 @@ +import { NavigationContainer } from "@react-navigation/native"; +import { createNativeStackNavigator } from "@react-navigation/native-stack"; + import DashboardScreen from "./src/screens/DashboardScreen"; import SettingsScreen from "./src/screens/SettingsScreen"; +// Types des routes (pour éviter les erreurs de navigation) +export type RootStackParamList = { + Dashboard: undefined; + Settings: undefined; +}; + +const Stack = createNativeStackNavigator(); + export default function App() { - return ; + return ( + + + + + + + ); } \ No newline at end of file diff --git a/mobile-ui/package-lock.json b/mobile-ui/package-lock.json index f065b53..29d5991 100644 --- a/mobile-ui/package-lock.json +++ b/mobile-ui/package-lock.json @@ -9,11 +9,14 @@ "version": "1.0.0", "dependencies": { "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/native": "^7.1.28", + "@react-navigation/native-stack": "^7.13.0", "expo": "~54.0.33", "expo-status-bar": "~3.0.9", "react": "19.1.0", "react-native": "0.81.5", - "react-native-safe-area-context": "~5.6.0" + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0" }, "devDependencies": { "@types/react": "~19.1.0", @@ -2952,6 +2955,123 @@ "integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==", "license": "MIT" }, + "node_modules/@react-navigation/core": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.14.0.tgz", + "integrity": "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^7.5.3", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "query-string": "^7.1.3", + "react-is": "^19.1.0", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "react": ">= 18.2.0" + } + }, + "node_modules/@react-navigation/core/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-navigation/core/node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" + }, + "node_modules/@react-navigation/elements": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.5.tgz", + "integrity": "sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g==", + "license": "MIT", + "dependencies": { + "color": "^4.2.3", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@react-native-masked-view/masked-view": ">= 0.2.0", + "@react-navigation/native": "^7.1.28", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0" + }, + "peerDependenciesMeta": { + "@react-native-masked-view/masked-view": { + "optional": true + } + } + }, + "node_modules/@react-navigation/native": { + "version": "7.1.28", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.28.tgz", + "integrity": "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ==", + "license": "MIT", + "dependencies": { + "@react-navigation/core": "^7.14.0", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "use-latest-callback": "^0.2.4" + }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*" + } + }, + "node_modules/@react-navigation/native-stack": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.13.0.tgz", + "integrity": "sha512-5OOp1IKEd5woHl9hGBU0qCAfrQ4+7Tqej0HzDzGQeXzS8tg9gq84x1qUdRvFk5BXbhuAyvJliY9F1/I07d2X0A==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.5", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0", + "warn-once": "^0.1.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.28", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/native/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-navigation/routers": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz", + "integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==", + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -3896,6 +4016,19 @@ "node": ">=0.8" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3911,6 +4044,34 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4076,6 +4237,15 @@ } } }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -4866,6 +5036,12 @@ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "license": "Apache-2.0" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4893,6 +5069,15 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -5265,6 +5450,12 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -7224,6 +7415,24 @@ "qrcode-terminal": "bin/qrcode-terminal.js" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -7276,6 +7485,18 @@ "ws": "^7" } }, + "node_modules/react-freeze": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", + "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -7355,6 +7576,23 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-screens": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", + "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "react-freeze": "^1.0.0", + "react-native-is-edge-to-edge": "^1.2.1", + "warn-once": "^0.1.0" + }, "peerDependencies": { "react": "*", "react-native": "*" @@ -7836,6 +8074,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sf-symbols-typescript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz", + "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7886,6 +8133,15 @@ "plist": "^3.0.5" } }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -7947,6 +8203,15 @@ "node": ">=0.10.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -8010,6 +8275,15 @@ "node": ">= 0.10.0" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -8515,6 +8789,24 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-latest-callback": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz", + "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -8566,6 +8858,12 @@ "makeerror": "1.0.12" } }, + "node_modules/warn-once": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", + "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", + "license": "MIT" + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/mobile-ui/package.json b/mobile-ui/package.json index 095c042..e7af7ea 100644 --- a/mobile-ui/package.json +++ b/mobile-ui/package.json @@ -10,11 +10,14 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/native": "^7.1.28", + "@react-navigation/native-stack": "^7.13.0", "expo": "~54.0.33", "expo-status-bar": "~3.0.9", "react": "19.1.0", "react-native": "0.81.5", - "react-native-safe-area-context": "~5.6.0" + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0" }, "devDependencies": { "@types/react": "~19.1.0", diff --git a/mobile-ui/src/screens/DashboardScreen.tsx b/mobile-ui/src/screens/DashboardScreen.tsx index 30d380b..5fe177b 100644 --- a/mobile-ui/src/screens/DashboardScreen.tsx +++ b/mobile-ui/src/screens/DashboardScreen.tsx @@ -1,37 +1,124 @@ -import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from "react-native"; -import { useEffect, useState } from "react"; -import { DashboardSummary, TradeDecision, AlertLevel } from "../types/DashboardSummary"; -import { fetchDashboardSummary } from "../services/dashboardService"; +import { + View, + Text, + StyleSheet, + ScrollView, + TouchableOpacity, +} from "react-native"; +import { useState, useCallback, useEffect } from "react"; import { SafeAreaView } from "react-native-safe-area-context"; -import { loadSettings } from "../utils/settingsStorage"; -import { UserSettings } from "../models/UserSettings"; +import { useFocusEffect, useNavigation } from "@react-navigation/native"; +import type { + DashboardSummary, + TradeDecision, + AlertLevel, +} from "../types/DashboardSummary"; +import { fetchDashboardSummary } from "../services/dashboardService"; +import { loadSettings } from "../utils/settingsStorage"; +import type { UserSettings } from "../models/UserSettings"; + +/** + * DashboardScreen + * -------------- + * Écran principal mobile (Step 1). + * Rôle : afficher des données (consommateur API) : + * - Marché (prix) + * - Signal (BUY/SELL/HOLD/STOP_LOSS) + niveau d'alerte (CRITICAL/WARNING/INFO) + * - Portefeuille (placeholder Step 1) + * - Actions rapides (dont accès aux Paramètres) + * + * Spécificité : + * - On recharge les données quand l'écran reprend le focus (retour depuis Settings). + * - Si refreshMode === "auto", on rafraîchit le dashboard périodiquement. + */ export default function DashboardScreen() { + // Données du dashboard (mock pour l'instant) const [summary, setSummary] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + + // Paramètres utilisateur (AsyncStorage) const [settings, setSettings] = useState(null); -useEffect(() => { - async function loadData() { - try { - const [dashboardData, userSettings] = await Promise.all([ - fetchDashboardSummary(), - loadSettings(), - ]); - - setSummary(dashboardData); - setSettings(userSettings); - } catch (err) { - setError("Impossible de charger le dashboard."); - } finally { - setLoading(false); - } - } + // États UI (loading / erreur) + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - loadData(); -}, []); + // Navigation (Stack) + const navigation = useNavigation(); + + /** + * Charge dashboard + settings à chaque focus sur l'écran. + * (utile quand on revient des Paramètres) + */ + useFocusEffect( + useCallback(() => { + let isActive = true; + + async function loadData() { + try { + setError(null); + setLoading(true); + + const [dashboardData, userSettings] = await Promise.all([ + fetchDashboardSummary(), + loadSettings(), + ]); + + if (isActive) { + setSummary(dashboardData); + setSettings(userSettings); + } + } catch (e) { + if (isActive) { + setError("Impossible de charger le dashboard."); + } + } finally { + if (isActive) { + setLoading(false); + } + } + } + + loadData(); + + return () => { + isActive = false; + }; + }, []) + ); + /** + * Rafraîchissement automatique si l'utilisateur l'a activé. + * On ne relance pas le loading global (pour éviter que l'écran "clignote"), + * on met juste à jour le summary. + */ + useEffect(() => { + // Pas de settings => impossible de savoir le mode + if (!settings) return; + + // Mode manuel => pas d'interval + if (settings.refreshMode !== "auto") return; + + let cancelled = false; + + const intervalId = setInterval(async () => { + try { + const data = await fetchDashboardSummary(); + if (!cancelled) setSummary(data); + } catch { + // On évite de spammer l'utilisateur : on peut juste logguer + // ou afficher une erreur discrète. + if (!cancelled) setError("Erreur lors du rafraîchissement automatique."); + } + }, 5000); // toutes les 5 secondes + + return () => { + cancelled = true; + clearInterval(intervalId); + }; + }, [settings]); + + // UI : Chargement if (loading) { return ( @@ -40,22 +127,27 @@ useEffect(() => { ); } - if (error) { + // UI : Erreur (erreur "bloquante") + if (error && (!summary || !settings)) { return ( - {error} + {error} ); } -if (!summary || !settings) { - return ( - - Initialisation… - - ); -} + // Fallback sécurité + if (!summary || !settings) { + return ( + + Initialisation… + + ); + } + /** + * Helpers d'affichage (couleurs) pour respecter le contrat mobile + */ const getDecisionColor = (decision: TradeDecision) => { switch (decision) { case "BUY": @@ -64,6 +156,7 @@ if (!summary || !settings) { return "#dc2626"; case "STOP_LOSS": return "#991b1b"; + case "HOLD": default: return "#ca8a04"; } @@ -76,16 +169,21 @@ if (!summary || !settings) { case "WARNING": return "#ca8a04"; case "INFO": - return "#2563eb"; default: - return "#6b7280"; + return "#2563eb"; } }; return ( - + {/* Petit warning non bloquant si l'auto-refresh a eu un souci */} + {error && ( + + {error} + + )} + {/* Carte Marché */} Marché @@ -95,17 +193,16 @@ if (!summary || !settings) { Prix actuel - {summary.price.toFixed(2)} € + {summary.price.toFixed(2)} {settings.currency} - Dernière mise à jour :{" "} - {new Date(summary.timestamp).toLocaleString()} + Dernière mise à jour : {new Date(summary.timestamp).toLocaleString()} - {/* Carte Stratégie */} + {/* Carte Stratégie + Signal */} Stratégie @@ -121,12 +218,10 @@ if (!summary || !settings) { {summary.alertLevel} @@ -141,18 +236,16 @@ if (!summary || !settings) { {summary.reason} - {/* Carte Portefeuille */} + {/* Carte Portefeuille (placeholder Step 1) */} Portefeuille Valeur totale - 10 000 € + 10 000 {settings.currency} - - Step 1 : mono-utilisateur / mono-crypto - + Step 1 : mono-utilisateur / mono-crypto {/* Carte Actions */} @@ -167,6 +260,13 @@ if (!summary || !settings) { Historique + + navigation.navigate("Settings" as never)} + > + Paramètres + @@ -174,16 +274,18 @@ if (!summary || !settings) { ); } -const styles = StyleSheet.create({ - container: { - padding: 16, - }, +/* ===================== STYLES ===================== */ +const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: "#fff", }, + container: { + padding: 16, + }, + card: { borderWidth: 1, borderColor: "#aaa", @@ -212,10 +314,16 @@ const styles = StyleSheet.create({ marginVertical: 10, }, + alertLevel: { + textAlign: "center", + fontWeight: "bold", + marginBottom: 6, + }, + row: { flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", + flexWrap: "wrap", + gap: 8, }, priceRow: { @@ -242,8 +350,11 @@ const styles = StyleSheet.create({ button: { backgroundColor: "#555", paddingHorizontal: 10, - paddingVertical: 6, + paddingVertical: 10, borderRadius: 4, + flexGrow: 1, + flexBasis: "48%", + alignItems: "center", }, buttonText: { @@ -257,5 +368,22 @@ const styles = StyleSheet.create({ marginTop: 6, textAlign: "center", }, -}); + errorText: { + color: "#dc2626", + fontWeight: "bold", + }, + + warningBanner: { + borderWidth: 1, + borderColor: "#ca8a04", + padding: 10, + borderRadius: 6, + marginBottom: 12, + }, + + warningText: { + color: "#ca8a04", + fontWeight: "bold", + }, +}); \ No newline at end of file