"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",
"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",
"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",
"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",
}
}
},
+ "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",
"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",
"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",
"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",
"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",
"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",
"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": "*"
"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",
"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",
"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",
"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",
"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",
"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",
-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<DashboardSummary | null>(null);
- const [loading, setLoading] = useState<boolean>(true);
- const [error, setError] = useState<string | null>(null);
+
+ // Paramètres utilisateur (AsyncStorage)
const [settings, setSettings] = useState<UserSettings | null>(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<boolean>(true);
+ const [error, setError] = useState<string | null>(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 (
<View style={styles.container}>
);
}
- if (error) {
+ // UI : Erreur (erreur "bloquante")
+ if (error && (!summary || !settings)) {
return (
<View style={styles.container}>
- <Text style={{ color: "red" }}>{error}</Text>
+ <Text style={styles.errorText}>{error}</Text>
</View>
);
}
-if (!summary || !settings) {
- return (
- <View style={styles.container}>
- <Text>Initialisation…</Text>
- </View>
- );
-}
+ // Fallback sécurité
+ if (!summary || !settings) {
+ return (
+ <View style={styles.container}>
+ <Text>Initialisation…</Text>
+ </View>
+ );
+ }
+ /**
+ * Helpers d'affichage (couleurs) pour respecter le contrat mobile
+ */
const getDecisionColor = (decision: TradeDecision) => {
switch (decision) {
case "BUY":
return "#dc2626";
case "STOP_LOSS":
return "#991b1b";
+ case "HOLD":
default:
return "#ca8a04";
}
case "WARNING":
return "#ca8a04";
case "INFO":
- return "#2563eb";
default:
- return "#6b7280";
+ return "#2563eb";
}
};
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView contentContainerStyle={styles.container}>
-
+ {/* Petit warning non bloquant si l'auto-refresh a eu un souci */}
+ {error && (
+ <View style={styles.warningBanner}>
+ <Text style={styles.warningText}>{error}</Text>
+ </View>
+ )}
+
{/* Carte Marché */}
<View style={styles.card}>
<Text style={styles.sectionTitle}>Marché</Text>
<View style={styles.priceRow}>
<Text style={styles.value}>Prix actuel</Text>
<Text style={styles.valueBold}>
- {summary.price.toFixed(2)} €
+ {summary.price.toFixed(2)} {settings.currency}
</Text>
</View>
<Text style={styles.updated}>
- Dernière mise à jour :{" "}
- {new Date(summary.timestamp).toLocaleString()}
+ Dernière mise à jour : {new Date(summary.timestamp).toLocaleString()}
</Text>
</View>
- {/* Carte Stratégie */}
+ {/* Carte Stratégie + Signal */}
<View style={styles.card}>
<Text style={styles.sectionTitle}>Stratégie</Text>
</Text>
<Text
- style={{
- textAlign: "center",
- fontWeight: "bold",
- color: getAlertColor(summary.alertLevel),
- marginBottom: 6,
- }}
+ style={[
+ styles.alertLevel,
+ { color: getAlertColor(summary.alertLevel) },
+ ]}
>
{summary.alertLevel}
</Text>
<Text style={styles.reason}>{summary.reason}</Text>
</View>
- {/* Carte Portefeuille */}
+ {/* Carte Portefeuille (placeholder Step 1) */}
<View style={styles.card}>
<Text style={styles.sectionTitle}>Portefeuille</Text>
<View style={styles.priceRow}>
<Text style={styles.value}>Valeur totale</Text>
- <Text style={styles.valueBold}>10 000 €</Text>
+ <Text style={styles.valueBold}>10 000 {settings.currency}</Text>
</View>
- <Text style={styles.updated}>
- Step 1 : mono-utilisateur / mono-crypto
- </Text>
+ <Text style={styles.updated}>Step 1 : mono-utilisateur / mono-crypto</Text>
</View>
{/* Carte Actions */}
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Historique</Text>
</TouchableOpacity>
+
+ <TouchableOpacity
+ style={styles.button}
+ onPress={() => navigation.navigate("Settings" as never)}
+ >
+ <Text style={styles.buttonText}>Paramètres</Text>
+ </TouchableOpacity>
</View>
</View>
</ScrollView>
);
}
-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",
marginVertical: 10,
},
+ alertLevel: {
+ textAlign: "center",
+ fontWeight: "bold",
+ marginBottom: 6,
+ },
+
row: {
flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
+ flexWrap: "wrap",
+ gap: 8,
},
priceRow: {
button: {
backgroundColor: "#555",
paddingHorizontal: 10,
- paddingVertical: 6,
+ paddingVertical: 10,
borderRadius: 4,
+ flexGrow: 1,
+ flexBasis: "48%",
+ alignItems: "center",
},
buttonText: {
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