feat: création branche singletons - début refactorisation
- Sauvegarde des fichiers critiques - Préparation transformation ApiService en singleton - Préparation création CurrentUserService et CurrentAmicaleService - Objectif: renommer Box users -> user
This commit is contained in:
@@ -6,20 +6,23 @@ import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/presentation/widgets/mapbox_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'custom_text_field.dart';
|
||||
|
||||
class AmicaleForm extends StatefulWidget {
|
||||
final AmicaleModel? amicale;
|
||||
final Function(AmicaleModel)? onSubmit;
|
||||
final bool readOnly;
|
||||
final UserRepository userRepository; // Nouveau paramètre
|
||||
final ApiService? apiService; // Nouveau paramètre optionnel
|
||||
|
||||
const AmicaleForm({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.amicale,
|
||||
this.onSubmit,
|
||||
this.readOnly = false,
|
||||
}) : super(key: key);
|
||||
required this.userRepository, // Requis
|
||||
this.apiService, // Optionnel
|
||||
});
|
||||
|
||||
@override
|
||||
State<AmicaleForm> createState() => _AmicaleFormState();
|
||||
@@ -59,8 +62,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
_nameController = TextEditingController(text: amicale?.name ?? '');
|
||||
_adresse1Controller = TextEditingController(text: amicale?.adresse1 ?? '');
|
||||
_adresse2Controller = TextEditingController(text: amicale?.adresse2 ?? '');
|
||||
_codePostalController =
|
||||
TextEditingController(text: amicale?.codePostal ?? '');
|
||||
_codePostalController = TextEditingController(text: amicale?.codePostal ?? '');
|
||||
_villeController = TextEditingController(text: amicale?.ville ?? '');
|
||||
_phoneController = TextEditingController(text: amicale?.phone ?? '');
|
||||
_mobileController = TextEditingController(text: amicale?.mobile ?? '');
|
||||
@@ -125,9 +127,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
};
|
||||
|
||||
// Ajouter les champs réservés aux administrateurs si l'utilisateur est admin
|
||||
final userRepository =
|
||||
Provider.of<UserRepository>(context, listen: false);
|
||||
final userRole = userRepository.getUserRole();
|
||||
final userRole = widget.userRepository.getUserRole();
|
||||
if (userRole > 2) {
|
||||
data['gps_lat'] = amicale.gpsLat;
|
||||
data['gps_lng'] = amicale.gpsLng;
|
||||
@@ -136,56 +136,71 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
data['chk_active'] = amicale.chkActive;
|
||||
}
|
||||
|
||||
// Appeler l'API
|
||||
try {
|
||||
// Obtenir l'instance du service API
|
||||
final apiService = Provider.of<ApiService>(context, listen: false);
|
||||
// Fermer l'indicateur de chargement
|
||||
Navigator.of(context).pop();
|
||||
|
||||
// Appeler la méthode post du service API
|
||||
await apiService.post('/entite/update', data: data);
|
||||
// Appeler l'API si le service est disponible
|
||||
if (widget.apiService != null) {
|
||||
try {
|
||||
await widget.apiService!.post('/entite/update', data: data);
|
||||
|
||||
// Fermer l'indicateur de chargement
|
||||
Navigator.of(context).pop();
|
||||
|
||||
// Afficher un message de succès
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Amicale mise à jour avec succès'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
|
||||
// Appeler la fonction onSubmit si elle existe
|
||||
if (widget.onSubmit != null) {
|
||||
widget.onSubmit!(amicale);
|
||||
// Afficher un message de succès
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Amicale mise à jour avec succès'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// Afficher un message d'erreur
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur lors de la mise à jour de l\'amicale: $error'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
return; // Sortir de la fonction en cas d'erreur
|
||||
}
|
||||
} else {
|
||||
// Pas d'API service, afficher un message d'information
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Modifications enregistrées localement'),
|
||||
backgroundColor: Colors.blue,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fermer le formulaire
|
||||
Navigator.of(context).pop();
|
||||
} catch (error) {
|
||||
// Fermer l'indicateur de chargement
|
||||
Navigator.of(context).pop();
|
||||
// Appeler la fonction onSubmit si elle existe
|
||||
if (widget.onSubmit != null) {
|
||||
widget.onSubmit!(amicale);
|
||||
}
|
||||
|
||||
// Afficher un message d'erreur
|
||||
// Fermer le formulaire
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} catch (e) {
|
||||
// Fermer l'indicateur de chargement si encore ouvert
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
// Afficher un message d'erreur
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content:
|
||||
Text('Erreur lors de la mise à jour de l\'amicale: $error'),
|
||||
content: Text('Erreur: ${e.toString()}'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Fermer l'indicateur de chargement
|
||||
Navigator.of(context).pop();
|
||||
|
||||
// Afficher un message d'erreur
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur: ${e.toString()}'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,13 +210,13 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
if (_phoneController.text.isEmpty && _mobileController.text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content:
|
||||
Text('Veuillez renseigner au moins un numéro de téléphone'),
|
||||
content: Text('Veuillez renseigner au moins un numéro de téléphone'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final amicale = widget.amicale?.copyWith(
|
||||
name: _nameController.text,
|
||||
adresse1: _adresse1Controller.text,
|
||||
@@ -246,10 +261,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
// Appeler l'API pour mettre à jour l'amicale
|
||||
_updateAmicale(amicale);
|
||||
|
||||
// Appeler la fonction onSubmit si elle existe (pour la compatibilité avec le code existant)
|
||||
if (widget.onSubmit != null) {
|
||||
widget.onSubmit!(amicale);
|
||||
}
|
||||
// Ne pas appeler widget.onSubmit ici car c'est fait dans _updateAmicale
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,8 +305,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
// TODO: Implémenter la sélection d'image
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Fonctionnalité de modification du logo à venir'),
|
||||
content: Text('Fonctionnalité de modification du logo à venir'),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -447,7 +458,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
@@ -481,7 +492,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
"Adresse",
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -566,7 +577,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
"Région",
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -580,7 +591,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
"Contact",
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -657,7 +668,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
"Informations avancées",
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -671,8 +682,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
child: CustomTextField(
|
||||
controller: _gpsLatController,
|
||||
label: "GPS Latitude",
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
readOnly: restrictedFieldsReadOnly,
|
||||
),
|
||||
),
|
||||
@@ -682,8 +692,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
child: CustomTextField(
|
||||
controller: _gpsLngController,
|
||||
label: "GPS Longitude",
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
readOnly: restrictedFieldsReadOnly,
|
||||
),
|
||||
),
|
||||
@@ -749,7 +758,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
Text(
|
||||
"Accepte les règlements en CB",
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
@@ -760,8 +769,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
controller: _stripeIdController,
|
||||
label: "ID Stripe Paiements CB",
|
||||
readOnly: restrictedFieldsReadOnly,
|
||||
helperText:
|
||||
"Les règlements par CB sont taxés d'une commission de 1.4%",
|
||||
helperText: "Les règlements par CB sont taxés d'une commission de 1.4%",
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -774,7 +782,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
"Options",
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -849,8 +857,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: const Color(0xFF20335E),
|
||||
side: const BorderSide(color: Color(0xFF20335E)),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24, vertical: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
@@ -871,8 +878,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF20335E),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24, vertical: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
@@ -895,70 +901,73 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
|
||||
// Vérifier si les informations avancées doivent être affichées
|
||||
bool _shouldShowAdvancedInfo() {
|
||||
final userRepository = Provider.of<UserRepository>(context, listen: false);
|
||||
final userRole = userRepository.getUserRole();
|
||||
final userRole = widget.userRepository.getUserRole();
|
||||
final bool canEditRestrictedFields = userRole > 2;
|
||||
|
||||
return canEditRestrictedFields ||
|
||||
_gpsLatController.text.isNotEmpty ||
|
||||
_gpsLngController.text.isNotEmpty ||
|
||||
_stripeIdController.text.isNotEmpty;
|
||||
return canEditRestrictedFields || _gpsLatController.text.isNotEmpty || _gpsLngController.text.isNotEmpty || _stripeIdController.text.isNotEmpty;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final userRepository = Provider.of<UserRepository>(context, listen: false);
|
||||
final userRole = userRepository.getUserRole();
|
||||
final userRole = widget.userRepository.getUserRole();
|
||||
|
||||
// Déterminer si l'utilisateur peut modifier les champs restreints
|
||||
final bool canEditRestrictedFields = userRole > 2;
|
||||
|
||||
// Lecture seule pour les champs restreints si l'utilisateur n'a pas les droits
|
||||
final bool restrictedFieldsReadOnly =
|
||||
widget.readOnly || !canEditRestrictedFields;
|
||||
final bool restrictedFieldsReadOnly = widget.readOnly || !canEditRestrictedFields;
|
||||
|
||||
// Calculer la largeur maximale du formulaire pour les écrans larges
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final maxFormWidth = screenWidth > 800 ? 800.0 : screenWidth;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
widget.readOnly ? 'Détails de l\'amicale' : 'Modifier l\'amicale'),
|
||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
||||
foregroundColor: theme.appBarTheme.foregroundColor,
|
||||
),
|
||||
body: Center(
|
||||
child: Container(
|
||||
width: maxFormWidth,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
final formContent = Container(
|
||||
width: maxFormWidth,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header avec logo et minimap
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// Header avec logo et minimap
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// Section Logo
|
||||
_buildLogoSection(),
|
||||
// Section MiniMap
|
||||
_buildMiniMap(),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Formulaire principal
|
||||
_buildMainForm(theme, restrictedFieldsReadOnly),
|
||||
// Section Logo
|
||||
_buildLogoSection(),
|
||||
// Section MiniMap
|
||||
_buildMiniMap(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Formulaire principal
|
||||
_buildMainForm(theme, restrictedFieldsReadOnly),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Vérifier si on est dans une Dialog en regardant le type du widget parent
|
||||
final route = ModalRoute.of(context);
|
||||
final isInDialog = route?.settings.name == null;
|
||||
|
||||
// Si on est dans une Dialog, ne pas utiliser Scaffold
|
||||
if (isInDialog) {
|
||||
return Center(child: formContent);
|
||||
}
|
||||
|
||||
// Sinon, utiliser Scaffold pour les pages complètes
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.readOnly ? 'Détails de l\'amicale' : 'Modifier l\'amicale'),
|
||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
||||
foregroundColor: theme.appBarTheme.foregroundColor,
|
||||
),
|
||||
body: Center(child: formContent),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/app.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
|
||||
/// Widget pour afficher une ligne du tableau d'amicales
|
||||
/// Affiche les colonnes id, name, codePostal, libRegion et une colonne Actions
|
||||
/// La colonne Actions contient un bouton Delete pour les utilisateurs avec rôle > 2
|
||||
/// La ligne entière est cliquable pour afficher les détails de l'amicale
|
||||
/// Affiche les colonnes id, name, codePostal, libRegion et une colonne Actions (conditionnelle)
|
||||
/// La colonne Actions contient des boutons Edit et Delete selon les permissions
|
||||
/// Pour un admin d'amicale (rôle 2), seule la ligne est cliquable sans colonne Actions
|
||||
class AmicaleRowWidget extends StatelessWidget {
|
||||
final AmicaleModel amicale;
|
||||
final Function(AmicaleModel)? onTap;
|
||||
final Function(AmicaleModel)? onEdit;
|
||||
final Function(AmicaleModel)? onDelete;
|
||||
final bool isHeader;
|
||||
final bool isAlternate;
|
||||
final bool showActionsColumn;
|
||||
|
||||
const AmicaleRowWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.amicale,
|
||||
this.onTap,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
this.isHeader = false,
|
||||
this.isAlternate = false,
|
||||
}) : super(key: key);
|
||||
this.showActionsColumn = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final userRole = userRepository.getUserRole();
|
||||
|
||||
// Définir les styles en fonction du type de ligne (en-tête ou données)
|
||||
final textStyle = isHeader
|
||||
@@ -36,11 +38,7 @@ class AmicaleRowWidget extends StatelessWidget {
|
||||
: theme.textTheme.bodyMedium;
|
||||
|
||||
// Couleur de fond en fonction du type de ligne
|
||||
final backgroundColor = isHeader
|
||||
? theme.colorScheme.primary.withOpacity(0.1)
|
||||
: (isAlternate
|
||||
? theme.colorScheme.surface
|
||||
: theme.colorScheme.background);
|
||||
final backgroundColor = isHeader ? theme.colorScheme.primary.withOpacity(0.1) : (isAlternate ? theme.colorScheme.surface : theme.colorScheme.surface);
|
||||
|
||||
return InkWell(
|
||||
onTap: isHeader || onTap == null ? null : () => onTap!(amicale),
|
||||
@@ -55,7 +53,7 @@ class AmicaleRowWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: Row(
|
||||
children: [
|
||||
// Colonne ID
|
||||
@@ -103,7 +101,7 @@ class AmicaleRowWidget extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(
|
||||
isHeader ? 'Ville' : (amicale.ville ?? ''),
|
||||
isHeader ? 'Ville' : amicale.ville,
|
||||
style: textStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -123,8 +121,8 @@ class AmicaleRowWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
|
||||
// Colonne Actions - seulement si l'utilisateur a le rôle > 2 et onDelete n'est pas null
|
||||
if (isHeader || (userRole > 2 && onDelete != null))
|
||||
// Colonne Actions (conditionnelle)
|
||||
if (showActionsColumn && (isHeader || onEdit != null || onDelete != null))
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Padding(
|
||||
@@ -138,22 +136,40 @@ class AmicaleRowWidget extends StatelessWidget {
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
// Bouton Edit
|
||||
if (onEdit != null)
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.edit,
|
||||
color: theme.colorScheme.primary,
|
||||
size: 20,
|
||||
),
|
||||
tooltip: 'Modifier',
|
||||
onPressed: () => onEdit!(amicale),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 36,
|
||||
minHeight: 36,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
// Bouton Delete
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
color: theme.colorScheme.error,
|
||||
size: 20,
|
||||
if (onDelete != null)
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
color: theme.colorScheme.error,
|
||||
size: 20,
|
||||
),
|
||||
tooltip: 'Supprimer',
|
||||
onPressed: () => onDelete!(amicale),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 36,
|
||||
minHeight: 36,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
tooltip: 'Supprimer',
|
||||
onPressed: () => onDelete!(amicale),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 36,
|
||||
minHeight: 36,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/app.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/repositories/region_repository.dart';
|
||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
import 'package:geosector_app/core/repositories/amicale_repository.dart';
|
||||
import 'package:geosector_app/presentation/widgets/amicale_row_widget.dart';
|
||||
import 'package:geosector_app/presentation/widgets/amicale_form.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Widget de tableau pour afficher une liste d'amicales
|
||||
///
|
||||
@@ -19,19 +18,83 @@ import 'package:provider/provider.dart';
|
||||
/// Lorsqu'on clique sur une ligne, une modale s'affiche avec le formulaire EntiteForm
|
||||
class AmicaleTableWidget extends StatelessWidget {
|
||||
final List<AmicaleModel> amicales;
|
||||
final Function(AmicaleModel)? onEdit;
|
||||
final Function(AmicaleModel)? onDelete;
|
||||
final AmicaleRepository amicaleRepository;
|
||||
final UserRepository userRepository; // Nouveau paramètre
|
||||
final ApiService? apiService; // Nouveau paramètre optionnel
|
||||
final bool isLoading;
|
||||
final String? emptyMessage;
|
||||
final bool readOnly;
|
||||
final bool showActionsColumn;
|
||||
|
||||
const AmicaleTableWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.amicales,
|
||||
required this.amicaleRepository,
|
||||
required this.userRepository, // Requis
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
this.apiService, // Optionnel
|
||||
this.isLoading = false,
|
||||
this.emptyMessage,
|
||||
this.readOnly = false,
|
||||
}) : super(key: key);
|
||||
this.showActionsColumn = true,
|
||||
});
|
||||
|
||||
// Ajouter cette nouvelle méthode pour ouvrir directement le formulaire d'édition :
|
||||
void _showAmicaleEditForm(BuildContext context, AmicaleModel amicale) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (dialogContext) => Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Container(
|
||||
width: MediaQuery.of(dialogContext).size.width * 0.9,
|
||||
height: MediaQuery.of(dialogContext).size.height * 0.9,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
// Header de la dialog
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Modifier l\'amicale',
|
||||
style: Theme.of(dialogContext).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(dialogContext).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
|
||||
// Contenu du formulaire
|
||||
Expanded(
|
||||
child: AmicaleForm(
|
||||
amicale: amicale,
|
||||
readOnly: false,
|
||||
userRepository: userRepository,
|
||||
apiService: apiService,
|
||||
onSubmit: (updatedAmicale) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
// La mise à jour sera gérée par les ValueListenableBuilder
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -51,7 +114,9 @@ class AmicaleTableWidget extends StatelessWidget {
|
||||
),
|
||||
isHeader: true,
|
||||
onTap: null,
|
||||
onEdit: null,
|
||||
onDelete: null,
|
||||
showActionsColumn: showActionsColumn,
|
||||
),
|
||||
|
||||
// Corps du tableau
|
||||
@@ -90,8 +155,7 @@ class AmicaleTableWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
emptyMessage ?? 'Aucune amicale trouvée',
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -107,10 +171,18 @@ class AmicaleTableWidget extends StatelessWidget {
|
||||
final amicale = amicales[index];
|
||||
return AmicaleRowWidget(
|
||||
amicale: amicale,
|
||||
isAlternate: index % 2 == 1, // Alterner les couleurs
|
||||
onTap: (selectedAmicale) =>
|
||||
_showAmicaleDetails(context, selectedAmicale),
|
||||
isAlternate: index % 2 == 1,
|
||||
onTap: (selectedAmicale) {
|
||||
// Si pas de colonne Actions, ouvrir directement le formulaire d'édition
|
||||
if (!showActionsColumn) {
|
||||
_showAmicaleEditForm(context, selectedAmicale);
|
||||
} else {
|
||||
_showAmicaleDetails(context, selectedAmicale);
|
||||
}
|
||||
},
|
||||
onEdit: onEdit,
|
||||
onDelete: onDelete,
|
||||
showActionsColumn: showActionsColumn,
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -118,63 +190,48 @@ class AmicaleTableWidget extends StatelessWidget {
|
||||
|
||||
// Afficher une modale avec le formulaire EntiteForm
|
||||
void _showAmicaleDetails(BuildContext context, AmicaleModel amicale) {
|
||||
// Utiliser l'instance globale de userRepository définie dans app.dart
|
||||
final userRepo = userRepository;
|
||||
// Créer une instance de RegionRepository
|
||||
final regionRepo = RegionRepository();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => MultiProvider(
|
||||
providers: [
|
||||
// Fournir les repositories nécessaires au formulaire
|
||||
Provider<UserRepository>.value(value: userRepo),
|
||||
Provider<RegionRepository>.value(value: regionRepo),
|
||||
],
|
||||
child: Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Container(
|
||||
width: MediaQuery.of(dialogContext).size.width * 0.6,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Détails de l\'amicale',
|
||||
style: Theme.of(dialogContext)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
Theme.of(dialogContext).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Formulaire EntiteForm en mode lecture seule
|
||||
AmicaleForm(
|
||||
amicale: amicale,
|
||||
readOnly: readOnly,
|
||||
onSubmit: (updatedAmicale) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
// Ici, vous pourriez ajouter une logique pour mettre à jour l'amicale
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
builder: (dialogContext) => Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Container(
|
||||
width: MediaQuery.of(dialogContext).size.width * 0.6,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Détails de l\'amicale',
|
||||
style: Theme.of(dialogContext).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(dialogContext).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Formulaire AmicaleForm en mode lecture seule
|
||||
AmicaleForm(
|
||||
amicale: amicale,
|
||||
readOnly: true,
|
||||
userRepository: userRepository,
|
||||
apiService: apiService,
|
||||
onSubmit: (updatedAmicale) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -26,14 +26,14 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final VoidCallback? onLogoutPressed;
|
||||
|
||||
const DashboardAppBar({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.title,
|
||||
this.pageTitle,
|
||||
this.showNewPassageButton = true,
|
||||
this.onNewPassagePressed,
|
||||
this.isAdmin = false,
|
||||
this.onLogoutPressed,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -82,10 +82,12 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
),
|
||||
);
|
||||
|
||||
actions.add(const SizedBox(width: 8));
|
||||
|
||||
// Ajouter la version de l'application
|
||||
actions.add(
|
||||
Text(
|
||||
AppInfoService.fullVersion,
|
||||
"v${AppInfoService.version}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white70,
|
||||
@@ -93,11 +95,12 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
),
|
||||
);
|
||||
|
||||
actions.add(const SizedBox(width: 8));
|
||||
|
||||
actions.add(
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.add_location_alt, color: Colors.white),
|
||||
label: const Text('Nouveau passage',
|
||||
style: TextStyle(color: Colors.white)),
|
||||
label: const Text('Nouveau passage', style: TextStyle(color: Colors.white)),
|
||||
onPressed: onNewPassagePressed,
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.secondary,
|
||||
@@ -106,6 +109,8 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
),
|
||||
);
|
||||
|
||||
actions.add(const SizedBox(width: 8));
|
||||
|
||||
// Ajouter le bouton "Mon compte"
|
||||
actions.add(
|
||||
IconButton(
|
||||
@@ -128,6 +133,8 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
),
|
||||
);
|
||||
|
||||
actions.add(const SizedBox(width: 8));
|
||||
|
||||
// Ajouter le bouton de déconnexion
|
||||
actions.add(
|
||||
IconButton(
|
||||
@@ -139,8 +146,7 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: const Text('Déconnexion'),
|
||||
content:
|
||||
const Text('Voulez-vous vraiment vous déconnecter ?'),
|
||||
content: const Text('Voulez-vous vraiment vous déconnecter ?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
@@ -157,8 +163,7 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
// Vérification supplémentaire et navigation forcée si nécessaire
|
||||
if (success && context.mounted) {
|
||||
// Attendre un court instant pour que les changements d'état se propagent
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 100));
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Navigation forcée vers la page d'accueil
|
||||
context.go('/');
|
||||
|
||||
@@ -3,110 +3,207 @@ import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
|
||||
class MembreRowWidget extends StatelessWidget {
|
||||
final MembreModel membre;
|
||||
final Function()? onEdit;
|
||||
final Function()? onDelete;
|
||||
final Function(MembreModel)? onEdit;
|
||||
final Function(MembreModel)? onDelete;
|
||||
final bool isAlternate;
|
||||
|
||||
const MembreRowWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.membre,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
}) : super(key: key);
|
||||
this.isAlternate = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
// Couleur de fond alternée
|
||||
final backgroundColor = isAlternate ? theme.colorScheme.surface : theme.colorScheme.surface;
|
||||
|
||||
return InkWell(
|
||||
onTap: () => _showMembreDetails(context),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// ID
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
membre.id.toString(),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
|
||||
// Prénom (firstName)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
membre.firstName,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
// Nom (name)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
membre.name,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
// Email
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
membre.email,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
// Rôle (fkRole)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
_getRoleName(membre.fkRole),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
|
||||
// Statut (actif/inactif)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: membre.chkActive == 1 ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: membre.chkActive == 1 ? Colors.green.withOpacity(0.3) : Colors.red.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
membre.chkActive == 1 ? 'Actif' : 'Inactif',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: membre.chkActive == 1 ? Colors.green[700] : Colors.red[700],
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
if (onEdit != null || onDelete != null)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
// Bouton Edit
|
||||
if (onEdit != null)
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.edit,
|
||||
size: 20,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
onPressed: () => onEdit!(membre),
|
||||
tooltip: 'Modifier',
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 36,
|
||||
minHeight: 36,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
|
||||
// Espacement entre les boutons
|
||||
if (onEdit != null && onDelete != null) const SizedBox(width: 8),
|
||||
|
||||
// Bouton Delete
|
||||
if (onDelete != null)
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
size: 20,
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
onPressed: () => onDelete!(membre),
|
||||
tooltip: 'Supprimer',
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 36,
|
||||
minHeight: 36,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Afficher les détails du membre dans une boîte de dialogue
|
||||
void _showMembreDetails(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('${membre.firstName} ${membre.name}'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildDetailRow('ID', membre.id.toString()),
|
||||
_buildDetailRow('Email', membre.email),
|
||||
_buildDetailRow('Username', membre.username),
|
||||
_buildDetailRow('Rôle', _getRoleName(membre.fkRole)),
|
||||
_buildDetailRow('Titre', membre.fkTitre.toString()),
|
||||
_buildDetailRow('Secteur', membre.sectName ?? 'Non défini'),
|
||||
_buildDetailRow('Statut', membre.chkActive == 1 ? 'Actif' : 'Inactif'),
|
||||
if (membre.dateNaissance != null)
|
||||
_buildDetailRow('Date de naissance', '${membre.dateNaissance!.day}/${membre.dateNaissance!.month}/${membre.dateNaissance!.year}'),
|
||||
if (membre.dateEmbauche != null)
|
||||
_buildDetailRow('Date d\'embauche', '${membre.dateEmbauche!.day}/${membre.dateEmbauche!.month}/${membre.dateEmbauche!.year}'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ID
|
||||
Expanded(
|
||||
flex: 1,
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
membre.id.toString(),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
'$label:',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
|
||||
// Prénom (firstName)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
membre.firstName,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
// Nom (name)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
membre.name,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
// Secteur (sectName)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
membre.sectName ?? '',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
// Rôle (fkRole)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
_getRoleName(membre.fkRole),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
// Bouton Edit
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit, size: 20),
|
||||
color: theme.colorScheme.primary,
|
||||
onPressed: onEdit,
|
||||
tooltip: 'Modifier',
|
||||
constraints: const BoxConstraints(),
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
|
||||
// Bouton Delete
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, size: 20),
|
||||
color: theme.colorScheme.error,
|
||||
onPressed: onDelete,
|
||||
tooltip: 'Supprimer',
|
||||
constraints: const BoxConstraints(),
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(value),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
import 'package:geosector_app/core/repositories/membre_repository.dart';
|
||||
import 'package:geosector_app/presentation/widgets/membre_row_widget.dart';
|
||||
|
||||
class MembreTableWidget extends StatelessWidget {
|
||||
final List<MembreModel> membres;
|
||||
final Function(MembreModel)? onEdit;
|
||||
final Function(MembreModel)? onDelete;
|
||||
final MembreRepository membreRepository;
|
||||
final bool showHeader;
|
||||
final double? height;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final bool isLoading;
|
||||
final String? emptyMessage;
|
||||
|
||||
const MembreTableWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.membres,
|
||||
required this.membreRepository,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
this.showHeader = true,
|
||||
this.height,
|
||||
this.padding,
|
||||
}) : super(key: key);
|
||||
this.isLoading = false,
|
||||
this.emptyMessage,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -44,8 +51,7 @@ class MembreTableWidget extends StatelessWidget {
|
||||
// En-tête du tableau
|
||||
if (showHeader)
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
|
||||
padding: const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
// ID
|
||||
@@ -55,6 +61,7 @@ class MembreTableWidget extends StatelessWidget {
|
||||
'ID',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -66,6 +73,7 @@ class MembreTableWidget extends StatelessWidget {
|
||||
'Prénom',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -77,17 +85,19 @@ class MembreTableWidget extends StatelessWidget {
|
||||
'Nom',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Secteur (sectName)
|
||||
// Email
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: 3,
|
||||
child: Text(
|
||||
'Secteur',
|
||||
'Email',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -99,51 +109,83 @@ class MembreTableWidget extends StatelessWidget {
|
||||
'Rôle',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
// Statut
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: 1,
|
||||
child: Text(
|
||||
'Actions',
|
||||
'Statut',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
|
||||
// Actions (si onEdit ou onDelete sont fournis)
|
||||
if (onEdit != null || onDelete != null)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'Actions',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Liste des membres
|
||||
// Corps du tableau
|
||||
Expanded(
|
||||
child: membres.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
'Aucun membre disponible',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
)
|
||||
: ListView.separated(
|
||||
itemCount: membres.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
const SizedBox(height: 8.0),
|
||||
itemBuilder: (context, index) {
|
||||
final membre = membres[index];
|
||||
return MembreRowWidget(
|
||||
membre: membre,
|
||||
onEdit: onEdit != null ? () => onEdit!(membre) : null,
|
||||
onDelete:
|
||||
onDelete != null ? () => onDelete!(membre) : null,
|
||||
);
|
||||
},
|
||||
),
|
||||
child: _buildTableContent(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableContent(BuildContext context) {
|
||||
// Afficher un indicateur de chargement si isLoading est true
|
||||
if (isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
// Afficher un message si la liste est vide
|
||||
if (membres.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
emptyMessage ?? 'Aucun membre trouvé',
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Afficher la liste des membres
|
||||
return ListView.separated(
|
||||
itemCount: membres.length,
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.3),
|
||||
height: 1,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final membre = membres[index];
|
||||
return MembreRowWidget(
|
||||
membre: membre,
|
||||
onEdit: onEdit,
|
||||
onDelete: onDelete,
|
||||
isAlternate: index % 2 == 1,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user