]> git.digitality.be Git - pdw25-26/commitdiff
feat(dashboard): Dashboard fonctionnel (mock, styles, services)
authorThibaud Moustier <thibaudmoustier0@gmail.com>
Mon, 9 Feb 2026 17:11:54 +0000 (18:11 +0100)
committerThibaud Moustier <thibaudmoustier0@gmail.com>
Mon, 9 Feb 2026 17:11:54 +0000 (18:11 +0100)
App.tsx
package-lock.json
package.json
src/mocks/dashboard.mock.ts [new file with mode: 0644]
src/screens/DashboardScreen.tsx [new file with mode: 0644]
src/services/dashboardService.ts [new file with mode: 0644]
src/services/mockApi.ts [new file with mode: 0644]
src/types/DashboardSummary.ts [new file with mode: 0644]
src/utils/settingsStorage.ts [new file with mode: 0644]

diff --git a/App.tsx b/App.tsx
index 0329d0c93c3a2f575e4d87faeea9b6029d400ef1..bbfe4f38c7525a1978c12ece24a2abc2ddf51e61 100644 (file)
--- a/App.tsx
+++ b/App.tsx
@@ -1,20 +1,5 @@
-import { StatusBar } from 'expo-status-bar';
-import { StyleSheet, Text, View } from 'react-native';
+import DashboardScreen from "./src/screens/DashboardScreen";
 
 export default function App() {
-  return (
-    <View style={styles.container}>
-      <Text>Open up App.tsx to start working on your app!</Text>
-      <StatusBar style="auto" />
-    </View>
-  );
-}
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    backgroundColor: '#fff',
-    alignItems: 'center',
-    justifyContent: 'center',
-  },
-});
+  return <DashboardScreen />;
+}
\ No newline at end of file
index 45dcfafa4e0469ca2dd4b6ae4dbc963a47281bae..d8dffc82479c827485337f82e7460103e061f378 100644 (file)
@@ -11,7 +11,8 @@
         "expo": "~54.0.33",
         "expo-status-bar": "~3.0.9",
         "react": "19.1.0",
-        "react-native": "0.81.5"
+        "react-native": "0.81.5",
+        "react-native-safe-area-context": "~5.6.0"
       },
       "devDependencies": {
         "@types/react": "~19.1.0",
       "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz",
       "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@babel/runtime": "^7.20.0",
         "@expo/cli": "54.0.23",
         "react-native": "*"
       }
     },
