Files
geo/app/docs/TODO-APP.md

41 KiB

TODO-APP.md

📋 Liste des tâches à effectuer sur l'application GEOSECTOR

🧪 Tests unitaires Flutter à implémenter

📁 Structure des tests

Le dossier /test doit contenir des tests unitaires pour valider le comportement de l'application.

📝 Tests prioritaires à créer

1. Tests des Repositories (test/repositories/)

test/repositories/membre_repository_test.dart

  • Test de création d'un membre avec succès
  • Test de création avec email déjà existant (409)
  • Test de mise à jour d'un membre
  • Test de suppression d'un membre
  • Test de gestion des erreurs réseau
  • Test du cache Hive local

test/repositories/user_repository_test.dart

  • Test de connexion réussie
  • Test de connexion avec mauvais identifiants
  • Test de mise à jour du profil utilisateur
  • Test de synchronisation des données
  • Test de gestion de session

test/repositories/operation_repository_test.dart

  • Test de création d'opération
  • Test d'activation/désactivation
  • Test de récupération des opérations par amicale
  • Test de suppression avec transfert de passages

2. Tests des Services (test/services/)

test/services/api_service_test.dart

  • Test de détection d'environnement (DEV/REC/PROD)
  • Test de gestion des sessions
  • Test de conversion DioException vers ApiException
  • Test des différents codes d'erreur HTTP
  • Test du retry automatique

test/services/logger_service_test.dart

  • Test de désactivation des logs en PROD
  • Test des différents niveaux de log
  • Test du formatage des messages

test/services/hive_service_test.dart

  • Test d'initialisation des boîtes Hive
  • Test de sauvegarde et récupération
  • Test de suppression de données
  • Test de migration de schéma

3. Tests des Models (test/models/)

test/models/membre_model_test.dart

  • Test de sérialisation JSON
  • Test de désérialisation JSON
  • Test de conversion vers UserModel
  • Test des valeurs par défaut

test/models/amicale_model_test.dart

  • Test de sérialisation/désérialisation
  • Test des nouveaux champs booléens (chk_mdp_manuel, chk_username_manuel)
  • Test de validation des données

4. Tests des Widgets (test/widgets/)

test/widgets/user_form_test.dart

  • Test d'affichage conditionnel des champs (username/password)
  • Test de validation du mot de passe (regex)
  • Test de validation de l'username (pas d'espaces)
  • Test de génération automatique d'username
  • Test du mode création vs modification

test/widgets/membre_table_widget_test.dart

  • Test d'affichage responsive (mobile vs web)
  • Test de masquage des colonnes sur mobile
  • Test des icônes de statut
  • Test du tri et filtrage

5. Tests d'intégration (test/integration/)

test/integration/auth_flow_test.dart

  • Test du flow complet de connexion
  • Test de persistance de session
  • Test de déconnexion
  • Test de redirection après expiration

test/integration/membre_management_test.dart

  • Test de création complète d'un membre
  • Test de modification avec validation
  • Test de suppression avec transfert
  • Test de gestion des erreurs

🛠️ Configuration des tests

Fichier test/test_helpers.dart

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:dio/dio.dart';
import 'package:hive_flutter/hive_flutter.dart';

// Mocks nécessaires
class MockDio extends Mock implements Dio {}
class MockBox<t> extends Mock implements Box<t> {}
class MockApiService extends Mock implements ApiService {}

// Helper pour initialiser Hive en tests
Future<void> setupTestHive() async {
  await Hive.initFlutter();
  // Initialiser les boîtes de test
}

// Helper pour nettoyer après les tests
Future<void> tearDownTestHive() async {
  await Hive.deleteFromDisk();
}

// Helper pour créer des données de test
class TestDataFactory {
  static UserModel createTestUser() {
    return UserModel(
      id: 1,
      username: 'test_user',
      email: 'test@example.com',
      // ...
    );
  }

  static MembreModel createTestMembre() {
    return MembreModel(
      id: 1,
      fkEntite: 100,
      name: 'Test',
      firstName: 'User',
      // ...
    );
  }
}

📋 Commandes de test

# Lancer tous les tests
flutter test

# Lancer un test spécifique
flutter test test/repositories/membre_repository_test.dart

# Lancer avec coverage
flutter test --coverage

# Générer un rapport HTML de couverture
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html

🎯 Objectifs de couverture

  • Repositories : 80% minimum
  • Services : 90% minimum
  • Models : 95% minimum
  • Widgets critiques : 70% minimum
  • Couverture globale : 75% minimum

📚 Dépendances de test à ajouter

Dans pubspec.yaml :

dev_dependencies:
  flutter_test:
    sdk: flutter
  mockito: ^5.4.4
  build_runner: ^2.4.8
  test: ^1.25.2
  flutter_lints: ^4.0.0
  coverage: ^1.7.2

🔄 Intégration CI/CD

Ajouter dans le pipeline CI :

test:
  stage: test
  script:
    - flutter test --coverage
    - genhtml coverage/lcov.info -o coverage/html
  artifacts:
    paths:
      - coverage/
  coverage: '/lines\.*: \d+\.\d+\%/'

