fix(UI-02): guards rôle sur pages organisations — FAB, édition, suppression SUPER_ADMIN only

- OrgTypesPage: FAB création, tap/édition/suppression type visibles uniquement SUPER_ADMIN
- OrganizationDetailPage: bouton édition visible orgAdmin+superAdmin, suppression SUPER_ADMIN only, section Actions cachée si non autorisé
This commit is contained in:
dahoud
2026-04-11 01:27:42 +00:00
parent 04896349d6
commit cf2866a0aa
2 changed files with 65 additions and 27 deletions

View File

@@ -5,6 +5,8 @@ import '../../../../shared/widgets/core_card.dart';
import '../../../../core/di/injection.dart'; import '../../../../core/di/injection.dart';
import '../../bloc/org_types_bloc.dart'; import '../../bloc/org_types_bloc.dart';
import '../../domain/entities/type_reference_entity.dart'; import '../../domain/entities/type_reference_entity.dart';
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
import '../../../../features/authentication/data/models/user_role.dart';
class OrgTypesPage extends StatelessWidget { class OrgTypesPage extends StatelessWidget {
const OrgTypesPage({super.key}); const OrgTypesPage({super.key});
@@ -77,11 +79,17 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
); );
}, },
), ),
floatingActionButton: FloatingActionButton.small( floatingActionButton: Builder(builder: (ctx) {
onPressed: () => _showTypeForm(context, null), final authState = ctx.read<AuthBloc>().state;
backgroundColor: AppColors.primaryGreen, final isSuperAdmin = authState is AuthAuthenticated &&
child: const Icon(Icons.add, color: Colors.white), authState.effectiveRole == UserRole.superAdmin;
), if (!isSuperAdmin) return const SizedBox.shrink();
return FloatingActionButton.small(
onPressed: () => _showTypeForm(context, null),
backgroundColor: AppColors.primaryGreen,
child: const Icon(Icons.add, color: Colors.white),
);
}),
); );
} }
@@ -96,12 +104,15 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
Widget _buildTypeCard(BuildContext context, TypeReferenceEntity type, OrgTypesState state) { Widget _buildTypeCard(BuildContext context, TypeReferenceEntity type, OrgTypesState state) {
final isOperating = state is OrgTypeOperating; final isOperating = state is OrgTypeOperating;
final color = _parseColor(type.couleur) ?? AppColors.primaryGreen; final color = _parseColor(type.couleur) ?? AppColors.primaryGreen;
final authState = context.read<AuthBloc>().state;
final isSuperAdmin = authState is AuthAuthenticated &&
authState.effectiveRole == UserRole.superAdmin;
return Opacity( return Opacity(
opacity: isOperating ? 0.6 : 1.0, opacity: isOperating ? 0.6 : 1.0,
child: CoreCard( child: CoreCard(
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
onTap: (!type.estSysteme && !isOperating) ? () => _showTypeForm(context, type) : null, onTap: (isSuperAdmin && !type.estSysteme && !isOperating) ? () => _showTypeForm(context, type) : null,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border(left: BorderSide(color: color, width: 3)), border: Border(left: BorderSide(color: color, width: 3)),
@@ -163,7 +174,7 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
], ],
), ),
), ),
if (!type.estSysteme && !isOperating) ...[ if (isSuperAdmin && !type.estSysteme && !isOperating) ...[
IconButton( IconButton(
icon: const Icon(Icons.edit_outlined, size: 16), icon: const Icon(Icons.edit_outlined, size: 16),
color: AppColors.textSecondaryLight, color: AppColors.textSecondaryLight,
@@ -187,6 +198,10 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
} }
Widget _buildEmptyState(BuildContext context) { Widget _buildEmptyState(BuildContext context) {
final authState = context.read<AuthBloc>().state;
final isSuperAdmin = authState is AuthAuthenticated &&
authState.effectiveRole == UserRole.superAdmin;
return Center( return Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
@@ -201,22 +216,26 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
'Créez votre premier type d\'organisation', isSuperAdmin
? 'Créez votre premier type d\'organisation'
: 'Aucun type d\'organisation disponible',
style: TextStyle(fontSize: 12, color: Colors.grey[500]), style: TextStyle(fontSize: 12, color: Colors.grey[500]),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 16), if (isSuperAdmin) ...[
ElevatedButton.icon( const SizedBox(height: 16),
onPressed: () => _showTypeForm(context, null), ElevatedButton.icon(
icon: const Icon(Icons.add, size: 16), onPressed: () => _showTypeForm(context, null),
label: const Text('Créer un type'), icon: const Icon(Icons.add, size: 16),
style: ElevatedButton.styleFrom( label: const Text('Créer un type'),
backgroundColor: AppColors.primaryGreen, style: ElevatedButton.styleFrom(
foregroundColor: Colors.white, backgroundColor: AppColors.primaryGreen,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
), ),
), ],
], ],
), ),
), ),

