- Amélioration de la gestion des entités et des utilisateurs - Mise à jour des modèles Amicale et Client avec champs supplémentaires - Ajout du service de logging et amélioration du chargement UI - Refactoring des formulaires utilisateur et amicale - Intégration de file_picker et image_picker pour la gestion des fichiers - Amélioration de la gestion des membres et de leur suppression - Optimisation des performances de l'API - Mise à jour de la documentation technique 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1399 lines
47 KiB
Markdown
Executable File
1399 lines
47 KiB
Markdown
Executable File
# GEOSECTOR v2.0
|
||
|
||
🚒 **Application de gestion des distributions de calendriers par secteurs géographiques pour les amicales de pompiers**
|
||
|
||
---
|
||
|
||
## 🎯 Vue d'ensemble
|
||
|
||
GEOSECTOR est une solution complète développée en Flutter qui révolutionne la gestion des campagnes de distribution de calendriers pour les amicales de pompiers. L'application combine géolocalisation, gestion multi-rôles et synchronisation en temps réel pour optimiser les tournées et maximiser l'efficacité des équipes.
|
||
|
||
### 🏆 Points forts de la v2.0
|
||
|
||
- **Architecture moderne** sans Provider, basée sur l'injection de dépendances
|
||
- **Réactivité native** avec ValueListenableBuilder et Hive
|
||
- **Interface adaptative** selon les rôles utilisateur
|
||
- **Performance optimisée** avec un ApiService singleton
|
||
- **Gestion avancée des permissions** multi-niveaux
|
||
- **Gestion d'erreurs centralisée** avec ApiException
|
||
- **Interface utilisateur unifiée** avec UserFormDialog réutilisable
|
||
|
||
---
|
||
|
||
## 📋 Table des matières
|
||
|
||
1. [Fonctionnalités](#-fonctionnalités)
|
||
2. [Architecture technique](#️-architecture-technique)
|
||
3. [Installation](#-installation-et-configuration)
|
||
4. [Modèles de données](#️-modèles-de-données)
|
||
5. [Architecture des composants](#-architecture-des-composants)
|
||
6. [Gestion des rôles](#-gestion-des-rôles)
|
||
7. [Interface utilisateur](#-interface-utilisateur)
|
||
8. [API et synchronisation](#-api-et-synchronisation)
|
||
9. [Gestion des erreurs](#-gestion-des-erreurs)
|
||
10. [Cartes et géolocalisation](#️-cartes-et-géolocalisation)
|
||
|
||
---
|
||
|
||
## 🚀 Fonctionnalités
|
||
|
||
### 🎯 Fonctionnalités métier
|
||
|
||
#### Pour les **Membres** (Rôle 1)
|
||
|
||
- ✅ Visualisation des secteurs assignés sur carte interactive
|
||
- ✅ Suivi GPS en temps réel des tournées
|
||
- ✅ Enregistrement des passages avec géolocalisation
|
||
- ✅ Gestion des stocks de calendriers
|
||
- ✅ Historique des distributions
|
||
- ✅ Chat intégré avec l'équipe
|
||
|
||
#### Pour les **Admins Amicale** (Rôle 2)
|
||
|
||
- ✅ Gestion de leur amicale (informations, coordonnées)
|
||
- ✅ Gestion des membres de l'amicale (création, modification, suppression)
|
||
- ✅ Attribution des rôles aux membres (Membre/Administrateur)
|
||
- ✅ Gestion du statut actif des comptes membres
|
||
- ✅ Consultation des statistiques de l'amicale
|
||
- ✅ Attribution des secteurs aux membres
|
||
- ✅ Suivi des performances équipe
|
||
|
||
#### Pour les **Super Admins** (Rôle 3+)
|
||
|
||
- ✅ Gestion globale multi-amicales
|
||
- ✅ Administration des utilisateurs et permissions
|
||
- ✅ Configuration des paramètres système
|
||
- ✅ Analytics avancées et reporting
|
||
- ✅ Gestion des secteurs géographiques
|
||
|
||
### 🔧 Fonctionnalités techniques
|
||
|
||
- **🗺️ Cartographie avancée** : Flutter Map avec tuiles Mapbox
|
||
- **📍 Géolocalisation précise** : Suivi GPS des équipes
|
||
- **💾 Stockage hybride** : Cache local Hive + synchronisation cloud avec optimisation des performances
|
||
- **💬 Communication** : Chat MQTT en temps réel
|
||
- **🔐 Sécurité** : Authentification JWT + gestion fine des permissions
|
||
- **📱 Multi-plateforme** : iOS, Android, Web
|
||
- **🌐 Mode hors-ligne** : Fonctionnement dégradé sans connexion
|
||
- **⚡ Gestion d'erreurs robuste** : Extraction automatique des messages API
|
||
- **🎨 Interface responsive** : Adaptation automatique selon la taille d'écran
|
||
|
||
---
|
||
|
||
## 🏗️ Architecture technique
|
||
|
||
### Stack technologique
|
||
|
||
| Composant | Technologie | Version | Usage |
|
||
| ------------------- | ---------------------- | ------- | ------------------------------ |
|
||
| **Framework** | Flutter | 3.32+ | Interface multi-plateforme |
|
||
| **Langage** | Dart | 3.0+ | Logique applicative |
|
||
| **Navigation** | GoRouter | 12.1.3 | Routing déclaratif |
|
||
| **Stockage local** | Hive | 2.2.3 | Base de données NoSQL locale |
|
||
| **Réactivité** | ValueListenableBuilder | Native | Écoute des changements Hive |
|
||
| **HTTP** | Dio | 5.4.0 | Client HTTP avec intercepteurs |
|
||
| **Cartes** | Flutter Map | 6.1.0 | Rendu cartographique |
|
||
| **Géolocalisation** | Geolocator | 10.1.0 | Services de localisation |
|
||
| **Chat** | MQTT5 Client | 4.2.0 | Messagerie temps réel |
|
||
| **UI** | Material Design 3 | Native | Composants d'interface |
|
||
| **Logging** | LoggerService | Custom | Logs conditionnels par env |
|
||
|
||
### 🏛️ Architecture en couches
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[UI Layer - Widgets] --> B[Repository Layer - Business Logic]
|
||
B --> C[Data Layer - Hive + API]
|
||
|
||
A1[ValueListenableBuilder] --> A
|
||
A2[Custom Widgets] --> A
|
||
A3[UserFormDialog] --> A
|
||
|
||
B1[UserRepository] --> B
|
||
B2[AmicaleRepository] --> B
|
||
B3[MembreRepository] --> B
|
||
|
||
C1[Hive Boxes] --> C
|
||
C2[API Service Singleton] --> C
|
||
C3[ApiException Handler] --> C
|
||
```
|
||
|
||
### 📁 Structure du projet
|
||
|
||
```
|
||
app/
|
||
├── lib/
|
||
│ ├── core/ # Couche centrale
|
||
│ │ ├── constants/ # Constantes globales
|
||
│ │ │ ├── app_keys.dart # Clés des Box Hive
|
||
│ │ │ └── api_endpoints.dart # Endpoints API
|
||
│ │ ├── data/
|
||
│ │ │ └── models/ # Modèles Hive
|
||
│ │ │ ├── user_model.dart # @HiveType(typeId: 3)
|
||
│ │ │ ├── amicale_model.dart # @HiveType(typeId: 4)
|
||
│ │ │ └── membre_model.dart # @HiveType(typeId: 5)
|
||
│ │ ├── repositories/ # Logique métier
|
||
│ │ │ ├── user_repository.dart
|
||
│ │ │ ├── amicale_repository.dart
|
||
│ │ │ └── membre_repository.dart
|
||
│ │ ├── services/ # Services externes
|
||
│ │ │ ├── api_service.dart # HTTP Singleton
|
||
│ │ │ ├── chat_service.dart # MQTT
|
||
│ │ │ └── location_service.dart # GPS
|
||
│ │ └── utils/ # Utilitaires
|
||
│ │ ├── validators.dart
|
||
│ │ └── formatters.dart
|
||
│ ├── presentation/ # Interface utilisateur
|
||
│ │ ├── admin/ # Pages administrateur
|
||
│ │ │ ├── admin_dashboard_page.dart
|
||
│ │ │ ├── admin_amicale_page.dart
|
||
│ │ │ └── admin_statistics_page.dart
|
||
│ │ ├── user/ # Pages utilisateur
|
||
│ │ │ ├── user_dashboard_page.dart
|
||
│ │ │ ├── map_page.dart
|
||
│ │ │ └── distribution_page.dart
|
||
│ │ ├── widgets/ # Composants réutilisables
|
||
│ │ │ ├── tables/
|
||
│ │ │ │ ├── amicale_table_widget.dart
|
||
│ │ │ │ ├── amicale_row_widget.dart
|
||
│ │ │ │ ├── membre_table_widget.dart
|
||
│ │ │ │ └── membre_row_widget.dart
|
||
│ │ │ ├── forms/
|
||
│ │ │ │ ├── amicale_form.dart
|
||
│ │ │ │ └── custom_text_field.dart
|
||
│ │ │ └── common/
|
||
│ │ │ ├── dashboard_layout.dart
|
||
│ │ │ └── loading_widget.dart
|
||
│ │ └── theme/
|
||
│ │ └── app_theme.dart
|
||
│ ├── app.dart # Configuration app
|
||
│ └── main.dart # Point d'entrée
|
||
├── assets/ # Ressources statiques
|
||
│ ├── images/
|
||
│ ├── icons/
|
||
│ └── fonts/
|
||
├── test/ # Tests unitaires
|
||
├── integration_test/ # Tests d'intégration
|
||
└── docs/ # Documentation
|
||
```
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 🚀 Installation et configuration
|
||
|
||
### Prérequis système
|
||
|
||
- **Flutter SDK** : 3.32 ou supérieur
|
||
- **Dart SDK** : 3.0 ou supérieur
|
||
- **IDE** : Android Studio, VS Code, ou IntelliJ
|
||
- **Environnement** :
|
||
- Android : SDK 21+ (Android 5.0+)
|
||
- iOS : iOS 12.0+
|
||
- Web : Navigateurs modernes
|
||
|
||
## 🔐 Configuration des clés API
|
||
|
||
### Mapbox (Cartographie)
|
||
|
||
1. Créer un compte sur [Mapbox](https://www.mapbox.com/)
|
||
2. Générer un token d'accès
|
||
3. Ajouter le token dans `.env`
|
||
|
||
### Configuration MQTT (Chat)
|
||
|
||
1. Configurer votre broker MQTT
|
||
2. Créer les credentials
|
||
3. Tester la connexion
|
||
|
||
---
|
||
|
||
## 🗄️ Modèles de données
|
||
|
||
### Registres Hive des adaptateurs
|
||
|
||
```dart
|
||
// Modèles principaux
|
||
UserModelAdapter() // typeId: 0
|
||
OperationModelAdapter() // typeId: 1
|
||
SectorModelAdapter() // typeId: 3
|
||
PassageModelAdapter() // typeId: 4
|
||
MembreModelAdapter() // typeId: 5
|
||
UserSectorModelAdapter() // typeId: 6
|
||
RegionModelAdapter() // typeId: 7
|
||
ClientModelAdapter() // typeId: 10
|
||
AmicaleModelAdapter() // typeId: 11
|
||
|
||
// Modèles de chat
|
||
ConversationModelAdapter() // typeId: 20
|
||
MessageModelAdapter() // typeId: 21
|
||
ParticipantModelAdapter() // typeId: 22
|
||
AnonymousUserModelAdapter() // typeId: 23
|
||
AudienceTargetModelAdapter() // typeId: 24
|
||
NotificationSettingsAdapter() // typeId: 25
|
||
```
|
||
|
||
### Clarification importante : UserModel vs MembreModel vs UserSectorModel
|
||
|
||
⚠️ **ATTENTION** : Il existe une distinction cruciale entre ces trois modèles :
|
||
|
||
#### **UserModel** (Box: `users`)
|
||
- Représente **uniquement l'utilisateur courant connecté** (current user)
|
||
- Stocké dans la box Hive `users` qui ne contient qu'un seul enregistrement
|
||
- Utilisé pour l'authentification et la session de l'utilisateur actuel
|
||
- **Ne pas confondre avec les membres de l'amicale**
|
||
|
||
#### **MembreModel** (Box: `membres`)
|
||
- Représente **tous les membres d'une amicale**
|
||
- Stocké dans la box Hive `membres` qui contient plusieurs enregistrements
|
||
- Utilisé pour la gestion des équipes et l'attribution aux secteurs
|
||
- Chaque membre a son propre ID unique
|
||
|
||
#### **UserSectorModel** (Box: `user_sector`)
|
||
- Représente **l'association entre un membre et un secteur**
|
||
- ⚠️ **IMPORTANT** : Le champ `id` dans `UserSectorModel` correspond à l'ID du **membre** (MembreModel.id), **PAS** à l'ID de l'utilisateur (UserModel.id)
|
||
- Permet de savoir quels membres sont affectés à quels secteurs
|
||
- Nom trompeur : devrait s'appeler "MemberSectorModel" pour éviter la confusion
|
||
|
||
### Compatibilité entre modèles
|
||
- **UserModel ↔ MembreModel** : Conversion bidirectionnelle via `toUserModel()` et `fromUserModel()`
|
||
- **Synchronisation** : Maintien de la cohérence entre les deux représentations
|
||
- **Champs spécialisés** : Préservation des données spécifiques à chaque modèle
|
||
🎨 Interface utilisateur
|
||
Architecture des composants
|
||
UserFormDialog - Modale unifiée
|
||
Réutilisabilité : Même widget pour "Mon Compte" et "Gestion des Membres"
|
||
Personnalisation contextuelle :
|
||
Sélecteur de rôle (Membre/Administrateur)
|
||
Checkbox statut actif/inactif
|
||
Édition du nom d'utilisateur selon le contexte
|
||
Gestion du nom de tournée (sectName)
|
||
Interface responsive : Adaptation automatique selon la largeur d'écran
|
||
UserForm - Formulaire intelligent
|
||
Layout adaptatif :
|
||
|
||
> 900px : Champs groupés en lignes (username+email, prénom+nom, téléphones, dates)
|
||
> ≤ 900px : Champs empilés verticalement
|
||
> Validation conditionnelle : Au moins nom OU nom de tournée requis
|
||
> Champs dynamiques : Affichage selon les permissions et le contexte
|
||
> Indicateurs visuels : Points rouges sur les champs obligatoires
|
||
> Tableaux interactifs
|
||
> AmicaleTableWidget : Gestion des amicales avec édition inline
|
||
> MembreTableWidget : Gestion des membres avec actions contextuelles
|
||
> Alternance de couleurs : Amélioration de la lisibilité
|
||
> Clic sur ligne : Ouverture directe du formulaire d'édition
|
||
|
||
## 🔗 API et synchronisation
|
||
|
||
Principe "API First"
|
||
|
||
### Flow de mise à jour
|
||
|
||
Validation API : Tentative de mise à jour sur le serveur
|
||
Succès → Sauvegarde locale avec isSynced: true
|
||
Erreur → Aucune modification locale + affichage de l'erreur
|
||
|
||
### Avantages
|
||
|
||
Cohérence des données : Local toujours synchronisé avec le serveur
|
||
Gestion d'erreurs propre : Pas de conflits entre données locales et distantes
|
||
UX claire : Feedback immédiat sur les erreurs de validation
|
||
|
||
### ApiService Singleton
|
||
|
||
- Thread-safe : Initialisation sécurisée avec verrous
|
||
- Auto-configuration : Détection automatique de l'environnement (DEV/REC/PROD)
|
||
- Gestion de session : Headers d'authentification automatiques
|
||
- Retry logic : Nouvelles tentatives pour les erreurs réseau
|
||
|
||
## ⚠️ Gestion des erreurs
|
||
|
||
### 🎯 Système ApiException intelligent
|
||
|
||
GEOSECTOR v2.0 utilise un **système centralisé de gestion des messages** qui s'adapte automatiquement au contexte d'affichage pour garantir une visibilité optimale des notifications utilisateur.
|
||
|
||
#### **🧠 Détection automatique de contexte**
|
||
|
||
L'`ApiException` détecte intelligemment si elle est appelée depuis une Dialog et adapte l'affichage :
|
||
|
||
- **📱 Contexte normal** : SnackBar standard en bas d'écran
|
||
- **💬 Contexte Dialog** : Overlay SnackBar positionné au-dessus de la Dialog estompée
|
||
- **🌐 Mobile/Web** : Adaptation automatique selon la plateforme
|
||
- **🎨 Cohérence visuelle** : Couleurs, icônes et comportements unifiés
|
||
|
||
```dart
|
||
// Même API partout - détection intelligente du contexte
|
||
void _handleValidation() {
|
||
if (formInvalid) {
|
||
// Dans une Dialog : overlay au-dessus, Dialog reste ouverte
|
||
ApiException.showError(context, Exception("Champs requis manquants"));
|
||
return;
|
||
}
|
||
|
||
// Succès : fermer Dialog puis afficher confirmation
|
||
Navigator.pop(context);
|
||
ApiException.showSuccess(context, "Données sauvegardées");
|
||
}
|
||
```
|
||
|
||
#### **✨ Avantages de l'approche unifiée**
|
||
|
||
| Aspect | Avant | Avec ApiException |
|
||
|--------|-------|-------------------|
|
||
| **Visibilité** | SnackBar masqué par Dialog | Overlay visible au-dessus |
|
||
| **Consistance** | Messages dispersés | API unifiée dans toute l'app |
|
||
| **Maintenance** | Code répétitif | Système centralisé |
|
||
| **UX** | Frustrant (messages cachés) | Fluide et prévisible |
|
||
|
||
### Architecture centralisée
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant UI as dashboard_app_bar.dart
|
||
participant UR as user_repository.dart
|
||
participant AS as api_service.dart
|
||
participant API as API Server
|
||
participant AE as ApiException
|
||
participant EU as ErrorUtils
|
||
|
||
Note over UI: Utilisateur clique "Enregistrer"
|
||
UI->>UR: updateUser(updatedUser)
|
||
|
||
Note over UR: Tente la mise à jour
|
||
UR->>AS: updateUser(user)
|
||
|
||
Note over AS: Appel HTTP PUT
|
||
AS->>API: PUT /users/123 {email: "test@test.com"}
|
||
|
||
alt Succès API
|
||
API-->>AS: 200 OK {user data}
|
||
AS-->>UR: UserModel (mis à jour)
|
||
UR-->>UI: UserModel (succès)
|
||
UI->>EU: showSuccessSnackBar()
|
||
Note over UI: ✅ "Profil mis à jour"
|
||
|
||
else Erreur API (ex: email déjà utilisé)
|
||
API-->>AS: 409 Conflict {"message": "Cet email est déjà utilisé"}
|
||
|
||
Note over AS: Conversion en ApiException
|
||
AS->>AE: ApiException.fromDioException(dioError)
|
||
AE-->>AS: ApiException("Cet email est déjà utilisé")
|
||
AS-->>UR: throw ApiException
|
||
UR-->>UI: throw ApiException
|
||
|
||
Note over UI: Gestion de l'erreur
|
||
UI->>EU: showErrorSnackBar(context, exception)
|
||
EU->>AE: extractErrorMessage(exception)
|
||
AE-->>EU: "Cet email est déjà utilisé"
|
||
Note over UI: ❌ "Erreur: Cet email est déjà utilisé"
|
||
Note over UI: Dialog reste ouvert
|
||
|
||
else Erreur réseau
|
||
API-->>AS: Network Error / Timeout
|
||
AS->>AE: ApiException.fromDioException(networkError)
|
||
AE-->>AS: ApiException("Problème de connexion réseau")
|
||
AS-->>UR: throw ApiException
|
||
UR-->>UI: throw ApiException
|
||
UI->>EU: showErrorSnackBar(context, exception)
|
||
Note over UI: ❌ "Problème de connexion réseau"
|
||
end
|
||
```
|
||
|
||
## Composants de gestion d'erreurs
|
||
|
||
### ApiException
|
||
|
||
Extraction intelligente : Messages spécifiques depuis la réponse API
|
||
Codes HTTP standardisés : Mapping automatique des erreurs communes
|
||
Types d'erreurs : Classification (validation, authentification, réseau, conflit)
|
||
Méthodes d'affichage : showError() et showSuccess() intégrées
|
||
|
||
### Responsabilités par couche
|
||
|
||
ApiService : Conversion des erreurs Dio en ApiException
|
||
Repository : Propagation transparente des erreurs
|
||
Interface : Affichage utilisateur via ApiException.showError()
|
||
|
||
### Messages d'erreurs contextuels
|
||
|
||
409 Conflict : "Cet email est déjà utilisé par un autre utilisateur"
|
||
400 Bad Request : "Données invalides"
|
||
401 Unauthorized : "Non autorisé : veuillez vous reconnecter"
|
||
500 Server Error : "Erreur serveur interne"
|
||
Network Errors : "Problème de connexion réseau"
|
||
Timeout : "Délai d'attente dépassé"
|
||
|
||
## 🔧 Pattern de gestion des erreurs API dans les Repositories
|
||
|
||
### 🎯 Problème à résoudre
|
||
|
||
Les messages d'erreur spécifiques de l'API (comme "Cet email est déjà utilisé") n'étaient pas affichés à l'utilisateur. À la place, un message générique "Erreur inattendue" apparaissait.
|
||
|
||
### 📝 Modifications requises
|
||
|
||
#### **1. ApiService - Conversion automatique des DioException**
|
||
|
||
Toutes les méthodes HTTP génériques doivent convertir les `DioException` en `ApiException` :
|
||
|
||
```dart
|
||
// ✅ CORRECT - ApiService avec conversion automatique
|
||
Future<Response> put(String path, {dynamic data}) async {
|
||
try {
|
||
return await _dio.put(path, data: data);
|
||
} on DioException catch (e) {
|
||
throw ApiException.fromDioException(e); // ← Extraction automatique du message
|
||
} catch (e) {
|
||
if (e is ApiException) rethrow;
|
||
throw ApiException('Erreur inattendue lors de la requête PUT', originalError: e);
|
||
}
|
||
}
|
||
```
|
||
|
||
Appliquer le même pattern pour `post()`, `get()`, `delete()`.
|
||
|
||
#### **2. Repository - Simplification de la gestion des erreurs**
|
||
|
||
```dart
|
||
// ❌ INCORRECT - Code inutile qui ne sera jamais exécuté
|
||
Future<bool> updateMembre(MembreModel membre) async {
|
||
try {
|
||
final response = await ApiService.instance.put('/users/${membre.id}', data: data);
|
||
|
||
if (response.statusCode == 200) {
|
||
await saveMembreBox(membre);
|
||
return true;
|
||
}
|
||
|
||
// ⚠️ CE CODE NE SERA JAMAIS ATTEINT car Dio lance une exception pour les codes d'erreur
|
||
if (response.data != null && response.data is Map<String, dynamic>) {
|
||
final responseData = response.data as Map<String, dynamic>;
|
||
if (responseData['status'] == 'error') {
|
||
throw Exception(responseData['message']);
|
||
}
|
||
}
|
||
return false;
|
||
} catch (e) {
|
||
rethrow;
|
||
}
|
||
}
|
||
```
|
||
|
||
```dart
|
||
// ✅ CORRECT - Code simplifié et fonctionnel
|
||
Future<bool> updateMembre(MembreModel membre) async {
|
||
try {
|
||
final response = await ApiService.instance.put('/users/${membre.id}', data: data);
|
||
|
||
// Si on arrive ici, c'est que la requête a réussi (200)
|
||
await saveMembreBox(membre);
|
||
return true;
|
||
|
||
} catch (e) {
|
||
// L'ApiException contient déjà le message extrait de l'API
|
||
rethrow; // Propager l'exception pour affichage
|
||
}
|
||
}
|
||
```
|
||
|
||
#### **3. Amélioration des logs (avec LoggerService)**
|
||
|
||
```dart
|
||
// ✅ CORRECT - Logs propres sans détails techniques
|
||
catch (e) {
|
||
// Ne pas logger les détails techniques de DioException
|
||
if (e is ApiException) {
|
||
LoggerService.error('Erreur lors de la mise à jour: ${e.message}');
|
||
} else {
|
||
LoggerService.error('Erreur lors de la mise à jour');
|
||
}
|
||
rethrow;
|
||
}
|
||
```
|
||
|
||
N'oubliez pas d'importer `ApiException` :
|
||
```dart
|
||
import 'package:geosector_app/core/utils/api_exception.dart';
|
||
```
|
||
|
||
### 🔄 Flux d'erreur corrigé
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant API as API Server
|
||
participant Dio as Dio Client
|
||
participant AS as ApiService
|
||
participant AE as ApiException
|
||
participant R as Repository
|
||
participant UI as Interface
|
||
|
||
UI->>R: updateData()
|
||
R->>AS: put('/endpoint')
|
||
AS->>Dio: HTTP PUT
|
||
Dio->>API: Request
|
||
|
||
alt Code d'erreur (409, 400, etc.)
|
||
API-->>Dio: Error + JSON body
|
||
Note over API: {"status": "error",<br/>"message": "Message spécifique"}
|
||
|
||
Dio-->>AS: DioException
|
||
AS->>AE: fromDioException()
|
||
Note over AE: Extrait message<br/>depuis response.data
|
||
AE-->>AS: ApiException("Message spécifique")
|
||
AS-->>R: throw ApiException
|
||
R-->>UI: throw ApiException
|
||
UI->>UI: showError("Message spécifique")
|
||
else Succès (200, 201)
|
||
API-->>Dio: Success
|
||
Dio-->>AS: Response
|
||
AS-->>R: Response
|
||
R->>R: Sauvegarde Hive
|
||
R-->>UI: return true
|
||
end
|
||
```
|
||
|
||
### ✅ Checklist de migration
|
||
|
||
Pour chaque repository :
|
||
|
||
- [ ] Vérifier que l'ApiService convertit les DioException en ApiException
|
||
- [ ] Simplifier le code : supprimer les vérifications de statut après l'appel API
|
||
- [ ] Propager les exceptions avec `rethrow`
|
||
- [ ] Améliorer les logs pour ne pas afficher les détails techniques
|
||
- [ ] Importer `ApiException` si nécessaire
|
||
- [ ] Tester avec une erreur 409 pour vérifier l'affichage du message
|
||
|
||
### 📊 Résultat attendu
|
||
|
||
| Avant | Après |
|
||
|-------|-------|
|
||
| "Erreur inattendue" | "Cet email est déjà utilisé par un autre utilisateur" |
|
||
| Logs avec stack trace Dio | Message d'erreur simple et clair |
|
||
| Code complexe avec vérifications inutiles | Code simplifié et maintenable |
|
||
|
||
Cette approche garantit que tous les messages d'erreur de l'API sont correctement affichés à l'utilisateur, améliorant ainsi l'expérience utilisateur et facilitant le débogage.
|
||
|
||
## 📝 Service de Logging Intelligent
|
||
|
||
### 🎯 Vue d'ensemble
|
||
|
||
GEOSECTOR v2.0 implémente un **LoggerService centralisé** qui désactive automatiquement les logs de debug en production, optimisant ainsi les performances et la sécurité tout en facilitant le développement.
|
||
|
||
### 🔍 Détection automatique de l'environnement
|
||
|
||
Le LoggerService détecte automatiquement l'environnement d'exécution :
|
||
|
||
```dart
|
||
// Détection basée sur l'URL pour le web
|
||
if (currentUrl.contains('dapp.geosector.fr')) → DEV
|
||
if (currentUrl.contains('rapp.geosector.fr')) → REC
|
||
Sinon → PROD
|
||
|
||
// Pour mobile/desktop : utilise kReleaseMode de Flutter
|
||
```
|
||
|
||
**Comportement par environnement :**
|
||
|
||
| Environnement | Logs Debug | Logs Erreur | Stack Traces |
|
||
|---------------|------------|-------------|--------------|
|
||
| **DEV** | ✅ Activés | ✅ Activés | ✅ Complètes |
|
||
| **REC** | ✅ Activés | ✅ Activés | ✅ Complètes |
|
||
| **PROD** | ❌ Désactivés | ✅ Activés | ❌ Masquées |
|
||
|
||
### 🛠️ API du LoggerService
|
||
|
||
#### **Méthodes principales**
|
||
|
||
```dart
|
||
// Remplacement direct de debugPrint
|
||
LoggerService.log('Message simple');
|
||
|
||
// Logs catégorisés avec emojis automatiques
|
||
LoggerService.info('Information'); // ℹ️
|
||
LoggerService.success('Opération réussie'); // ✅
|
||
LoggerService.warning('Attention'); // ⚠️
|
||
LoggerService.error('Erreur', exception); // ❌ (toujours affiché)
|
||
LoggerService.debug('Debug', emoji: '🔧'); // 🔧
|
||
|
||
// Logs spécialisés
|
||
LoggerService.api('Requête API envoyée'); // 🔗
|
||
LoggerService.database('Box Hive ouverte'); // 💾
|
||
LoggerService.navigation('Route: /admin'); // 🧭
|
||
LoggerService.performance('Temps: 45ms'); // ⏱️
|
||
```
|
||
|
||
#### **Fonctionnalités avancées**
|
||
|
||
```dart
|
||
// Logs groupés pour améliorer la lisibilité
|
||
LoggerService.group('Traitement utilisateur', [
|
||
'Validation des données',
|
||
'Appel API',
|
||
'Sauvegarde locale',
|
||
'Notification envoyée'
|
||
]);
|
||
// Affiche :
|
||
// ┌─ Traitement utilisateur
|
||
// ├─ Validation des données
|
||
// ├─ Appel API
|
||
// ├─ Sauvegarde locale
|
||
// └─ Notification envoyée
|
||
|
||
// Log JSON formaté
|
||
LoggerService.json('Payload API', {
|
||
'id': 123,
|
||
'name': 'Test',
|
||
'active': true
|
||
});
|
||
// Affiche :
|
||
// 📋 Payload API:
|
||
// id: 123
|
||
// name: Test
|
||
// active: true
|
||
|
||
// Log conditionnel
|
||
LoggerService.conditional(
|
||
'Debug spécifique',
|
||
condition: user.role > 2
|
||
);
|
||
```
|
||
|
||
### 🔄 Migration depuis debugPrint
|
||
|
||
#### **Avant (debugPrint partout)**
|
||
|
||
```dart
|
||
debugPrint('🔄 Début de la création d\'un nouveau membre');
|
||
debugPrint('📤 Données envoyées à l\'API: $data');
|
||
debugPrint('❌ Erreur: $e');
|
||
```
|
||
|
||
#### **Après (LoggerService)**
|
||
|
||
```dart
|
||
LoggerService.info('Début de la création d\'un nouveau membre');
|
||
LoggerService.api('Données envoyées à l\'API: $data');
|
||
LoggerService.error('Erreur lors de la création', e);
|
||
```
|
||
|
||
### 📋 Utilisation avec les extensions
|
||
|
||
Pour une syntaxe encore plus concise, utilisez les extensions String :
|
||
|
||
```dart
|
||
// Import nécessaire
|
||
import 'package:geosector_app/core/services/logger_service.dart';
|
||
|
||
// Utilisation directe sur les strings
|
||
'Connexion réussie'.logSuccess();
|
||
'Erreur de validation'.logError(exception);
|
||
'Requête GET /users'.logApi();
|
||
'Box membres ouverte'.logDatabase();
|
||
```
|
||
|
||
### 🎯 Bonnes pratiques
|
||
|
||
#### **1. Catégorisation des logs**
|
||
|
||
```dart
|
||
// ✅ BON - Log catégorisé et clair
|
||
LoggerService.api('POST /users - Création membre');
|
||
LoggerService.database('Sauvegarde dans Box membres');
|
||
|
||
// ❌ MAUVAIS - Log générique sans contexte
|
||
debugPrint('Traitement en cours...');
|
||
```
|
||
|
||
#### **2. Gestion des erreurs**
|
||
|
||
```dart
|
||
try {
|
||
await operation();
|
||
LoggerService.success('Opération terminée');
|
||
} catch (e, stackTrace) {
|
||
// Les erreurs sont TOUJOURS loggées, même en PROD
|
||
LoggerService.error('Échec de l\'opération', e, stackTrace);
|
||
}
|
||
```
|
||
|
||
#### **3. Logs de performance**
|
||
|
||
```dart
|
||
final stopwatch = Stopwatch()..start();
|
||
await heavyOperation();
|
||
stopwatch.stop();
|
||
LoggerService.performance('Opération lourde: ${stopwatch.elapsedMilliseconds}ms');
|
||
```
|
||
|
||
### 🔒 Sécurité et performances
|
||
|
||
#### **Avantages en production**
|
||
|
||
1. **Sécurité** : Aucune information sensible exposée dans la console
|
||
2. **Performance** : Pas d'appels inutiles à debugPrint
|
||
3. **Taille** : Bundle JavaScript plus léger (tree shaking)
|
||
4. **Professionnalisme** : Console propre pour les utilisateurs finaux
|
||
|
||
#### **Conservation des logs d'erreur**
|
||
|
||
Les erreurs restent visibles en production pour faciliter le support :
|
||
|
||
```dart
|
||
// Toujours affiché, même en PROD
|
||
LoggerService.error('Erreur critique détectée', error);
|
||
// Mais sans la stack trace complète qui pourrait révéler
|
||
// des détails d'implémentation
|
||
```
|
||
|
||
### 📊 Impact sur le codebase
|
||
|
||
| Métrique | Avant | Après |
|
||
|----------|-------|-------|
|
||
| **Logs en PROD** | Tous visibles | Erreurs uniquement |
|
||
| **Performance web** | debugPrint actifs | Désactivés automatiquement |
|
||
| **Maintenance** | debugPrint dispersés | Service centralisé |
|
||
| **Lisibilité** | Emojis manuels | Catégorisation automatique |
|
||
|
||
### 🚀 Configuration et initialisation
|
||
|
||
Le LoggerService fonctionne automatiquement sans configuration :
|
||
|
||
```dart
|
||
// main.dart - Aucune initialisation requise
|
||
void main() async {
|
||
// LoggerService détecte automatiquement l'environnement
|
||
// via ApiService.getCurrentEnvironment()
|
||
|
||
runApp(GeosectorApp());
|
||
}
|
||
|
||
// Utilisation immédiate dans n'importe quel fichier
|
||
LoggerService.info('Application démarrée');
|
||
```
|
||
|
||
Cette architecture garantit un système de logging professionnel, sécurisé et performant, adapté aux besoins de développement tout en protégeant la production. 📝✨
|
||
|
||
## 🎯 Gestion des rôles
|
||
|
||
### Hiérarchie des permissions
|
||
|
||
Membre (Rôle 1) : Consultation et distribution dans ses secteurs
|
||
Admin Amicale (Rôle 2) : Gestion complète de son amicale et ses membres
|
||
Super Admin (Rôle 3+) : Administration globale multi-amicales
|
||
|
||
### Fonctionnalités par rôle
|
||
|
||
Admin Amicale - Gestion des membres
|
||
Création : Nouveaux membres avec attribution de rôle
|
||
Modification : Informations personnelles, rôle, statut actif
|
||
Suppression : Avec confirmation obligatoire
|
||
Validation : Contrôle d'unicité email/username par l'API
|
||
|
||
### Interface adaptative
|
||
|
||
Sélecteur de rôle : Visible uniquement pour les admins
|
||
Checkbox statut actif : Contrôle d'accès aux comptes
|
||
Édition contextuelle : Champs modifiables selon les permissions
|
||
Actions conditionnelles : Boutons disponibles selon le niveau d'autorisation
|
||
|
||
## 👥 Gestion des membres (Admin Amicale)
|
||
|
||
### 🎯 Vue d'ensemble
|
||
|
||
La gestion des membres est une fonctionnalité clé pour les **Admins Amicale** (Rôle 2) qui permet une administration complète des équipes au sein de leur amicale. Cette interface centralise toutes les opérations liées aux membres avec une approche sécurisée et intuitive.
|
||
|
||
### 📱 Interface AdminAmicalePage
|
||
|
||
L'interface principale `admin_amicale_page.dart` offre une vue d'ensemble complète :
|
||
|
||
- **Informations de l'amicale** : Affichage des détails de l'amicale courante
|
||
- **Liste des membres** : Tableau interactif avec actions contextuelles
|
||
- **Ajout de membres** : Bouton d'action pour créer de nouveaux comptes
|
||
- **Opération courante** : Indication de l'opération active en cours
|
||
|
||
### ✨ Fonctionnalités principales
|
||
|
||
#### 🆕 Création de nouveaux membres
|
||
|
||
```dart
|
||
// Workflow de création
|
||
1. Clic sur "Ajouter un membre"
|
||
2. Ouverture du UserFormDialog
|
||
3. Formulaire vierge avec valeurs par défaut
|
||
4. Sélection du rôle (Membre/Administrateur)
|
||
5. Configuration du statut actif
|
||
6. Validation et création via API
|
||
7. Attribution automatique à l'amicale courante
|
||
```
|
||
|
||
**Champs obligatoires :**
|
||
|
||
- Email (unique dans le système)
|
||
- Au moins un nom (nom de famille OU nom de tournée)
|
||
- Rôle dans l'amicale
|
||
|
||
**Champs optionnels :**
|
||
|
||
- Nom d'utilisateur (éditable pour les admins)
|
||
- Prénom, téléphones, dates
|
||
- Nom de tournée (pour identification terrain)
|
||
|
||
#### ✏️ Modification des membres existants
|
||
|
||
```dart
|
||
// Actions disponibles
|
||
- Clic sur une ligne → Ouverture du formulaire d'édition
|
||
- Modification de tous les champs personnels
|
||
- Changement de rôle (Membre ↔ Administrateur)
|
||
- Activation/Désactivation du compte
|
||
- Gestion du nom de tournée
|
||
```
|
||
|
||
**Workflow de modification :**
|
||
|
||
1. Sélection du membre dans le tableau
|
||
2. Ouverture automatique du `UserFormDialog`
|
||
3. Formulaire pré-rempli avec données existantes
|
||
4. Modification des champs souhaités
|
||
5. Validation et mise à jour via API
|
||
6. Synchronisation automatique avec Hive
|
||
|
||
#### 🗑️ Suppression intelligente des membres
|
||
|
||
La suppression des membres intègre une **logique métier avancée** pour préserver l'intégrité des données :
|
||
|
||
##### 🔍 Vérification des passages
|
||
|
||
```dart
|
||
// Algorithme de vérification
|
||
1. Récupération de opération courante
|
||
2. Analyse des passages du membre pour cette opération
|
||
3. Classification : passages réalisés vs passages à finaliser
|
||
4. Comptage total des passages actifs
|
||
```
|
||
|
||
##### 📊 Scénarios de suppression
|
||
|
||
**Cas 1 : Aucun passage (suppression simple)**
|
||
|
||
```http
|
||
DELETE /users/{id}
|
||
```
|
||
|
||
- Confirmation simple
|
||
- Suppression directe
|
||
- Aucun transfert nécessaire
|
||
|
||
**Cas 2 : Passages existants (suppression avec transfert)**
|
||
|
||
```http
|
||
DELETE /users/{id}?transfer_to={destinataire}&operation_id={operation}
|
||
```
|
||
|
||
- Dialog d'avertissement avec détails
|
||
- Sélection obligatoire d'un membre destinataire
|
||
- Transfert automatique de tous les passages
|
||
- Préservation de l'historique
|
||
|
||
**Cas 3 : Alternative recommandée (désactivation)**
|
||
|
||
```dart
|
||
// Mise à jour du statut
|
||
membre.copyWith(isActive: false)
|
||
```
|
||
|
||
- Préservation complète des données
|
||
- Blocage de la connexion
|
||
- Maintien de l'historique
|
||
- Réactivation possible ultérieurement
|
||
|
||
### 🔐 Sécurité et permissions
|
||
|
||
#### Contrôles d'accès
|
||
|
||
- **Isolation par amicale** : Admins limités à leur amicale
|
||
- **Vérification des rôles** : Validation côté client et serveur
|
||
- **Opération courante** : Filtrage par contexte d'opération
|
||
- **Validation API** : Contrôles d'unicité et cohérence
|
||
|
||
#### Gestion des erreurs
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[Action utilisateur] --> B[Validation locale]
|
||
B --> C[Appel API]
|
||
C --> D{Succès ?}
|
||
D -->|Oui| E[Mise à jour Hive]
|
||
D -->|Non| F[Affichage erreur]
|
||
E --> G[Notification succès]
|
||
F --> H[Dialog reste ouvert]
|
||
```
|
||
|
||
### 🎨 Interface utilisateur
|
||
|
||
#### Tableaux interactifs
|
||
|
||
**MembreTableWidget** - Composant principal :
|
||
|
||
- Colonnes : ID, Prénom, Nom, Email, Rôle, Statut
|
||
- Actions : Modification, Suppression
|
||
- Alternance de couleurs pour lisibilité
|
||
- Tri et navigation intuitifs
|
||
|
||
**MembreRowWidget** - Ligne individuelle :
|
||
|
||
- Clic pour édition rapide
|
||
- Boutons d'action contextuels
|
||
- Indicateurs visuels de statut
|
||
- Tooltip informatifs
|
||
|
||
#### Formulaires adaptatifs
|
||
|
||
**UserFormDialog** - Modale réutilisable :
|
||
|
||
- Layout responsive (>900px vs mobile)
|
||
- Validation en temps réel
|
||
- Gestion des erreurs inline
|
||
- Sauvegarde avec feedback
|
||
|
||
### 📡 Synchronisation et réactivité
|
||
|
||
#### Architecture ValueListenableBuilder
|
||
|
||
```dart
|
||
// Écoute automatique des changements
|
||
ValueListenableBuilder<Box<MembreModel>>(
|
||
valueListenable: membreRepository.getMembresBox().listenable(),
|
||
builder: (context, membresBox, child) {
|
||
// Interface mise à jour automatiquement
|
||
final membres = membresBox.values
|
||
.where((m) => m.fkEntite == currentUser.fkEntite)
|
||
.toList();
|
||
|
||
return MembreTableWidget(membres: membres);
|
||
},
|
||
)
|
||
```
|
||
|
||
#### Principe "API First"
|
||
|
||
1. **Validation API** : Tentative sur serveur en priorité
|
||
2. **Succès** → Sauvegarde locale + mise à jour interface
|
||
3. **Erreur** → Affichage message + maintien état local
|
||
4. **Cohérence** : Données locales toujours synchronisées
|
||
|
||
### 🔄 Workflow complet
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant A as Admin
|
||
participant UI as Interface
|
||
participant R as Repository
|
||
participant API as Serveur
|
||
participant H as Hive
|
||
|
||
A->>UI: Action membre
|
||
UI->>R: Appel repository
|
||
R->>API: Requête HTTP
|
||
API-->>R: Réponse
|
||
|
||
alt Succès
|
||
R->>H: Sauvegarde locale
|
||
H-->>UI: Notification changement
|
||
UI-->>A: Interface mise à jour
|
||
else Erreur
|
||
R-->>UI: Exception
|
||
UI-->>A: Message d'erreur
|
||
end
|
||
```
|
||
|
||
### 🎯 Bonnes pratiques
|
||
|
||
#### Pour les administrateurs
|
||
|
||
1. **Vérification avant suppression** : Toujours examiner les passages
|
||
2. **Préférer la désactivation** : Éviter la perte de données
|
||
3. **Noms de tournée** : Utiliser des identifiants terrain clairs
|
||
4. **Rôles appropriés** : Limiter les administrateurs aux besoins
|
||
|
||
#### Gestion des erreurs courantes
|
||
|
||
| Erreur | Cause | Solution |
|
||
| ----------------------- | ------------- | ------------------------ |
|
||
| Email déjà utilisé | Duplication | Choisir un autre email |
|
||
| Membre avec passages | Données liées | Transférer ou désactiver |
|
||
| Aucune opération active | Configuration | Vérifier les opérations |
|
||
| Accès refusé | Permissions | Vérifier le rôle admin |
|
||
|
||
Cette architecture garantit une gestion des membres robuste, sécurisée et intuitive, optimisant l'efficacité administrative tout en préservant l'intégrité des données opérationnelles. 👥✨
|
||
|
||
## 🗺️ Cartes et géolocalisation
|
||
|
||
Flutter Map : Rendu cartographique haute performance
|
||
Tuiles Mapbox : Cartographie détaillée et personnalisable
|
||
Géolocalisation temps réel : Suivi GPS des équipes
|
||
Secteurs géographiques : Visualisation et attribution dynamique
|
||
Passages géolocalisés : Enregistrement précis des distributions
|
||
|
||
## 🔄 Synchronisation et réactivité
|
||
|
||
### Hive + ValueListenableBuilder
|
||
|
||
Réactivité native : Mise à jour automatique de l'interface
|
||
Performance optimisée : Pas de Provider, injection directe
|
||
Écoute sélective : Réactivité fine par Box Hive
|
||
Cohérence des données : Synchronisation bidirectionnelle User/Membre
|
||
|
||
### Services Singleton
|
||
|
||
CurrentUserService : Gestion de l'utilisateur connecté
|
||
CurrentAmicaleService : Amicale de l'utilisateur actuel
|
||
ApiService : Communication centralisée avec l'API
|
||
DataLoadingService : Orchestration du chargement des données
|
||
|
||
## 🚀 Optimisation des performances Hive
|
||
|
||
### 📈 Gestion des Box Hive avec cache
|
||
|
||
GEOSECTOR v2.0 implémente une **stratégie de cache avancée** pour les Box Hive afin d'éliminer les goulots d'étranglement de performance lors d'opérations haute fréquence.
|
||
|
||
#### **🎯 Problème résolu**
|
||
|
||
Avant l'optimisation, l'application effectuait jusqu'à **848 vérifications** `Hive.isBoxOpen()` par chargement de page, causant des ralentissements significatifs lors du rendu des listes et du filtrage des données.
|
||
|
||
#### **💡 Solution : Cache lazy des Box**
|
||
|
||
```dart
|
||
// Pattern implémenté dans tous les repositories
|
||
class OptimizedRepository {
|
||
Box<ModelType>? _cachedBox;
|
||
|
||
Box<ModelType> get _modelBox {
|
||
if (_cachedBox == null) {
|
||
if (!Hive.isBoxOpen(AppKeys.boxName)) {
|
||
throw Exception('Box non ouverte');
|
||
}
|
||
_cachedBox = Hive.box<ModelType>(AppKeys.boxName);
|
||
debugPrint('Repository: Box mise en cache');
|
||
}
|
||
return _cachedBox!;
|
||
}
|
||
|
||
void _resetCache() {
|
||
_cachedBox = null;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### **🔄 Gestion du cache et réactivité**
|
||
|
||
**Point critique** : Le cache doit être réinitialisé après **toute modification** pour garantir le bon fonctionnement de `ValueListenableBuilder` :
|
||
|
||
```dart
|
||
// ✅ OBLIGATOIRE après modification
|
||
Future<void> saveData(ModelType data) async {
|
||
await _modelBox.put(data.id, data);
|
||
_resetCache(); // ← Crucial pour la réactivité UI
|
||
notifyListeners();
|
||
}
|
||
|
||
// ✅ OBLIGATOIRE après suppression
|
||
Future<void> deleteData(int id) async {
|
||
await _modelBox.delete(id);
|
||
_resetCache(); // ← Crucial pour la réactivité UI
|
||
notifyListeners();
|
||
}
|
||
|
||
// ✅ OBLIGATOIRE après vidage ou traitement API
|
||
Future<void> processApiData(List<dynamic> data) async {
|
||
await _modelBox.clear();
|
||
// ... traitement des données
|
||
_resetCache(); // ← Crucial pour la réactivité UI
|
||
notifyListeners();
|
||
}
|
||
```
|
||
|
||
#### **📊 Impact performance**
|
||
|
||
| Métrique | Avant optimisation | Après optimisation |
|
||
|----------|-------------------|-------------------|
|
||
| **Vérifications box** | 848 par page | 1 par session |
|
||
| **Temps de rendu** | 200-500ms | <50ms |
|
||
| **Filtrage liste** | Lent (check répétés) | Instantané |
|
||
| **Mémoire** | Overhead minimal | Impact négligeable |
|
||
|
||
#### **🏗️ Repositories optimisés**
|
||
|
||
L'optimisation est implémentée dans tous les repositories critiques :
|
||
|
||
- ✅ **SectorRepository** : Gestion des secteurs géographiques
|
||
- ✅ **PassageRepository** : Suivi des distributions
|
||
- ✅ **MembreRepository** : Gestion des équipes
|
||
- ✅ **OperationRepository** : Campagnes et opérations
|
||
- ✅ **AmicaleRepository** : Organisations
|
||
|
||
#### **🎯 Règles d'implémentation**
|
||
|
||
1. **Cache systématique** : Tous les repositories fréquemment utilisés
|
||
2. **Reset obligatoire** : Après toute opération de modification
|
||
3. **Getter lazy** : Accès différé à la box uniquement si nécessaire
|
||
4. **Debug logging** : Traçabilité du cache en développement
|
||
5. **Cohérence** : Pattern appliqué uniformément
|
||
|
||
Cette architecture garantit une application performante, maintenable et évolutive avec une excellente expérience utilisateur. 🚀
|
||
|
||
## 🎨 Architecture des Dialogs Auto-Gérées
|
||
|
||
### 🎯 Principe de conception
|
||
|
||
GEOSECTOR v2.0 implémente une **architecture simplifiée des dialogs** qui élimine la complexité des callbacks asynchrones et garantit une gestion robuste des formulaires modaux.
|
||
|
||
### 🏗️ Pattern "Dialog Auto-Gérée"
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[Page Parente] -->|Injection Repository| B[Dialog Auto-Gérée]
|
||
B -->|Appel Direct| C[Repository]
|
||
C -->|API Call| D[Serveur]
|
||
D -->|Réponse| C
|
||
C -->|Sauvegarde| E[Hive Local]
|
||
B -->|Auto-Fermeture| A
|
||
B -->|Callback Simple| A
|
||
E -->|ValueListenableBuilder| A
|
||
```
|
||
|
||
### ✨ Composants de l'architecture
|
||
|
||
#### **1. Page Parente (ex: AdminOperationsPage)**
|
||
|
||
```dart
|
||
// Ouverture simplifiée de dialog
|
||
void _showEditOperationDialog(OperationModel op) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (dialogContext) => OperationFormDialog(
|
||
operation: op,
|
||
operationRepository: widget.operationRepository, // ← Injection directe
|
||
onSuccess: () {
|
||
if (mounted) setState(() {}); // ← Simple rafraîchissement
|
||
},
|
||
),
|
||
);
|
||
}
|
||
```
|
||
|
||
#### **2. Dialog Auto-Gérée (ex: OperationFormDialog)**
|
||
|
||
```dart
|
||
// Gestion complète dans la dialog
|
||
void _handleSubmit() async {
|
||
try {
|
||
// Appel direct du repository
|
||
final success = await widget.operationRepository.saveOperationFromModel(operationData);
|
||
|
||
if (success && mounted) {
|
||
// Délai pour synchronisation Hive
|
||
Future.delayed(const Duration(milliseconds: 200), () {
|
||
Navigator.of(context).pop(); // ← Auto-fermeture
|
||
widget.onSuccess?.call(); // ← Notification simple
|
||
ApiException.showSuccess(context, "Opération sauvegardée");
|
||
});
|
||
}
|
||
} catch (e) {
|
||
ApiException.showError(context, e); // ← Erreur sans fermeture
|
||
}
|
||
}
|
||
```
|
||
|
||
### 🎯 Avantages de cette approche
|
||
|
||
| Aspect | Avant (Complexe) | Après (Simplifié) |
|
||
| --------------------- | --------------------- | ------------------------------------ |
|
||
| **Callbacks** | Asynchrones complexes | Simple `onSuccess: () => setState()` |
|
||
| **Fermeture** | Gérée par le parent | Auto-fermeture dans la dialog |
|
||
| **Gestion d'erreurs** | Dispersée | Centralisée dans la dialog |
|
||
| **Synchronisation** | Problématique | Délai de 200ms pour Hive |
|
||
| **Maintenance** | Code dispersé | Logique unifiée |
|
||
|
||
### 🔧 Responsabilités claires
|
||
|
||
#### **Page Parente**
|
||
|
||
- ✅ Ouverture des dialogs avec injection de dépendances
|
||
- ✅ Rafraîchissement de l'interface via `setState()`
|
||
- ✅ Gestion des tableaux et listes intégrés
|
||
|
||
#### **Dialog Auto-Gérée**
|
||
|
||
- ✅ Validation et soumission du formulaire
|
||
- ✅ Appel direct des repositories
|
||
- ✅ Auto-fermeture en cas de succès
|
||
- ✅ Gestion des erreurs sans fermeture
|
||
|
||
#### **Repository**
|
||
|
||
- ✅ Logique métier (création vs modification)
|
||
- ✅ Appels API appropriés
|
||
- ✅ Sauvegarde locale dans Hive
|
||
- ✅ Propagation des exceptions
|
||
|
||
### 🚀 Exemple d'implémentation
|
||
|
||
```dart
|
||
// 1. Page parente - Code minimal
|
||
void _showCreateDialog() {
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => OperationFormDialog(
|
||
title: 'Créer une opération',
|
||
operationRepository: widget.operationRepository,
|
||
onSuccess: () => setState(() {}),
|
||
),
|
||
);
|
||
}
|
||
|
||
// 2. Dialog - Auto-gestion complète
|
||
class OperationFormDialog extends StatefulWidget {
|
||
final OperationRepository operationRepository;
|
||
final VoidCallback? onSuccess;
|
||
|
||
// Gestion interne : validation, API, fermeture, erreurs
|
||
}
|
||
|
||
// 3. Repository - Logique métier pure
|
||
Future<bool> saveOperationFromModel(OperationModel operation) async {
|
||
if (operation.id == 0) {
|
||
return await createOperation(...); // POST
|
||
} else {
|
||
return await updateOperation(...); // PUT
|
||
}
|
||
}
|
||
```
|
||
|
||
### ✅ Résultats obtenus
|
||
|
||
- **🎯 Simplicité** : Code plus lisible et maintenable
|
||
- **🔒 Robustesse** : Gestion d'erreurs centralisée
|
||
- **⚡ Performance** : Synchronisation optimisée avec Hive
|
||
- **🎨 UX** : Fermeture automatique et messages appropriés
|
||
- **🔧 Maintenance** : Architecture cohérente et prévisible
|
||
|
||
Cette approche **"Dialog Auto-Gérée"** constitue un pattern architectural clé de GEOSECTOR v2.0, garantissant une expérience utilisateur fluide et un code maintenable. 🎉
|
||
|
||
## Fonction de création d'une opération
|
||
|
||
## Fonction de création d'une opération
|
||
|
||
### 🔄 Traitement des données complexes
|
||
|
||
Lors de la création d'une opération via `OperationRepository.createOperation()`, l'API retourne en réponse **201 Created** ou **200 OK** un payload complexe contenant 4 groupes de données essentiels :
|
||
|
||
```json
|
||
{
|
||
"operations": [...], // 3 dernières opérations dont la nouvelle active
|
||
"secteurs": [...], // Secteurs de la nouvelle opération active
|
||
"passages": [...], // Passages de la nouvelle opération active
|
||
"users_sectors": [...] // Associations user-secteurs de la nouvelle opération
|
||
}
|
||
```
|
||
|
||
### 📦 Groupes de données attendus
|
||
|
||
#### 🎯 Groupe **operations**
|
||
|
||
Contient les **3 dernières opérations** de l'amicale, incluant celle qui vient d'être créée et qui devient automatiquement active.
|
||
|
||
#### 🗺️ Groupe **secteurs**
|
||
|
||
Contient tous les **secteurs géographiques** associés à la dernière opération créée et active.
|
||
|
||
#### 📍 Groupe **passages**
|
||
|
||
Contient l'ensemble des **passages** générés pour cette dernière opération créée et active.
|
||
|
||
#### 👥 Groupe **users_sectors**
|
||
|
||
Contient les **associations utilisateur-secteurs** définissant les attributions pour cette dernière opération créée et active.
|
||
|
||
### ⚙️ Traitement automatique des données
|
||
|
||
Ces 4 groupes sont traités de manière identique au processus de connexion utilisateur, en utilisant le **DataLoadingService** avec la logique suivante :
|
||
|
||
```dart
|
||
// Dans OperationRepository._processCreationResponse()
|
||
await _processOperationCreationData(responseData);
|
||
|
||
Future<void> _processOperationCreationData(Map<String, dynamic> data) async {
|
||
// 1. Vidage des Box Hive concernées (comme au login)
|
||
await _clearRelatedBoxes();
|
||
|
||
// 2. Traitement des 4 groupes via DataLoadingService
|
||
if (data.containsKey('operations')) {
|
||
await DataLoadingService.instance.processOperationsFromApi(data['operations']);
|
||
}
|
||
|
||
if (data.containsKey('secteurs')) {
|
||
await DataLoadingService.instance.processSectorsFromApi(data['secteurs']);
|
||
}
|
||
|
||
if (data.containsKey('passages')) {
|
||
await DataLoadingService.instance.processPassagesFromApi(data['passages']);
|
||
}
|
||
|
||
if (data.containsKey('users_sectors')) {
|
||
await DataLoadingService.instance.processUserSectorsFromApi(data['users_sectors']);
|
||
}
|
||
}
|
||
|
||
Future<void> _clearRelatedBoxes() async {
|
||
// Vidage des Box respectives avant rechargement
|
||
final operationsBox = HiveService.instance.getTypedBox<OperationModel>(AppKeys.operationsBoxName);
|
||
final sectorsBox = HiveService.instance.getTypedBox<SectorModel>(AppKeys.sectorsBoxName);
|
||
final passagesBox = HiveService.instance.getTypedBox<PassageModel>(AppKeys.passagesBoxName);
|
||
final userSectorsBox = HiveService.instance.getTypedBox<UserSectorModel>(AppKeys.userSectorBoxName);
|
||
|
||
await operationsBox.clear();
|
||
await sectorsBox.clear();
|
||
await passagesBox.clear();
|
||
await userSectorsBox.clear();
|
||
}
|
||
```
|
||
|
||
### 🔄 Flux de synchronisation
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant UI as Interface
|
||
participant OR as OperationRepository
|
||
participant API as Serveur API
|
||
participant HS as HiveService
|
||
participant DLS as DataLoadingService
|
||
participant VLB as ValueListenableBuilder
|
||
|
||
UI->>OR: createOperation(newOperation)
|
||
OR->>API: POST /operations
|
||
API-->>OR: 201 Created + 4 groupes
|
||
|
||
Note over OR: Traitement des données complexes
|
||
OR->>HS: clear() sur 4 Box concernées
|
||
OR->>DLS: processOperationsFromApi()
|
||
OR->>DLS: processSectorsFromApi()
|
||
OR->>DLS: processPassagesFromApi()
|
||
OR->>DLS: processUserSectorsFromApi()
|
||
|
||
Note over DLS: Sauvegarde dans Hive
|
||
DLS->>HS: put() dans Box typées
|
||
HS-->>VLB: Notifications de changement
|
||
VLB-->>UI: Mise à jour automatique
|
||
```
|
||
|
||
### ✅ Avantages de cette approche
|
||
|
||
- **Cohérence totale** : Données locales parfaitement synchronisées avec le serveur
|
||
- **Performance optimisée** : Un seul appel API pour toutes les données nécessaires
|
||
- **Réactivité immédiate** : Interface mise à jour automatiquement via ValueListenableBuilder
|
||
- **Logique centralisée** : Réutilisation du DataLoadingService existant
|
||
- **Gestion d'erreurs** : Rollback automatique en cas d'échec du traitement
|
||
|
||
Cette architecture garantit une synchronisation robuste et performante lors de la création d'opérations, en maintenant la cohérence des données tout en optimisant l'expérience utilisateur. 🚀
|