+    "node_modules/react-native-safe-area-context": {
+      "version": "5.6.2",
+      "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",
+      "peerDependencies": {
+        "react": "*",
+        "react-native": "*"
+      }
+    },
     "node_modules/react-native/node_modules/@react-native/virtualized-lists": {
       "version": "0.81.5",
       "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
index ddcfc76cfd2723e421bd57ee3cb5af7429d56e00..0e7b4c476baf80043d24addd96c96e682ad8c1e3 100644 (file)
@@ -12,7 +12,8 @@
     "expo": "~54.0.33",
     "expo-status-bar": "~3.0.9",
     "react": "19.1.0",
-    "react-native": "0.81.5"
+    "react-native": "0.81.5",
+    "react-native-safe-area-context": "~5.6.0"
   },
   "devDependencies": {
     "@types/react": "~19.1.0",
diff --git a/src/mocks/dashboard.mock.ts b/src/mocks/dashboard.mock.ts
new file mode 100644 (file)
index 0000000..fa3d321
--- /dev/null
@@ -0,0 +1,13 @@
+import { DashboardSummary } from "../types/DashboardSummary";
+
+export async function getDashboardSummary(): Promise<DashboardSummary> {
+  return {
+    pair: "BTC/EUR",
+    price: 42150.23,
+    strategy: "RSI_SIMPLE",
+    decision: "BUY",
+    confidence: 0.82,
+    reason: "RSI < 30, retournement haussier détecté",
+    timestamp: Date.now(),
+  };
+}
diff --git a/src/screens/DashboardScreen.tsx b/src/screens/DashboardScreen.tsx
new file mode 100644 (file)
index 0000000..d632cfb
--- /dev/null
@@ -0,0 +1,198 @@
+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<DashboardSummary | null>(null);
+
+  useEffect(() => {
+    fetchDashboardSummary().then(setSummary);
+  }, []);
+
+  if (!summary) {
+    return (
+      <View style={styles.container}>
+        <Text>Chargement du dashboard…</Text>
+      </View>
+    );
+  }
+
+  const getDecisionColor = (decision: TradeDecision) => {
+    switch (decision) {
+      case "BUY":
+        return "#16a34a";
+      case "SELL":
+        return "#dc2626";
+      default:
+        return "#ca8a04";
+    }
+  };
+
+  return (
+  <SafeAreaView style={styles.safeArea}>
+    <ScrollView contentContainerStyle={styles.container}>
+      {/* Carte Marché */}
+      <View style={styles.card}>
+        <Text style={styles.sectionTitle}>Marché</Text>
+
+        <Text style={styles.price}>{summary.pair}</Text>
+
+        <View style={styles.priceRow}>
+          <Text style={styles.value}>Prix actuel</Text>
+          <Text style={styles.valueBold}>
+            {summary.price.toFixed(2)} €
+          </Text>
+        </View>
+
+        <Text style={styles.updated}>
+          Dernière mise à jour :{" "}
+          {new Date(summary.timestamp).toLocaleString()}
+        </Text>
+      </View>
+
+      {/* Carte Stratégie */}
+      <View style={styles.card}>
+        <Text style={styles.sectionTitle}>Stratégie</Text>
+
+        <Text style={styles.valueBold}>{summary.strategy}</Text>
+
+        <Text
+          style={[
+            styles.decision,
+            { color: getDecisionColor(summary.decision) },
+          ]}
+        >
+          {summary.decision}
+        </Text>
+
+        <View style={styles.priceRow}>
+          <Text style={styles.value}>Confiance</Text>
+          <Text style={styles.valueBold}>
+            {(summary.confidence * 100).toFixed(1)} %
+          </Text>
+        </View>
+
+        <Text style={styles.reason}>{summary.reason}</Text>
+      </View>
+
+      {/* Carte Portefeuille (Step 1 simplifié) */}
+      <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>
+        </View>
+
+        <Text style={styles.updated}>
+          Step 1 : mono-utilisateur / mono-crypto
+        </Text>
+      </View>
+
+      {/* Carte Actions */}
+      <View style={styles.card}>
+        <Text style={styles.sectionTitle}>Actions</Text>
+
+        <View style={styles.row}>
+          <TouchableOpacity style={styles.button}>
+            <Text style={styles.buttonText}>Voir stratégie</Text>
+          </TouchableOpacity>
+
+          <TouchableOpacity style={styles.button}>
+            <Text style={styles.buttonText}>Historique</Text>
+          </TouchableOpacity>
+        </View>
+      </View>
+    </ScrollView>
+    </SafeAreaView>
+  );
+}
+
+/* ===================== 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
new file mode 100644 (file)
index 0000000..3ee7bb0
--- /dev/null
@@ -0,0 +1,6 @@
+import { DashboardSummary } from "../types/DashboardSummary";
+import { getDashboardSummary } from "./mockApi";
+
+export async function fetchDashboardSummary(): Promise<DashboardSummary> {
+  return await getDashboardSummary();
+}
diff --git a/src/services/mockApi.ts b/src/services/mockApi.ts
new file mode 100644 (file)
index 0000000..3b3b889
--- /dev/null
@@ -0,0 +1,18 @@
+import { DashboardSummary } from "../types/DashboardSummary";
+
+export const dashboardMock: DashboardSummary = {
+  pair: "BTC/EUR",
+  price: 42150.23,
+  strategy: "RSI_SIMPLE",
+  decision: "BUY",
+  confidence: 0.82,
+  reason: "RSI inférieur à 30 avec retournement haussier",
+  timestamp: Date.now(),
+};
+
+export const getDashboardSummary = async (): Promise<DashboardSummary> => {
+  // simulation d'appel API
+  return new Promise((resolve) => {
+    setTimeout(() => resolve(dashboardMock), 300);
+  });
+};
diff --git a/src/types/DashboardSummary.ts b/src/types/DashboardSummary.ts
new file mode 100644 (file)
index 0000000..461f66b
--- /dev/null
@@ -0,0 +1,11 @@
+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
+}
diff --git a/src/utils/settingsStorage.ts b/src/utils/settingsStorage.ts
new file mode 100644 (file)
index 0000000..a623b96
--- /dev/null
@@ -0,0 +1,27 @@
+import AsyncStorage from "@react-native-async-storage/async-storage";
+import type { UserSettings } from "../models/UserSettings";
+
+const KEY = "userSettings";
+
+const DEFAULT_SETTINGS: UserSettings = {
+  currency: "EUR",
+  favoriteSymbol: "BTC",
+  alertPreference: "critical",
+  refreshMode: "manual",
+};
+
+export async function loadSettings(): Promise<UserSettings> {
+  const raw = await AsyncStorage.getItem(KEY);
+  if (!raw) return DEFAULT_SETTINGS;
+
+  try {
+    const parsed = JSON.parse(raw) as Partial<UserSettings>;
+    return { ...DEFAULT_SETTINGS, ...parsed };
+  } catch {
+    return DEFAULT_SETTINGS;
+  }
+}
+
+export async function saveSettings(settings: UserSettings): Promise<void> {
+  await AsyncStorage.setItem(KEY, JSON.stringify(settings));
+}