feat: Ajout filtres membre/secteur dans historique admin (#42)
- Ajout de 2 dropdowns de filtres dans history_page.dart (admin uniquement) - Filtre par membre (fkUser) : liste dynamique depuis passages - Filtre par secteur (fkSector) : liste dynamique depuis passages - Valeurs par défaut : "Tous" pour chaque filtre - Tri alphabétique des dropdowns - Mise à jour du planning : #42 validée (26/01) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
140
app/lib/presentation/pages/history_page.dart
Normal file → Executable file
140
app/lib/presentation/pages/history_page.dart
Normal file → Executable file
@@ -55,6 +55,8 @@ class _HistoryContentState extends State<HistoryContent> {
|
|||||||
String _selectedTypeFilter = 'Tous les types';
|
String _selectedTypeFilter = 'Tous les types';
|
||||||
String _searchQuery = '';
|
String _searchQuery = '';
|
||||||
int? selectedTypeId;
|
int? selectedTypeId;
|
||||||
|
int? _selectedMemberId; // null = "Tous" (admin uniquement)
|
||||||
|
int? _selectedSectorId; // null = "Tous" (admin uniquement)
|
||||||
|
|
||||||
// Contrôleur de recherche
|
// Contrôleur de recherche
|
||||||
final TextEditingController _searchController = TextEditingController();
|
final TextEditingController _searchController = TextEditingController();
|
||||||
@@ -221,6 +223,20 @@ class _HistoryContentState extends State<HistoryContent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filtre par membre (admin uniquement)
|
||||||
|
if (isAdmin && _selectedMemberId != null) {
|
||||||
|
if (passage.fkUser != _selectedMemberId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtre par secteur (admin uniquement)
|
||||||
|
if (isAdmin && _selectedSectorId != null) {
|
||||||
|
if (passage.fkSector != _selectedSectorId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Filtre par recherche textuelle
|
// Filtre par recherche textuelle
|
||||||
if (_searchQuery.isNotEmpty) {
|
if (_searchQuery.isNotEmpty) {
|
||||||
final query = _searchQuery.toLowerCase();
|
final query = _searchQuery.toLowerCase();
|
||||||
@@ -302,6 +318,7 @@ class _HistoryContentState extends State<HistoryContent> {
|
|||||||
children: [
|
children: [
|
||||||
// Barre de recherche
|
// Barre de recherche
|
||||||
Expanded(
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@@ -319,6 +336,21 @@ class _HistoryContentState extends State<HistoryContent> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Filtres admin uniquement
|
||||||
|
if (isAdmin) ...[
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
// Filtre par membre
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: _buildMemberDropdown(),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
// Filtre par secteur
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: _buildSectorDropdown(),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -563,4 +595,112 @@ class _HistoryContentState extends State<HistoryContent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construit le dropdown de sélection de membre (admin uniquement)
|
||||||
|
Widget _buildMemberDropdown() {
|
||||||
|
// Récupérer les membres uniques depuis les passages de l'opération courante
|
||||||
|
final memberIds = <int>{};
|
||||||
|
final memberNames = <int, String>{};
|
||||||
|
|
||||||
|
for (final passage in _originalPassages) {
|
||||||
|
if (!memberIds.contains(passage.fkUser)) {
|
||||||
|
memberIds.add(passage.fkUser);
|
||||||
|
// Utiliser le nom du passage (qui contient prenom + nom)
|
||||||
|
memberNames[passage.fkUser] = passage.name.isNotEmpty ? passage.name : 'Membre #${passage.fkUser}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trier par nom
|
||||||
|
final sortedMembers = memberIds.toList()
|
||||||
|
..sort((a, b) => (memberNames[a] ?? '').compareTo(memberNames[b] ?? ''));
|
||||||
|
|
||||||
|
return DropdownButtonFormField<int?>(
|
||||||
|
value: _selectedMemberId,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
isDense: true,
|
||||||
|
labelText: 'Membre',
|
||||||
|
prefixIcon: Icon(Icons.person, size: 20),
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
const DropdownMenuItem<int?>(
|
||||||
|
value: null,
|
||||||
|
child: Text('Tous'),
|
||||||
|
),
|
||||||
|
...sortedMembers.map((memberId) {
|
||||||
|
return DropdownMenuItem<int?>(
|
||||||
|
value: memberId,
|
||||||
|
child: Text(
|
||||||
|
memberNames[memberId] ?? 'Membre #$memberId',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
onChanged: (int? newValue) {
|
||||||
|
setState(() {
|
||||||
|
_selectedMemberId = newValue;
|
||||||
|
});
|
||||||
|
_applyFilters();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construit le dropdown de sélection de secteur (admin uniquement)
|
||||||
|
Widget _buildSectorDropdown() {
|
||||||
|
// Récupérer les secteurs uniques depuis les passages de l'opération courante
|
||||||
|
final sectorIds = <int>{};
|
||||||
|
|
||||||
|
for (final passage in _originalPassages) {
|
||||||
|
if (passage.fkSector != null && !sectorIds.contains(passage.fkSector)) {
|
||||||
|
sectorIds.add(passage.fkSector!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les noms des secteurs depuis le repository
|
||||||
|
final allSectors = sectorRepository.getAllSectors();
|
||||||
|
final sectorNames = <int, String>{};
|
||||||
|
for (final sector in allSectors) {
|
||||||
|
if (sectorIds.contains(sector.id)) {
|
||||||
|
sectorNames[sector.id] = sector.libelle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trier par nom
|
||||||
|
final sortedSectors = sectorIds.toList()
|
||||||
|
..sort((a, b) => (sectorNames[a] ?? '').compareTo(sectorNames[b] ?? ''));
|
||||||
|
|
||||||
|
return DropdownButtonFormField<int?>(
|
||||||
|
value: _selectedSectorId,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
isDense: true,
|
||||||
|
labelText: 'Secteur',
|
||||||
|
prefixIcon: Icon(Icons.location_on, size: 20),
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
const DropdownMenuItem<int?>(
|
||||||
|
value: null,
|
||||||
|
child: Text('Tous'),
|
||||||
|
),
|
||||||
|
...sortedSectors.map((sectorId) {
|
||||||
|
return DropdownMenuItem<int?>(
|
||||||
|
value: sectorId,
|
||||||
|
child: Text(
|
||||||
|
sectorNames[sectorId] ?? 'Secteur #$sectorId',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
onChanged: (int? newValue) {
|
||||||
|
setState(() {
|
||||||
|
_selectedSectorId = newValue;
|
||||||
|
});
|
||||||
|
_applyFilters();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
docs/PLANNING-2026-Q1.md
Normal file → Executable file
25
docs/PLANNING-2026-Q1.md
Normal file → Executable file
@@ -31,11 +31,9 @@
|
|||||||
|-------|------------|--------------------------------------------|--------|
|
|-------|------------|--------------------------------------------|--------|
|
||||||
| 19/01 | `#13` Jour 1 | ✅ `#204` Design couleurs flashy | à livrer v3.6.3 |
|
| 19/01 | `#13` Jour 1 | ✅ `#204` Design couleurs flashy | à livrer v3.6.3 |
|
||||||
| 19/01 | | ✅ `#205` Écrans utilisateurs simplifiés | à livrer v3.6.3 |
|
| 19/01 | | ✅ `#205` Écrans utilisateurs simplifiés | à livrer v3.6.3 |
|
||||||
| 20/01 | `#13` Jour 2 | `#113` Couleur repasses orange | |
|
| 20/01 | `#13` Jour 2 | ✅ `#113` Couleur repasses orange | ✅ Validé 26/01 |
|
||||||
| 20/01 | | `#72`Épaisseur police lisibilité | |
|
| 20/01 | | ✅ `#72` Épaisseur police lisibilité (theme + BtnPassages) | ✅ Livré 26/01 |
|
||||||
| 21/01 | `#13` Jour 3 | `#71`Visibilité bouton "Envoyer message" | |
|
| 21/01 | `#13` Jour 3 | ✅ `#42` Filtres membre/secteur history (admin) | ✅ Livré 26/01 |
|
||||||
| 21/01 | | `#59`Listing rues invisible (clavier) | |
|
|
||||||
| 22/01 | `#13` Jour 4 | `#42`Historique adresses cliquables | |
|
|
||||||
| 23/01 | `#13` Jour 5 | `#74`Simplifier DashboardLayout/AppScaffold | |
|
| 23/01 | `#13` Jour 5 | `#74`Simplifier DashboardLayout/AppScaffold | |
|
||||||
| 24/01 | | `#28`Gestion reçus Flutter nouveaux champs | |
|
| 24/01 | | `#28`Gestion reçus Flutter nouveaux champs | |
|
||||||
| 25/01 | | `#50`Modifier secteur au clic | |
|
| 25/01 | | `#50`Modifier secteur au clic | |
|
||||||
@@ -143,17 +141,28 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## PHASE 7 : DIVERS (sans date)
|
||||||
|
### Tâches diverses - À planifier ultérieurement
|
||||||
|
|
||||||
|
| ID | Tâche | Cat | Statut |
|
||||||
|
|------|----------------------------------------|------|--------|
|
||||||
|
| `#71` | Visibilité bouton "Envoyer message" | UX | |
|
||||||
|
| `#59` | Listing rues invisible (clavier) | UX | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## RÉCAPITULATIF
|
## RÉCAPITULATIF
|
||||||
|
|
||||||
| Phase | Période | Jours | Tâches | Focus |
|
| Phase | Période | Jours | Tâches | Focus |
|
||||||
|-----------|--------------|-------|--------|---------------------|
|
|-----------|--------------|-------|--------|---------------------|
|
||||||
| 1 | 16-18/01 | 3 | 5 | Bugs critiques |
|
| 1 | 16-18/01 | 3 | 7 | Bugs critiques |
|
||||||
| 2 | 19-25/01 | 7 | 10 | Stripe iOS + UX |
|
| 2 | 19-25/01 | 7 | 8 | Stripe iOS + UX |
|
||||||
| 3 | 26/01-07/02 | 10 | 25 | MAP / Carte |
|
| 3 | 26/01-07/02 | 10 | 25 | MAP / Carte |
|
||||||
| 4 | 08-14/02 | 6 | 11 | Stripe + Passages |
|
| 4 | 08-14/02 | 6 | 11 | Stripe + Passages |
|
||||||
| 5 | 15-22/02 | 7 | 22 | Admin + Membres |
|
| 5 | 15-22/02 | 7 | 22 | Admin + Membres |
|
||||||
| 6 | 23-28/02 | 5 | 15 | Export + Divers |
|
| 6 | 23-28/02 | 5 | 15 | Export + Divers |
|
||||||
| **TOTAL** | **44 jours** | | **88** | |
|
| 7 | Sans date | - | 2 | Divers |
|
||||||
|
| **TOTAL** | **44 jours** | | **90** | |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user