- Dossiers unifiés #119, pending-families enrichi, validation admin (wizards) - Front: modèles dossier_unifie / pending_family, NIR, auth - Migrations dossier_famille, scripts de test API - Résolution conflits: parents.*, docs tickets, auth_service, nir_utils Made-with: Cursor
291 lines
8.9 KiB
Dart
291 lines
8.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:p_tits_pas/screens/administrateurs/creation/gestionnaires_create.dart';
|
|
import 'package:p_tits_pas/services/user_service.dart';
|
|
import 'package:p_tits_pas/widgets/admin/admin_management_widget.dart';
|
|
import 'package:p_tits_pas/widgets/admin/assistante_maternelle_management_widget.dart';
|
|
import 'package:p_tits_pas/widgets/admin/dashboard_admin.dart';
|
|
import 'package:p_tits_pas/widgets/admin/gestionnaire_management_widget.dart';
|
|
import 'package:p_tits_pas/widgets/admin/parent_managmant_widget.dart';
|
|
import 'package:p_tits_pas/widgets/admin/pending_validation_widget.dart';
|
|
|
|
class UserManagementPanel extends StatefulWidget {
|
|
/// Afficher l'onglet Administrateurs (sinon 3 onglets : Gestionnaires, Parents, AM).
|
|
final bool showAdministrateursTab;
|
|
|
|
const UserManagementPanel({
|
|
super.key,
|
|
this.showAdministrateursTab = true,
|
|
});
|
|
|
|
@override
|
|
State<UserManagementPanel> createState() => _UserManagementPanelState();
|
|
}
|
|
|
|
class _UserManagementPanelState extends State<UserManagementPanel> {
|
|
int _subIndex = 0;
|
|
int _gestionnaireRefreshTick = 0;
|
|
int _adminRefreshTick = 0;
|
|
final TextEditingController _searchController = TextEditingController();
|
|
final TextEditingController _amCapacityController = TextEditingController();
|
|
String? _parentStatus;
|
|
bool _hasPending = false;
|
|
bool _pendingLoading = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_searchController.addListener(_onFilterChanged);
|
|
_amCapacityController.addListener(_onFilterChanged);
|
|
_loadPending();
|
|
}
|
|
|
|
Future<void> _loadPending() async {
|
|
try {
|
|
final am = await UserService.getPendingUsers(role: 'assistante_maternelle');
|
|
final families = await UserService.getPendingFamilies();
|
|
if (!mounted) return;
|
|
final hasPending = am.isNotEmpty || families.isNotEmpty;
|
|
setState(() {
|
|
final hadPending = _hasPending;
|
|
_hasPending = hasPending;
|
|
_pendingLoading = false;
|
|
// Si on passe à "plus de dossiers", recaler l'index (onglet À valider disparaît).
|
|
if (hadPending && !hasPending) {
|
|
_subIndex = (_subIndex > 0 ? _subIndex - 1 : 0).clamp(0, _tabLabels.length - 1);
|
|
} else if (!hadPending && hasPending) {
|
|
_subIndex = 0; // Afficher l'onglet À valider
|
|
}
|
|
});
|
|
} catch (_) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_hasPending = false;
|
|
_pendingLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.removeListener(_onFilterChanged);
|
|
_amCapacityController.removeListener(_onFilterChanged);
|
|
_searchController.dispose();
|
|
_amCapacityController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onFilterChanged() {
|
|
if (!mounted) return;
|
|
setState(() {});
|
|
}
|
|
|
|
List<String> get _tabLabels {
|
|
const base = ['Parents', 'Assistantes maternelles', 'Gestionnaires'];
|
|
final withAdmin = [...base, 'Administrateurs'];
|
|
final list = widget.showAdministrateursTab ? withAdmin : base;
|
|
// Onglet « À valider » visible seulement s'il y a des dossiers en attente (ticket #107).
|
|
if (!_pendingLoading && _hasPending) return ['À valider', ...list];
|
|
return list;
|
|
}
|
|
|
|
void _onSubTabChange(int index) {
|
|
final maxIndex = _tabLabels.length - 1;
|
|
setState(() {
|
|
_subIndex = index.clamp(0, maxIndex);
|
|
_searchController.clear();
|
|
_parentStatus = null;
|
|
_amCapacityController.clear();
|
|
});
|
|
}
|
|
|
|
/// Index du contenu : -1 = À valider (si visible), 0 = Parents, 1 = AM, 2 = Gestionnaires, 3 = Admin.
|
|
int get _contentIndexOffset => (_hasPending && !_pendingLoading) ? 1 : 0;
|
|
|
|
String _searchHintForTab() {
|
|
final contentIndex = _subIndex - _contentIndexOffset;
|
|
switch (contentIndex) {
|
|
case -1:
|
|
return 'À valider (pas de recherche)';
|
|
case 0:
|
|
return 'Rechercher un parent...';
|
|
case 1:
|
|
return 'Rechercher une assistante...';
|
|
case 2:
|
|
return 'Rechercher un gestionnaire...';
|
|
case 3:
|
|
return 'Rechercher un administrateur...';
|
|
default:
|
|
return 'Rechercher...';
|
|
}
|
|
}
|
|
|
|
Widget? _subBarFilterControl() {
|
|
if (_subIndex == _contentIndexOffset + 0) {
|
|
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)),
|
|
),
|
|
),
|
|
],
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_parentStatus = value;
|
|
});
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
if (_subIndex == _contentIndexOffset + 1) {
|
|
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() {
|
|
final contentIndex = _subIndex - _contentIndexOffset;
|
|
if (_hasPending && !_pendingLoading && contentIndex == -1) {
|
|
return PendingValidationWidget(onRefresh: _loadPending);
|
|
}
|
|
switch (contentIndex) {
|
|
case 0:
|
|
return ParentManagementWidget(
|
|
searchQuery: _searchController.text,
|
|
statusFilter: _parentStatus,
|
|
);
|
|
case 1:
|
|
return AssistanteMaternelleManagementWidget(
|
|
searchQuery: _searchController.text,
|
|
capacityMin: int.tryParse(_amCapacityController.text),
|
|
);
|
|
case 2:
|
|
return GestionnaireManagementWidget(
|
|
key: ValueKey('gestionnaires-$_gestionnaireRefreshTick'),
|
|
searchQuery: _searchController.text,
|
|
);
|
|
case 3:
|
|
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;
|
|
final isAValiderTab = _hasPending && !_pendingLoading && _subIndex == 0;
|
|
return Column(
|
|
children: [
|
|
DashboardUserManagementSubBar(
|
|
selectedSubIndex: _subIndex,
|
|
onSubTabChange: _onSubTabChange,
|
|
searchController: _searchController,
|
|
searchHint: _searchHintForTab(),
|
|
filterControl: _subBarFilterControl(),
|
|
onAddPressed: isAValiderTab ? null : _handleAddPressed,
|
|
addLabel: 'Ajouter',
|
|
subTabCount: labels.length,
|
|
tabLabels: labels,
|
|
),
|
|
Expanded(child: _buildBody()),
|
|
],
|
|
);
|
|
}
|
|
|
|
Future<void> _handleAddPressed() async {
|
|
final contentIndex = _subIndex - _contentIndexOffset;
|
|
if (contentIndex == 2) {
|
|
final created = await showDialog<bool>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (dialogContext) {
|
|
return const AdminUserFormDialog();
|
|
},
|
|
);
|
|
|
|
if (!mounted) return;
|
|
if (created == true) {
|
|
setState(() {
|
|
_gestionnaireRefreshTick++;
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (contentIndex == 3) {
|
|
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++;
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text(
|
|
'La création est disponible pour les gestionnaires et administrateurs.',
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|