fix(#143): masquer Supprimer quand un gestionnaire édite sa propre fiche

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
MARTIN Julien 2026-07-12 12:10:06 +02:00
parent d6dac181d2
commit 6788351070
2 changed files with 137 additions and 2 deletions

View File

@ -0,0 +1,111 @@
/**
* Crée l'issue Gitea gestionnaire ne doit pas pouvoir se supprimer.
* Usage: node backend/scripts/create-gitea-issue-gestionnaire-self-delete.js
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '../..');
const MILESTONE_0_1_0 = 10;
let token = process.env.GITEA_TOKEN;
if (!token) {
try {
const tokenFile = path.join(repoRoot, '.gitea-token');
if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim();
} catch (_) {}
}
if (!token) {
try {
const briefing = fs.readFileSync(
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
'utf8',
);
const m = briefing.match(/Token:\s*(gitebu_[a-f0-9]+)/);
if (m) token = m[1].trim();
} catch (_) {}
}
if (!token) {
console.error('Token non trouvé : .gitea-token ou GITEA_TOKEN');
process.exit(1);
}
const body = `## Contexte
Quand un **gestionnaire** connecté ouvre sa propre fiche dans l'onglet **Gestionnaires** (Gestion des utilisateurs), la modale **Modifier un "Gestionnaire"** affiche le bouton **Supprimer**.
Comportement actuel : le gestionnaire voit et peut tenter de supprimer son propre compte.
## Comportement attendu
Comme pour le **super administrateur** (bouton Supprimer masqué sur la fiche super admin) :
- **Pas de bouton Supprimer** quand l'utilisateur édite **sa propre fiche**
- **Modification** des informations (prénom, nom, email, téléphone, relais, mot de passe) **toujours autorisée**
## Périmètre
- Frontend : \`AdminUserFormDialog\` (\`gestionnaires_create.dart\`)
- Comparer \`initialUser.id\` avec l'utilisateur connecté (\`AuthService.getCurrentUser\`)
- Masquer Supprimer si édition de soi-même ; conserver garde existante super admin
## Critères d'acceptation
- [ ] Gestionnaire connecté ouvre sa fiche **pas** de bouton Supprimer
- [ ] Gestionnaire connecté peut **Modifier** ses informations
- [ ] Super admin / admin peut toujours supprimer **un autre** gestionnaire (si droits API)
- [ ] Pas de régression sur fiche super administrateur (Supprimer toujours masqué)
## Fichiers clés
- \`frontend/lib/screens/administrateurs/creation/gestionnaires_create.dart\`
- \`frontend/lib/widgets/admin/gestionnaire_management_widget.dart\`
## Milestone
**0.1.0** correction UX / sécurité gestion utilisateurs.`;
const payloadClean = JSON.stringify({
title: '[Bug] Gestionnaire peut voir Supprimer sur sa propre fiche',
body,
milestone: MILESTONE_0_1_0,
});
const opts = {
hostname: 'git.ptits-pas.fr',
path: '/api/v1/repos/jmartin/petitspas/issues',
method: 'POST',
headers: {
Authorization: 'token ' + token,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payloadClean),
},
};
const req = https.request(opts, (res) => {
let d = '';
res.on('data', (c) => (d += c));
res.on('end', () => {
try {
const o = JSON.parse(d);
if (o.number) {
console.log('NUMBER:', o.number);
console.log('URL:', o.html_url);
console.log('MILESTONE:', o.milestone?.title ?? '(aucun)');
} else {
console.error('Réponse:', d);
process.exit(1);
}
} catch (e) {
console.error(d);
process.exit(1);
}
});
});
req.on('error', (e) => {
console.error(e);
process.exit(1);
});
req.write(payloadClean);
req.end();

View File

@ -5,6 +5,7 @@ import 'package:p_tits_pas/utils/email_utils.dart';
import 'package:p_tits_pas/widgets/email_text_field.dart';
import 'package:p_tits_pas/widgets/french_phone_field.dart';
import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/relais_service.dart';
import 'package:p_tits_pas/services/user_service.dart';
@ -41,9 +42,15 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
bool _isLoadingRelais = true;
List<RelaisModel> _relais = [];
String? _selectedRelaisId;
String? _currentUserId;
bool get _isEditMode => widget.initialUser != null;
bool get _isSuperAdminTarget =>
(widget.initialUser?.role ?? '').toLowerCase() == 'super_admin';
bool get _isSelfTarget =>
_isEditMode &&
_currentUserId != null &&
widget.initialUser!.id == _currentUserId;
bool get _canDeleteTarget => !_isSuperAdminTarget && !_isSelfTarget;
bool get _isLockedAdminIdentity =>
_isEditMode && widget.adminMode && _isSuperAdminTarget;
String get _targetRoleKey {
@ -109,6 +116,23 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
} else {
_isLoadingRelais = false;
}
_loadCurrentUserId();
}
Future<void> _loadCurrentUserId() async {
final cached = await AuthService.getCurrentUser();
if (!mounted) return;
if (cached != null) {
setState(() {
_currentUserId = cached.id;
});
return;
}
final refreshed = await AuthService.refreshCurrentUser();
if (!mounted || refreshed == null) return;
setState(() {
_currentUserId = refreshed.id;
});
}
@override
@ -335,7 +359,7 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
Future<void> _delete() async {
if (widget.readOnly) return;
if (_isSuperAdminTarget) return;
if (!_canDeleteTarget) return;
if (!_isEditMode || _isSubmitting) return;
final confirmed = await showDialog<bool>(
@ -462,7 +486,7 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
child: const Text('Fermer'),
),
] else if (_isEditMode) ...[
if (!_isSuperAdminTarget)
if (_canDeleteTarget)
OutlinedButton(
onPressed: _isSubmitting ? null : _delete,
style: OutlinedButton.styleFrom(foregroundColor: Colors.red.shade700),