Files
unionflow-mobile-apps/lib/features/organizations/presentation/pages/organizations_page.dart
dahoud 70cbd1c873 fix(mobile): URL changement mdp corrigée + v3.0 — multi-org, AppAuth, sécurité prod
Auth:
- profile_repository.dart: /api/auth/change-password → /api/membres/auth/change-password

Multi-org (Phase 3):
- OrgSelectorPage, OrgSwitcherBloc, OrgSwitcherEntry
- org_context_service.dart: headers X-Active-Organisation-Id + X-Active-Role

Navigation:
- MorePage: navigation conditionnelle par typeOrganisation
- Suppression adaptive_navigation (remplacé par main_navigation_layout)

Auth AppAuth:
- keycloak_webview_auth_service: fixes AppAuth Android
- AuthBloc: gestion REAUTH_REQUIS + premierLoginComplet

Onboarding:
- Nouveaux états: payment_method_page, onboarding_shared_widgets
- SouscriptionStatusModel mis à jour StatutValidationSouscription

Android:
- build.gradle: ProGuard/R8, network_security_config
- Gradle wrapper mis à jour
2026-04-07 20:56:03 +00:00

857 lines
29 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../bloc/organizations_bloc.dart';
import '../../bloc/organizations_event.dart';
import '../../bloc/organizations_state.dart';
import '../../data/models/organization_model.dart';
import '../widgets/organization_card.dart';
import '../widgets/create_organization_dialog.dart';
import '../widgets/edit_organization_dialog.dart';
import 'organization_detail_page.dart';
import 'org_types_page.dart';
import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../shared/design_system/components/animated_fade_in.dart';
import '../../../../shared/design_system/components/animated_slide_in.dart';
import '../../../../shared/design_system/components/african_pattern_background.dart';
import '../../../../shared/design_system/components/uf_app_bar.dart';
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
import '../../../../features/authentication/data/models/user_role.dart';
/// Page de gestion des organisations - Interface sophistiquée et exhaustive
///
/// Cette page offre une interface complète pour la gestion des organisations
/// avec des fonctionnalités avancées de recherche, filtrage, statistiques
/// et actions de gestion basées sur les permissions utilisateur.
///
/// **Design System V2** - Utilise UnionFlowColors et composants standardisés
/// **Backend connecté** - Toutes les données proviennent d'OrganizationsBloc
class OrganizationsPage extends StatefulWidget {
const OrganizationsPage({super.key});
@override
State<OrganizationsPage> createState() => _OrganizationsPageState();
}
class _OrganizationsPageState extends State<OrganizationsPage> with TickerProviderStateMixin {
// Controllers et état
final TextEditingController _searchController = TextEditingController();
TabController? _tabController;
final ScrollController _scrollController = ScrollController();
List<String?> _availableTypes = [];
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
// Les organisations sont déjà chargées par OrganizationsPageWrapper
// Le TabController sera initialisé dans didChangeDependencies
}
@override
void dispose() {
_tabController?.dispose();
_searchController.dispose();
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent * 0.9) {
// Charger plus d'organisations quand on approche du bas
context.read<OrganizationsBloc>().add(const LoadMoreOrganizations());
}
}
/// Calcule les types d'organisations disponibles dans les données
List<String?> _calculateAvailableTypes(List<OrganizationModel> organizations) {
if (organizations.isEmpty) {
return [null]; // Seulement "Toutes"
}
// Extraire tous les types uniques
final typesSet = organizations.map((org) => org.typeOrganisation).toSet();
final types = typesSet.toList()..sort((a, b) => a.compareTo(b));
// null en premier pour "Toutes", puis les types triés alphabétiquement
return [null, ...types];
}
/// Initialise ou met à jour le TabController si les types ont changé
void _updateTabController(List<String?> newTypes) {
if (_availableTypes.length != newTypes.length ||
!_availableTypes.every((type) => newTypes.contains(type))) {
_availableTypes = newTypes;
_tabController?.dispose();
_tabController = TabController(length: _availableTypes.length, vsync: this);
}
}
@override
Widget build(BuildContext context) {
return BlocConsumer<OrganizationsBloc, OrganizationsState>(
listener: (context, state) {
// Gestion des messages de succès et erreurs
if (state is OrganizationsError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: AppColors.error,
duration: const Duration(seconds: 4),
action: SnackBarAction(
label: 'Réessayer',
textColor: Colors.white,
onPressed: () {
context.read<OrganizationsBloc>().add(const LoadOrganizations(refresh: true));
},
),
),
);
} else if (state is OrganizationCreated) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Organisation créée avec succès'),
backgroundColor: AppColors.success,
duration: Duration(seconds: 2),
),
);
} else if (state is OrganizationUpdated) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Organisation mise à jour avec succès'),
backgroundColor: AppColors.success,
duration: Duration(seconds: 2),
),
);
} else if (state is OrganizationDeleted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Organisation supprimée avec succès'),
backgroundColor: AppColors.success,
duration: Duration(seconds: 2),
),
);
}
},
builder: (context, state) {
return AfricanPatternBackground(
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: UFAppBar(
title: 'Gestion des Organisations',
backgroundColor: AppColors.lightSurface,
foregroundColor: AppColors.textPrimaryLight,
elevation: 0,
actions: [
IconButton(
icon: const Icon(Icons.category_outlined),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const OrgTypesPage(),
),
);
},
tooltip: 'Types d\'organisations',
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
context.read<OrganizationsBloc>().add(const RefreshOrganizations());
},
tooltip: 'Rafraîchir',
),
],
),
body: SafeArea(
child: _buildBody(state),
),
floatingActionButton: _buildActionButton(state),
),
);
},
);
}
Widget _buildBody(OrganizationsState state) {
if (state is OrganizationsInitial || state is OrganizationsLoading) {
return _buildLoadingState();
}
if (state is OrganizationsLoaded) {
final loadedState = state;
// Calculer les types disponibles et mettre à jour le TabController
final availableTypes = _calculateAvailableTypes(loadedState.organizations);
_updateTabController(availableTypes);
return RefreshIndicator(
onRefresh: () async {
context.read<OrganizationsBloc>().add(const RefreshOrganizations());
},
child: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(SpacingTokens.md),
physics: const AlwaysScrollableScrollPhysics(),
child: AnimatedFadeIn(
duration: const Duration(milliseconds: 400),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header avec design system
AnimatedSlideIn(
duration: const Duration(milliseconds: 500),
curve: Curves.easeOut,
child: _buildHeader(loadedState),
),
const SizedBox(height: SpacingTokens.md),
// Section statistiques
AnimatedSlideIn(
duration: const Duration(milliseconds: 600),
curve: Curves.easeOut,
child: _buildStatsSection(loadedState),
),
const SizedBox(height: SpacingTokens.md),
// Barre de recherche
AnimatedSlideIn(
duration: const Duration(milliseconds: 700),
curve: Curves.easeOut,
child: _buildSearchBar(loadedState),
),
const SizedBox(height: SpacingTokens.md),
// Onglets de catégories dynamiques
AnimatedSlideIn(
duration: const Duration(milliseconds: 800),
curve: Curves.easeOut,
child: _buildCategoryTabs(availableTypes),
),
const SizedBox(height: SpacingTokens.md),
// Liste des organisations
AnimatedSlideIn(
duration: const Duration(milliseconds: 900),
curve: Curves.easeOut,
child: _buildOrganizationsList(loadedState),
),
const SizedBox(height: 80), // Espace pour le FAB
],
),
),
),
);
}
if (state is OrganizationsLoadingMore) {
// Show current organizations with loading indicator at bottom
return SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(SpacingTokens.md),
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLoadingMorePlaceholder(state.currentOrganizations),
const Padding(
padding: EdgeInsets.all(SpacingTokens.md),
child: Center(
child: CircularProgressIndicator(
color: AppColors.primaryGreen,
),
),
),
const SizedBox(height: 80),
],
),
);
}
if (state is OrganizationsError) {
return _buildErrorState(state);
}
return _buildLoadingState();
}
/// Placeholder pour affichage pendant le chargement de plus d'organisations
Widget _buildLoadingMorePlaceholder(List<OrganizationModel> currentOrganizations) {
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: currentOrganizations.length,
separatorBuilder: (context, index) => const SizedBox(height: SpacingTokens.sm),
itemBuilder: (context, index) {
final org = currentOrganizations[index];
return OrganizationCard(
organization: org,
onTap: () => _showOrganizationDetails(org),
showActions: false,
);
},
);
}
/// Bouton d'action harmonisé avec Design System V2
Widget? _buildActionButton(OrganizationsState state) {
// Afficher le FAB seulement si les données sont chargées
if (state is! OrganizationsLoaded && state is! OrganizationsLoadingMore) {
return null;
}
// Réservé au Super Admin uniquement
final authState = context.read<AuthBloc>().state;
if (authState is! AuthAuthenticated ||
authState.effectiveRole != UserRole.superAdmin) {
return null;
}
return FloatingActionButton.extended(
onPressed: _showCreateOrganizationDialog,
backgroundColor: AppColors.primaryGreen,
elevation: 8,
icon: const Icon(Icons.add, color: Colors.white),
label: const Text(
'Nouvelle organisation',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
);
}
/// Header épuré avec Design System V2
Widget _buildHeader(OrganizationsLoaded state) {
return Container(
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [AppColors.brandGreen, AppColors.primaryGreen],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(RadiusTokens.lg),
boxShadow: [
BoxShadow(
color: AppColors.primaryGreen.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(SpacingTokens.sm),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(RadiusTokens.md),
),
child: const Icon(
Icons.business,
color: Colors.white,
size: 24,
),
),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Gestion des Organisations',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
'${state.filteredOrganizations.length} organisation(s)',
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
IconButton(
onPressed: () {
context.read<OrganizationsBloc>().add(const RefreshOrganizations());
},
icon: const Icon(Icons.refresh, color: Colors.white),
tooltip: 'Rafraîchir',
),
],
),
);
}
/// Section statistiques avec données réelles et Design System V2
Widget _buildStatsSection(OrganizationsLoaded state) {
final totalOrgs = state.organizations.length;
final activeOrgs = state.organizations.where((o) => o.statut == StatutOrganization.active).length;
final totalMembers = state.organizations.fold<int>(0, (sum, o) => sum + o.nombreMembres);
return Container(
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
color: AppColors.lightSurface,
borderRadius: BorderRadius.circular(RadiusTokens.lg),
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 2))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(
Icons.analytics_outlined,
color: AppColors.textSecondaryLight,
size: 20,
),
const SizedBox(width: SpacingTokens.xs),
const Text(
'Statistiques',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.textPrimaryLight,
),
),
],
),
const SizedBox(height: SpacingTokens.md),
Row(
children: [
Expanded(
child: _buildStatCard(
'Total',
totalOrgs.toString(),
Icons.business_outlined,
AppColors.primaryGreen,
),
),
const SizedBox(width: SpacingTokens.sm),
Expanded(
child: _buildStatCard(
'Actives',
activeOrgs.toString(),
Icons.check_circle_outline,
AppColors.success,
),
),
const SizedBox(width: SpacingTokens.sm),
Expanded(
child: _buildStatCard(
'Membres',
totalMembers.toString(),
Icons.people_outline,
AppColors.primaryGreen,
),
),
],
),
],
),
);
}
/// Carte de statistique avec Design System V2
Widget _buildStatCard(String label, String value, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(SpacingTokens.sm),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.md),
border: Border.all(
color: color.withOpacity(0.2),
width: 1,
),
),
child: Column(
children: [
Icon(icon, color: color, size: 20),
const SizedBox(height: SpacingTokens.xs),
Text(
value,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textPrimaryLight,
),
),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: AppColors.textSecondaryLight,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
/// Barre de recherche avec Design System V2
Widget _buildSearchBar(OrganizationsLoaded state) {
return Container(
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
color: AppColors.lightSurface,
borderRadius: BorderRadius.circular(RadiusTokens.lg),
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 2))],
),
child: Container(
decoration: BoxDecoration(
color: AppColors.lightBackground,
borderRadius: BorderRadius.circular(RadiusTokens.md),
border: Border.all(
color: AppColors.lightBorder,
width: 1,
),
),
child: TextField(
controller: _searchController,
onChanged: (value) {
context.read<OrganizationsBloc>().add(SearchOrganizations(value));
},
decoration: InputDecoration(
hintText: 'Rechercher par nom, type, localisation...',
hintStyle: const TextStyle(
color: AppColors.textSecondaryLight,
fontSize: 14,
),
prefixIcon: const Icon(Icons.search, color: AppColors.primaryGreen),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
onPressed: () {
_searchController.clear();
context.read<OrganizationsBloc>().add(const SearchOrganizations(''));
},
icon: const Icon(Icons.clear, color: AppColors.textSecondaryLight),
)
: null,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.md,
vertical: SpacingTokens.sm,
),
),
),
),
);
}
/// Onglets de catégories générés dynamiquement selon les types disponibles
Widget _buildCategoryTabs(List<String?> availableTypes) {
if (_tabController == null || availableTypes.isEmpty) {
return const SizedBox.shrink();
}
return BlocBuilder<OrganizationsBloc, OrganizationsState>(
builder: (context, state) {
return Container(
decoration: BoxDecoration(
color: AppColors.lightSurface,
borderRadius: BorderRadius.circular(RadiusTokens.lg),
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 2))],
),
child: TabBar(
controller: _tabController!,
isScrollable: availableTypes.length > 4, // Scrollable si plus de 4 types
labelColor: AppColors.primaryGreen,
unselectedLabelColor: AppColors.textSecondaryLight,
indicatorColor: AppColors.primaryGreen,
indicatorWeight: 3,
indicatorSize: TabBarIndicatorSize.tab,
labelStyle: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
unselectedLabelStyle: const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
),
onTap: (index) {
// Filtrer par type selon l'onglet sélectionné
final selectedType = availableTypes[index];
if (selectedType != null) {
context.read<OrganizationsBloc>().add(FilterOrganizationsByType(selectedType));
} else {
// null = "Toutes" → effacer les filtres
context.read<OrganizationsBloc>().add(const ClearOrganizationsFilters());
}
},
tabs: availableTypes.map((type) {
final label = type == null ? 'Toutes' : type;
return Tab(text: label);
}).toList(),
),
);
},
);
}
/// Liste des organisations avec données réelles et OrganizationCard
Widget _buildOrganizationsList(OrganizationsLoaded state) {
final organizations = state.filteredOrganizations;
if (organizations.isEmpty) {
return _buildEmptyState();
}
// Vérifier le rôle une seule fois pour toute la liste (UI-02)
final authState = context.read<AuthBloc>().state;
final canManageOrgs = authState is AuthAuthenticated &&
(authState.effectiveRole == UserRole.superAdmin ||
authState.effectiveRole == UserRole.orgAdmin);
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: organizations.length,
separatorBuilder: (context, index) => const SizedBox(height: SpacingTokens.sm),
itemBuilder: (context, index) {
final org = organizations[index];
return AnimatedFadeIn(
duration: Duration(milliseconds: 300 + (index * 50)),
child: OrganizationCard(
organization: org,
onTap: () => _showOrganizationDetails(org),
onEdit: canManageOrgs ? () => _showEditOrganizationDialog(org) : null,
onDelete: canManageOrgs ? () => _confirmDeleteOrganization(org) : null,
showActions: canManageOrgs,
),
);
},
);
}
/// État vide avec Design System V2
Widget _buildEmptyState() {
return Container(
padding: const EdgeInsets.all(SpacingTokens.xl),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(SpacingTokens.xl),
decoration: BoxDecoration(
color: AppColors.primaryGreen.withOpacity(0.12),
shape: BoxShape.circle,
),
child: const Icon(
Icons.business_outlined,
size: 64,
color: AppColors.primaryGreen,
),
),
const SizedBox(height: SpacingTokens.lg),
const Text(
'Aucune organisation trouvée',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.textPrimaryLight,
),
),
const SizedBox(height: SpacingTokens.xs),
const Text(
'Essayez de modifier vos critères de recherche\nou créez une nouvelle organisation',
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondaryLight,
),
textAlign: TextAlign.center,
),
const SizedBox(height: SpacingTokens.lg),
ElevatedButton.icon(
onPressed: () {
context.read<OrganizationsBloc>().add(const ClearOrganizationsFilters());
_searchController.clear();
},
icon: const Icon(Icons.clear_all),
label: const Text('Réinitialiser les filtres'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.lg,
vertical: SpacingTokens.sm,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(RadiusTokens.md),
),
),
),
],
),
),
);
}
/// État de chargement avec Design System V2
Widget _buildLoadingState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: AppColors.primaryGreen,
strokeWidth: 3,
),
const SizedBox(height: SpacingTokens.md),
const Text(
'Chargement des organisations...',
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondaryLight,
),
),
],
),
);
}
/// État d'erreur avec Design System V2
Widget _buildErrorState(OrganizationsError state) {
return Center(
child: Container(
margin: const EdgeInsets.all(SpacingTokens.xl),
padding: const EdgeInsets.all(SpacingTokens.xl),
decoration: BoxDecoration(
color: AppColors.error.withOpacity(0.08),
borderRadius: BorderRadius.circular(RadiusTokens.lg),
border: Border.all(
color: AppColors.error.withOpacity(0.3),
width: 1,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: AppColors.error,
),
const SizedBox(height: SpacingTokens.md),
Text(
state.message,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimaryLight,
),
textAlign: TextAlign.center,
),
if (state.details != null) ...[
const SizedBox(height: SpacingTokens.xs),
Text(
state.details!,
style: const TextStyle(
fontSize: 12,
color: AppColors.textSecondaryLight,
),
textAlign: TextAlign.center,
),
],
const SizedBox(height: SpacingTokens.lg),
ElevatedButton.icon(
onPressed: () {
context.read<OrganizationsBloc>().add(const LoadOrganizations(refresh: true));
},
icon: const Icon(Icons.refresh),
label: const Text('Réessayer'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.lg,
vertical: SpacingTokens.sm,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(RadiusTokens.md),
),
),
),
],
),
),
);
}
// Méthodes d'actions
void _showOrganizationDetails(OrganizationModel org) {
final orgId = org.id;
if (orgId == null || orgId.isEmpty) return;
final bloc = context.read<OrganizationsBloc>();
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => BlocProvider.value(
value: bloc,
child: OrganizationDetailPage(organizationId: orgId),
),
),
);
}
void _showCreateOrganizationDialog() {
final bloc = context.read<OrganizationsBloc>();
showDialog(
context: context,
builder: (_) => BlocProvider.value(
value: bloc,
child: const CreateOrganizationDialog(),
),
);
}
void _showEditOrganizationDialog(OrganizationModel org) {
final bloc = context.read<OrganizationsBloc>();
showDialog(
context: context,
builder: (_) => BlocProvider.value(
value: bloc,
child: EditOrganizationDialog(organization: org),
),
);
}
void _confirmDeleteOrganization(OrganizationModel org) {
final bloc = context.read<OrganizationsBloc>();
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('Supprimer l\'organisation'),
content: Text('Voulez-vous vraiment supprimer "${org.nom}" ?'),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () {
if (org.id != null) {
bloc.add(DeleteOrganization(org.id!));
}
Navigator.of(dialogContext).pop();
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.error,
foregroundColor: Colors.white,
),
child: const Text('Supprimer'),
),
],
),
);
}
}