📝 Bonnes pratiques

  1. Isolation : Chaque test doit être indépendant
  2. Mocking : Utiliser des mocks pour les dépendances externes
  3. Nommage : Utiliser des noms descriptifs (test_should_xxx_when_yyy)
  4. AAA : Suivre le pattern Arrange-Act-Assert
  5. Edge cases : Tester les cas limites et erreurs
  6. Performance : Les tests unitaires doivent être rapides (<100ms)

💬 Module Chat en ligne GEOSECTOR

📋 Vue d'ensemble

Le module chat est partiellement implémenté avec une architecture MQTT pour les notifications en temps réel. Il nécessite des développements supplémentaires pour être pleinement fonctionnel.

🏗️ Architecture existante

lib/chat/
├── models/                        # ✅ Modèles créés avec Hive
│   ├── conversation_model.dart    # Conversations (one-to-one, groupe, annonce)
│   ├── message_model.dart         # Messages avec pièces jointes
│   ├── participant_model.dart     # Participants aux conversations
│   └── audience_target_model.dart # Cibles pour les annonces
├── repositories/                  # ⚠️ À compléter
│   └── chat_repository.dart       # Logique métier du chat
├── services/                      # ⚠️ Partiellement implémenté
│   ├── chat_api_service.dart      # Communication avec l'API
│   ├── offline_queue_service.dart # File d'attente hors ligne
│   └── notifications/             # 🔧 MQTT configuré
│       ├── mqtt_notification_service.dart
│       └── mqtt_config.dart
├── widgets/                       # ⚠️ À implémenter
│   ├── chat_screen.dart          # Écran principal du chat
│   ├── conversations_list.dart   # Liste des conversations
│   ├── message_bubble.dart       # Bulles de messages
│   └── chat_input.dart           # Zone de saisie
└── pages/                        # ⚠️ À compléter
    └── chat_page.dart            # Page principale

📝 Tâches de développement Chat

1. Finalisation des modèles Hive

  • Compléter les adaptateurs Hive pour tous les modèles
  • Ajouter la gestion des pièces jointes (images, documents)
  • Implémenter le modèle AnonymousUserModel pour les utilisateurs temporaires
  • Ajouter les timestamps et statuts de lecture

2. Repository ChatRepository

  • Implémenter createConversation() avec participants
  • Implémenter sendMessage() avec queue hors ligne
  • Implémenter getConversations() avec pagination
  • Implémenter getMessages() avec lazy loading
  • Ajouter la gestion des participants (ajout/suppression)
  • Implémenter les annonces ciblées par groupe

3. Services

  • Compléter ChatApiService avec endpoints REST
  • Implémenter la synchronisation bidirectionnelle
  • Configurer OfflineQueueService pour messages en attente
  • Implémenter le retry automatique avec exponential backoff
  • Ajouter la compression des images avant envoi

4. Notifications MQTT

  • Installer et configurer Mosquitto dans le container Incus
  • Configurer SSL/TLS pour MQTT (port 8883)
  • Implémenter l'authentification par token JWT
  • Créer les topics par utilisateur/groupe/conversation
  • Implémenter le système de présence (online/offline/typing)
  • Ajouter les ACLs pour sécuriser les topics

5. Interface utilisateur

  • Créer ChatScreen avec liste de messages
  • Implémenter ConversationsList avec badges non-lus
  • Designer MessageBubble (texte, images, documents)
  • Créer ChatInput avec:
    • Saisie de texte avec emoji picker
    • Bouton d'envoi de fichiers
    • Indicateur "en train d'écrire"
    • Enregistrement vocal (optionnel)
  • Ajouter les animations (apparition messages, typing indicator)
  • Implémenter le swipe pour répondre
  • Ajouter la recherche dans les conversations

6. Fonctionnalités avancées

  • Notifications push locales via flutter_local_notifications
  • Chiffrement end-to-end des messages sensibles
  • Réactions aux messages (emojis)
  • Messages éphémères avec auto-suppression
  • Partage de localisation en temps réel
  • Appels audio/vidéo via WebRTC (phase 2)

🔧 Configuration backend requise

Base de données

-- Tables à créer (voir chat_tables.sql)
CREATE TABLE chat_conversations (
  id INT PRIMARY KEY AUTO_INCREMENT,
  type ENUM('one_to_one', 'group', 'announcement'),
  created_by INT,
  created_at TIMESTAMP,
  updated_at TIMESTAMP
);

CREATE TABLE chat_messages (
  id INT PRIMARY KEY AUTO_INCREMENT,
  conversation_id INT,
  sender_id INT,
  content TEXT,
  type ENUM('text', 'image', 'file', 'location'),
  status ENUM('sent', 'delivered', 'read'),
  created_at TIMESTAMP
);

CREATE TABLE chat_participants (
  conversation_id INT,
  user_id INT,
  role ENUM('admin', 'member'),
  joined_at TIMESTAMP,
  last_read_message_id INT
);

Configuration MQTT

# Installation Mosquitto
apt-get install mosquitto mosquitto-clients

