Compare commits

..
Author SHA1 Message Date
jmartinandCursor d32d956b0e feat(dashboard-admin): connect admin dashboard to real API data (Ticket #92)
- Frontend:
  - Create UserService to handle user-related API calls (gestionnaires, parents, AMs, admins)
  - Update AdminDashboardScreen to use dynamic widgets
  - Implement dynamic management widgets:
    - GestionnaireManagementWidget
    - ParentManagementWidget
    - AssistanteMaternelleManagementWidget
    - AdminManagementWidget
  - Add data models: ParentModel, AssistanteMaternelleModel
  - Update AppUser model
  - Update ApiConfig

- Backend:
  - Update controllers (Parents, AMs, Gestionnaires, Users) to allow ADMINISTRATEUR role to list users
  - Fix: Activate endpoint GET /gestionnaires (import GestionnairesModule in UserModule)

- Docs:
  - Add note about backend fix for Gestionnaires module
  - Update .cursorrules to forbid worktrees

- Seed:
  - Add test data seed script (reset-and-seed-db.sh)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 22:17:51 +01:00
3 changed files with 24 additions and 46 deletions
+16 -28
View File
@@ -32,42 +32,30 @@ class AppUser {
});
factory AppUser.fromJson(Map<String, dynamic> json) {
final id = json['id']?.toString();
final email = json['email']?.toString();
final role = json['role']?.toString();
if (id == null || id.isEmpty) {
throw Exception('Profil invalide: id manquant');
}
if (email == null || email.isEmpty) {
throw Exception('Profil invalide: email manquant');
}
if (role == null || role.isEmpty) {
throw Exception('Profil invalide: rôle manquant');
}
return AppUser(
id: id,
email: email,
role: role,
id: json['id'] as String,
email: json['email'] as String,
role: json['role'] as String,
createdAt: json['cree_le'] != null
? DateTime.tryParse(json['cree_le'].toString()) ?? DateTime.now()
? DateTime.parse(json['cree_le'] as String)
: (json['createdAt'] != null
? DateTime.tryParse(json['createdAt'].toString()) ?? DateTime.now()
? DateTime.parse(json['createdAt'] as String)
: DateTime.now()),
updatedAt: json['modifie_le'] != null
? DateTime.tryParse(json['modifie_le'].toString()) ?? DateTime.now()
? DateTime.parse(json['modifie_le'] as String)
: (json['updatedAt'] != null
? DateTime.tryParse(json['updatedAt'].toString()) ?? DateTime.now()
? DateTime.parse(json['updatedAt'] as String)
: DateTime.now()),
changementMdpObligatoire:
json['changement_mdp_obligatoire'] == true,
nom: json['nom']?.toString(),
prenom: json['prenom']?.toString(),
statut: json['statut']?.toString(),
telephone: json['telephone']?.toString(),
photoUrl: json['photo_url']?.toString(),
adresse: json['adresse']?.toString(),
ville: json['ville']?.toString(),
codePostal: json['code_postal']?.toString(),
json['changement_mdp_obligatoire'] as bool? ?? false,
nom: json['nom'] as String?,
prenom: json['prenom'] as String?,
statut: json['statut'] as String?,
telephone: json['telephone'] as String?,
photoUrl: json['photo_url'] as String?,
adresse: json['adresse'] as String?,
ville: json['ville'] as String?,
codePostal: json['code_postal'] as String?,
);
}
+4 -4
View File
@@ -236,20 +236,20 @@ class _LoginPageState extends State<LoginScreen> with WidgetsBindingObserver {
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
color: Colors.red.shade50,
color: Colors.red[50],
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.red.shade300),
border: Border.all(color: Colors.red[300]!),
),
child: Row(
children: [
Icon(Icons.error_outline, color: Colors.red.shade700, size: 20),
Icon(Icons.error_outline, color: Colors.red[700], size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
_errorMessage!,
style: GoogleFonts.merienda(
fontSize: 12,
color: Colors.red.shade700,
color: Colors.red[700],
),
),
),
+4 -14
View File
@@ -43,8 +43,7 @@ class AuthService {
}
} catch (e) {
if (e is Exception) rethrow;
if (e is Error) throw Exception('Erreur interne: ${e.toString()}');
throw Exception('Erreur réseau: impossible de se connecter au serveur ($e)');
throw Exception('Erreur réseau: impossible de se connecter au serveur');
}
}
@@ -57,22 +56,14 @@ class AuthService {
);
if (response.statusCode == 200) {
final raw = jsonDecode(response.body);
if (raw is! Map<String, dynamic>) {
throw Exception('Profil invalide: réponse serveur inattendue');
}
// Accepter réponse directe ou wrapper { data: {...} }
final data = raw.containsKey('data') && raw['data'] is Map<String, dynamic>
? raw['data'] as Map<String, dynamic>
: raw;
final data = jsonDecode(response.body);
return AppUser.fromJson(data);
} else {
throw Exception('Erreur lors de la récupération du profil');
}
} catch (e) {
if (e is Exception) rethrow;
if (e is Error) throw Exception('Erreur interne: ${e.toString()}');
throw Exception('Erreur réseau: impossible de récupérer le profil ($e)');
throw Exception('Erreur réseau: impossible de récupérer le profil');
}
}
@@ -107,8 +98,7 @@ class AuthService {
await _saveCurrentUser(user);
} catch (e) {
if (e is Exception) rethrow;
if (e is Error) throw Exception('Erreur interne: ${e.toString()}');
throw Exception('Erreur réseau: impossible de changer le mot de passe ($e)');
throw Exception('Erreur réseau: impossible de changer le mot de passe');
}
}