- 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>
347 lines
14 KiB
Markdown
Executable File
347 lines
14 KiB
Markdown
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**:
|
|
|
|
```dart
|
|
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**:
|
|
|
|
```dart
|
|
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<Widget>?| 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**:
|
|
|
|
```dart
|
|
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<Marker>? | Liste des marqueurs à afficher | Non |
|
|
| polygons | List<Polygon>? | 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**:
|
|
|
|
```dart
|
|
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**:
|
|
|
|
```dart
|
|
[
|
|
{
|
|
'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**:
|
|
|
|
```dart
|
|
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<int> | 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**:
|
|
|
|
```dart
|
|
[
|
|
{
|
|
'date': '2025-04-01',
|
|
'type_passage': 1,
|
|
'nb': 5
|
|
},
|
|
{
|
|
'date': '2025-04-01',
|
|
'type_passage': 3,
|
|
'nb': 2
|
|
},
|
|
// ...
|
|
]
|
|
```
|
|
|
|
**Exemple d'utilisation**:
|
|
|
|
```dart
|
|
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.dart`
|
|
- `lib/presentation/widgets/charts/payment_pie_chart.dart`
|
|
|
|
Ces widgets utilisent les mêmes principes que ActivityChart mais avec une visualisation circulaire.
|
|
|
|
**Exemple d'utilisation**:
|
|
|
|
```dart
|
|
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:
|
|
|
|
1. **Détection de la taille d'écran**:
|
|
```dart
|
|
final size = MediaQuery.of(context).size;
|
|
final isDesktop = size.width > 900;
|
|
```
|
|
|
|
2. **Layouts conditionnels**:
|
|
```dart
|
|
isDesktop
|
|
? _buildDesktopLayout()
|
|
: _buildMobileLayout()
|
|
```
|
|
|
|
3. **Flexibilité des widgets**:
|
|
- Utilisation fréquente de `Expanded` et `Flexible`
|
|
- Définition de contraintes minimales et maximales
|
|
- Ajustement du nombre de colonnes selon l'espace disponible
|
|
|
|
## Bonnes pratiques implémentées
|
|
|
|
1. **Gestion des erreurs robuste** - Chaque widget inclut une gestion d'erreurs pour éviter les plantages lors des problèmes de données.
|
|
|
|
2. **Optimisation des performances** - Évitement des rebuilds inutiles et utilisation d'animations fluides.
|
|
|
|
3. **Séparation des préoccupations** - Les widgets se concentrent sur l'affichage, laissant la logique métier aux repositories.
|
|
|
|
4. **Documentation complète** - Chaque widget comporte des commentaires de documentation détaillés.
|
|
|
|
5. **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:
|
|
|
|
```dart
|
|
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.
|