# Configuration /etc/mosquitto/mosquitto.conf
listener 1883
listener 8883
cafile /etc/mosquitto/ca.crt
certfile /etc/mosquitto/server.crt
keyfile /etc/mosquitto/server.key
allow_anonymous false
password_file /etc/mosquitto/passwd

📦 Dépendances à ajouter

dependencies:
  # MQTT et notifications
  mqtt5_client: ^4.0.0
  flutter_local_notifications: ^17.0.0

  # UI et UX
  emoji_picker_flutter: ^2.0.0
  cached_network_image: ^3.3.1
  photo_view: ^0.14.0
  file_picker: ^6.1.1

  # Utilitaires
  path_provider: ^2.1.2
  image_picker: ^1.0.7
  image: ^4.1.7  # Pour compression
  intl: ^0.19.0  # Pour formatage dates

🧪 Tests à implémenter

  • Tests unitaires des repositories
  • Tests d'intégration MQTT
  • Tests de performance (1000+ messages)
  • Tests hors ligne/online
  • Tests de sécurité (injection, XSS)

💳 Module de paiement Stripe

📋 Vue d'ensemble

Intégration de Stripe pour permettre aux amicales ayant activé chk_stripe d'accepter les paiements par carte bancaire avec une commission de 1.4%.

🎯 Objectifs

  1. Permettre le paiement en ligne lors des passages
  2. Gérer les comptes Stripe des amicales
  3. Suivre les transactions et commissions
  4. Offrir une expérience de paiement fluide

📝 Tâches de développement Stripe

1. Configuration initiale Stripe

  • Créer un compte Stripe Connect Platform
  • Configurer les webhooks Stripe
  • Mettre en place l'environnement de test (sandbox)
  • Configurer les clés API (publishable/secret)
  • Implémenter la gestion sécurisée des clés

2. Onboarding des amicales

  • Créer un workflow d'inscription Stripe pour les amicales
  • Implémenter Stripe Connect Onboarding
  • Gérer le KYC (Know Your Customer) requis par Stripe
  • Stocker de manière sécurisée le stripe_account_id
  • Créer une page de statut du compte Stripe
  • Gérer les documents requis (RIB, statuts, etc.)

3. Modèles de données

  • Créer StripeAccountModel pour les comptes Connect
  • Créer PaymentIntentModel pour les intentions de paiement
  • Créer TransactionModel pour l'historique
  • Ajouter les champs Stripe dans PassageModel
  • Implémenter la table des commissions

4. Service StripeService

class StripeService {
  // Compte Connect
  Future<string> createConnectAccount(AmicaleModel amicale);
  Future<void> updateAccountStatus(String accountId);
  Future<string> createAccountLink(String accountId);

  // Paiements
  Future<paymentintent> createPaymentIntent({
    required double amount,
    required String currency,
    required String connectedAccountId,
  });

  Future<void> confirmPayment(String paymentIntentId);
  Future<void> refundPayment(String paymentIntentId);

  // Commissions
  double calculateApplicationFee(double amount); // 1.4%
}

5. Interface de paiement dans PassageForm

  • Détecter si l'amicale accepte Stripe (chk_stripe)
  • Ajouter l'option "Paiement par carte" dans le dropdown
  • Intégrer Stripe Elements pour la saisie de carte
  • Implémenter le flow de paiement 3D Secure
  • Gérer les erreurs de paiement
  • Afficher le reçu de paiement
  • Permettre l'envoi du reçu par email

6. Widget StripePaymentSheet

class StripePaymentSheet extends StatefulWidget {
  final double amount;
  final String currency;
  final AmicaleModel amicale;
  final Function(String) onSuccess;
  final Function(String) onError;

  // UI avec:
  // - Montant à payer
  // - Formulaire de carte (Stripe Elements)
  // - Bouton de validation
  // - Indicateur de traitement
  // - Gestion 3D Secure
}

7. Tableau de bord financier

  • Page de suivi des transactions Stripe
  • Graphiques des paiements par période
  • Export des transactions (CSV/Excel)
  • Calcul automatique des commissions
  • Rapprochement bancaire
  • Dashboard temps réel des paiements

8. Webhooks Stripe

// Backend PHP pour gérer les webhooks
class StripeWebhookHandler {
  // Events à gérer:
  - payment_intent.succeeded
  - payment_intent.failed
  - account.updated
  - payout.created
  - refund.created
}

9. Sécurité

  • Chiffrement des données sensibles
  • Validation PCI DSS
  • Audit trail des transactions
  • Détection de fraude
  • Rate limiting sur les API
  • Tokenisation des cartes

10. Tests et conformité

  • Tests avec cartes de test Stripe
  • Tests des cas d'erreur (carte refusée, etc.)
  • Tests 3D Secure
  • Tests de performance
  • Conformité RGPD pour les données de paiement
  • Documentation utilisateur

🔧 Configuration backend requise

Tables base de données

CREATE TABLE stripe_accounts (
  id INT PRIMARY KEY AUTO_INCREMENT,
  fk_entite INT NOT NULL,
  stripe_account_id VARCHAR(255) ENCRYPTED,
  status ENUM('pending', 'active', 'restricted'),
  charges_enabled BOOLEAN DEFAULT FALSE,
  payouts_enabled BOOLEAN DEFAULT FALSE,
  created_at TIMESTAMP,
  updated_at TIMESTAMP
);