View File

@@ -51,7 +51,14 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
title: const Text('Détail Organisation'), title: const Text('Détail Organisation'),
elevation: 0, elevation: 0,
actions: [ actions: [
IconButton(onPressed: _showEditPage, icon: const Icon(Icons.edit), tooltip: 'Modifier'), Builder(builder: (ctx) {
final authState = ctx.read<AuthBloc>().state;
final canEdit = authState is AuthAuthenticated &&
(authState.effectiveRole == UserRole.superAdmin ||
authState.effectiveRole == UserRole.orgAdmin);
if (!canEdit) return const SizedBox.shrink();
return IconButton(onPressed: _showEditPage, icon: const Icon(Icons.edit), tooltip: 'Modifier');
}),
Builder(builder: (ctx) { Builder(builder: (ctx) {
final authState = ctx.read<AuthBloc>().state; final authState = ctx.read<AuthBloc>().state;
final isSuperAdmin = authState is AuthAuthenticated && final isSuperAdmin = authState is AuthAuthenticated &&
@@ -314,6 +321,16 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
// ── Actions ───────────────────────────────────────────────────────────────── // ── Actions ─────────────────────────────────────────────────────────────────
Widget _buildActionsCard(OrganizationModel org) { Widget _buildActionsCard(OrganizationModel org) {
final authState = context.read<AuthBloc>().state;
final isSuperAdmin = authState is AuthAuthenticated &&
authState.effectiveRole == UserRole.superAdmin;
final canEdit = authState is AuthAuthenticated &&
(authState.effectiveRole == UserRole.superAdmin ||
authState.effectiveRole == UserRole.orgAdmin);
// Aucune action autorisée → ne pas afficher la section
if (!canEdit) return const SizedBox.shrink();
return _buildCard('Actions', Icons.bolt, [ return _buildCard('Actions', Icons.bolt, [
Row(children: [ Row(children: [
Expanded(child: ElevatedButton.icon( Expanded(child: ElevatedButton.icon(
@@ -322,13 +339,15 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
label: const Text('Modifier'), label: const Text('Modifier'),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryGreen, foregroundColor: Colors.white), style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryGreen, foregroundColor: Colors.white),
)), )),
const SizedBox(width: 12), if (isSuperAdmin) ...[
Expanded(child: OutlinedButton.icon( const SizedBox(width: 12),
onPressed: () => _showDeleteConfirmation(org), Expanded(child: OutlinedButton.icon(
icon: const Icon(Icons.delete), onPressed: () => _showDeleteConfirmation(org),
label: const Text('Supprimer'), icon: const Icon(Icons.delete),
style: OutlinedButton.styleFrom(foregroundColor: Colors.red, side: const BorderSide(color: Colors.red)), label: const Text('Supprimer'),
)), style: OutlinedButton.styleFrom(foregroundColor: Colors.red, side: const BorderSide(color: Colors.red)),
)),
],
]), ]),
]); ]);
} }