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

1376 lines
41 KiB
Markdown

## 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`
```plaintext
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
```plaintext
# 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` :
```plaintext
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 :
```plaintext
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
```plaintext
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
```plaintext
-- 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
```plaintext
# 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
```plaintext
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**
```plaintext
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**
```plaintext
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**
```php
// 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
```plaintext
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
```plaintext
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
```plaintext
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
- [x] **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
- [x] **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 :
```apache
# .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 :
```javascript
// 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>` :
```html
<!-- 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)
```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` :
```bash
#!/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 :
```javascript
// 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 :
```dart
// 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
```bash
# 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** :
```dart
// 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** :
```dart
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** :
```dart
} 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()`.
```dart
..._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.
```dart
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é)** :
```dart
userSectorIds = userSectorBox.values
.where((us) => us.id == currentUser.id)
.map((us) => us.fkSector)
.toSet();
```
**Code APRÈS (corrigé)** :
```dart
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`
```dart
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** :
```dart
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** :
```dart
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** :
```dart
import 'package:geosector_app/core/services/current_user_service.dart';
import 'package:go_router/go_router.dart';
```
**Implémentation** :
##### a) Callback `onPointTap` dans StackedColumnSeries
```dart
onPointTap: widget.showPeriodButtons ? (ChartPointDetails details) {
_handlePointTap(details, typeId);
} : null,
```
##### b) Méthode `_handlePointTap` (lignes 532-573)
```dart
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