627 lines
20 KiB
Dart
627 lines
20 KiB
Dart
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();
|
|
}
|
|
}
|