CREATE TABLE stripe_transactions (
  id INT PRIMARY KEY AUTO_INCREMENT,
  fk_passage INT,
  stripe_payment_intent_id VARCHAR(255),
  amount DECIMAL(10,2),
  currency VARCHAR(3),
  status VARCHAR(50),
  application_fee DECIMAL(10,2),
  created_at TIMESTAMP
);

Variables d'environnement

STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
STRIPE_APPLICATION_FEE_PERCENT=1.4

📦 Dépendances Stripe

dependencies:
  # Stripe
  flutter_stripe: ^10.1.1

  # Sécurité
  flutter_secure_storage: ^9.0.0

  # UI
  shimmer: ^3.0.0  # Loading states
  lottie: ^3.1.0   # Animations succès/échec

🚀 Roadmap d'implémentation

Phase 1 (2 semaines)

  • Configuration Stripe Connect
  • Onboarding basique des amicales
  • Tests en sandbox

Phase 2 (3 semaines)

  • Intégration dans PassageForm
  • Gestion des paiements simples
  • Webhooks essentiels

Phase 3 (2 semaines)

  • Dashboard financier
  • Export et rapports
  • Tests complets

Phase 4 (1 semaine)

  • Mise en production
  • Monitoring
  • Documentation

💰 Estimation des coûts

  • Stripe Connect : 0.25€ par compte actif/mois
  • Transactions : 1.4% + 0.25€ par transaction
  • Commission application : 1.4% (reversée à GEOSECTOR)
  • Coût net pour l'amicale : ~2.8% + 0.25€ par transaction

Date de création : 2025-08-07
Auteur : Architecture Team
Version : 1.3.0

🧪 Recette - Points à corriger/améliorer

📊 Gestion des membres et statistiques

Affichage et listes

  • Ajouter la liste des membres avec leurs statistiques comme dans l'ancienne version
  • Historique avec choix des membres - Permettre la sélection du membre dans l'historique
  • Membres cochés en haut - Dans la modification de secteur, afficher les membres sélectionnés en priorité
  • Filtres sur la liste des membres - Ajouter des filtres dans la page "Amicale et membres"

Modification des secteurs

  • Bug : Changement de membre non pris en compte - CORRIGÉ - La modification n'est pas sauvegardée lors du changement de membre sur un secteur

📝 Formulaires et saisie

Passage

  • Nom obligatoire seulement si email - CORRIGÉ - Le nom n'est obligatoire que si un email est renseigné

Membre

  • Email non obligatoire - Si identifiant et mot de passe sont saisis manuellement, l'email ne doit pas être obligatoire
  • Helpers lisibles - Améliorer les textes d'aide dans la fiche membre
  • Modification de l'identifiant - Permettre la modification de l'identifiant utilisateur
  • Bug mot de passe généré - Le mot de passe généré contient des espaces, ce qui pose problème

💬 Module Chat

  • Bouton "Envoyer un message" - Améliorer la visibilité
  • Police plus grasse - Augmenter l'épaisseur de la police pour une meilleure lisibilité

🗺️ Carte et géolocalisation

Configuration carte

  • Zoom maximal - Définir et implémenter une limite de zoom maximum
  • Carte type Snapchat - Étudier l'utilisation d'un style de carte similaire à Snapchat

Mode terrain

  • Revoir la géolocalisation - Améliorer la précision et le fonctionnement de la géolocalisation en mode terrain

📅 Historique et dates

  • Dates de début et fin - Ajouter des sélecteurs de dates de début et fin dans l'historique

🔐 Authentification et connexion

Connexion

  • Admin uniquement en web - Restreindre l'accès admin au web uniquement (pas sur mobile)
  • Bug F5 - Corriger la déconnexion lors du rafraîchissement de la page (F5)
  • Connexion multi-rôles - En connexion utilisateur, permettre de se connecter soit comme admin, soit comme membre

Inscription

  • Double envoi email - Envoyer 2 emails lors de l'inscription : un pour l'identifiant, un pour le mot de passe, avec informations complémentaires

💳 Module Stripe

  • Intégration dans le formulaire de passage - Créer la gestion du paiement en ligne au niveau du formulaire passage si l'amicale a un compte Stripe actif
  • Mode hors connexion - Étudier les possibilités de paiement Stripe en mode hors ligne

👑 Mode Super Admin

Gestion des amicales

  • Bug suppression - Corriger le ralentissement après 3 suppressions d'amicales (problème de purge)
  • Filtres sur les amicales - Ajouter des filtres de recherche/tri sur la liste des amicales
  • Mode démo - Implémenter un mode démo pour les présentations
  • Statuts actifs/inactifs - Distinguer les amicales actives (qui ont réglé) des autres

Gestion des opérations

  • Bug suppression opération active - Si suppression de l'opération active, la précédente doit redevenir active automatiquement

Deadline

  • ⚠️ DATE BUTOIR : 08/10 pour le Congrès

