refactor(#93): homogénéiser la présentation des onglets admin
Uniformise les 4 onglets de gestion admin avec des composants UI partagés (header, états de liste, carte utilisateur) pour garantir une expérience cohérente sans changement backend. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
fbafef8f2c
commit
b2d6414fab
@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/models/user.dart';
|
import 'package:p_tits_pas/models/user.dart';
|
||||||
import 'package:p_tits_pas/services/user_service.dart';
|
import 'package:p_tits_pas/services/user_service.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_list_header.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_list_state.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart';
|
||||||
|
|
||||||
class AdminManagementWidget extends StatefulWidget {
|
class AdminManagementWidget extends StatefulWidget {
|
||||||
const AdminManagementWidget({super.key});
|
const AdminManagementWidget({super.key});
|
||||||
@ -69,71 +72,51 @@ class _AdminManagementWidgetState extends State<AdminManagementWidget> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
AdminListHeader(
|
||||||
children: [
|
searchController: _searchController,
|
||||||
Expanded(
|
searchHint: 'Rechercher un administrateur...',
|
||||||
child: TextField(
|
actionLabel: 'Créer un admin',
|
||||||
controller: _searchController,
|
onActionPressed: () {
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: "Rechercher un administrateur...",
|
|
||||||
prefixIcon: Icon(Icons.search),
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
ElevatedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Créer admin
|
// TODO: Créer admin
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
label: const Text("Créer un admin"),
|
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(height: 16),
|
||||||
),
|
AdminListState(
|
||||||
const SizedBox(height: 24),
|
isLoading: _isLoading,
|
||||||
if (_isLoading)
|
error: _error,
|
||||||
const Center(child: CircularProgressIndicator())
|
isEmpty: _filteredAdmins.isEmpty,
|
||||||
else if (_error != null)
|
emptyMessage: 'Aucun administrateur trouvé.',
|
||||||
Center(child: Text('Erreur: $_error', style: const TextStyle(color: Colors.red)))
|
list: ListView.builder(
|
||||||
else if (_filteredAdmins.isEmpty)
|
|
||||||
const Center(child: Text("Aucun administrateur trouvé."))
|
|
||||||
else
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: _filteredAdmins.length,
|
itemCount: _filteredAdmins.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final user = _filteredAdmins[index];
|
final user = _filteredAdmins[index];
|
||||||
return Card(
|
return AdminUserCard(
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
title: user.fullName,
|
||||||
child: ListTile(
|
subtitleLines: [
|
||||||
leading: CircleAvatar(
|
user.email,
|
||||||
child: Text(user.fullName.isNotEmpty
|
'Rôle : ${user.role}',
|
||||||
? user.fullName[0].toUpperCase()
|
],
|
||||||
: 'A'),
|
avatarUrl: user.photoUrl,
|
||||||
),
|
actions: [
|
||||||
title: Text(user.fullName.isNotEmpty
|
|
||||||
? user.fullName
|
|
||||||
: 'Sans nom'),
|
|
||||||
subtitle: Text(user.email),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
onPressed: () {},
|
tooltip: 'Modifier',
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Modifier admin
|
||||||
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
onPressed: () {},
|
tooltip: 'Supprimer',
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Supprimer admin
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/models/assistante_maternelle_model.dart';
|
import 'package:p_tits_pas/models/assistante_maternelle_model.dart';
|
||||||
import 'package:p_tits_pas/services/user_service.dart';
|
import 'package:p_tits_pas/services/user_service.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_list_header.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_list_state.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart';
|
||||||
|
|
||||||
class AssistanteMaternelleManagementWidget extends StatefulWidget {
|
class AssistanteMaternelleManagementWidget extends StatefulWidget {
|
||||||
const AssistanteMaternelleManagementWidget({super.key});
|
const AssistanteMaternelleManagementWidget({super.key});
|
||||||
@ -79,58 +82,46 @@ class _AssistanteMaternelleManagementWidgetState
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 🔎 Zone de filtre
|
AdminListHeader(
|
||||||
_buildFilterSection(),
|
searchController: _zoneController,
|
||||||
|
searchHint: 'Rechercher une zone géographique...',
|
||||||
|
filters: _buildFilters(),
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
AdminListState(
|
||||||
// 📋 Liste des assistantes
|
isLoading: _isLoading,
|
||||||
if (_isLoading)
|
error: _error,
|
||||||
const Center(child: CircularProgressIndicator())
|
isEmpty: _filteredAssistantes.isEmpty,
|
||||||
else if (_error != null)
|
emptyMessage: 'Aucune assistante maternelle trouvée.',
|
||||||
Center(child: Text('Erreur: $_error', style: const TextStyle(color: Colors.red)))
|
list: ListView.builder(
|
||||||
else if (_filteredAssistantes.isEmpty)
|
|
||||||
const Center(child: Text("Aucune assistante maternelle trouvée."))
|
|
||||||
else
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: _filteredAssistantes.length,
|
itemCount: _filteredAssistantes.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final assistante = _filteredAssistantes[index];
|
final assistante = _filteredAssistantes[index];
|
||||||
return Card(
|
return AdminUserCard(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
title: assistante.user.fullName,
|
||||||
child: ListTile(
|
avatarUrl: assistante.user.photoUrl,
|
||||||
leading: CircleAvatar(
|
fallbackIcon: Icons.face,
|
||||||
backgroundImage: assistante.user.photoUrl != null
|
subtitleLines: [
|
||||||
? NetworkImage(assistante.user.photoUrl!)
|
assistante.user.email,
|
||||||
: null,
|
'N° Agrément : ${assistante.approvalNumber ?? 'N/A'}',
|
||||||
child: assistante.user.photoUrl == null
|
'Zone : ${assistante.residenceCity ?? 'N/A'} | Capacité : ${assistante.maxChildren ?? 0}',
|
||||||
? const Icon(Icons.face)
|
],
|
||||||
: null,
|
actions: [
|
||||||
),
|
|
||||||
title: Text(assistante.user.fullName.isNotEmpty
|
|
||||||
? assistante.user.fullName
|
|
||||||
: 'Sans nom'),
|
|
||||||
subtitle: Text(
|
|
||||||
"N° Agrément : ${assistante.approvalNumber ?? 'N/A'}\nZone : ${assistante.residenceCity ?? 'N/A'} | Capacité : ${assistante.maxChildren ?? 0}"),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: 'Modifier',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: Ajouter modification
|
// TODO: Ajouter modification
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
|
tooltip: 'Supprimer',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: Ajouter suppression
|
// TODO: Ajouter suppression
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -140,29 +131,19 @@ class _AssistanteMaternelleManagementWidgetState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFilterSection() {
|
Widget _buildFilters() {
|
||||||
return Wrap(
|
return Wrap(
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 200,
|
width: 240,
|
||||||
child: TextField(
|
|
||||||
controller: _zoneController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "Zone géographique",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
prefixIcon: Icon(Icons.location_on),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 200,
|
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _capacityController,
|
controller: _capacityController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: "Capacité minimum",
|
labelText: 'Capacité minimum',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
|
isDense: true,
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
),
|
),
|
||||||
|
|||||||
58
frontend/lib/widgets/admin/common/admin_list_header.dart
Normal file
58
frontend/lib/widgets/admin/common/admin_list_header.dart
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AdminListHeader extends StatelessWidget {
|
||||||
|
final TextEditingController searchController;
|
||||||
|
final String searchHint;
|
||||||
|
final String? actionLabel;
|
||||||
|
final VoidCallback? onActionPressed;
|
||||||
|
final Widget? filters;
|
||||||
|
|
||||||
|
const AdminListHeader({
|
||||||
|
super.key,
|
||||||
|
required this.searchController,
|
||||||
|
required this.searchHint,
|
||||||
|
this.actionLabel,
|
||||||
|
this.onActionPressed,
|
||||||
|
this.filters,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: searchController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: searchHint,
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (actionLabel != null && onActionPressed != null) ...[
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: onActionPressed,
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(actionLabel!),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (filters != null) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
filters!,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
frontend/lib/widgets/admin/common/admin_list_state.dart
Normal file
49
frontend/lib/widgets/admin/common/admin_list_state.dart
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AdminListState extends StatelessWidget {
|
||||||
|
final bool isLoading;
|
||||||
|
final String? error;
|
||||||
|
final bool isEmpty;
|
||||||
|
final String emptyMessage;
|
||||||
|
final Widget list;
|
||||||
|
|
||||||
|
const AdminListState({
|
||||||
|
super.key,
|
||||||
|
required this.isLoading,
|
||||||
|
required this.error,
|
||||||
|
required this.isEmpty,
|
||||||
|
required this.emptyMessage,
|
||||||
|
required this.list,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (isLoading) {
|
||||||
|
return const Expanded(
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
return Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'Erreur: $error',
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
return Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(emptyMessage),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expanded(child: list);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
frontend/lib/widgets/admin/common/admin_user_card.dart
Normal file
40
frontend/lib/widgets/admin/common/admin_user_card.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AdminUserCard extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final List<String> subtitleLines;
|
||||||
|
final String? avatarUrl;
|
||||||
|
final IconData fallbackIcon;
|
||||||
|
final List<Widget> actions;
|
||||||
|
|
||||||
|
const AdminUserCard({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.subtitleLines,
|
||||||
|
this.avatarUrl,
|
||||||
|
this.fallbackIcon = Icons.person,
|
||||||
|
this.actions = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl!) : null,
|
||||||
|
child: avatarUrl == null ? Icon(fallbackIcon) : null,
|
||||||
|
),
|
||||||
|
title: Text(title.isNotEmpty ? title : 'Sans nom'),
|
||||||
|
subtitle: Text(subtitleLines.join('\n')),
|
||||||
|
isThreeLine: subtitleLines.length > 1,
|
||||||
|
trailing: actions.isEmpty
|
||||||
|
? null
|
||||||
|
: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: actions,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class GestionnaireCard extends StatelessWidget {
|
|
||||||
final String name;
|
|
||||||
final String email;
|
|
||||||
|
|
||||||
const GestionnaireCard({
|
|
||||||
Key? key,
|
|
||||||
required this.name,
|
|
||||||
required this.email,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// 🔹 Infos principales
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(name, style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
||||||
Text(email, style: const TextStyle(color: Colors.grey)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// 🔹 Attribution à des RPE (dropdown fictif ici)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Text("RPE attribué : "),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
DropdownButton<String>(
|
|
||||||
value: "RPE 1",
|
|
||||||
items: const [
|
|
||||||
DropdownMenuItem(value: "RPE 1", child: Text("RPE 1")),
|
|
||||||
DropdownMenuItem(value: "RPE 2", child: Text("RPE 2")),
|
|
||||||
DropdownMenuItem(value: "RPE 3", child: Text("RPE 3")),
|
|
||||||
],
|
|
||||||
onChanged: (value) {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// 🔹 Boutons d'action
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Réinitialisation mot de passe
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.lock_reset),
|
|
||||||
label: const Text("Réinitialiser MDP"),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Suppression du compte
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.delete, color: Colors.red),
|
|
||||||
label: const Text("Supprimer", style: TextStyle(color: Colors.red)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/models/parent_model.dart';
|
import 'package:p_tits_pas/models/parent_model.dart';
|
||||||
import 'package:p_tits_pas/services/user_service.dart';
|
import 'package:p_tits_pas/services/user_service.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_list_header.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_list_state.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart';
|
||||||
|
|
||||||
class ParentManagementWidget extends StatefulWidget {
|
class ParentManagementWidget extends StatefulWidget {
|
||||||
const ParentManagementWidget({super.key});
|
const ParentManagementWidget({super.key});
|
||||||
@ -59,13 +62,8 @@ class _ParentManagementWidgetState extends State<ParentManagementWidget> {
|
|||||||
_filteredParents = _parents.where((p) {
|
_filteredParents = _parents.where((p) {
|
||||||
final matchesName = p.user.fullName.toLowerCase().contains(query) ||
|
final matchesName = p.user.fullName.toLowerCase().contains(query) ||
|
||||||
p.user.email.toLowerCase().contains(query);
|
p.user.email.toLowerCase().contains(query);
|
||||||
final matchesStatus = _selectedStatus == null ||
|
final matchesStatus =
|
||||||
_selectedStatus == 'Tous' ||
|
_selectedStatus == null || p.user.statut == _selectedStatus;
|
||||||
(p.user.statut?.toLowerCase() == _selectedStatus?.toLowerCase());
|
|
||||||
|
|
||||||
// Mapping simple pour le statut affiché vs backend
|
|
||||||
// Backend: en_attente, actif, suspendu
|
|
||||||
// Dropdown: En attente, Actif, Suspendu
|
|
||||||
|
|
||||||
return matchesName && matchesStatus;
|
return matchesName && matchesStatus;
|
||||||
}).toList();
|
}).toList();
|
||||||
@ -79,65 +77,52 @@ class _ParentManagementWidgetState extends State<ParentManagementWidget> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildSearchSection(),
|
AdminListHeader(
|
||||||
|
searchController: _searchController,
|
||||||
|
searchHint: 'Rechercher un parent...',
|
||||||
|
filters: _buildFilters(),
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (_isLoading)
|
AdminListState(
|
||||||
const Center(child: CircularProgressIndicator())
|
isLoading: _isLoading,
|
||||||
else if (_error != null)
|
error: _error,
|
||||||
Center(child: Text('Erreur: $_error', style: const TextStyle(color: Colors.red)))
|
isEmpty: _filteredParents.isEmpty,
|
||||||
else if (_filteredParents.isEmpty)
|
emptyMessage: 'Aucun parent trouvé.',
|
||||||
const Center(child: Text("Aucun parent trouvé."))
|
list: ListView.builder(
|
||||||
else
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: _filteredParents.length,
|
itemCount: _filteredParents.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final parent = _filteredParents[index];
|
final parent = _filteredParents[index];
|
||||||
return Card(
|
return AdminUserCard(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
title: parent.user.fullName,
|
||||||
child: ListTile(
|
avatarUrl: parent.user.photoUrl,
|
||||||
leading: CircleAvatar(
|
subtitleLines: [
|
||||||
backgroundImage: parent.user.photoUrl != null
|
parent.user.email,
|
||||||
? NetworkImage(parent.user.photoUrl!)
|
'Statut : ${_displayStatus(parent.user.statut)}',
|
||||||
: null,
|
'Enfants : ${parent.childrenCount}',
|
||||||
child: parent.user.photoUrl == null
|
],
|
||||||
? const Icon(Icons.person)
|
actions: [
|
||||||
: null,
|
|
||||||
),
|
|
||||||
title: Text(parent.user.fullName.isNotEmpty
|
|
||||||
? parent.user.fullName
|
|
||||||
: 'Sans nom'),
|
|
||||||
subtitle: Text(
|
|
||||||
"${parent.user.email}\nStatut : ${parent.user.statut ?? 'Inconnu'} | Enfants : ${parent.childrenCount}",
|
|
||||||
),
|
|
||||||
isThreeLine: true,
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.visibility),
|
icon: const Icon(Icons.visibility),
|
||||||
tooltip: "Voir dossier",
|
tooltip: 'Voir dossier',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: Voir le statut du dossier
|
// TODO: Voir le statut du dossier
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
tooltip: "Modifier",
|
tooltip: 'Modifier',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: Modifier parent
|
// TODO: Modifier parent
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
tooltip: "Supprimer",
|
tooltip: 'Supprimer',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: Supprimer compte
|
// TODO: Supprimer compte
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -147,45 +132,48 @@ class _ParentManagementWidgetState extends State<ParentManagementWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSearchSection() {
|
Widget _buildFilters() {
|
||||||
return Wrap(
|
return SizedBox(
|
||||||
spacing: 16,
|
width: 240,
|
||||||
runSpacing: 8,
|
child: DropdownButtonFormField<String?>(
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 220,
|
|
||||||
child: TextField(
|
|
||||||
controller: _searchController,
|
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: "Nom du parent",
|
labelText: 'Statut',
|
||||||
border: OutlineInputBorder(),
|
|
||||||
prefixIcon: Icon(Icons.search),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 220,
|
|
||||||
child: DropdownButtonFormField<String>(
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "Statut",
|
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
|
isDense: true,
|
||||||
),
|
),
|
||||||
value: _selectedStatus,
|
value: _selectedStatus,
|
||||||
items: const [
|
items: const [
|
||||||
DropdownMenuItem(value: null, child: Text("Tous")),
|
DropdownMenuItem<String?>(value: null, child: Text('Tous')),
|
||||||
DropdownMenuItem(value: "actif", child: Text("Actif")),
|
DropdownMenuItem<String?>(value: 'actif', child: Text('Actif')),
|
||||||
DropdownMenuItem(value: "en_attente", child: Text("En attente")),
|
DropdownMenuItem<String?>(
|
||||||
DropdownMenuItem(value: "suspendu", child: Text("Suspendu")),
|
value: 'en_attente',
|
||||||
|
child: Text('En attente'),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<String?>(
|
||||||
|
value: 'suspendu',
|
||||||
|
child: Text('Suspendu'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedStatus = value;
|
_selectedStatus = value;
|
||||||
_filter();
|
|
||||||
});
|
});
|
||||||
|
_filter();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _displayStatus(String? status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'actif':
|
||||||
|
return 'Actif';
|
||||||
|
case 'en_attente':
|
||||||
|
return 'En attente';
|
||||||
|
case 'suspendu':
|
||||||
|
return 'Suspendu';
|
||||||
|
default:
|
||||||
|
return 'Inconnu';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user