Version propre - Dashboard enhanced
This commit is contained in:
@@ -0,0 +1,626 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../../shared/theme/app_theme.dart';
|
||||
import '../../../../shared/widgets/custom_text_field.dart';
|
||||
|
||||
/// Widget de recherche avancée pour les membres
|
||||
class MembresAdvancedSearch extends StatefulWidget {
|
||||
const MembresAdvancedSearch({
|
||||
super.key,
|
||||
required this.onSearch,
|
||||
this.initialFilters,
|
||||
});
|
||||
|
||||
final Function(Map<String, dynamic>) onSearch;
|
||||
final Map<String, dynamic>? initialFilters;
|
||||
|
||||
@override
|
||||
State<MembresAdvancedSearch> createState() => _MembresAdvancedSearchState();
|
||||
}
|
||||
|
||||
class _MembresAdvancedSearchState extends State<MembresAdvancedSearch> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
// Contrôleurs de texte
|
||||
final _nomController = TextEditingController();
|
||||
final _prenomController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _telephoneController = TextEditingController();
|
||||
final _numeroMembreController = TextEditingController();
|
||||
final _professionController = TextEditingController();
|
||||
final _villeController = TextEditingController();
|
||||
|
||||
// Filtres de statut
|
||||
bool? _actifFilter;
|
||||
|
||||
// Filtres de date
|
||||
DateTime? _dateAdhesionDebut;
|
||||
DateTime? _dateAdhesionFin;
|
||||
DateTime? _dateNaissanceDebut;
|
||||
DateTime? _dateNaissanceFin;
|
||||
|
||||
// Filtres d'âge
|
||||
int? _ageMin;
|
||||
int? _ageMax;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeFilters();
|
||||
}
|
||||
|
||||
void _initializeFilters() {
|
||||
if (widget.initialFilters != null) {
|
||||
final filters = widget.initialFilters!;
|
||||
_nomController.text = filters['nom'] ?? '';
|
||||
_prenomController.text = filters['prenom'] ?? '';
|
||||
_emailController.text = filters['email'] ?? '';
|
||||
_telephoneController.text = filters['telephone'] ?? '';
|
||||
_numeroMembreController.text = filters['numeroMembre'] ?? '';
|
||||
_professionController.text = filters['profession'] ?? '';
|
||||
_villeController.text = filters['ville'] ?? '';
|
||||
_actifFilter = filters['actif'];
|
||||
_dateAdhesionDebut = filters['dateAdhesionDebut'];
|
||||
_dateAdhesionFin = filters['dateAdhesionFin'];
|
||||
_dateNaissanceDebut = filters['dateNaissanceDebut'];
|
||||
_dateNaissanceFin = filters['dateNaissanceFin'];
|
||||
_ageMin = filters['ageMin'];
|
||||
_ageMax = filters['ageMax'];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nomController.dispose();
|
||||
_prenomController.dispose();
|
||||
_emailController.dispose();
|
||||
_telephoneController.dispose();
|
||||
_numeroMembreController.dispose();
|
||||
_professionController.dispose();
|
||||
_villeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// En-tête
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Contenu scrollable
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Informations personnelles
|
||||
_buildSection(
|
||||
'Informations personnelles',
|
||||
Icons.person,
|
||||
[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _nomController,
|
||||
label: 'Nom',
|
||||
prefixIcon: Icons.person_outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _prenomController,
|
||||
label: 'Prénom',
|
||||
prefixIcon: Icons.person_outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomTextField(
|
||||
controller: _numeroMembreController,
|
||||
label: 'Numéro de membre',
|
||||
prefixIcon: Icons.badge,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomTextField(
|
||||
controller: _professionController,
|
||||
label: 'Profession',
|
||||
prefixIcon: Icons.work,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Contact et localisation
|
||||
_buildSection(
|
||||
'Contact et localisation',
|
||||
Icons.contact_phone,
|
||||
[
|
||||
CustomTextField(
|
||||
controller: _emailController,
|
||||
label: 'Email',
|
||||
prefixIcon: Icons.email,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomTextField(
|
||||
controller: _telephoneController,
|
||||
label: 'Téléphone',
|
||||
prefixIcon: Icons.phone,
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomTextField(
|
||||
controller: _villeController,
|
||||
label: 'Ville',
|
||||
prefixIcon: Icons.location_city,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Statut et dates
|
||||
_buildSection(
|
||||
'Statut et dates',
|
||||
Icons.calendar_today,
|
||||
[
|
||||
_buildStatusFilter(),
|
||||
const SizedBox(height: 16),
|
||||
_buildDateRangeFilter(
|
||||
'Période d\'adhésion',
|
||||
_dateAdhesionDebut,
|
||||
_dateAdhesionFin,
|
||||
(debut, fin) {
|
||||
setState(() {
|
||||
_dateAdhesionDebut = debut;
|
||||
_dateAdhesionFin = fin;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildDateRangeFilter(
|
||||
'Période de naissance',
|
||||
_dateNaissanceDebut,
|
||||
_dateNaissanceFin,
|
||||
(debut, fin) {
|
||||
setState(() {
|
||||
_dateNaissanceDebut = debut;
|
||||
_dateNaissanceFin = fin;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildAgeRangeFilter(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Boutons d'action
|
||||
_buildActionButtons(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.search,
|
||||
color: AppTheme.primaryColor,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'Recherche avancée',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppTheme.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close),
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(String title, IconData icon, List<Widget> children) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: AppTheme.primaryColor,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppTheme.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...children,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusFilter() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Statut du membre',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppTheme.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: RadioListTile<bool?>(
|
||||
title: const Text('Tous', style: TextStyle(fontSize: 14)),
|
||||
value: null,
|
||||
groupValue: _actifFilter,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_actifFilter = value;
|
||||
});
|
||||
},
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RadioListTile<bool?>(
|
||||
title: const Text('Actifs', style: TextStyle(fontSize: 14)),
|
||||
value: true,
|
||||
groupValue: _actifFilter,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_actifFilter = value;
|
||||
});
|
||||
},
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RadioListTile<bool?>(
|
||||
title: const Text('Inactifs', style: TextStyle(fontSize: 14)),
|
||||
value: false,
|
||||
groupValue: _actifFilter,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_actifFilter = value;
|
||||
});
|
||||
},
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateRangeFilter(
|
||||
String title,
|
||||
DateTime? dateDebut,
|
||||
DateTime? dateFin,
|
||||
Function(DateTime?, DateTime?) onChanged,
|
||||
) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppTheme.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => _selectDate(context, dateDebut, (date) {
|
||||
onChanged(date, dateFin);
|
||||
}),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppTheme.borderColor),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
color: AppTheme.textSecondary,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
dateDebut != null
|
||||
? DateFormat('dd/MM/yyyy').format(dateDebut)
|
||||
: 'Date début',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: dateDebut != null
|
||||
? AppTheme.textPrimary
|
||||
: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => _selectDate(context, dateFin, (date) {
|
||||
onChanged(dateDebut, date);
|
||||
}),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppTheme.borderColor),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
color: AppTheme.textSecondary,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
dateFin != null
|
||||
? DateFormat('dd/MM/yyyy').format(dateFin)
|
||||
: 'Date fin',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: dateFin != null
|
||||
? AppTheme.textPrimary
|
||||
: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAgeRangeFilter() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Tranche d\'âge',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppTheme.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: _ageMin?.toString(),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Âge minimum',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
_ageMin = int.tryParse(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: _ageMax?.toString(),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Âge maximum',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
_ageMax = int.tryParse(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtons() {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _clearFilters,
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
side: BorderSide(color: AppTheme.borderColor),
|
||||
),
|
||||
child: const Text('Effacer'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: ElevatedButton(
|
||||
onPressed: _performSearch,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: const Text('Rechercher'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _selectDate(
|
||||
BuildContext context,
|
||||
DateTime? initialDate,
|
||||
Function(DateTime?) onDateSelected,
|
||||
) async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate ?? DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
);
|
||||
|
||||
if (date != null) {
|
||||
onDateSelected(date);
|
||||
}
|
||||
}
|
||||
|
||||
void _clearFilters() {
|
||||
setState(() {
|
||||
_nomController.clear();
|
||||
_prenomController.clear();
|
||||
_emailController.clear();
|
||||
_telephoneController.clear();
|
||||
_numeroMembreController.clear();
|
||||
_professionController.clear();
|
||||
_villeController.clear();
|
||||
_actifFilter = null;
|
||||
_dateAdhesionDebut = null;
|
||||
_dateAdhesionFin = null;
|
||||
_dateNaissanceDebut = null;
|
||||
_dateNaissanceFin = null;
|
||||
_ageMin = null;
|
||||
_ageMax = null;
|
||||
});
|
||||
}
|
||||
|
||||
void _performSearch() {
|
||||
final filters = <String, dynamic>{};
|
||||
|
||||
// Ajout des filtres texte
|
||||
if (_nomController.text.isNotEmpty) {
|
||||
filters['nom'] = _nomController.text;
|
||||
}
|
||||
if (_prenomController.text.isNotEmpty) {
|
||||
filters['prenom'] = _prenomController.text;
|
||||
}
|
||||
if (_emailController.text.isNotEmpty) {
|
||||
filters['email'] = _emailController.text;
|
||||
}
|
||||
if (_telephoneController.text.isNotEmpty) {
|
||||
filters['telephone'] = _telephoneController.text;
|
||||
}
|
||||
if (_numeroMembreController.text.isNotEmpty) {
|
||||
filters['numeroMembre'] = _numeroMembreController.text;
|
||||
}
|
||||
if (_professionController.text.isNotEmpty) {
|
||||
filters['profession'] = _professionController.text;
|
||||
}
|
||||
if (_villeController.text.isNotEmpty) {
|
||||
filters['ville'] = _villeController.text;
|
||||
}
|
||||
|
||||
// Ajout des filtres de statut
|
||||
if (_actifFilter != null) {
|
||||
filters['actif'] = _actifFilter;
|
||||
}
|
||||
|
||||
// Ajout des filtres de date
|
||||
if (_dateAdhesionDebut != null) {
|
||||
filters['dateAdhesionDebut'] = _dateAdhesionDebut;
|
||||
}
|
||||
if (_dateAdhesionFin != null) {
|
||||
filters['dateAdhesionFin'] = _dateAdhesionFin;
|
||||
}
|
||||
if (_dateNaissanceDebut != null) {
|
||||
filters['dateNaissanceDebut'] = _dateNaissanceDebut;
|
||||
}
|
||||
if (_dateNaissanceFin != null) {
|
||||
filters['dateNaissanceFin'] = _dateNaissanceFin;
|
||||
}
|
||||
|
||||
// Ajout des filtres d'âge
|
||||
if (_ageMin != null) {
|
||||
filters['ageMin'] = _ageMin;
|
||||
}
|
||||
if (_ageMax != null) {
|
||||
filters['ageMax'] = _ageMax;
|
||||
}
|
||||
|
||||
widget.onSearch(filters);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user