Ouvre POST/PATCH /gestionnaires et POST /users/admin aux administrateurs (en plus du super_admin). Garde-fou front : pas de création staff sur le dashboard gestionnaire.
422 lines
12 KiB
Dart
422 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:p_tits_pas/screens/administrateurs/creation/gestionnaires_create.dart';
|
||
import 'package:p_tits_pas/widgets/admin/admin_management_widget.dart';
|
||
import 'package:p_tits_pas/widgets/admin/am_dossier_create_modal.dart';
|
||
import 'package:p_tits_pas/widgets/admin/assistante_maternelle_management_widget.dart';
|
||
import 'package:p_tits_pas/widgets/admin/common/admin_child_detail_modal.dart';
|
||
import 'package:p_tits_pas/widgets/admin/dashboard_admin.dart';
|
||
import 'package:p_tits_pas/widgets/admin/dossiers_management_widget.dart';
|
||
import 'package:p_tits_pas/widgets/admin/enfant_management_widget.dart';
|
||
import 'package:p_tits_pas/widgets/admin/gestionnaire_management_widget.dart';
|
||
import 'package:p_tits_pas/widgets/admin/parent_dossier_create_modal.dart';
|
||
import 'package:p_tits_pas/widgets/admin/parent_managmant_widget.dart';
|
||
|
||
class UserManagementPanel extends StatefulWidget {
|
||
/// Afficher l'onglet Administrateurs (sinon sans Administrateurs).
|
||
final bool showAdministrateursTab;
|
||
|
||
/// Création gestionnaire / admin (#161). False pour le dashboard gestionnaire.
|
||
final bool allowStaffAccountCreation;
|
||
|
||
const UserManagementPanel({
|
||
super.key,
|
||
this.showAdministrateursTab = true,
|
||
this.allowStaffAccountCreation = true,
|
||
});
|
||
|
||
@override
|
||
State<UserManagementPanel> createState() => _UserManagementPanelState();
|
||
}
|
||
|
||
class _UserManagementPanelState extends State<UserManagementPanel> {
|
||
int _subIndex = 0;
|
||
int _gestionnaireRefreshTick = 0;
|
||
int _parentRefreshTick = 0;
|
||
int _adminRefreshTick = 0;
|
||
int _enfantRefreshTick = 0;
|
||
int _amRefreshTick = 0;
|
||
int _dossiersRefreshTick = 0;
|
||
final TextEditingController _searchController = TextEditingController();
|
||
final TextEditingController _amCapacityController = TextEditingController();
|
||
String? _parentStatus;
|
||
String? _enfantStatus;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_searchController.addListener(_onFilterChanged);
|
||
_amCapacityController.addListener(_onFilterChanged);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_searchController.removeListener(_onFilterChanged);
|
||
_amCapacityController.removeListener(_onFilterChanged);
|
||
_searchController.dispose();
|
||
_amCapacityController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _onFilterChanged() {
|
||
if (!mounted) return;
|
||
setState(() {});
|
||
}
|
||
|
||
/// Ordre #153 : Dossiers | Parents | Enfants | AM | Gestionnaires | (Admin).
|
||
List<String> get _tabLabels {
|
||
const base = [
|
||
'Dossiers',
|
||
'Parents',
|
||
'Enfants',
|
||
'Assistantes maternelles',
|
||
'Gestionnaires',
|
||
];
|
||
if (widget.showAdministrateursTab) {
|
||
return [...base, 'Administrateurs'];
|
||
}
|
||
return base;
|
||
}
|
||
|
||
void _onSubTabChange(int index) {
|
||
final maxIndex = _tabLabels.length - 1;
|
||
setState(() {
|
||
_subIndex = index.clamp(0, maxIndex);
|
||
_searchController.clear();
|
||
_parentStatus = null;
|
||
_enfantStatus = null;
|
||
_amCapacityController.clear();
|
||
});
|
||
}
|
||
|
||
bool get _isDossiersTab => _subIndex == 0;
|
||
|
||
bool get _isStaffAccountsTab =>
|
||
_subIndex == 4 || (widget.showAdministrateursTab && _subIndex == 5);
|
||
|
||
bool get _canShowAddButton {
|
||
if (_isDossiersTab) return false;
|
||
if (_isStaffAccountsTab && !widget.allowStaffAccountCreation) return false;
|
||
return true;
|
||
}
|
||
|
||
String _searchHintForTab() {
|
||
switch (_subIndex) {
|
||
case 0:
|
||
return 'Rechercher un dossier';
|
||
case 1:
|
||
return 'Rechercher un parent...';
|
||
case 2:
|
||
return 'Rechercher un enfant...';
|
||
case 3:
|
||
return 'Rechercher une assistante...';
|
||
case 4:
|
||
return 'Rechercher un gestionnaire...';
|
||
case 5:
|
||
return 'Rechercher un administrateur...';
|
||
default:
|
||
return 'Rechercher...';
|
||
}
|
||
}
|
||
|
||
String? _searchTooltipForTab() {
|
||
if (_subIndex != 0) return null;
|
||
return 'Recherche possible : n° de dossier, nom, prénom ou e-mail.';
|
||
}
|
||
|
||
Widget? _subBarFilterControl() {
|
||
// Parents
|
||
if (_subIndex == 1) {
|
||
return DropdownButtonHideUnderline(
|
||
child: DropdownButton<String?>(
|
||
value: _parentStatus,
|
||
isExpanded: true,
|
||
hint: const Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Statut', style: TextStyle(fontSize: 12)),
|
||
),
|
||
items: const [
|
||
DropdownMenuItem<String?>(
|
||
value: null,
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Tous', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
DropdownMenuItem<String?>(
|
||
value: 'actif',
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Actif', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
DropdownMenuItem<String?>(
|
||
value: 'en_attente',
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('En attente', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
DropdownMenuItem<String?>(
|
||
value: 'suspendu',
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Suspendu', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
DropdownMenuItem<String?>(
|
||
value: 'refuse',
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Refusé', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
],
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_parentStatus = value;
|
||
});
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
// Enfants
|
||
if (_subIndex == 2) {
|
||
return DropdownButtonHideUnderline(
|
||
child: DropdownButton<String?>(
|
||
value: _enfantStatus,
|
||
isExpanded: true,
|
||
hint: const Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Statut', style: TextStyle(fontSize: 12)),
|
||
),
|
||
items: const [
|
||
DropdownMenuItem<String?>(
|
||
value: null,
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Tous', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
DropdownMenuItem<String?>(
|
||
value: 'a_naitre',
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('À naître', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
DropdownMenuItem<String?>(
|
||
value: 'sans_garde',
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Sans garde', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
DropdownMenuItem<String?>(
|
||
value: 'garde',
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Gardé', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
DropdownMenuItem<String?>(
|
||
value: 'scolarise',
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 10),
|
||
child: Text('Scolarisé', style: TextStyle(fontSize: 12)),
|
||
),
|
||
),
|
||
],
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_enfantStatus = value;
|
||
});
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
// AM
|
||
if (_subIndex == 3) {
|
||
return TextField(
|
||
controller: _amCapacityController,
|
||
decoration: const InputDecoration(
|
||
hintText: 'Capacité min',
|
||
hintStyle: TextStyle(fontSize: 12),
|
||
border: InputBorder.none,
|
||
isDense: true,
|
||
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||
),
|
||
keyboardType: TextInputType.number,
|
||
);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
Widget _buildBody() {
|
||
switch (_subIndex) {
|
||
case 0:
|
||
return DossiersManagementWidget(
|
||
key: ValueKey('dossiers-$_dossiersRefreshTick'),
|
||
searchQuery: _searchController.text,
|
||
);
|
||
case 1:
|
||
return ParentManagementWidget(
|
||
key: ValueKey('parents-$_parentRefreshTick'),
|
||
searchQuery: _searchController.text,
|
||
statusFilter: _parentStatus,
|
||
);
|
||
case 2:
|
||
return EnfantManagementWidget(
|
||
key: ValueKey('enfants-$_enfantRefreshTick'),
|
||
searchQuery: _searchController.text,
|
||
statusFilter: _enfantStatus,
|
||
);
|
||
case 3:
|
||
return AssistanteMaternelleManagementWidget(
|
||
key: ValueKey('ams-$_amRefreshTick'),
|
||
searchQuery: _searchController.text,
|
||
capacityMin: int.tryParse(_amCapacityController.text),
|
||
);
|
||
case 4:
|
||
return GestionnaireManagementWidget(
|
||
key: ValueKey('gestionnaires-$_gestionnaireRefreshTick'),
|
||
searchQuery: _searchController.text,
|
||
);
|
||
case 5:
|
||
return AdminManagementWidget(
|
||
key: ValueKey('admins-$_adminRefreshTick'),
|
||
searchQuery: _searchController.text,
|
||
);
|
||
default:
|
||
return const Center(child: Text('Page non trouvée'));
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final labels = _tabLabels;
|
||
return Column(
|
||
children: [
|
||
DashboardUserManagementSubBar(
|
||
selectedSubIndex: _subIndex,
|
||
onSubTabChange: _onSubTabChange,
|
||
searchController: _searchController,
|
||
searchHint: _searchHintForTab(),
|
||
searchTooltip: _searchTooltipForTab(),
|
||
filterControl: _subBarFilterControl(),
|
||
// Pas de « Créer » sur l’onglet Dossiers (#153).
|
||
// Pas de création staff pour le dashboard gestionnaire (#161).
|
||
onAddPressed: _canShowAddButton ? _handleAddPressed : null,
|
||
addLabel: 'Ajouter',
|
||
subTabCount: labels.length,
|
||
tabLabels: labels,
|
||
),
|
||
Expanded(child: _buildBody()),
|
||
],
|
||
);
|
||
}
|
||
|
||
Future<void> _handleAddPressed() async {
|
||
// 1 Parents, 2 Enfants, 3 AM, 4 Gestionnaires, 5 Admin
|
||
if (_isStaffAccountsTab && !widget.allowStaffAccountCreation) {
|
||
return;
|
||
}
|
||
if (_subIndex == 1) {
|
||
await showDialog<void>(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (dialogContext) {
|
||
return ParentDossierCreateModal(
|
||
onClose: () => Navigator.of(dialogContext).pop(),
|
||
onSuccess: () {
|
||
Navigator.of(dialogContext).pop();
|
||
if (!mounted) return;
|
||
setState(() {
|
||
_parentRefreshTick++;
|
||
_dossiersRefreshTick++;
|
||
});
|
||
},
|
||
);
|
||
},
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (_subIndex == 2) {
|
||
await showDialog<void>(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (dialogContext) {
|
||
return AdminChildDetailModal.create(
|
||
onSaved: () {
|
||
if (!mounted) return;
|
||
setState(() => _enfantRefreshTick++);
|
||
},
|
||
);
|
||
},
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (_subIndex == 3) {
|
||
await showDialog<void>(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (dialogContext) {
|
||
return AmDossierCreateModal(
|
||
onClose: () => Navigator.of(dialogContext).pop(),
|
||
onSuccess: () {
|
||
Navigator.of(dialogContext).pop();
|
||
if (!mounted) return;
|
||
setState(() {
|
||
_amRefreshTick++;
|
||
_dossiersRefreshTick++;
|
||
});
|
||
},
|
||
);
|
||
},
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (_subIndex == 4) {
|
||
final created = await showDialog<bool>(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (dialogContext) {
|
||
return const AdminUserFormDialog();
|
||
},
|
||
);
|
||
|
||
if (!mounted) return;
|
||
if (created == true) {
|
||
setState(() {
|
||
_gestionnaireRefreshTick++;
|
||
});
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (_subIndex == 5) {
|
||
final created = await showDialog<bool>(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (dialogContext) {
|
||
return const AdminUserFormDialog(
|
||
adminMode: true,
|
||
withRelais: false,
|
||
);
|
||
},
|
||
);
|
||
|
||
if (!mounted) return;
|
||
if (created == true) {
|
||
setState(() {
|
||
_adminRefreshTick++;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|