- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
Executable File
Widgets communs GEOSECTOR
Description générale
GEOSECTOR utilise un ensemble de widgets communs réutilisables pour maintenir une cohérence visuelle et simplifier le développement. Ces widgets sont conçus pour s'adapter à la fois à l'interface web et mobile, et respectent les thèmes définis dans l'application. Ce document détaille les widgets principaux, leurs propriétés et leur utilisation.
Interface utilisateur de base
CustomButton
Un bouton stylisé et réutilisable avec gestion de l'état de chargement.
Fichier: lib/presentation/widgets/custom_button.dart
Propriétés principales:
| Propriété | Type | Description | Requis |
|---|---|---|---|
| onPressed | VoidCallback? | Fonction appelée lors du clic | Oui |
| text | String | Texte du bouton | Oui |
| icon | IconData? | Icône à afficher à gauche du texte | Non |
| isLoading | bool | Afficher un indicateur de chargement | Non |
| width | double? | Largeur personnalisée | Non |
| backgroundColor | Color? | Couleur de fond (utilise primary par défaut) | Non |
| textColor | Color? | Couleur du texte (utilise white par défaut) | Non |
Exemple d'utilisation:
CustomButton(
onPressed: () => saveData(),
text: 'Enregistrer',
icon: Icons.save,
isLoading: isSaving,
backgroundColor: Colors.green,
)
CustomTextField
Champ de texte stylisé et réutilisable avec gestion des validateurs.
Fichier: lib/presentation/widgets/custom_text_field.dart
Propriétés principales:
| Propriété | Type | Description | Requis |
|---|---|---|---|
| controller | TextEditingController | Contrôleur du champ | Oui |
| label | String | Libellé du champ | Oui |
| hintText | String? | Texte indicatif lorsque vide | Non |
| prefixIcon | IconData? | Icône au début du champ | Non |
| suffixIcon | Widget? | Widget à la fin du champ | Non |
| obscureText | bool | Masquer le texte (pour les mots de passe) | Non |
| keyboardType | TextInputType | Type de clavier | Non |
| validator | Function? | Fonction de validation | Non |
| maxLines | int? | Nombre maximum de lignes | Non |
| minLines | int? | Nombre minimum de lignes | Non |
| readOnly | bool | Lecture seule | Non |
| onChanged | Function? | Fonction appelée lors des modifications | Non |
| fillColor | Color? | Couleur de fond du champ | Non |
Exemple d'utilisation:
CustomTextField(
controller: _emailController,
label: 'Adresse email',
hintText: 'Saisissez votre email',
prefixIcon: Icons.email,
keyboardType: TextInputType.emailAddress,
validator: (value) => value.isEmpty ? 'Email requis' : null,
)
Navigation et mise en page
DashboardAppBar
Barre d'applications personnalisée pour les tableaux de bord.
Fichier: lib/presentation/widgets/dashboard_app_bar.dart
Propriétés principales:
| Propriété | Type | Description | Requis |
|---|---|---|---|
| title | String | Titre principal de l'AppBar | Oui |
| pageTitle | String? | Titre de la page actuelle | Non |
| additionalActions | List? | Actions supplémentaires | Non |
| showNewPassageButton | bool | Afficher le bouton "Nouveau passage" | Non |
| onNewPassagePressed | VoidCallback? | Fonction appelée pour créer un nouveau passage | Non |
| isAdmin | bool | Indique si l'utilisateur est un administrateur | Non |
Exemple d'utilisation:
DashboardAppBar(
title: 'GEOSECTOR',
pageTitle: 'Tableau de bord',
isAdmin: userRepository.isAdmin(),
showNewPassageButton: true,
onNewPassagePressed: () => Navigator.pushNamed(context, '/passage/new'),
)
Visualisation cartographique
MapboxMap
Widget de carte réutilisable utilisant Mapbox pour afficher des marqueurs et des polygones.
Fichier: lib/presentation/widgets/mapbox_map.dart
Propriétés principales:
| Propriété | Type | Description | Requis |
|---|---|---|---|
| initialPosition | LatLng | Position initiale de la carte | Non |
| initialZoom | double | Niveau de zoom initial | Non |
| markers | List? | Liste des marqueurs à afficher | Non |
| polygons | List? | Liste des polygones à afficher | Non |
| mapController | MapController? | Contrôleur de carte externe | Non |
| onMapEvent | Function(MapEvent)? | Callback lors des événements de carte | Non |
| showControls | bool | Afficher les boutons de contrôle | Non |
| mapStyle | String? | Style de la carte Mapbox | Non |
Exemple d'utilisation:
MapboxMap(
initialPosition: LatLng(48.1173, -1.6778),
initialZoom: 13.0,
markers: sectorMarkers,
polygons: [
Polygon(
points: sector.getCoordinates().map((p) => LatLng(p[0], p[1])).toList(),
color: Color(int.parse(sector.color.replaceAll('#', '0xFF'))),
borderStrokeWidth: 2,
borderColor: Colors.black,
),
],
showControls: true,
)
Affichage des données
PassagesListWidget
Widget réutilisable pour afficher une liste de passages avec filtres et actions.
Fichier: lib/presentation/widgets/passages/passages_list_widget.dart
Propriétés principales:
| Propriété | Type | Description | Requis |
|---|---|---|---|
| passages | List<Map<String, dynamic>> | Liste des passages à afficher | Oui |
| title | String? | Titre de la section | Non |
| maxPassages | int? | Nombre maximum de passages à afficher | Non |
| showFilters | bool | Afficher les filtres | Non |
| showSearch | bool | Afficher la barre de recherche | Non |
| showActions | bool | Afficher les boutons d'action | Non |
| onPassageSelected | Function(Map<String, dynamic>)? | Callback à la sélection d'un passage | Non |
| onPassageEdit | Function(Map<String, dynamic>)? | Callback pour éditer un passage | Non |
| onReceiptView | Function(Map<String, dynamic>)? | Callback pour voir un reçu | Non |
| initialTypeFilter | String? | Filtre de type initial | Non |
| initialPaymentFilter | String? | Filtre de paiement initial | Non |
Format attendu des passages:
[
{
'id': 1001,
'address': '12 Rue des Lilas, Paris',
'type': 1, // Correspond à AppKeys.typesPassages
'payment': 2, // Correspond à AppKeys.typesReglements
'date': DateTime(2025, 4, 15),
'amount': 25.50,
'name': 'Martin Dupont',
'notes': 'Premier étage à droite',
'hasError': false,
'fkUser': 123 // ID de l'utilisateur qui a effectué le passage
},
// ...
]
Exemple d'utilisation:
PassagesListWidget(
passages: passagesList,
title: 'Historique des passages',
showFilters: true,
showSearch: true,
onPassageSelected: (passage) => showPassageDetails(passage),
onPassageEdit: (passage) => editPassage(passage),
onReceiptView: (passage) => viewReceipt(passage),
)
Visualisation de données
ActivityChart
Graphique d'activité affichant les passages par jour/semaine/mois.
Fichier: lib/presentation/widgets/charts/activity_chart.dart
Propriétés principales:
| Propriété | Type | Description | Requis |
|---|---|---|---|
| passageData | List<Map<String, dynamic>>? | Données des passages | Non* |
| periodType | String | Type de période (Jour, Semaine, Mois) | Non |
| height | double | Hauteur du graphique | Non |
| daysToShow | int | Nombre de jours à afficher | Non |
| userId | int? | ID de l'utilisateur pour filtrer | Non |
| excludePassageTypes | List | Types de passages à exclure | Non |
| loadFromHive | bool | Charger depuis Hive au lieu des données fournies | Non |
| title | String | Titre du graphique | Non |
| showDataLabels | bool | Afficher les étiquettes de valeur | Non |
| columnWidth | double | Largeur des colonnes | Non |
| columnSpacing | double | Espacement entre les colonnes | Non |
| showAllPassages | bool | Afficher tous les passages sans filtrage | Non |
* Soit passageData doit être fourni, soit loadFromHive doit être true.
Format attendu de passageData:
[
{
'date': '2025-04-01',
'type_passage': 1,
'nb': 5
},
{
'date': '2025-04-01',
'type_passage': 3,
'nb': 2
},
// ...
]
Exemple d'utilisation:
ActivityChart(
loadFromHive: true,
periodType: 'Jour',
height: 350,
daysToShow: 15,
userId: userRepository.userId,
excludePassageTypes: [2, 6],
title: 'Activité des 15 derniers jours',
)
Autres widgets
PassagePieChart et PaymentPieChart
Graphiques circulaires affichant la répartition des passages et des paiements.
Fichiers:
lib/presentation/widgets/charts/passage_pie_chart.dartlib/presentation/widgets/charts/payment_pie_chart.dart
Ces widgets utilisent les mêmes principes que ActivityChart mais avec une visualisation circulaire.
Exemple d'utilisation:
Row(
children: [
Expanded(
child: PassagePieChart(
loadFromHive: true,
title: 'Répartition par type',
height: 300,
),
),
Expanded(
child: PaymentPieChart(
loadFromHive: true,
title: 'Répartition par règlement',
height: 300,
),
),
],
)
Adaptabilité et responsive design
Tous les widgets communs sont conçus pour s'adapter aux différentes tailles d'écran:
-
Détection de la taille d'écran:
final size = MediaQuery.of(context).size; final isDesktop = size.width > 900; -
Layouts conditionnels:
isDesktop ? _buildDesktopLayout() : _buildMobileLayout() -
Flexibilité des widgets:
- Utilisation fréquente de
ExpandedetFlexible - Définition de contraintes minimales et maximales
- Ajustement du nombre de colonnes selon l'espace disponible
- Utilisation fréquente de
Bonnes pratiques implémentées
-
Gestion des erreurs robuste - Chaque widget inclut une gestion d'erreurs pour éviter les plantages lors des problèmes de données.
-
Optimisation des performances - Évitement des rebuilds inutiles et utilisation d'animations fluides.
-
Séparation des préoccupations - Les widgets se concentrent sur l'affichage, laissant la logique métier aux repositories.
-
Documentation complète - Chaque widget comporte des commentaires de documentation détaillés.
-
Paramétrage flexible - La plupart des widgets peuvent être personnalisés via leurs propriétés.
Utilisation avec les thèmes
Les widgets communs respectent le thème de l'application défini dans app_theme.dart. Ils utilisent systématiquement:
final theme = Theme.of(context);
// Utilisation des couleurs du thème
theme.colorScheme.primary
theme.colorScheme.onPrimary
// Utilisation des styles de texte du thème
theme.textTheme.bodyLarge
Cela garantit une apparence cohérente et le support des thèmes clairs et sombres.