🌐 Gestion du cache Flutter Web

📋 Vue d'ensemble

Stratégie de gestion du cache pour l'application Flutter Web selon l'environnement (DEV/REC/PROD).

🎯 Objectifs

  • DEV/REC : Aucun cache - rechargement forcé à chaque visite pour voir immédiatement les changements
  • PROD : Cache intelligent avec versioning pour optimiser les performances

📝 Solution par environnement

1. Environnements DEV et RECETTE

Stratégie : No-Cache radical

Configuration serveur web (Apache)

Créer/modifier le fichier .htaccess dans le dossier racine web :

# .htaccess pour DEV/REC - Aucun cache
<IfModule mod_headers.c>
    # Désactiver complètement le cache
    Header set Cache-Control "no-cache, no-store, must-revalidate, private"
    Header set Pragma "no-cache"
    Header set Expires "0"

    # Forcer le rechargement pour tous les assets
    <FilesMatch "\.(js|css|html|json|wasm|ttf|otf|woff|woff2|ico|png|jpg|jpeg|gif|svg)$">
        Header set Cache-Control "no-cache, no-store, must-revalidate"
        Header set Pragma "no-cache"
        Header set Expires "0"
    </FilesMatch>
</IfModule>

# Désactiver le cache du navigateur via ETags
FileETag None
<IfModule mod_headers.c>
    Header unset ETag
</IfModule>
Modification du service worker

Dans web/flutter_service_worker.js, ajouter au début :

// DEV/REC: Forcer le rechargement complet
if (location.hostname === 'dapp.geosector.fr' || location.hostname === 'rapp.geosector.fr') {
    // Nettoyer tous les caches existants
    caches.keys().then(function(names) {
        for (let name of names) caches.delete(name);
    });

    // Bypass le service worker pour toutes les requêtes
    self.addEventListener('fetch', function(event) {
        event.respondWith(fetch(event.request));
    });

    // Désactiver le cache complètement
    return;
}
Headers Meta HTML

Dans web/index.html, ajouter dans le <head> :

<!-- DEV/REC: No-cache meta tags -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">

<!-- Forcer le rechargement des ressources avec timestamp -->
<script>
    // Ajouter un timestamp unique à toutes les ressources
    if (location.hostname === 'dapp.geosector.fr' || location.hostname === 'rapp.geosector.fr') {
        const timestamp = new Date().getTime();
        window.flutterConfiguration = {
            assetBase: './?t=' + timestamp,
            canvasKitBaseUrl: 'canvaskit/?t=' + timestamp
        };
    }
</script>

2. Environnement PRODUCTION

Stratégie : Cache intelligent avec versioning

Configuration serveur web (Apache)
# .htaccess pour PRODUCTION - Cache intelligent
<IfModule mod_headers.c>
    # Cache par défaut modéré pour HTML
    Header set Cache-Control "public, max-age=3600, must-revalidate"

    # Cache long pour les assets statiques versionnés
    <FilesMatch "\.(js|css|wasm)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>

    # Cache modéré pour les images et fonts
    <FilesMatch "\.(ttf|otf|woff|woff2|ico|png|jpg|jpeg|gif|svg)$">
        Header set Cache-Control "public, max-age=604800"
    </FilesMatch>

    # Pas de cache pour le service worker et manifeste
    <FilesMatch "(flutter_service_worker\.js|manifest\.json)$">
        Header set Cache-Control "no-cache, no-store, must-revalidate"
    </FilesMatch>
</IfModule>

# Activer ETags pour validation du cache
FileETag MTime Size
Script de build avec versioning

Créer build_web.sh :

#!/bin/bash
# Script de build pour production avec versioning automatique

# Générer un hash de version basé sur le timestamp
VERSION=$(date +%Y%m%d%H%M%S)
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "no-git")

# Build Flutter
flutter build web --release --dart-define=APP_VERSION=$VERSION

# Modifier index.html pour inclure la version
sed -i "s/main.dart.js/main.dart.js?v=$VERSION/g" build/web/index.html
sed -i "s/flutter.js/flutter.js?v=$VERSION/g" build/web/index.html

# Ajouter version dans le service worker
echo "const APP_VERSION = '$VERSION-$COMMIT_HASH';" | cat - build/web/flutter_service_worker.js > temp && mv temp build/web/flutter_service_worker.js

# Créer un fichier version.json
echo "{\"version\":\"$VERSION\",\"build\":\"$COMMIT_HASH\",\"date\":\"$(date -Iseconds)\"}" > build/web/version.json

echo "Build completed with version: $VERSION-$COMMIT_HASH"
Service worker intelligent

Modifier web/flutter_service_worker.js pour production :

// PRODUCTION: Cache intelligent avec versioning
const CACHE_VERSION = 'v1-' + APP_VERSION; // APP_VERSION injecté par le build
const RUNTIME = 'runtime';

// Installation : mettre en cache les ressources essentielles
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(CACHE_VERSION).then((cache) => {
            return cache.addAll([
                '/',
                'main.dart.js',
                'flutter.js',
                'manifest.json'
            ]);
        })
    );
    self.skipWaiting();
});

