From: Thibaud Moustier Date: Mon, 16 Feb 2026 10:59:50 +0000 (+0100) Subject: Modification Dashboard et ajout page setting X-Git-Url: https://git.digitality.be/?a=commitdiff_plain;h=fbf567261d457a2dc52ed905b4c2da8af6f5adcf;p=pdw25-26 Modification Dashboard et ajout page setting --- diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..b2f7e44 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,94 @@ + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..88cf87c --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d13baaf --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..576b273 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pdw25-26.iml b/.idea/pdw25-26.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/pdw25-26.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/App.tsx b/mobile-ui/App.tsx similarity index 67% rename from App.tsx rename to mobile-ui/App.tsx index bbfe4f3..a4eeb87 100644 --- a/App.tsx +++ b/mobile-ui/App.tsx @@ -1,4 +1,5 @@ import DashboardScreen from "./src/screens/DashboardScreen"; +import SettingsScreen from "./src/screens/SettingsScreen"; export default function App() { return ; diff --git a/app.json b/mobile-ui/app.json similarity index 88% rename from app.json rename to mobile-ui/app.json index 2fe1a75..c5fe7c6 100644 --- a/app.json +++ b/mobile-ui/app.json @@ -1,7 +1,7 @@ { "expo": { - "name": "conseiller-crypto-mobile", - "slug": "conseiller-crypto-mobile", + "name": "wall-e-tte", + "slug": "wall-e-tte", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", diff --git a/index.ts b/mobile-ui/index.ts similarity index 100% rename from index.ts rename to mobile-ui/index.ts diff --git a/package-lock.json b/mobile-ui/package-lock.json similarity index 99% rename from package-lock.json rename to mobile-ui/package-lock.json index d8dffc8..f065b53 100644 --- a/package-lock.json +++ b/mobile-ui/package-lock.json @@ -1,13 +1,14 @@ { - "name": "conseiller-crypto-mobile", + "name": "wall-e-tte", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "conseiller-crypto-mobile", + "name": "wall-e-tte", "version": "1.0.0", "dependencies": { + "@react-native-async-storage/async-storage": "2.2.0", "expo": "~54.0.33", "expo-status-bar": "~3.0.9", "react": "19.1.0", @@ -2685,6 +2686,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz", @@ -5300,6 +5313,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -6198,6 +6220,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7253,6 +7287,7 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", diff --git a/package.json b/mobile-ui/package.json similarity index 85% rename from package.json rename to mobile-ui/package.json index 0e7b4c4..095c042 100644 --- a/package.json +++ b/mobile-ui/package.json @@ -1,5 +1,5 @@ { - "name": "conseiller-crypto-mobile", + "name": "wall-e-tte", "version": "1.0.0", "main": "index.ts", "scripts": { @@ -9,6 +9,7 @@ "web": "expo start --web" }, "dependencies": { + "@react-native-async-storage/async-storage": "2.2.0", "expo": "~54.0.33", "expo-status-bar": "~3.0.9", "react": "19.1.0", diff --git a/src/mocks/dashboard.mock.ts b/mobile-ui/src/mocks/dashboard.mock.ts similarity index 66% rename from src/mocks/dashboard.mock.ts rename to mobile-ui/src/mocks/dashboard.mock.ts index fa3d321..ae3cdb1 100644 --- a/src/mocks/dashboard.mock.ts +++ b/mobile-ui/src/mocks/dashboard.mock.ts @@ -1,6 +1,12 @@ import { DashboardSummary } from "../types/DashboardSummary"; +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + export async function getDashboardSummary(): Promise { + await delay(500); // simulation réseau + return { pair: "BTC/EUR", price: 42150.23, @@ -8,6 +14,8 @@ export async function getDashboardSummary(): Promise { decision: "BUY", confidence: 0.82, reason: "RSI < 30, retournement haussier détecté", + alertLevel: "WARNING", timestamp: Date.now(), }; } + diff --git a/mobile-ui/src/models/UserSettings.ts b/mobile-ui/src/models/UserSettings.ts new file mode 100644 index 0000000..c8e552e --- /dev/null +++ b/mobile-ui/src/models/UserSettings.ts @@ -0,0 +1,17 @@ +export type Currency = "EUR" | "USD"; + +export type AlertPreference = + | "critical" + | "warning" + | "all"; + +export type RefreshMode = + | "manual" + | "auto"; + +export interface UserSettings { + currency: Currency; + favoriteSymbol: string; + alertPreference: AlertPreference; + refreshMode: RefreshMode; +} diff --git a/mobile-ui/src/screens/DashboardScreen.tsx b/mobile-ui/src/screens/DashboardScreen.tsx new file mode 100644 index 0000000..30d380b --- /dev/null +++ b/mobile-ui/src/screens/DashboardScreen.tsx @@ -0,0 +1,261 @@ +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 { SafeAreaView } from "react-native-safe-area-context"; +import { loadSettings } from "../utils/settingsStorage"; +import { UserSettings } from "../models/UserSettings"; + +export default function DashboardScreen() { + const [summary, setSummary] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + 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); + } + } + + loadData(); +}, []); + + if (loading) { + return ( + + Chargement du dashboard… + + ); + } + + if (error) { + return ( + + {error} + + ); + } + +if (!summary || !settings) { + return ( + + Initialisation… + + ); +} + + const getDecisionColor = (decision: TradeDecision) => { + switch (decision) { + case "BUY": + return "#16a34a"; + case "SELL": + return "#dc2626"; + case "STOP_LOSS": + return "#991b1b"; + default: + return "#ca8a04"; + } + }; + + const getAlertColor = (level: AlertLevel) => { + switch (level) { + case "CRITICAL": + return "#b91c1c"; + case "WARNING": + return "#ca8a04"; + case "INFO": + return "#2563eb"; + default: + return "#6b7280"; + } + }; + + return ( + + + + {/* Carte Marché */} + + Marché + + {summary.pair} + + + Prix actuel + + {summary.price.toFixed(2)} € + + + + + Dernière mise à jour :{" "} + {new Date(summary.timestamp).toLocaleString()} + + + + {/* Carte Stratégie */} + + Stratégie + + {summary.strategy} + + + {summary.decision} + + + + {summary.alertLevel} + + + + Confiance + + {(summary.confidence * 100).toFixed(1)} % + + + + {summary.reason} + + + {/* Carte Portefeuille */} + + Portefeuille + + + Valeur totale + 10 000 € + + + + Step 1 : mono-utilisateur / mono-crypto + + + + {/* Carte Actions */} + + Actions + + + + Voir stratégie + + + + Historique + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + padding: 16, + }, + + safeArea: { + flex: 1, + backgroundColor: "#fff", + }, + + card: { + borderWidth: 1, + borderColor: "#aaa", + padding: 12, + marginBottom: 12, + borderRadius: 6, + }, + + sectionTitle: { + fontWeight: "bold", + marginBottom: 6, + fontSize: 16, + }, + + price: { + fontSize: 26, + fontWeight: "bold", + textAlign: "center", + marginVertical: 6, + }, + + decision: { + fontSize: 32, + fontWeight: "bold", + textAlign: "center", + marginVertical: 10, + }, + + row: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + + priceRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginVertical: 4, + }, + + value: { + fontSize: 16, + }, + + valueBold: { + fontSize: 16, + fontWeight: "bold", + }, + + reason: { + marginTop: 6, + fontStyle: "italic", + }, + + button: { + backgroundColor: "#555", + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: 4, + }, + + buttonText: { + color: "#fff", + fontWeight: "bold", + }, + + updated: { + fontSize: 12, + opacity: 0.6, + marginTop: 6, + textAlign: "center", + }, +}); + diff --git a/mobile-ui/src/screens/SettingsScreen.tsx b/mobile-ui/src/screens/SettingsScreen.tsx new file mode 100644 index 0000000..66dd3b2 --- /dev/null +++ b/mobile-ui/src/screens/SettingsScreen.tsx @@ -0,0 +1,93 @@ +import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import { useEffect, useState } from "react"; +import { loadSettings, saveSettings } from "../utils/settingsStorage"; +import { UserSettings } from "../models/UserSettings"; +import { SafeAreaView } from "react-native-safe-area-context"; + +export default function SettingsScreen() { + const [settings, setSettings] = useState(null); + + useEffect(() => { + async function init() { + const data = await loadSettings(); + setSettings(data); + } + init(); + }, []); + + if (!settings) { + return ( + + Chargement des paramètres… + + ); + } + + const toggleCurrency = () => { + const newCurrency = settings.currency === "EUR" ? "USD" : "EUR"; + setSettings({ ...settings, currency: newCurrency }); + }; + + const toggleRefreshMode = () => { + const newMode = settings.refreshMode === "manual" ? "auto" : "manual"; + setSettings({ ...settings, refreshMode: newMode }); + }; + + const handleSave = async () => { + await saveSettings(settings); + }; + + return ( + + + Paramètres + + Devise : {settings.currency} + + Changer devise + + + Mode rafraîchissement : {settings.refreshMode} + + Changer mode + + + + Sauvegarder + + + + ); +} + +const styles = StyleSheet.create({ + safeArea: { + flex: 1, + backgroundColor: "#fff", + }, + container: { + padding: 16, + }, + title: { + fontSize: 22, + fontWeight: "bold", + marginBottom: 16, + }, + button: { + backgroundColor: "#555", + padding: 10, + marginVertical: 6, + borderRadius: 4, + }, + saveButton: { + backgroundColor: "#2563eb", + padding: 12, + marginTop: 20, + borderRadius: 6, + }, + buttonText: { + color: "#fff", + fontWeight: "bold", + textAlign: "center", + }, +}); diff --git a/mobile-ui/src/services/dashboardService.ts b/mobile-ui/src/services/dashboardService.ts new file mode 100644 index 0000000..ef2b300 --- /dev/null +++ b/mobile-ui/src/services/dashboardService.ts @@ -0,0 +1,11 @@ +import { DashboardSummary } from "../types/DashboardSummary"; +import { getDashboardSummary } from "../mocks/dashboard.mock"; + +export async function fetchDashboardSummary(): Promise { + try { + return await getDashboardSummary(); + } catch (error) { + console.error("Erreur lors du chargement du dashboard:", error); + throw error; + } +} diff --git a/src/services/mockApi.ts b/mobile-ui/src/services/mockApi.ts similarity index 95% rename from src/services/mockApi.ts rename to mobile-ui/src/services/mockApi.ts index 3b3b889..546eb84 100644 --- a/src/services/mockApi.ts +++ b/mobile-ui/src/services/mockApi.ts @@ -8,6 +8,7 @@ export const dashboardMock: DashboardSummary = { confidence: 0.82, reason: "RSI inférieur à 30 avec retournement haussier", timestamp: Date.now(), + alertLevel: "WARNING", }; export const getDashboardSummary = async (): Promise => { diff --git a/tsconfig.json b/mobile-ui/src/tsconfig.json similarity index 100% rename from tsconfig.json rename to mobile-ui/src/tsconfig.json diff --git a/mobile-ui/src/types/DashboardSummary.ts b/mobile-ui/src/types/DashboardSummary.ts new file mode 100644 index 0000000..78af51b --- /dev/null +++ b/mobile-ui/src/types/DashboardSummary.ts @@ -0,0 +1,22 @@ +export type TradeDecision = + | "BUY" + | "SELL" + | "HOLD" + | "STOP_LOSS"; + +export type AlertLevel = + | "CRITICAL" + | "WARNING" + | "INFO"; + +export interface DashboardSummary { + pair: string; + price: number; + strategy: string; + decision: TradeDecision; + confidence: number; + reason: string; + alertLevel: AlertLevel; + timestamp: number; +} + diff --git a/src/utils/settingsStorage.ts b/mobile-ui/src/utils/settingsStorage.ts similarity index 55% rename from src/utils/settingsStorage.ts rename to mobile-ui/src/utils/settingsStorage.ts index a623b96..4936eec 100644 --- a/src/utils/settingsStorage.ts +++ b/mobile-ui/src/utils/settingsStorage.ts @@ -11,17 +11,30 @@ const DEFAULT_SETTINGS: UserSettings = { }; export async function loadSettings(): Promise { - const raw = await AsyncStorage.getItem(KEY); - if (!raw) return DEFAULT_SETTINGS; - try { + const raw = await AsyncStorage.getItem(KEY); + + if (!raw) { + return DEFAULT_SETTINGS; + } + const parsed = JSON.parse(raw) as Partial; - return { ...DEFAULT_SETTINGS, ...parsed }; - } catch { + + return { + ...DEFAULT_SETTINGS, + ...parsed, + }; + } catch (error) { + console.error("Erreur lors du chargement des settings :", error); return DEFAULT_SETTINGS; } } export async function saveSettings(settings: UserSettings): Promise { - await AsyncStorage.setItem(KEY, JSON.stringify(settings)); + try { + await AsyncStorage.setItem(KEY, JSON.stringify(settings)); + } catch (error) { + console.error("Erreur lors de la sauvegarde des settings :", error); + throw error; + } } diff --git a/mobile-ui/tsconfig.json b/mobile-ui/tsconfig.json new file mode 100644 index 0000000..0e6371f --- /dev/null +++ b/mobile-ui/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": {}, + "extends": "expo/tsconfig.base" +} diff --git a/src/screens/DashboardScreen.tsx b/src/screens/DashboardScreen.tsx deleted file mode 100644 index d632cfb..0000000 --- a/src/screens/DashboardScreen.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from "react-native"; -import { useEffect, useState } from "react"; -import { DashboardSummary, TradeDecision } from "../types/DashboardSummary"; -import { fetchDashboardSummary } from "../services/dashboardService"; -import { SafeAreaView } from "react-native-safe-area-context"; - -export default function DashboardScreen() { - const [summary, setSummary] = useState(null); - - useEffect(() => { - fetchDashboardSummary().then(setSummary); - }, []); - - if (!summary) { - return ( - - Chargement du dashboard… - - ); - } - - const getDecisionColor = (decision: TradeDecision) => { - switch (decision) { - case "BUY": - return "#16a34a"; - case "SELL": - return "#dc2626"; - default: - return "#ca8a04"; - } - }; - - return ( - - - {/* Carte Marché */} - - Marché - - {summary.pair} - - - Prix actuel - - {summary.price.toFixed(2)} € - - - - - Dernière mise à jour :{" "} - {new Date(summary.timestamp).toLocaleString()} - - - - {/* Carte Stratégie */} - - Stratégie - - {summary.strategy} - - - {summary.decision} - - - - Confiance - - {(summary.confidence * 100).toFixed(1)} % - - - - {summary.reason} - - - {/* Carte Portefeuille (Step 1 simplifié) */} - - Portefeuille - - - Valeur totale - 10 000 € - - - - Step 1 : mono-utilisateur / mono-crypto - - - - {/* Carte Actions */} - - Actions - - - - Voir stratégie - - - - Historique - - - - - - ); -} - -/* ===================== STYLES ===================== */ - -const styles = StyleSheet.create({ - container: { - padding: 16, - }, - - card: { - borderWidth: 1, - borderColor: "#aaa", - padding: 12, - marginBottom: 12, - borderRadius: 6, - }, - - sectionTitle: { - fontWeight: "bold", - marginBottom: 6, - fontSize: 16, - }, - - safeArea: { - flex: 1, - backgroundColor: "#fff", -}, - - price: { - fontSize: 26, - fontWeight: "bold", - textAlign: "center", - marginVertical: 6, - }, - - decision: { - fontSize: 32, - fontWeight: "bold", - textAlign: "center", - marginVertical: 10, - }, - - row: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - }, - - priceRow: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - marginVertical: 4, - }, - - value: { - fontSize: 16, - }, - - valueBold: { - fontSize: 16, - fontWeight: "bold", - }, - - reason: { - marginTop: 6, - fontStyle: "italic", - }, - - button: { - backgroundColor: "#555", - paddingHorizontal: 10, - paddingVertical: 6, - borderRadius: 4, - }, - - buttonText: { - color: "#fff", - fontWeight: "bold", - }, - - updated: { - fontSize: 12, - opacity: 0.6, - marginTop: 6, - textAlign: "center", - }, -}); \ No newline at end of file diff --git a/src/services/dashboardService.ts b/src/services/dashboardService.ts deleted file mode 100644 index 3ee7bb0..0000000 --- a/src/services/dashboardService.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { DashboardSummary } from "../types/DashboardSummary"; -import { getDashboardSummary } from "./mockApi"; - -export async function fetchDashboardSummary(): Promise { - return await getDashboardSummary(); -} diff --git a/src/types/DashboardSummary.ts b/src/types/DashboardSummary.ts deleted file mode 100644 index 461f66b..0000000 --- a/src/types/DashboardSummary.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type TradeDecision = "BUY" | "SELL" | "HOLD"; - -export interface DashboardSummary { - pair: string; // ex: "BTC/EUR" - price: number; // dernier close_price - strategy: string; // user_strategies.strategy_key - decision: TradeDecision; - confidence: number; // signals.confidence (0..1) - reason: string; // signals.reason - timestamp: number; // signals.timestamp_ms -}