feat: Début des évolutions interfaces mobiles v3.2.4
- Préparation de la nouvelle branche pour les évolutions - Mise à jour de la version vers 3.2.4 - Intégration des modifications en cours 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -274,30 +274,8 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
|
||||
/// Construction du titre de l'AppBar
|
||||
Widget _buildTitle(BuildContext context) {
|
||||
// Si aucun titre de page n'est fourni, afficher simplement le titre principal
|
||||
if (pageTitle == null) {
|
||||
return Text(title);
|
||||
}
|
||||
|
||||
// Utiliser LayoutBuilder pour détecter la largeur disponible
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
// Déterminer si on est sur mobile ou écran étroit
|
||||
final isNarrowScreen = constraints.maxWidth < 600;
|
||||
final isMobilePlatform =
|
||||
Theme.of(context).platform == TargetPlatform.android ||
|
||||
Theme.of(context).platform == TargetPlatform.iOS;
|
||||
|
||||
// Sur mobile ou écrans étroits, afficher seulement le titre principal
|
||||
if (isNarrowScreen || isMobilePlatform) {
|
||||
return Text(title);
|
||||
}
|
||||
|
||||
// Sur écrans larges (web desktop), afficher le titre de la page ou le titre principal
|
||||
// Pour les admins, on affiche directement le titre de la page sans préfixe
|
||||
return Text(pageTitle!);
|
||||
},
|
||||
);
|
||||
// Titre vide pour économiser de l'espace sur mobile
|
||||
return const Text('');
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -6,6 +6,8 @@ import 'package:geosector_app/core/services/current_amicale_service.dart';
|
||||
import 'package:geosector_app/core/utils/api_exception.dart';
|
||||
import 'package:geosector_app/app.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||
import 'package:geosector_app/presentation/widgets/passage_form_dialog.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
@@ -25,6 +27,13 @@ class PassagesListWidget extends StatefulWidget {
|
||||
|
||||
/// Si vrai, la barre de recherche sera affichée
|
||||
final bool showSearch;
|
||||
|
||||
/// Contrôle de l'affichage des filtres individuels
|
||||
final bool showTypeFilter;
|
||||
final bool showPaymentFilter;
|
||||
final bool showSectorFilter;
|
||||
final bool showUserFilter;
|
||||
final bool showPeriodFilter;
|
||||
|
||||
/// Si vrai, les boutons d'action (détails, modifier, etc.) seront affichés
|
||||
final bool showActions;
|
||||
@@ -76,6 +85,18 @@ class PassagesListWidget extends StatefulWidget {
|
||||
|
||||
/// Callback appelé lorsque le bouton d'ajout est cliqué
|
||||
final VoidCallback? onAddPassage;
|
||||
|
||||
/// Données pour les filtres avancés
|
||||
final List<SectorModel>? sectors;
|
||||
final List<UserModel>? members;
|
||||
|
||||
/// Valeurs initiales pour les filtres avancés
|
||||
final int? initialSectorId;
|
||||
final int? initialUserId;
|
||||
final String? initialPeriod;
|
||||
|
||||
/// Callback appelé lorsque les filtres changent
|
||||
final Function(Map<String, dynamic>)? onFiltersChanged;
|
||||
|
||||
const PassagesListWidget({
|
||||
super.key,
|
||||
@@ -85,6 +106,11 @@ class PassagesListWidget extends StatefulWidget {
|
||||
this.showFilters = true,
|
||||
this.showSearch = true,
|
||||
this.showActions = true,
|
||||
this.showTypeFilter = true,
|
||||
this.showPaymentFilter = true,
|
||||
this.showSectorFilter = false,
|
||||
this.showUserFilter = false,
|
||||
this.showPeriodFilter = false,
|
||||
this.onPassageSelected,
|
||||
this.onPassageEdit,
|
||||
this.onReceiptView,
|
||||
@@ -102,6 +128,12 @@ class PassagesListWidget extends StatefulWidget {
|
||||
this.sortingButtons,
|
||||
this.showAddButton = false,
|
||||
this.onAddPassage,
|
||||
this.sectors,
|
||||
this.members,
|
||||
this.initialSectorId,
|
||||
this.initialUserId,
|
||||
this.initialPeriod,
|
||||
this.onFiltersChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -113,6 +145,10 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
late String _selectedTypeFilter;
|
||||
late String _selectedPaymentFilter;
|
||||
late String _searchQuery;
|
||||
late int? _selectedSectorId;
|
||||
late int? _selectedUserId;
|
||||
late String _selectedPeriod;
|
||||
DateTimeRange? _selectedDateRange;
|
||||
|
||||
// Contrôleur de recherche
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
@@ -121,10 +157,29 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialiser les filtres
|
||||
_selectedTypeFilter = widget.initialTypeFilter ?? 'Tous';
|
||||
_selectedPaymentFilter = widget.initialPaymentFilter ?? 'Tous';
|
||||
_selectedTypeFilter = widget.initialTypeFilter ?? 'Tous les types';
|
||||
_selectedPaymentFilter = widget.initialPaymentFilter ?? 'Tous les règlements';
|
||||
_searchQuery = widget.initialSearchQuery ?? '';
|
||||
_searchController.text = _searchQuery;
|
||||
_selectedSectorId = widget.initialSectorId;
|
||||
_selectedUserId = widget.initialUserId;
|
||||
_selectedPeriod = widget.initialPeriod ?? 'Toutes les périodes';
|
||||
_selectedDateRange = widget.dateRange;
|
||||
}
|
||||
|
||||
// Notifier les changements de filtres
|
||||
void _notifyFiltersChanged() {
|
||||
if (widget.onFiltersChanged != null) {
|
||||
widget.onFiltersChanged!({
|
||||
'typeFilter': _selectedTypeFilter,
|
||||
'paymentFilter': _selectedPaymentFilter,
|
||||
'searchQuery': _searchQuery,
|
||||
'sectorId': _selectedSectorId,
|
||||
'userId': _selectedUserId,
|
||||
'period': _selectedPeriod,
|
||||
'dateRange': _selectedDateRange,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier si l'amicale autorise la suppression des passages
|
||||
@@ -204,13 +259,13 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(typeInfo?['couleur1'] ?? Colors.blue.value)
|
||||
color: Color(typeInfo?['couleur1'] ?? Colors.blue.toARGB32())
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
typeInfo?['icon_data'] ?? Icons.receipt_long,
|
||||
color: Color(typeInfo?['couleur1'] ?? Colors.blue.value),
|
||||
color: Color(typeInfo?['couleur1'] ?? Colors.blue.toARGB32()),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
@@ -231,7 +286,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Color(typeInfo?['couleur1'] ?? Colors.blue.value)
|
||||
Color(typeInfo?['couleur1'] ?? Colors.blue.toARGB32())
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
@@ -239,7 +294,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
typeInfo?['titre'] ?? 'Inconnu',
|
||||
style: TextStyle(
|
||||
color: Color(
|
||||
typeInfo?['couleur1'] ?? Colors.blue.value),
|
||||
typeInfo?['couleur1'] ?? Colors.blue.toARGB32()),
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -323,7 +378,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(paymentInfo?['couleur'] ??
|
||||
Colors.grey.value)
|
||||
Colors.grey.toARGB32())
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
@@ -331,7 +386,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
paymentInfo?['titre'] ?? 'Inconnu',
|
||||
style: TextStyle(
|
||||
color: Color(paymentInfo?['couleur'] ??
|
||||
Colors.grey.value),
|
||||
Colors.grey.toARGB32()),
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -749,14 +804,58 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
}
|
||||
|
||||
// Filtrer par secteur
|
||||
if (widget.filterBySectorId != null &&
|
||||
if (_selectedSectorId != null &&
|
||||
passage.containsKey('fkSector') &&
|
||||
passage['fkSector'] != widget.filterBySectorId) {
|
||||
passage['fkSector'] != _selectedSectorId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtrer par membre/utilisateur
|
||||
if (_selectedUserId != null &&
|
||||
passage.containsKey('fkUser') &&
|
||||
passage['fkUser'] != _selectedUserId) {
|
||||
// Les passages de type 2 sont partagés
|
||||
if (passage.containsKey('type') && passage['type'] == 2) {
|
||||
// Ne pas filtrer les passages type 2
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Filtrer par période
|
||||
if (_selectedPeriod != 'Toutes les périodes' && passage.containsKey('date')) {
|
||||
final DateTime passageDate = passage['date'] as DateTime;
|
||||
final DateTime now = DateTime.now();
|
||||
|
||||
switch (_selectedPeriod) {
|
||||
case 'Dernières 24h':
|
||||
if (now.difference(passageDate).inHours > 24) return false;
|
||||
break;
|
||||
case 'Dernières 48h':
|
||||
if (now.difference(passageDate).inHours > 48) return false;
|
||||
break;
|
||||
case 'Derniers 7 jours':
|
||||
if (now.difference(passageDate).inDays > 7) return false;
|
||||
break;
|
||||
case 'Derniers 15 jours':
|
||||
if (now.difference(passageDate).inDays > 15) return false;
|
||||
break;
|
||||
case 'Dernier mois':
|
||||
if (now.difference(passageDate).inDays > 30) return false;
|
||||
break;
|
||||
case 'Personnalisée':
|
||||
if (_selectedDateRange != null) {
|
||||
if (passageDate.isBefore(_selectedDateRange!.start) ||
|
||||
passageDate.isAfter(_selectedDateRange!.end.add(const Duration(days: 1)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Filtre par type
|
||||
if (_selectedTypeFilter != 'Tous') {
|
||||
if (_selectedTypeFilter != 'Tous les types') {
|
||||
try {
|
||||
final typeEntries = AppKeys.typesPassages.entries.where(
|
||||
(entry) => entry.value['titre'] == _selectedTypeFilter);
|
||||
@@ -774,7 +873,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
}
|
||||
|
||||
// Filtre par type de règlement
|
||||
if (_selectedPaymentFilter != 'Tous') {
|
||||
if (_selectedPaymentFilter != 'Tous les règlements') {
|
||||
try {
|
||||
final paymentEntries = AppKeys.typesReglements.entries.where(
|
||||
(entry) => entry.value['titre'] == _selectedPaymentFilter);
|
||||
@@ -1043,9 +1142,17 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
// Dans les pages user, seuls les passages de l'utilisateur courant sont affichés normalement
|
||||
final bool shouldGreyOut = !isAdminPage && !isOwnedByCurrentUser;
|
||||
final bool isClickable = isAdminPage || isOwnedByCurrentUser;
|
||||
|
||||
// Dimensions responsives
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final bool isMobile = screenWidth < 600;
|
||||
final cardMargin = isMobile ? 4.0 : 6.0;
|
||||
final horizontalPadding = isMobile ? 10.0 : 12.0;
|
||||
final verticalPadding = isMobile ? 8.0 : 10.0;
|
||||
final iconSize = isMobile ? 32.0 : 36.0;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 6), // Réduit de 8 à 6
|
||||
margin: EdgeInsets.only(bottom: cardMargin),
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -1059,8 +1166,9 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
onTap: isClickable ? () => _handlePassageClick(passage) : null,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0, vertical: 10.0), // Réduit de 16 à 12/10
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: horizontalPadding,
|
||||
vertical: verticalPadding),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -1069,8 +1177,8 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
children: [
|
||||
// Icône du type de passage avec bordure couleur2
|
||||
Container(
|
||||
width: 36, // Réduit de 40 à 36
|
||||
height: 36,
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(typePassage['couleur1'] as int)
|
||||
.withValues(alpha: 0.1),
|
||||
@@ -1296,13 +1404,15 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
) {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
'$label:',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
if (label.isNotEmpty) ...[
|
||||
Text(
|
||||
'$label:',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
@@ -1337,6 +1447,190 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Construction du filtre de secteur
|
||||
Widget _buildSectorFilter(ThemeData theme, bool isCompact) {
|
||||
if (widget.sectors == null || widget.sectors!.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final options = ['Tous les secteurs'] +
|
||||
widget.sectors!.map((s) => s.libelle).toList();
|
||||
|
||||
final selectedValue = _selectedSectorId == null
|
||||
? 'Tous les secteurs'
|
||||
: () {
|
||||
final sector = widget.sectors!.firstWhere((s) => s.id == _selectedSectorId,
|
||||
orElse: () => widget.sectors!.first);
|
||||
return sector.libelle;
|
||||
}();
|
||||
|
||||
return isCompact
|
||||
? _buildCompactDropdownFilter(
|
||||
'Secteur',
|
||||
selectedValue,
|
||||
options,
|
||||
(value) {
|
||||
setState(() {
|
||||
if (value == 'Tous les secteurs') {
|
||||
_selectedSectorId = null;
|
||||
} else {
|
||||
_selectedSectorId = widget.sectors!.firstWhere((s) => s.libelle == value).id;
|
||||
}
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
theme,
|
||||
)
|
||||
: _buildDropdownFilter(
|
||||
'',
|
||||
selectedValue,
|
||||
options,
|
||||
(value) {
|
||||
setState(() {
|
||||
if (value == 'Tous les secteurs') {
|
||||
_selectedSectorId = null;
|
||||
} else {
|
||||
_selectedSectorId = widget.sectors!.firstWhere((s) => s.libelle == value).id;
|
||||
}
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
theme,
|
||||
);
|
||||
}
|
||||
|
||||
// Construction du filtre de membre/utilisateur
|
||||
Widget _buildUserFilter(ThemeData theme, bool isCompact) {
|
||||
if (widget.members == null || widget.members!.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final options = ['Tous les membres'] +
|
||||
widget.members!.map((u) => '${u.firstName} ${u.name}'.trim()).toList();
|
||||
|
||||
final selectedValue = _selectedUserId == null
|
||||
? 'Tous les membres'
|
||||
: () {
|
||||
final user = widget.members!.firstWhere((u) => u.id == _selectedUserId,
|
||||
orElse: () => widget.members!.first);
|
||||
return '${user.firstName} ${user.name}'.trim();
|
||||
}();
|
||||
|
||||
return isCompact
|
||||
? _buildCompactDropdownFilter(
|
||||
'Membre',
|
||||
selectedValue,
|
||||
options,
|
||||
(value) {
|
||||
setState(() {
|
||||
if (value == 'Tous les membres') {
|
||||
_selectedUserId = null;
|
||||
} else {
|
||||
_selectedUserId = widget.members!.firstWhere(
|
||||
(u) => '${u.firstName} ${u.name}'.trim() == value
|
||||
).id;
|
||||
}
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
theme,
|
||||
)
|
||||
: _buildDropdownFilter(
|
||||
'',
|
||||
selectedValue,
|
||||
options,
|
||||
(value) {
|
||||
setState(() {
|
||||
if (value == 'Tous les membres') {
|
||||
_selectedUserId = null;
|
||||
} else {
|
||||
_selectedUserId = widget.members!.firstWhere(
|
||||
(u) => '${u.firstName} ${u.name}'.trim() == value
|
||||
).id;
|
||||
}
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
theme,
|
||||
);
|
||||
}
|
||||
|
||||
// Construction du filtre de période
|
||||
Widget _buildPeriodFilter(ThemeData theme, bool isCompact) {
|
||||
final options = [
|
||||
'Toutes les périodes',
|
||||
'Dernières 24h',
|
||||
'Dernières 48h',
|
||||
'Derniers 7 jours',
|
||||
'Derniers 15 jours',
|
||||
'Dernier mois',
|
||||
];
|
||||
|
||||
if (_selectedDateRange != null && _selectedPeriod == 'Personnalisée') {
|
||||
options.add('Personnalisée');
|
||||
}
|
||||
|
||||
return isCompact
|
||||
? _buildCompactDropdownFilter(
|
||||
'Période',
|
||||
_selectedPeriod,
|
||||
options,
|
||||
(value) async {
|
||||
if (value == 'Personnalisée') {
|
||||
final picked = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: DateTime.now().subtract(const Duration(days: 365)),
|
||||
lastDate: DateTime.now(),
|
||||
initialDateRange: _selectedDateRange,
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
_selectedDateRange = picked;
|
||||
_selectedPeriod = 'Personnalisée';
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_selectedPeriod = value;
|
||||
_selectedDateRange = null;
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
}
|
||||
},
|
||||
theme,
|
||||
)
|
||||
: _buildDropdownFilter(
|
||||
'',
|
||||
_selectedPeriod,
|
||||
options,
|
||||
(value) async {
|
||||
if (value == 'Personnalisée') {
|
||||
final picked = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: DateTime.now().subtract(const Duration(days: 365)),
|
||||
lastDate: DateTime.now(),
|
||||
initialDateRange: _selectedDateRange,
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
_selectedDateRange = picked;
|
||||
_selectedPeriod = 'Personnalisée';
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_selectedPeriod = value;
|
||||
_selectedDateRange = null;
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
}
|
||||
},
|
||||
theme,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -1536,185 +1830,236 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isDesktop)
|
||||
// Version compacte pour le web (desktop)
|
||||
// Barre de recherche (si activée) - toujours en premier
|
||||
if (widget.showSearch)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Barre de recherche (si activée)
|
||||
if (widget.showSearch)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Rechercher par adresse ou nom...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
setState(() {
|
||||
_searchQuery = '';
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.outline,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 14.0),
|
||||
),
|
||||
onChanged: (value) {
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Rechercher par adresse ou nom...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
_searchQuery = '';
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.outline,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 14.0),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
if (isDesktop)
|
||||
// Version compacte pour le web (desktop)
|
||||
Column(
|
||||
children: [
|
||||
// Première ligne : Type, Règlement, Secteur
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Filtre par type de passage
|
||||
if (widget.showTypeFilter)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: _buildCompactDropdownFilter(
|
||||
'Type',
|
||||
_selectedTypeFilter,
|
||||
[
|
||||
'Tous les types',
|
||||
...AppKeys.typesPassages.values
|
||||
.map((type) => type['titre'] as String)
|
||||
],
|
||||
(value) {
|
||||
setState(() {
|
||||
_selectedTypeFilter = value;
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
theme,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Filtre par type de passage
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: _buildCompactDropdownFilter(
|
||||
'Type',
|
||||
_selectedTypeFilter,
|
||||
[
|
||||
'Tous',
|
||||
...AppKeys.typesPassages.values
|
||||
.map((type) => type['titre'] as String)
|
||||
],
|
||||
(value) {
|
||||
setState(() {
|
||||
_selectedTypeFilter = value;
|
||||
});
|
||||
},
|
||||
theme,
|
||||
// Filtre par type de règlement
|
||||
if (widget.showPaymentFilter)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: _buildCompactDropdownFilter(
|
||||
'Règlement',
|
||||
_selectedPaymentFilter,
|
||||
[
|
||||
'Tous les règlements',
|
||||
...AppKeys.typesReglements.values
|
||||
.map((type) => type['titre'] as String)
|
||||
],
|
||||
(value) {
|
||||
setState(() {
|
||||
_selectedPaymentFilter = value;
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
theme,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Filtre par type de règlement
|
||||
Expanded(
|
||||
child: _buildCompactDropdownFilter(
|
||||
'Règlement',
|
||||
_selectedPaymentFilter,
|
||||
[
|
||||
'Tous',
|
||||
...AppKeys.typesReglements.values
|
||||
.map((type) => type['titre'] as String)
|
||||
|
||||
// Filtre par secteur
|
||||
if (widget.showSectorFilter && widget.sectors != null)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: _buildSectorFilter(theme, true),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Deuxième ligne : Membre et Période (si nécessaire)
|
||||
if (widget.showUserFilter || widget.showPeriodFilter)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Filtre par membre
|
||||
if (widget.showUserFilter && widget.members != null)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: _buildUserFilter(theme, true),
|
||||
),
|
||||
),
|
||||
|
||||
// Filtre par période
|
||||
if (widget.showPeriodFilter)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: _buildPeriodFilter(theme, true),
|
||||
),
|
||||
),
|
||||
|
||||
// Spacer si un seul filtre sur la deuxième ligne
|
||||
if ((widget.showUserFilter && !widget.showPeriodFilter) ||
|
||||
(!widget.showUserFilter && widget.showPeriodFilter))
|
||||
const Expanded(child: SizedBox()),
|
||||
],
|
||||
(value) {
|
||||
setState(() {
|
||||
_selectedPaymentFilter = value;
|
||||
});
|
||||
},
|
||||
theme,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
// Version mobile (non-desktop)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Barre de recherche (si activée)
|
||||
if (widget.showSearch)
|
||||
// Première ligne : Type et Règlement
|
||||
if (widget.showTypeFilter || widget.showPaymentFilter)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Rechercher par adresse ou nom...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
// Filtre par type de passage
|
||||
if (widget.showTypeFilter)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: _buildDropdownFilter(
|
||||
'',
|
||||
_selectedTypeFilter,
|
||||
[
|
||||
'Tous les types',
|
||||
...AppKeys.typesPassages.values
|
||||
.map((type) => type['titre'] as String)
|
||||
],
|
||||
(value) {
|
||||
setState(() {
|
||||
_searchQuery = '';
|
||||
_selectedTypeFilter = value;
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.outline,
|
||||
width: 1.0,
|
||||
theme,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 14.0),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
});
|
||||
},
|
||||
|
||||
// Filtre par type de règlement
|
||||
if (widget.showPaymentFilter)
|
||||
Expanded(
|
||||
child: _buildDropdownFilter(
|
||||
'',
|
||||
_selectedPaymentFilter,
|
||||
[
|
||||
'Tous les règlements',
|
||||
...AppKeys.typesReglements.values
|
||||
.map((type) => type['titre'] as String)
|
||||
],
|
||||
(value) {
|
||||
setState(() {
|
||||
_selectedPaymentFilter = value;
|
||||
_notifyFiltersChanged();
|
||||
});
|
||||
},
|
||||
theme,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Filtres
|
||||
Row(
|
||||
children: [
|
||||
// Filtre par type de passage
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: _buildDropdownFilter(
|
||||
'Type',
|
||||
_selectedTypeFilter,
|
||||
[
|
||||
'Tous',
|
||||
...AppKeys.typesPassages.values
|
||||
.map((type) => type['titre'] as String)
|
||||
],
|
||||
(value) {
|
||||
setState(() {
|
||||
_selectedTypeFilter = value;
|
||||
});
|
||||
},
|
||||
theme,
|
||||
),
|
||||
),
|
||||
|
||||
// Deuxième ligne : Secteur et Période
|
||||
if (widget.showSectorFilter || widget.showPeriodFilter)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
// Filtre par secteur
|
||||
if (widget.showSectorFilter && widget.sectors != null)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: _buildSectorFilter(theme, false),
|
||||
),
|
||||
),
|
||||
|
||||
// Filtre par période
|
||||
if (widget.showPeriodFilter)
|
||||
Expanded(
|
||||
child: _buildPeriodFilter(theme, false),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Filtre par type de règlement
|
||||
Expanded(
|
||||
child: _buildDropdownFilter(
|
||||
'Règlement',
|
||||
_selectedPaymentFilter,
|
||||
[
|
||||
'Tous',
|
||||
...AppKeys.typesReglements.values
|
||||
.map((type) => type['titre'] as String)
|
||||
],
|
||||
(value) {
|
||||
setState(() {
|
||||
_selectedPaymentFilter = value;
|
||||
});
|
||||
},
|
||||
theme,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Troisième ligne : Membre (si nécessaire)
|
||||
if (widget.showUserFilter && widget.members != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: _buildUserFilter(theme, false),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user