feat(#95): implémenter la gestion Relais admin et le rattachement gestionnaire
Ajoute la section Paramètres territoriaux avec CRUD Relais, modale de saisie structurée, états visuels harmonisés, et rattachement d'un relais principal aux gestionnaires via l'API. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:p_tits_pas/models/relais_model.dart';
|
||||
import 'package:p_tits_pas/models/user.dart';
|
||||
import 'package:p_tits_pas/services/relais_service.dart';
|
||||
import 'package:p_tits_pas/services/user_service.dart';
|
||||
import 'package:p_tits_pas/widgets/admin/gestionnaire_card.dart';
|
||||
|
||||
class GestionnaireManagementWidget extends StatefulWidget {
|
||||
const GestionnaireManagementWidget({Key? key}) : super(key: key);
|
||||
@@ -16,6 +17,7 @@ class _GestionnaireManagementWidgetState
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
List<AppUser> _gestionnaires = [];
|
||||
List<RelaisModel> _relais = [];
|
||||
List<AppUser> _filteredGestionnaires = [];
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@@ -38,11 +40,19 @@ class _GestionnaireManagementWidgetState
|
||||
_error = null;
|
||||
});
|
||||
try {
|
||||
final list = await UserService.getGestionnaires();
|
||||
final gestionnaires = await UserService.getGestionnaires();
|
||||
List<RelaisModel> relais = [];
|
||||
try {
|
||||
relais = await RelaisService.getRelais();
|
||||
} catch (_) {
|
||||
// L'ecran reste utilisable meme si la route Relais n'est pas disponible.
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_gestionnaires = list;
|
||||
_filteredGestionnaires = list;
|
||||
_gestionnaires = gestionnaires;
|
||||
_relais = relais;
|
||||
_filteredGestionnaires = gestionnaires;
|
||||
_isLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -65,6 +75,84 @@ class _GestionnaireManagementWidgetState
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _openRelaisAssignmentDialog(AppUser user) async {
|
||||
String? selectedRelaisId = user.relaisId;
|
||||
final saved = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setStateDialog) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Rattacher ${user.fullName.isEmpty ? user.email : user.fullName}',
|
||||
),
|
||||
content: DropdownButtonFormField<String?>(
|
||||
value: selectedRelaisId,
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Relais principal',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String?>(
|
||||
value: null,
|
||||
child: Text('Aucun relais'),
|
||||
),
|
||||
..._relais.map(
|
||||
(relais) => DropdownMenuItem<String?>(
|
||||
value: relais.id,
|
||||
child: Text(relais.nom),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
setStateDialog(() {
|
||||
selectedRelaisId = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(false),
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
child: const Text('Enregistrer'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (saved != true) return;
|
||||
|
||||
try {
|
||||
await UserService.updateGestionnaireRelais(
|
||||
gestionnaireId: user.id,
|
||||
relaisId: selectedRelaisId,
|
||||
);
|
||||
if (!mounted) return;
|
||||
await _loadGestionnaires();
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Rattachement relais mis a jour.')),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
e.toString().replaceAll('Exception: ', ''),
|
||||
),
|
||||
backgroundColor: Colors.red.shade600,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
@@ -72,14 +160,13 @@ class _GestionnaireManagementWidgetState
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 🔹 Barre du haut avec bouton
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Rechercher un gestionnaire...",
|
||||
hintText: 'Rechercher un gestionnaire...',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
@@ -88,35 +175,70 @@ class _GestionnaireManagementWidgetState
|
||||
const SizedBox(width: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Rediriger vers la page de création
|
||||
// TODO: Rediriger vers la page de creation.
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Créer un gestionnaire"),
|
||||
label: const Text('Creer un gestionnaire'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 🔹 Liste des gestionnaires
|
||||
if (_isLoading)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else if (_error != null)
|
||||
Center(child: Text('Erreur: $_error', style: const TextStyle(color: Colors.red)))
|
||||
Center(
|
||||
child: Text(
|
||||
'Erreur: $_error',
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
)
|
||||
else if (_filteredGestionnaires.isEmpty)
|
||||
const Center(child: Text("Aucun gestionnaire trouvé."))
|
||||
const Center(child: Text('Aucun gestionnaire trouve.'))
|
||||
else
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: _filteredGestionnaires.length,
|
||||
itemBuilder: (context, index) {
|
||||
final user = _filteredGestionnaires[index];
|
||||
return GestionnaireCard(
|
||||
name: user.fullName.isNotEmpty ? user.fullName : "Sans nom",
|
||||
email: user.email,
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
child: Text(
|
||||
(user.prenom?.isNotEmpty == true
|
||||
? user.prenom!.substring(0, 1)
|
||||
: user.email.substring(0, 1))
|
||||
.toUpperCase(),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
user.fullName.isNotEmpty ? user.fullName : 'Sans nom',
|
||||
),
|
||||
subtitle: Text(
|
||||
'${user.email} • Relais: ${user.relaisNom ?? 'Non rattache'}',
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.location_city_outlined),
|
||||
tooltip: 'Rattacher un relais',
|
||||
onPressed: () => _openRelaisAssignmentDialog(user),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
tooltip: 'Modifier',
|
||||
onPressed: () {
|
||||
// TODO: Modifier gestionnaire.
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user