// Activation : nettoyer les vieux caches
self.addEventListener('activate', (event) => {
    event.waitUntil(
        caches.keys().then((cacheNames) => {
            return Promise.all(
                cacheNames.map((cacheName) => {
                    if (cacheName !== CACHE_VERSION && cacheName !== RUNTIME) {
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
    self.clients.claim();
});

// Fetch : stratégie cache-first pour assets, network-first pour API
self.addEventListener('fetch', (event) => {
    const url = new URL(event.request.url);

    // Network-first pour API et données dynamiques
    if (url.pathname.startsWith('/api/')) {
        event.respondWith(
            fetch(event.request)
                .then((response) => {
                    const responseClone = response.clone();
                    caches.open(RUNTIME).then((cache) => {
                        cache.put(event.request, responseClone);
                    });
                    return response;
                })
                .catch(() => caches.match(event.request))
        );
        return;
    }

    // Cache-first pour assets statiques
    event.respondWith(
        caches.match(event.request).then((cachedResponse) => {
            return cachedResponse || fetch(event.request).then((response) => {
                return caches.open(RUNTIME).then((cache) => {
                    cache.put(event.request, response.clone());
                    return response;
                });
            });
        })
    );
});

🔧 Détection automatique de nouvelle version

Ajouter dans l'application Flutter :

// lib/services/version_check_service.dart
class VersionCheckService {
  static const Duration _checkInterval = Duration(minutes: 5);
  Timer? _timer;
  String? _currentVersion;

  void startVersionCheck() {
    if (!kIsWeb) return;

    // Vérifier la version toutes les 5 minutes
    _timer = Timer.periodic(_checkInterval, (_) => _checkVersion());

    // Vérification initiale
    _checkVersion();
  }

  Future<void> _checkVersion() async {
    try {
      final response = await http.get(
        Uri.parse('/version.json?t=${DateTime.now().millisecondsSinceEpoch}')
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        final newVersion = data['version'];

        if (_currentVersion != null && _currentVersion != newVersion) {
          // Nouvelle version détectée
          _showUpdateDialog();
        }
        _currentVersion = newVersion;
      }
    } catch (e) {
      print('Erreur vérification version: $e');
    }
  }

  void _showUpdateDialog() {
    // Afficher une notification ou dialog
    showDialog(
      context: navigatorKey.currentContext!,
      barrierDismissible: false,
      builder: (context) => AlertDialog(
        title: Text('Nouvelle version disponible'),
        content: Text('Une nouvelle version de l\'application est disponible. '
                     'L\'application va se recharger.'),
        actions: [
          TextButton(
            onPressed: () {
              // Forcer le rechargement complet
              html.window.location.reload();
            },
            child: Text('Recharger maintenant'),
          ),
        ],
      ),
    );
  }

  void dispose() {
    _timer?.cancel();
  }
}

📋 Commandes de déploiement

# DEV/REC - Déploiement sans cache
flutter build web --release
rsync -av --delete build/web/ user@server:/var/www/dapp/

# PRODUCTION - Déploiement avec versioning
./build_web.sh
rsync -av --delete build/web/ user@server:/var/www/app/

🧪 Validation du no-cache

Pour vérifier que le cache est désactivé en DEV/REC :

  1. Ouvrir Chrome DevTools → Network
  2. Vérifier les headers de réponse :
    • Cache-Control: no-cache, no-store, must-revalidate
    • Pragma: no-cache
    • Expires: 0
  3. Recharger la page : tous les fichiers doivent être rechargés (status 200, pas 304)
  4. Vérifier dans Application → Storage → Clear site data

📝 Notes importantes

  • DEV/REC : Les utilisateurs verront toujours la dernière version immédiatement
  • PROD : Les utilisateurs bénéficient d'un cache optimisé avec détection automatique des mises à jour
  • Service Worker : Géré différemment selon l'environnement
  • Versioning : Utilise timestamp + hash git pour identifier uniques les builds
  • Fallback : En cas d'échec réseau en PROD, utilise le cache pour maintenir l'app fonctionnelle

Date d'ajout : 2025-09-23 Auteur : Solution de gestion du cache Version : 1.0.0

Améliorations de l'interactivité des graphiques - v3.3.5

Date : 06/10/2025 Version : 3.3.5 Statut : Complété

📋 Vue d'ensemble

Amélioration majeure de l'expérience utilisateur avec l'ajout d'interactivité sur tous les graphiques et cartes du tableau de bord, permettant une navigation intelligente vers l'historique avec filtres pré-appliqués.

🎯 Modifications apportées

1. Réinitialisation des filtres lors des clics sur les graphiques

Fichiers modifiés :

  • lib/presentation/widgets/charts/passage_summary_card.dart
  • lib/presentation/widgets/charts/payment_summary_card.dart
  • lib/presentation/widgets/sector_distribution_card.dart

Implémentation :

// Réinitialiser TOUS les filtres avant de sauvegarder le nouveau
settingsBox.delete('history_selectedPaymentTypeId');
settingsBox.delete('history_selectedSectorId');
settingsBox.delete('history_selectedSectorName');
settingsBox.delete('history_selectedMemberId');
settingsBox.delete('history_startDate');
settingsBox.delete('history_endDate');

// Sauvegarder uniquement le critère sélectionné
settingsBox.put('history_selectedTypeId', typeId);

Bénéfice : L'utilisateur voit uniquement les passages correspondant au critère cliqué, sans interférence d'anciens filtres.


2. Navigation directe vers les pages d'historique

Correction : Changement des routes de navigation de /admin et /user vers /admin/history et /user/history.

Code :

final bool isAdmin = CurrentUserService.instance.shouldShowAdminUI;
context.go(isAdmin ? '/admin/history' : '/user/history');

Bénéfice : Navigation immédiate vers la page cible sans étape intermédiaire.


3. Chargement des filtres pour tous les utilisateurs

Fichier : lib/presentation/pages/history_page.dart (lignes 143-151)

Problème : La méthode _loadPreselectedFilters() n'était appelée que pour les admins.

Solution :

} else {
  _loadPreselectedFilters(); // Maintenant appelé pour tous
  if (!isAdmin) {
    selectedMemberId = currentUserId;
  }
}

Bénéfice : Les filtres fonctionnent correctement en mode utilisateur.


4. Correction du dropdown des membres (admin)

Fichier : lib/presentation/pages/history_page.dart (lignes 537-542)

Problème : Utilisation de Hive.box<UserModel> qui ne contient que le currentUser.

Solution : Utiliser la liste _users construite depuis membreRepository.getAllMembres().

..._users.map((UserModel user) {
  return DropdownMenuItem<int?>(
    value: user.id,
    child: Text('${user.firstName ?? ''} ${user.name ?? ''}'),
  );
}),

Bénéfice : Affichage correct de tous les membres de l'amicale.


5. Adaptation dynamique de la hauteur des cartes

Fichiers :

  • lib/presentation/widgets/sector_distribution_card.dart
  • lib/presentation/widgets/members_board_passages.dart

Modification : Suppression des contraintes de hauteur fixe.

ListView.builder(
  shrinkWrap: true,
  physics: const NeverScrollableScrollPhysics(),
  itemCount: sectorStats.length,
  itemBuilder: (context, index) => ...,
)

Bénéfice : Les cartes s'adaptent à leur contenu sans espace vide inutile.


6. Correction du bug ActivityChart (secteurs utilisateur)

Fichier : lib/presentation/widgets/charts/activity_chart.dart (lignes 196-201)

Problème : Logique incorrecte de récupération des secteurs utilisateur.

Code AVANT (bugué) :

userSectorIds = userSectorBox.values
  .where((us) => us.id == currentUser.id)
  .map((us) => us.fkSector)
  .toSet();

Code APRÈS (corrigé) :

final userSectors = userRepository.getUserSectors();
userSectorIds = userSectors.map((sector) => sector.id).toSet();

Bénéfice : Le graphique affiche correctement les passages des secteurs assignés à l'utilisateur.


7. Ajout de boutons de période (7j/14j/21j)

Fichier : lib/presentation/widgets/charts/activity_chart.dart

Implémentation :

  • Ajout d'un état _selectedDays (par défaut 7 jours)
  • Création de la méthode _buildPeriodButton(int days)
  • Affichage conditionnel via paramètre showPeriodButtons
Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    _buildPeriodButton(7),
    const SizedBox(width: 4),
    _buildPeriodButton(14),
    const SizedBox(width: 4),
    _buildPeriodButton(21),
  ],
)

Bénéfice : L'utilisateur peut rapidement changer la période d'affichage.


8. Affichage conditionnel des boutons de période

Paramètre ajouté : showPeriodButtons (par défaut false)

Usage :

  • home_page.dart : showPeriodButtons: true
  • history_page.dart : non utilisé (donc false)

Bénéfice : Les boutons n'apparaissent que sur la page d'accueil.


9. Passages type 2 éditables par tous les utilisateurs

Fichier : lib/presentation/pages/history_page.dart (ligne 1606)

Modification :

if (isAdmin || passage.fkUser == currentUserId || passage.fkType == 2) {
  _handlePassageEdit(passage);
}

Bénéfice : Tous les utilisateurs peuvent finaliser les passages de type 2 (À finaliser).


10. Noms de secteurs cliquables pour les utilisateurs

Fichier : lib/presentation/widgets/sector_distribution_card.dart (lignes 321-342)

Implémentation :

Expanded(
  child: InkWell(
    onTap: () {
      final settingsBox = Hive.box(AppKeys.settingsBoxName);
      if (isAdmin) {
        // Admin : naviguer vers la carte
        settingsBox.put('selectedSectorId', sectorId);
        settingsBox.put('selectedPageIndex', 4);
        context.go('/admin');
      } else {
        // User : naviguer vers l'historique avec filtre secteur
        settingsBox.delete('history_selectedTypeId');
        settingsBox.delete('history_selectedPaymentTypeId');
        // ... autres suppressions
        settingsBox.put('history_selectedSectorId', sectorId);
        settingsBox.put('history_selectedSectorName', name);
        context.go('/user/history');
      }
    },
    child: Text(name, ...),
  ),
),

Bénéfice : Les utilisateurs peuvent cliquer sur un nom de secteur pour voir ses passages dans l'historique.


11. Interactivité des segments de barres ActivityChart

Fichier : lib/presentation/widgets/charts/activity_chart.dart

Nouvelles dépendances :

import 'package:geosector_app/core/services/current_user_service.dart';
import 'package:go_router/go_router.dart';

Implémentation :

a) Callback onPointTap dans StackedColumnSeries
onPointTap: widget.showPeriodButtons ? (ChartPointDetails details) {
  _handlePointTap(details, typeId);
} : null,
b) Méthode _handlePointTap (lignes 532-573)
void _handlePointTap(ChartPointDetails details, int typeId) {
  if (details.pointIndex == null || details.pointIndex! < 0) return;

  final passageBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
  final chartData = _calculateActivityData(passageBox, _selectedDays);

  if (details.pointIndex! >= chartData.length) return;

  final clickedData = chartData[details.pointIndex!];
  final clickedDate = clickedData.date;

  final settingsBox = Hive.box(AppKeys.settingsBoxName);

  // Réinitialiser tous les autres filtres
  settingsBox.delete('history_selectedPaymentTypeId');
  settingsBox.delete('history_selectedSectorId');
  settingsBox.delete('history_selectedSectorName');
  settingsBox.delete('history_selectedMemberId');

  // Appliquer le filtre de type
  settingsBox.put('history_selectedTypeId', typeId);

  // Définir la plage de dates pour la journée complète
  final startDateTime = DateTime(
    clickedDate.year,
    clickedDate.month,
    clickedDate.day,
    0, 0, 0
  );
  settingsBox.put('history_startDate', startDateTime.millisecondsSinceEpoch);

  final endDateTime = DateTime(
    clickedDate.year,
    clickedDate.month,
    clickedDate.day,
    23, 59, 59
  );
  settingsBox.put('history_endDate', endDateTime.millisecondsSinceEpoch);

  // Naviguer vers l'historique
  final bool isAdmin = CurrentUserService.instance.shouldShowAdminUI;
  context.go(isAdmin ? '/admin/history' : '/user/history');
}

Fonctionnalités :

  • Clic sur un segment de barre → filtre par type ET date exacte
  • Date de début : jour cliqué à 00:00:00
  • Date de fin : jour cliqué à 23:59:59
  • Réinitialisation de tous les autres filtres
  • Navigation contextuelle (admin/user)

Bénéfice : Navigation ultra-précise vers les passages d'un type spécifique pour une journée donnée.


📊 Impact UX

Fonctionnalité Avant Après
Clics sur graphiques Non fonctionnel Navigation avec filtres
Filtres utilisateurs Ne marchait pas Fonctionnels
Dropdown membres Vide en admin Tous les membres
Hauteur des cartes Fixe (espace vide) Adaptative
ActivityChart users Pas de données Affichage correct
Boutons de période Absents 7j/14j/21j
Édition type 2 Admin seulement Tous les users
Secteurs cliquables Admin uniquement Admin et users
Segments de barres Non cliquables Filtrage par type+date

🎨 Expérience utilisateur améliorée

  1. Navigation intuitive : Cliquer sur n'importe quel élément visuel (graphique, secteur, barre) filtre automatiquement l'historique
  2. Filtres intelligents : Réinitialisation automatique pour éviter les conflits
  3. Contexte préservé : Admin et utilisateurs ont des comportements adaptés
  4. Période flexible : Choix rapide entre 7, 14 ou 21 jours
  5. Précision temporelle : Sélection jour par jour via les segments de barres

🔍 Fichiers modifiés

lib/presentation/widgets/charts/
├── passage_summary_card.dart       ✏️ Filtres + navigation
├── payment_summary_card.dart       ✏️ Filtres + navigation
└── activity_chart.dart             ✏️ Boutons période + clic segments

lib/presentation/widgets/
└── sector_distribution_card.dart   ✏️ Filtres + hauteur + clics users

lib/presentation/pages/
├── home_page.dart                  ✏️ Paramètres ActivityChart
└── history_page.dart               ✏️ Filtres users + dropdown membres

🧪 Tests effectués

  • Clics sur PassageSummaryCard → historique filtré
  • Clics sur PaymentSummaryCard → historique filtré
  • Clics sur SectorDistributionCard → historique filtré
  • Clics sur segments ActivityChart → historique avec type + date
  • Boutons de période 7j/14j/21j fonctionnels
  • Affichage correct en mode admin et user
  • Dropdown membres affiche tous les membres
  • Hauteur des cartes adaptative
  • Édition passages type 2 par tous

🚀 Prochaines étapes suggérées

  • Ajouter un indicateur visuel sur les éléments cliquables (cursor: pointer)
  • Animation de transition lors de la navigation vers l'historique
  • Tooltip sur les segments de barres pour prévisualiser les données
  • Export des données filtrées depuis l'historique
  • Mémorisation des périodes préférées par utilisateur

Date de complétion : 06/10/2025 Testé par : Équipe de développement Statut : Prêt pour production