Files
unionflow-server-impl-quarkus/unionflow-mobile-apps/lib/features/members/presentation/pages/membre_edit_page.dart
2025-09-17 17:54:06 +00:00

1130 lines
34 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/models/membre_model.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../shared/widgets/custom_text_field.dart';
import '../../../../shared/widgets/buttons/buttons.dart';
import '../../../../core/auth/services/permission_service.dart';
import '../../../../shared/widgets/permission_widget.dart';
import '../bloc/membres_bloc.dart';
import '../bloc/membres_event.dart';
import '../bloc/membres_state.dart';
/// Page de modification d'un membre existant
class MembreEditPage extends StatefulWidget {
const MembreEditPage({
super.key,
required this.membre,
});
final MembreModel membre;
@override
State<MembreEditPage> createState() => _MembreEditPageState();
}
class _MembreEditPageState extends State<MembreEditPage>
with SingleTickerProviderStateMixin, PermissionMixin {
late MembresBloc _membresBloc;
late TabController _tabController;
final _formKey = GlobalKey<FormState>();
// Controllers pour les champs du formulaire
final _nomController = TextEditingController();
final _prenomController = TextEditingController();
final _emailController = TextEditingController();
final _telephoneController = TextEditingController();
final _adresseController = TextEditingController();
final _villeController = TextEditingController();
final _codePostalController = TextEditingController();
final _paysController = TextEditingController();
final _professionController = TextEditingController();
final _numeroMembreController = TextEditingController();
// Variables d'état
DateTime? _dateNaissance;
DateTime _dateAdhesion = DateTime.now();
bool _actif = true;
bool _isLoading = false;
int _currentStep = 0;
bool _hasChanges = false;
@override
void initState() {
super.initState();
// Vérification des permissions d'accès
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!permissionService.canEditMembers) {
showPermissionError(context, 'Vous n\'avez pas les permissions pour modifier les membres');
Navigator.of(context).pop();
return;
}
});
_membresBloc = getIt<MembresBloc>();
_tabController = TabController(length: 3, vsync: this);
// Pré-remplir les champs avec les données existantes
_populateFields();
// Écouter les changements pour détecter les modifications
_setupChangeListeners();
}
@override
void dispose() {
_tabController.dispose();
_nomController.dispose();
_prenomController.dispose();
_emailController.dispose();
_telephoneController.dispose();
_adresseController.dispose();
_villeController.dispose();
_codePostalController.dispose();
_paysController.dispose();
_professionController.dispose();
_numeroMembreController.dispose();
super.dispose();
}
void _populateFields() {
_numeroMembreController.text = widget.membre.numeroMembre;
_nomController.text = widget.membre.nom;
_prenomController.text = widget.membre.prenom;
_emailController.text = widget.membre.email;
_telephoneController.text = widget.membre.telephone;
_adresseController.text = widget.membre.adresse ?? '';
_villeController.text = widget.membre.ville ?? '';
_codePostalController.text = widget.membre.codePostal ?? '';
_paysController.text = widget.membre.pays ?? 'Côte d\'Ivoire';
_professionController.text = widget.membre.profession ?? '';
_dateNaissance = widget.membre.dateNaissance;
_dateAdhesion = widget.membre.dateAdhesion;
_actif = widget.membre.actif;
}
void _setupChangeListeners() {
final controllers = [
_nomController, _prenomController, _emailController, _telephoneController,
_adresseController, _villeController, _codePostalController,
_paysController, _professionController
];
for (final controller in controllers) {
controller.addListener(_onFieldChanged);
}
}
void _onFieldChanged() {
if (!_hasChanges) {
setState(() {
_hasChanges = true;
});
}
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _membresBloc,
child: PopScope(
canPop: !_hasChanges,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
final shouldPop = await _onWillPop();
if (shouldPop && context.mounted) {
Navigator.of(context).pop();
}
},
child: Scaffold(
backgroundColor: AppTheme.backgroundLight,
appBar: _buildAppBar(),
body: BlocConsumer<MembresBloc, MembresState>(
listener: (context, state) {
if (state is MembreUpdated) {
setState(() {
_isLoading = false;
_hasChanges = false;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Membre modifié avec succès !'),
backgroundColor: AppTheme.successColor,
),
);
Navigator.of(context).pop(true); // Retourner true pour indiquer le succès
} else if (state is MembresError) {
setState(() {
_isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: AppTheme.errorColor,
),
);
}
},
builder: (context, state) {
return Column(
children: [
_buildProgressIndicator(),
Expanded(
child: _buildFormContent(),
),
_buildBottomActions(),
],
);
},
),
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: AppTheme.primaryColor,
foregroundColor: Colors.white,
title: Text(
'Modifier ${widget.membre.prenom} ${widget.membre.nom}',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
actions: [
if (_hasChanges)
PermissionIconButton(
permission: () => permissionService.canEditMembers,
icon: const Icon(Icons.save),
onPressed: _submitForm,
tooltip: 'Sauvegarder',
disabledMessage: 'Vous n\'avez pas les permissions pour modifier ce membre',
),
IconButton(
icon: const Icon(Icons.help_outline),
onPressed: _showHelp,
tooltip: 'Aide',
),
],
);
}
Widget _buildProgressIndicator() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: Column(
children: [
Row(
children: [
_buildStepIndicator(0, 'Informations\npersonnelles', Icons.person),
_buildStepConnector(0),
_buildStepIndicator(1, 'Contact &\nAdresse', Icons.contact_mail),
_buildStepConnector(1),
_buildStepIndicator(2, 'Finalisation', Icons.check_circle),
],
),
const SizedBox(height: 8),
LinearProgressIndicator(
value: (_currentStep + 1) / 3,
backgroundColor: AppTheme.backgroundLight,
valueColor: const AlwaysStoppedAnimation<Color>(AppTheme.primaryColor),
),
],
),
);
}
Widget _buildStepIndicator(int step, String label, IconData icon) {
final isActive = step == _currentStep;
final isCompleted = step < _currentStep;
Color color;
if (isCompleted) {
color = AppTheme.successColor;
} else if (isActive) {
color = AppTheme.primaryColor;
} else {
color = AppTheme.textHint;
}
return Expanded(
child: Column(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: isCompleted ? AppTheme.successColor :
isActive ? AppTheme.primaryColor : AppTheme.backgroundLight,
shape: BoxShape.circle,
border: Border.all(color: color, width: 2),
),
child: Icon(
isCompleted ? Icons.check : icon,
color: isCompleted || isActive ? Colors.white : color,
size: 20,
),
),
const SizedBox(height: 8),
Text(
label,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
color: color,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
),
),
],
),
);
}
Widget _buildStepConnector(int step) {
final isCompleted = step < _currentStep;
return Expanded(
child: Container(
height: 2,
margin: const EdgeInsets.only(bottom: 32),
color: isCompleted ? AppTheme.successColor : AppTheme.backgroundLight,
),
);
}
Widget _buildFormContent() {
return Form(
key: _formKey,
child: PageView(
controller: PageController(initialPage: _currentStep),
onPageChanged: (index) {
setState(() {
_currentStep = index;
});
},
children: [
_buildPersonalInfoStep(),
_buildContactStep(),
_buildFinalizationStep(),
],
),
);
}
Widget _buildContactStep() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionHeader(
'Contact & Adresse',
'Modifiez les informations de contact et adresse',
),
const SizedBox(height: 24),
// Email
CustomTextField(
controller: _emailController,
label: 'Email *',
hintText: 'exemple@email.com',
prefixIcon: Icons.email_outlined,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'L\'email est requis';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Format d\'email invalide';
}
return null;
},
),
const SizedBox(height: 16),
// Téléphone
CustomTextField(
controller: _telephoneController,
label: 'Téléphone *',
hintText: '+225 XX XX XX XX XX',
prefixIcon: Icons.phone_outlined,
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9+\-\s\(\)]')),
],
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Le téléphone est requis';
}
if (value.trim().length < 8) {
return 'Numéro de téléphone invalide';
}
return null;
},
),
const SizedBox(height: 24),
// Section Adresse
const Text(
'Adresse',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
// Adresse
CustomTextField(
controller: _adresseController,
label: 'Adresse',
hintText: 'Rue, quartier, etc.',
prefixIcon: Icons.location_on_outlined,
textInputAction: TextInputAction.next,
maxLines: 2,
),
const SizedBox(height: 16),
// Ville et Code postal
Row(
children: [
Expanded(
flex: 2,
child: CustomTextField(
controller: _villeController,
label: 'Ville',
hintText: 'Abidjan',
prefixIcon: Icons.location_city_outlined,
textInputAction: TextInputAction.next,
),
),
const SizedBox(width: 16),
Expanded(
child: CustomTextField(
controller: _codePostalController,
label: 'Code postal',
hintText: '00225',
prefixIcon: Icons.markunread_mailbox_outlined,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
),
),
],
),
const SizedBox(height: 16),
// Pays
CustomTextField(
controller: _paysController,
label: 'Pays',
prefixIcon: Icons.flag_outlined,
textInputAction: TextInputAction.done,
),
],
),
);
}
Widget _buildFinalizationStep() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionHeader(
'Finalisation',
'Vérifiez les modifications et finalisez',
),
const SizedBox(height: 24),
// Résumé des modifications
_buildChangesCard(),
const SizedBox(height: 24),
// Date d'adhésion
_buildDateField(
label: 'Date d\'adhésion',
value: _dateAdhesion,
onTap: _selectDateAdhesion,
icon: Icons.calendar_today_outlined,
),
const SizedBox(height: 16),
// Statut actif
Card(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: SwitchListTile(
title: const Text('Membre actif'),
subtitle: Text(
_actif
? 'Le membre peut accéder aux services'
: 'Le membre est désactivé',
),
value: _actif,
onChanged: (value) {
setState(() {
_actif = value;
_hasChanges = true;
});
},
activeColor: AppTheme.primaryColor,
),
),
const SizedBox(height: 16),
// Informations de version
_buildVersionInfo(),
],
),
);
}
Widget _buildSectionHeader(String title, String subtitle) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 8),
Text(
subtitle,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textSecondary,
),
),
],
);
}
Widget _buildDateField({
required String label,
required DateTime? value,
required VoidCallback onTap,
required IconData icon,
}) {
return InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: BoxDecoration(
border: Border.all(color: AppTheme.borderColor),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(icon, color: AppTheme.textSecondary),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
Text(
value != null
? DateFormat('dd/MM/yyyy').format(value)
: 'Sélectionner une date',
style: TextStyle(
fontSize: 16,
color: value != null
? AppTheme.textPrimary
: AppTheme.textHint,
),
),
],
),
),
const Icon(Icons.edit, color: AppTheme.textSecondary),
],
),
),
);
}
Widget _buildPersonalInfoStep() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionHeader(
'Informations personnelles',
'Modifiez les informations de base du membre',
),
const SizedBox(height: 24),
// Numéro de membre (non modifiable)
CustomTextField(
controller: _numeroMembreController,
label: 'Numéro de membre',
prefixIcon: Icons.badge,
enabled: false,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Le numéro de membre est requis';
}
return null;
},
),
const SizedBox(height: 16),
// Nom et Prénom
Row(
children: [
Expanded(
child: CustomTextField(
controller: _prenomController,
label: 'Prénom *',
hintText: 'Jean',
prefixIcon: Icons.person_outline,
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Le prénom est requis';
}
if (value.trim().length < 2) {
return 'Le prénom doit contenir au moins 2 caractères';
}
return null;
},
),
),
const SizedBox(width: 16),
Expanded(
child: CustomTextField(
controller: _nomController,
label: 'Nom *',
hintText: 'Dupont',
prefixIcon: Icons.person_outline,
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Le nom est requis';
}
if (value.trim().length < 2) {
return 'Le nom doit contenir au moins 2 caractères';
}
return null;
},
),
),
],
),
const SizedBox(height: 16),
// Date de naissance
_buildDateField(
label: 'Date de naissance',
value: _dateNaissance,
onTap: _selectDateNaissance,
icon: Icons.cake_outlined,
),
const SizedBox(height: 16),
// Profession
CustomTextField(
controller: _professionController,
label: 'Profession',
hintText: 'Enseignant, Commerçant, etc.',
prefixIcon: Icons.work_outline,
textInputAction: TextInputAction.next,
),
],
),
);
}
Widget _buildChangesCard() {
if (!_hasChanges) {
return Card(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: const Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Icon(Icons.info_outline, color: AppTheme.textSecondary),
SizedBox(width: 12),
Expanded(
child: Text(
'Aucune modification détectée',
style: TextStyle(
fontSize: 14,
color: AppTheme.textSecondary,
),
),
),
],
),
),
);
}
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.edit, color: AppTheme.warningColor),
SizedBox(width: 8),
Text(
'Modifications détectées',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
],
),
const SizedBox(height: 16),
_buildSummaryRow('Nom complet', '${_prenomController.text} ${_nomController.text}'),
_buildSummaryRow('Email', _emailController.text),
_buildSummaryRow('Téléphone', _telephoneController.text),
if (_dateNaissance != null)
_buildSummaryRow('Date de naissance', DateFormat('dd/MM/yyyy').format(_dateNaissance!)),
if (_professionController.text.isNotEmpty)
_buildSummaryRow('Profession', _professionController.text),
if (_adresseController.text.isNotEmpty)
_buildSummaryRow('Adresse', _adresseController.text),
_buildSummaryRow('Statut', _actif ? 'Actif' : 'Inactif'),
],
),
),
);
}
Widget _buildSummaryRow(String label, String value) {
if (value.trim().isEmpty) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
label,
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
fontWeight: FontWeight.w500,
),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textPrimary,
),
),
),
],
),
);
}
Widget _buildVersionInfo() {
return Card(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.info_outline, color: AppTheme.textSecondary),
SizedBox(width: 8),
Text(
'Informations de version',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
],
),
const SizedBox(height: 12),
Text(
'Version actuelle : ${widget.membre.version}',
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
const SizedBox(height: 4),
Text(
'Créé le : ${DateFormat('dd/MM/yyyy à HH:mm').format(widget.membre.dateCreation)}',
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
if (widget.membre.dateModification != null) ...[
const SizedBox(height: 4),
Text(
'Modifié le : ${DateFormat('dd/MM/yyyy à HH:mm').format(widget.membre.dateModification!)}',
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
],
],
),
),
);
}
Widget _buildBottomActions() {
return Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, -2),
),
],
),
child: Row(
children: [
if (_currentStep > 0)
Expanded(
child: OutlinedButton(
onPressed: _previousStep,
style: OutlinedButton.styleFrom(
foregroundColor: AppTheme.primaryColor,
side: const BorderSide(color: AppTheme.primaryColor),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('Précédent'),
),
),
if (_currentStep > 0) const SizedBox(width: 16),
Expanded(
flex: _currentStep == 0 ? 1 : 1,
child: ElevatedButton(
onPressed: _isLoading ? null : _handleNextOrSubmit,
style: ElevatedButton.styleFrom(
backgroundColor: _hasChanges ? AppTheme.primaryColor : AppTheme.textHint,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(_currentStep == 2 ? 'Sauvegarder' : 'Suivant'),
),
),
],
),
);
}
void _previousStep() {
if (_currentStep > 0) {
setState(() {
_currentStep--;
});
}
}
void _handleNextOrSubmit() {
if (_currentStep < 2) {
if (_validateCurrentStep()) {
setState(() {
_currentStep++;
});
}
} else {
_submitForm();
}
}
bool _validateCurrentStep() {
switch (_currentStep) {
case 0:
return _validatePersonalInfo();
case 1:
return _validateContactInfo();
case 2:
return true; // Pas de validation spécifique pour la finalisation
default:
return false;
}
}
bool _validatePersonalInfo() {
bool isValid = true;
if (_prenomController.text.trim().isEmpty) {
_showFieldError('Le prénom est requis');
isValid = false;
}
if (_nomController.text.trim().isEmpty) {
_showFieldError('Le nom est requis');
isValid = false;
}
return isValid;
}
bool _validateContactInfo() {
bool isValid = true;
if (_emailController.text.trim().isEmpty) {
_showFieldError('L\'email est requis');
isValid = false;
} else if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(_emailController.text)) {
_showFieldError('Format d\'email invalide');
isValid = false;
}
if (_telephoneController.text.trim().isEmpty) {
_showFieldError('Le téléphone est requis');
isValid = false;
}
return isValid;
}
void _showFieldError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: AppTheme.errorColor,
duration: const Duration(seconds: 2),
),
);
}
void _submitForm() {
// Vérification des permissions
if (!permissionService.canEditMembers) {
showPermissionError(context, 'Vous n\'avez pas les permissions pour modifier ce membre');
return;
}
if (!_formKey.currentState!.validate()) {
return;
}
if (!_hasChanges) {
_showFieldError('Aucune modification à sauvegarder');
return;
}
// Log de l'action pour audit
permissionService.logAction('Modification membre', details: {
'membreId': widget.membre.id,
'nom': '${widget.membre.prenom} ${widget.membre.nom}',
});
setState(() {
_isLoading = true;
});
// Créer le modèle membre modifié
final membreModifie = widget.membre.copyWith(
nom: _nomController.text.trim(),
prenom: _prenomController.text.trim(),
email: _emailController.text.trim(),
telephone: _telephoneController.text.trim(),
dateNaissance: _dateNaissance,
adresse: _adresseController.text.trim().isNotEmpty ? _adresseController.text.trim() : null,
ville: _villeController.text.trim().isNotEmpty ? _villeController.text.trim() : null,
codePostal: _codePostalController.text.trim().isNotEmpty ? _codePostalController.text.trim() : null,
pays: _paysController.text.trim().isNotEmpty ? _paysController.text.trim() : null,
profession: _professionController.text.trim().isNotEmpty ? _professionController.text.trim() : null,
dateAdhesion: _dateAdhesion,
actif: _actif,
version: widget.membre.version + 1,
dateModification: DateTime.now(),
);
// Envoyer l'événement de modification
final memberId = widget.membre.id;
if (memberId != null && memberId.isNotEmpty) {
_membresBloc.add(UpdateMembre(memberId, membreModifie));
} else {
_showFieldError('Erreur : ID du membre manquant');
setState(() {
_isLoading = false;
});
}
}
Future<void> _selectDateNaissance() async {
final date = await showDatePicker(
context: context,
initialDate: _dateNaissance ?? DateTime.now().subtract(const Duration(days: 365 * 25)),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
locale: const Locale('fr', 'FR'),
);
if (date != null && date != _dateNaissance) {
setState(() {
_dateNaissance = date;
_hasChanges = true;
});
}
}
Future<void> _selectDateAdhesion() async {
final date = await showDatePicker(
context: context,
initialDate: _dateAdhesion,
firstDate: DateTime(2000),
lastDate: DateTime.now().add(const Duration(days: 365)),
locale: const Locale('fr', 'FR'),
);
if (date != null && date != _dateAdhesion) {
setState(() {
_dateAdhesion = date;
_hasChanges = true;
});
}
}
Future<bool> _onWillPop() async {
if (!_hasChanges) {
return true;
}
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Modifications non sauvegardées'),
content: const Text(
'Vous avez des modifications non sauvegardées. '
'Voulez-vous vraiment quitter sans sauvegarder ?',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Annuler'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
style: TextButton.styleFrom(
foregroundColor: AppTheme.errorColor,
),
child: const Text('Quitter sans sauvegarder'),
),
],
),
);
return result ?? false;
}
void _showHelp() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Aide - Modification de membre'),
content: const SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Modification en 3 étapes :',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text('1. Informations personnelles : Nom, prénom, date de naissance'),
Text('2. Contact & Adresse : Email, téléphone, adresse'),
Text('3. Finalisation : Vérification et sauvegarde'),
SizedBox(height: 16),
Text(
'Fonctionnalités :',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text('• Détection automatique des modifications'),
Text('• Validation en temps réel'),
Text('• Confirmation avant sortie si modifications non sauvées'),
Text('• Gestion de version automatique'),
SizedBox(height: 16),
Text(
'Le numéro de membre ne peut pas être modifié pour des raisons de traçabilité.',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Fermer'),
),
],
),
);
}
}