Compare commits

..
Author SHA1 Message Date
jmartinandCursor e4f7a35f0f fix(#92): activer endpoint GET /gestionnaires (note backend)
- UserModule: importer GestionnairesModule
- GestionnairesModule: importer AuthModule (AuthGuard/JwtService)
- user_service: appeler /gestionnaires au lieu de /users + filtre

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 22:46:11 +01:00
jmartinandCursor 8a6768b316 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
  - Note: Gestionnaires endpoint is currently bypassed in frontend (using /users filter) due to module import issue (documented in docs/92_NOTE-BACKEND-GESTIONNAIRES.md)

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

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 21:43:27 +01:00
jmartinandCursor 3892a8beab feat(#92): seed données de test dashboard admin + script reset BDD
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 17:27:26 +01:00
jmartinandCursor d39bc55be3 chore(backend): variables LOG_API_REQUESTS et CONFIG_ENCRYPTION_KEY dans docker-compose
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 17:26:19 +01:00
jmartinandCursor e0debf0394 docs: README BDD seed/reset, liste tickets #92, procédure API Gitea, statut application
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 17:26:13 +01:00
9 changed files with 24 additions and 207 deletions
-18
View File
@@ -1,18 +0,0 @@
# Fins de ligne : toujours LF dans le dépôt (évite les conflits Linux/Windows)
* text=auto eol=lf
# Fichiers binaires : pas de conversion
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.webp binary
*.pdf binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
# Scripts shell : toujours LF
*.sh text eol=lf
+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');
}
}
-19
View File
@@ -1,19 +0,0 @@
# Créer l’issue #84 (correctifs modale MDP) via l’API Gitea
1. Définir un token valide :
`export GITEA_TOKEN="votre_token"`
ou créer `.gitea-token` à la racine du projet avec le token seul.
2. Créer l’issue :
```bash
cd /chemin/vers/PetitsPas
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d @scripts/issue-84-payload.json \
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues"
```
3. En cas de succès (HTTP 201), la réponse JSON contient le numéro de l’issue créée.
Payload utilisé : `scripts/issue-84-payload.json` (titre + corps depuis `scripts/issue-84-body.txt`).
-51
View File
@@ -1,51 +0,0 @@
#!/usr/bin/env bash
# Crée une issue Gitea via l'API.
# Usage: GITEA_TOKEN=xxx ./scripts/create-gitea-issue.sh
# Ou: mettre le token dans .gitea-token à la racine du projet.
set -e
BASE_URL="${GITEA_URL:-https://git.ptits-pas.fr/api/v1}"
REPO="jmartin/petitspas"
if [ -z "$GITEA_TOKEN" ]; then
if [ -f .gitea-token ]; then
GITEA_TOKEN=$(cat .gitea-token)
fi
fi
if [ -z "$GITEA_TOKEN" ]; then
echo "Définir GITEA_TOKEN ou créer .gitea-token avec votre token Gitea."
exit 1
fi
TITLE="$1"
BODY="$2"
if [ -z "$TITLE" ]; then
echo "Usage: $0 \"Titre de l'issue\" \"Corps (optionnel)\""
exit 1
fi
# Build JSON (escape body for JSON)
BODY_ESC=$(echo "$BODY" | jq -Rs . 2>/dev/null || echo "null")
if [ "$BODY_ESC" = "null" ] || [ -z "$BODY" ]; then
PAYLOAD=$(jq -n --arg t "$TITLE" '{title: $t}')
else
PAYLOAD=$(jq -n --arg t "$TITLE" --arg b "$BODY" '{title: $t, body: $b}')
fi
RESP=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD" \
"$BASE_URL/repos/$REPO/issues")
HTTP_CODE=$(echo "$RESP" | tail -1)
BODY_RESP=$(echo "$RESP" | sed '$d')
if [ "$HTTP_CODE" = "201" ]; then
ISSUE_NUM=$(echo "$BODY_RESP" | jq -r .number)
echo "Issue #$ISSUE_NUM créée."
echo "$BODY_RESP" | jq .
else
echo "Erreur HTTP $HTTP_CODE: $BODY_RESP"
exit 1
fi
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/env bash
# Poste un commentaire sur une issue Gitea puis la ferme.
# Usage: GITEA_TOKEN=xxx ./scripts/gitea-close-issue-with-comment.sh <numéro> "Commentaire"
# Ou: mettre le token dans .gitea-token à la racine du projet.
# Exemple: ./scripts/gitea-close-issue-with-comment.sh 15 "Livré : panneau Paramètres opérationnel."
set -e
ISSUE="${1:?Usage: $0 <numéro_issue> \"Commentaire\"}"
COMMENT="${2:?Usage: $0 <numéro_issue> \"Commentaire\"}"
BASE_URL="${GITEA_URL:-https://git.ptits-pas.fr/api/v1}"
REPO="jmartin/petitspas"
if [ -z "$GITEA_TOKEN" ]; then
if [ -f .gitea-token ]; then
GITEA_TOKEN=$(cat .gitea-token)
fi
fi
if [ -z "$GITEA_TOKEN" ]; then
echo "Définir GITEA_TOKEN ou créer .gitea-token avec votre token Gitea."
exit 1
fi
# 1) Poster le commentaire
echo "Ajout du commentaire sur l'issue #$ISSUE..."
# Échapper pour JSON (guillemets et backslash)
COMMENT_ESC=$(printf '%s' "$COMMENT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\r//g')
PAYLOAD="{\"body\":\"$COMMENT_ESC\"}"
RESP=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD" \
"$BASE_URL/repos/$REPO/issues/$ISSUE/comments")
HTTP_CODE=$(echo "$RESP" | tail -1)
BODY=$(echo "$RESP" | sed '$d')
if [ "$HTTP_CODE" != "201" ]; then
echo "Erreur HTTP $HTTP_CODE lors du commentaire: $BODY"
exit 1
fi
echo "Commentaire ajouté."
# 2) Fermer l'issue
echo "Fermeture de l'issue #$ISSUE..."
RESP2=$(curl -s -w "\n%{http_code}" -X PATCH \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"state":"closed"}' \
"$BASE_URL/repos/$REPO/issues/$ISSUE")
HTTP_CODE2=$(echo "$RESP2" | tail -1)
BODY2=$(echo "$RESP2" | sed '$d')
if [ "$HTTP_CODE2" = "200" ] || [ "$HTTP_CODE2" = "201" ]; then
echo "Issue #$ISSUE fermée."
else
echo "Erreur HTTP $HTTP_CODE2: $BODY2"
exit 1
fi
-14
View File
@@ -1,14 +0,0 @@
Correctifs et améliorations de la modale de changement de mot de passe obligatoire affichée à la première connexion admin.
**Périmètre :**
- Ajustements visuels / UX de la modale (ChangePasswordDialog)
- Cohérence charte graphique, espacements, lisibilité
- Comportement (validation, messages d'erreur, fermeture)
- Lien de test en debug sur l'écran login (« Test modale MDP ») pour faciliter les réglages
**Tâches :**
- [ ] Revoir le design de la modale (relief, bordures, couleurs)
- [ ] Vérifier les champs (MDP actuel, nouveau, confirmation) et validations
- [ ] Ajuster les textes et messages d'erreur
- [ ] Tester sur mobile et desktop
- [ ] Retirer ou conditionner le lien « Test modale MDP » en production si besoin
-1
View File
@@ -1 +0,0 @@
{"title": "[Frontend] Bug – Correctifs modale Changement MDP (première connexion admin)", "body": "Correctifs et améliorations de la modale de changement de mot de passe obligatoire affichée à la première connexion admin.\n\n**Périmètre :**\n- Ajustements visuels / UX de la modale (ChangePasswordDialog)\n- Cohérence charte graphique, espacements, lisibilité\n- Comportement (validation, messages d'erreur, fermeture)\n- Lien de test en debug sur l'écran login (« Test modale MDP ») pour faciliter les réglages\n\n**Tâches :**\n- [ ] Revoir le design de la modale (relief, bordures, couleurs)\n- [ ] Vérifier les champs (MDP actuel, nouveau, confirmation) et validations\n- [ ] Ajuster les textes et messages d'erreur\n- [ ] Tester sur mobile et desktop\n- [ ] Retirer ou conditionner le lien « Test modale MDP » en production si besoin\n"}