]> git.digitality.be Git - pdw25-26/commitdiff
Mobile : assets placeholder move to 'Wallette\mbole' + modification AlertsScreen
authorThibaud Moustier <thibaudmoustier0@gmail.com>
Wed, 25 Feb 2026 13:11:05 +0000 (14:11 +0100)
committerThibaud Moustier <thibaudmoustier0@gmail.com>
Wed, 25 Feb 2026 13:11:05 +0000 (14:11 +0100)
Wallette/mobile/assets/adaptive-icon.png [moved from assets/adaptive-icon.png with 100% similarity]
Wallette/mobile/assets/favicon.png [moved from assets/favicon.png with 100% similarity]
Wallette/mobile/assets/icon.png [moved from assets/icon.png with 100% similarity]
Wallette/mobile/assets/splash-icon.png [moved from assets/splash-icon.png with 100% similarity]
Wallette/mobile/src/screens/AlertsScreen.tsx

index 4333d5dfffe674196a68cb74a3fddc4c9702f49e..dff674ae43ed2b66058894c8867269f95aa7180c 100644 (file)
@@ -1,4 +1,4 @@
-import { View, Text, StyleSheet, FlatList } from "react-native";
+import { View, Text, StyleSheet, FlatList, TouchableOpacity, Alert as RNAlert } from "react-native";
 import { useEffect, useMemo, useState } from "react";
 import { SafeAreaView } from "react-native-safe-area-context";
 
@@ -6,10 +6,14 @@ import type { Alert } from "../types/Alert";
 import { alertStore } from "../services/alertStore";
 import { ui } from "../components/ui/uiStyles";
 
-// Types locaux pour le tri/couleurs (évite dépendre d'exports manquants)
+/**
+ * Types locaux (évite dépendre d'exports qui ne sont pas toujours présents)
+ */
 type AlertLevel = "CRITICAL" | "WARNING" | "INFO";
 type TradeDecision = "BUY" | "SELL" | "HOLD" | "STOP_LOSS";
 
