Files
unionflow-server-impl-quarkus/unionflow-mobile-apps/lib/features/members/presentation/widgets/membres_advanced_search.dart
2025-09-13 19:05:06 +00:00

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();
}
}