Restructuration majeure du projet: migration de flutt vers app, ajout de l'API et mise à jour du site web

This commit is contained in:
d6soft
2025-05-16 09:19:03 +02:00
parent b5aafc424b
commit 5c2620de30
391 changed files with 19780 additions and 7233 deletions

View File

@@ -0,0 +1,268 @@
import 'package:flutter/material.dart';
import 'package:geosector_app/app.dart';
import 'package:geosector_app/core/data/models/user_model.dart';
import 'package:geosector_app/presentation/widgets/user_form.dart';
class ProfileDialog extends StatefulWidget {
final UserModel user;
const ProfileDialog({
Key? key,
required this.user,
}) : super(key: key);
/// Méthode statique pour afficher la boîte de dialogue
static Future<bool?> show(BuildContext context, UserModel user) {
return showDialog<bool>(
context: context,
builder: (context) => ProfileDialog(user: user),
);
}
@override
State<ProfileDialog> createState() => _ProfileDialogState();
}
class _ProfileDialogState extends State<ProfileDialog> {
final _formKey = GlobalKey<FormState>();
late UserModel _user;
bool _isLoading = false;
@override
void initState() {
super.initState();
_user = widget.user;
}
// Fonction pour capitaliser la première lettre de chaque mot
String _capitalizeFirstLetter(String text) {
if (text.isEmpty) return text;
return text.split(' ').map((word) {
if (word.isEmpty) return word;
return word[0].toUpperCase() + word.substring(1).toLowerCase();
}).join(' ');
}
// Fonction pour mettre en majuscule
String _toUpperCase(String text) {
return text.toUpperCase();
}
// Fonction pour valider et soumettre le formulaire
Future<void> _saveProfile(UserModel updatedUser) async {
// Validation supplémentaire
if (!_validateUser(updatedUser)) {
return;
}
setState(() {
_isLoading = true;
});
try {
// Formatage des données
final formattedUser = updatedUser.copyWith(
name: _toUpperCase(updatedUser.name ?? ''),
firstName: _capitalizeFirstLetter(updatedUser.firstName ?? ''),
);
// Sauvegarde de l'utilisateur
await userRepository.saveUser(formattedUser);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Profil mis à jour avec succès'),
backgroundColor: Colors.green,
),
);
Navigator.of(context).pop(true); // Fermer la modale avec succès
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de la mise à jour du profil: $e'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
// Validation supplémentaire
bool _validateUser(UserModel user) {
// Vérifier que l'email est valide
if (user.email.isEmpty ||
!user.email.contains('@') ||
!user.email.contains('.')) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Veuillez entrer une adresse email valide'),
backgroundColor: Colors.red,
),
);
return false;
}
// Vérifier que le nom ou le sectName est renseigné
if ((user.name == null || user.name!.isEmpty) &&
(user.sectName == null || user.sectName!.isEmpty)) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Le nom ou le nom du secteur doit être renseigné'),
backgroundColor: Colors.red,
),
);
return false;
}
// Vérifier que le téléphone fixe est valide s'il est renseigné
if (user.phone != null &&
user.phone!.isNotEmpty &&
user.phone!.length != 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('Le numéro de téléphone fixe doit contenir 10 chiffres'),
backgroundColor: Colors.red,
),
);
return false;
}
// Vérifier que le téléphone mobile est valide s'il est renseigné
if (user.mobile != null &&
user.mobile!.isNotEmpty &&
user.mobile!.length != 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('Le numéro de téléphone mobile doit contenir 10 chiffres'),
backgroundColor: Colors.red,
),
);
return false;
}
return true;
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 0,
backgroundColor: Colors.transparent,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
constraints: const BoxConstraints(
maxWidth: 600,
maxHeight: 700,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Titre
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Mon compte',
style: theme.textTheme.headlineSmall?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
tooltip: 'Fermer',
),
],
),
const Divider(),
const SizedBox(height: 10),
// Formulaire
Expanded(
child: SingleChildScrollView(
child: UserForm(
user: _user,
onSubmit: _saveProfile,
),
),
),
// Boutons
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed:
_isLoading ? null : () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
),
child: const Text('Fermer'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: _isLoading
? null
: () {
// Appeler directement la méthode onSubmit du UserForm
// qui va déclencher la validation et la soumission
_saveProfile(_user);
},
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
foregroundColor: theme.colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
),
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Text('Enregistrer'),
),
],
),
],
),
),
);
}
}