+type AlertFilter = "ALL" | AlertLevel;
+
 function levelRank(level: AlertLevel) {
   switch (level) {
     case "CRITICAL":
@@ -48,9 +52,20 @@ function getActionColor(action: TradeDecision): string {
   }
 }
 
+/**
+ * AlertsScreen
+ * ------------
+ * Affiche les alertes reçues via Socket.IO (stockées dans alertStore).
+ *
+ * - Filtre optionnel (ALL / CRITICAL / WARNING / INFO)
+ * - Par défaut : ALL avec tri CRITICAL > WARNING > INFO puis plus récent
+ * - Bouton Clear avec confirmation
+ */
 export default function AlertsScreen() {
   const [alerts, setAlerts] = useState<Alert[]>([]);
+  const [filter, setFilter] = useState<AlertFilter>("ALL");
 
+  // abonnement au store (temps réel)
   useEffect(() => {
     const unsub = alertStore.subscribe(setAlerts);
     return () => {
@@ -58,27 +73,97 @@ export default function AlertsScreen() {
     };
   }, []);
 
-  const sorted = useMemo(() => {
-    return [...alerts].sort((a, b) => {
-      // cast car ton type Alert peut ne pas déclarer explicitement les unions
+  const filteredAndSorted = useMemo(() => {
+    const filtered =
+      filter === "ALL"
+        ? alerts
+        : alerts.filter((a) => (a.alertLevel as AlertLevel) === filter);
+
+    return [...filtered].sort((a, b) => {
       const la = a.alertLevel as AlertLevel;
       const lb = b.alertLevel as AlertLevel;
 
+      // tri par niveau d'alerte (desc)
       const byLevel = levelRank(lb) - levelRank(la);
       if (byLevel !== 0) return byLevel;
 
+      // tri par date (desc) si dispo
       const ta = a.timestamp ?? 0;
       const tb = b.timestamp ?? 0;
       return tb - ta;
     });
-  }, [alerts]);
+  }, [alerts, filter]);
+
+  const confirmClear = () => {
+    if (alerts.length === 0) return;
+
+    RNAlert.alert(
+      "Supprimer les alertes ?",
+      "Cette action efface la liste locale des alertes reçues (cela ne touche pas la base de données).",
+      [
+        { text: "Annuler", style: "cancel" },
+        {
+          text: "Supprimer",
+          style: "destructive",
+          onPress: () => alertStore.clear(),
+        },
+      ]
+    );
+  };
+
+  const FilterButton = ({ value, label }: { value: AlertFilter; label: string }) => {
+    const active = filter === value;
+    return (
+      <TouchableOpacity
+        style={[
+          styles.filterBtn,
+          active ? styles.filterBtnActive : styles.filterBtnInactive,
+        ]}
+        onPress={() => setFilter(value)}
+      >
+        <Text style={[styles.filterText, active && styles.filterTextActive]}>
+          {label}
+        </Text>
+      </TouchableOpacity>
+    );
+  };
 
   return (
     <SafeAreaView style={styles.safeArea}>
       <FlatList
         contentContainerStyle={ui.container}
-        data={sorted}
+        data={filteredAndSorted}
         keyExtractor={(_, idx) => `alert-${idx}`}
+        ListHeaderComponent={
+          <View style={ui.card}>
+            <View style={styles.headerRow}>
+              <Text style={ui.title}>Alertes</Text>
+
+              <TouchableOpacity
+                style={[styles.clearBtn, alerts.length === 0 && styles.clearBtnDisabled]}
+                onPress={confirmClear}
+                disabled={alerts.length === 0}
+              >
+                <Text style={styles.clearBtnText}>Clear</Text>
+              </TouchableOpacity>
+            </View>
+
+            <Text style={ui.muted}>
+              Filtre : {filter === "ALL" ? "Toutes" : filter} — Total : {alerts.length}
+            </Text>
+
+            <View style={styles.filtersRow}>
+              <FilterButton value="ALL" label="Toutes" />
+              <FilterButton value="CRITICAL" label="Critical" />
+              <FilterButton value="WARNING" label="Warning" />
+              <FilterButton value="INFO" label="Info" />
+            </View>
+
+            <Text style={[ui.muted, { marginTop: 6 }]}>
+              Tri par défaut : CRITICAL &gt; WARNING &gt; INFO, puis plus récent.
+            </Text>
+          </View>
+        }
         ListEmptyComponent={
           <View style={ui.card}>
             <Text style={ui.title}>Aucune alerte</Text>
@@ -137,4 +222,54 @@ const styles = StyleSheet.create({
     flex: 1,
     backgroundColor: ui.screen.backgroundColor,
   },
+
+  headerRow: {
+    flexDirection: "row",
+    justifyContent: "space-between",
+    alignItems: "center",
+  },
+
+  clearBtn: {
+    paddingHorizontal: 12,
+    paddingVertical: 8,
+    borderRadius: 10,
+    borderWidth: 1,
+    borderColor: "#e5e7eb",
+    backgroundColor: "#fff",
+  },
+  clearBtnDisabled: {
+    opacity: 0.5,
+  },
+  clearBtnText: {
+    fontWeight: "900",
+    color: "#dc2626",
+  },
+
+  filtersRow: {
+    flexDirection: "row",
+    flexWrap: "wrap",
+    gap: 8,
+    marginTop: 10,
+  },
+  filterBtn: {
+    paddingHorizontal: 12,
+    paddingVertical: 8,
+    borderRadius: 999,
+    borderWidth: 1,
+  },
+  filterBtnInactive: {
+    borderColor: "#e5e7eb",
+    backgroundColor: "#fff",
+  },
+  filterBtnActive: {
+    borderColor: "#14532D",
+    backgroundColor: "#14532D22",
+  },
+  filterText: {
+    fontWeight: "900",
+    color: "#0f172a",
+  },
+  filterTextActive: {
+    color: "#14532D",
+  },
 });
\ No newline at end of file