feat(ui): RefreshIndicator + AlwaysScrollable + dark mode sur 14 pages
RefreshIndicator ajouté (dispatche les events BLoC appropriés) : - adhesion_detail, adhesions_page, demande_aide_detail, demandes_aide_page - event_detail, organization_detail, org_selector, org_types - user_management_detail, reports (TabBarView), logs (Dashboard tab) - profile (onglet Perso), backup (3 onglets), notifications Fixes associés : - AlwaysScrollableScrollPhysics sur tous les scroll widgets (permet pull-to-refresh même si contenu < écran) - Empty states des listes : wrappés dans SingleChildScrollView pour refresh - Dark mode adaptatif sur textes/surfaces/borders hardcodés - backup_page : bouton retour ajouté dans le header gradient - org_types : chevron/star/border adaptatifs - reports : couleurs placeholders graphique + chevrons
This commit is contained in:
@@ -31,13 +31,14 @@ class _AdhesionDetailPageState extends State<AdhesionDetailPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: const UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'DÉTAIL ADHÉSION',
|
title: 'Détail Adhésion',
|
||||||
backgroundColor: AppColors.surface,
|
moduleGradient: ModuleColors.adhesionsGradient,
|
||||||
foregroundColor: AppColors.textPrimaryLight,
|
|
||||||
),
|
),
|
||||||
body: BlocConsumer<AdhesionsBloc, AdhesionsState>(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: BlocConsumer<AdhesionsBloc, AdhesionsState>(
|
||||||
listenWhen: (prev, curr) => prev.status != curr.status,
|
listenWhen: (prev, curr) => prev.status != curr.status,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.status == AdhesionsStatus.error && state.message != null) {
|
if (state.status == AdhesionsStatus.error && state.message != null) {
|
||||||
@@ -58,7 +59,7 @@ class _AdhesionDetailPageState extends State<AdhesionDetailPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.error_outline, size: 64, color: AppColors.textSecondaryLight),
|
Icon(Icons.error_outline, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Adhésion introuvable',
|
'Adhésion introuvable',
|
||||||
@@ -68,7 +69,12 @@ class _AdhesionDetailPageState extends State<AdhesionDetailPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
|
color: ModuleColors.adhesions,
|
||||||
|
onRefresh: () async =>
|
||||||
|
context.read<AdhesionsBloc>().add(LoadAdhesionById(widget.adhesionId)),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -118,10 +124,12 @@ class _AdhesionDetailPageState extends State<AdhesionDetailPage> {
|
|||||||
_ActionsSection(adhesion: a, currencyFormat: _currencyFormat, isGestionnaire: _isGestionnaire()),
|
_ActionsSection(adhesion: a, currencyFormat: _currencyFormat, isGestionnaire: _isGestionnaire()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
), // SingleChildScrollView
|
||||||
|
); // RefreshIndicator
|
||||||
},
|
},
|
||||||
),
|
), // BlocConsumer
|
||||||
);
|
), // SafeArea
|
||||||
|
); // Scaffold
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isGestionnaire() {
|
bool _isGestionnaire() {
|
||||||
@@ -157,7 +165,7 @@ class _InfoCard extends StatelessWidget {
|
|||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
color: AppColors.textSecondaryLight,
|
color: AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
@@ -187,13 +195,13 @@ Widget _buildStatutBadge(String? statut) {
|
|||||||
color = AppColors.error;
|
color = AppColors.error;
|
||||||
break;
|
break;
|
||||||
case 'EN_ATTENTE':
|
case 'EN_ATTENTE':
|
||||||
color = AppColors.brandGreenLight;
|
color = AppColors.primaryLight;
|
||||||
break;
|
break;
|
||||||
case 'EN_PAIEMENT':
|
case 'EN_PAIEMENT':
|
||||||
color = AppColors.warning;
|
color = AppColors.warning;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
color = AppColors.textSecondaryLight;
|
color = AppColors.textSecondary;
|
||||||
}
|
}
|
||||||
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
|
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
|
||||||
}
|
}
|
||||||
@@ -307,7 +315,7 @@ class _ActionsSection extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
|||||||
@@ -89,11 +89,10 @@ class _AdhesionsPageState extends State<AdhesionsPage>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'ADHÉSIONS',
|
title: 'Adhésions',
|
||||||
backgroundColor: AppColors.surface,
|
moduleGradient: ModuleColors.adhesionsGradient,
|
||||||
foregroundColor: AppColors.textPrimaryLight,
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.add, size: 20),
|
icon: const Icon(Icons.add, size: 20),
|
||||||
@@ -105,9 +104,9 @@ class _AdhesionsPageState extends State<AdhesionsPage>
|
|||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
onTap: _loadTab,
|
onTap: _loadTab,
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
labelColor: AppColors.primaryGreen,
|
labelColor: Colors.white,
|
||||||
unselectedLabelColor: AppColors.textSecondaryLight,
|
unselectedLabelColor: Colors.white70,
|
||||||
indicatorColor: AppColors.primaryGreen,
|
indicatorColor: Colors.white,
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
||||||
tabs: const [
|
tabs: const [
|
||||||
@@ -148,11 +147,17 @@ class _AdhesionsPageState extends State<AdhesionsPage>
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.assignment_outlined, size: 40, color: AppColors.textSecondaryLight),
|
Icon(
|
||||||
|
Icons.assignment_outlined,
|
||||||
|
size: 40,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary,
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Aucune demande d\'adhésion',
|
'Aucune demande d\'adhésion',
|
||||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 16, color: AppColors.textSecondaryLight),
|
style: AppTypography.bodyTextSmall.copyWith(fontSize: 16, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
@@ -249,7 +254,7 @@ class _AdhesionCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildStatutBadge(adhesion.statut),
|
_buildStatutBadge(context, adhesion.statut),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -262,7 +267,7 @@ class _AdhesionCard extends StatelessWidget {
|
|||||||
Text('FRAIS D\'ADHÉSION', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
|
Text('FRAIS D\'ADHÉSION', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
|
||||||
Text(
|
Text(
|
||||||
adhesion.fraisAdhesion != null ? currencyFormat.format(adhesion.fraisAdhesion) : '—',
|
adhesion.fraisAdhesion != null ? currencyFormat.format(adhesion.fraisAdhesion) : '—',
|
||||||
style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primaryGreen),
|
style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primary),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -283,7 +288,7 @@ class _AdhesionCard extends StatelessWidget {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'MEMBRE : ${adhesion.nomMembreComplet.toUpperCase()}',
|
'MEMBRE : ${adhesion.nomMembreComplet.toUpperCase()}',
|
||||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 8, color: AppColors.textSecondaryLight),
|
style: AppTypography.subtitleSmall.copyWith(fontSize: 8, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -291,7 +296,7 @@ class _AdhesionCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatutBadge(String? statut) {
|
Widget _buildStatutBadge(BuildContext context, String? statut) {
|
||||||
Color color;
|
Color color;
|
||||||
switch (statut) {
|
switch (statut) {
|
||||||
case 'APPROUVEE':
|
case 'APPROUVEE':
|
||||||
@@ -303,13 +308,15 @@ class _AdhesionCard extends StatelessWidget {
|
|||||||
color = AppColors.error;
|
color = AppColors.error;
|
||||||
break;
|
break;
|
||||||
case 'EN_ATTENTE':
|
case 'EN_ATTENTE':
|
||||||
color = AppColors.brandGreenLight;
|
color = AppColors.primaryLight;
|
||||||
break;
|
break;
|
||||||
case 'EN_PAIEMENT':
|
case 'EN_PAIEMENT':
|
||||||
color = AppColors.warning;
|
color = AppColors.warning;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
color = AppColors.textSecondaryLight;
|
color = Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary;
|
||||||
}
|
}
|
||||||
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
|
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,14 @@ class UserManagementDetailPage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: const UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'Détail utilisateur',
|
title: 'Détail utilisateur',
|
||||||
|
moduleGradient: ModuleColors.systemeGradient,
|
||||||
),
|
),
|
||||||
body: BlocBuilder<AdminUsersBloc, AdminUsersState>(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: BlocBuilder<AdminUsersBloc, AdminUsersState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is AdminUsersLoading) {
|
if (state is AdminUsersLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@@ -35,11 +38,15 @@ class UserManagementDetailPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(state.message),
|
const Icon(Icons.error_outline, size: 48, color: AppColors.error),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(state.message, textAlign: TextAlign.center),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton(
|
ElevatedButton.icon(
|
||||||
onPressed: () => context.read<AdminUsersBloc>().add(AdminUserDetailRequested(userId)),
|
onPressed: () => context.read<AdminUsersBloc>()
|
||||||
child: const Text('Réessayer'),
|
.add(AdminUserDetailWithRolesRequested(userId)),
|
||||||
|
icon: const Icon(Icons.refresh, size: 16),
|
||||||
|
label: const Text('Réessayer'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -53,9 +60,14 @@ class UserManagementDetailPage extends StatelessWidget {
|
|||||||
userId: userId,
|
userId: userId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// AdminUserRolesUpdated : rechargement en cours — garder un indicateur léger
|
||||||
|
if (state is AdminUserRolesUpdated) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +108,13 @@ class _UserDetailContentState extends State<_UserDetailContent> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
child: RefreshIndicator(
|
||||||
|
color: ModuleColors.systeme,
|
||||||
|
onRefresh: () async => context
|
||||||
|
.read<AdminUsersBloc>()
|
||||||
|
.add(AdminUserDetailWithRolesRequested(widget.userId)),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -122,41 +140,95 @@ class _UserDetailContentState extends State<_UserDetailContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
CoreCard(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.manage_accounts_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant, size: 16),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'RÔLES (SÉLECTION)',
|
'RÔLES',
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
letterSpacing: 1.1,
|
letterSpacing: 1.1,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
'${_selectedRoleNames.length} sélectionné(s)',
|
||||||
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
|
color: AppColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
if (widget.allRoles.isEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.warning_amber_outlined,
|
||||||
|
size: 16, color: AppColors.error),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Impossible de charger les rôles disponibles.',
|
||||||
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
|
color: AppColors.error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () => context.read<AdminUsersBloc>()
|
||||||
|
.add(AdminUserDetailWithRolesRequested(widget.userId)),
|
||||||
|
icon: const Icon(Icons.refresh, size: 14),
|
||||||
|
label: const Text('Recharger'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
...widget.allRoles.map((role) {
|
...widget.allRoles.map((role) {
|
||||||
final selected = _selectedRoleNames.contains(role.name);
|
final selected = _selectedRoleNames.contains(role.name);
|
||||||
return CheckboxListTile(
|
return CheckboxListTile(
|
||||||
title: Text(role.name, style: AppTypography.bodyTextSmall),
|
title: Text(role.name, style: AppTypography.bodyTextSmall),
|
||||||
activeColor: AppColors.primaryGreen,
|
subtitle: role.description != null && role.description!.isNotEmpty
|
||||||
contentPadding: EdgeInsets.zero,
|
? Text(role.description!,
|
||||||
|
style: AppTypography.subtitleSmall.copyWith(fontSize: 10))
|
||||||
|
: null,
|
||||||
|
activeColor: AppColors.primary,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
dense: true,
|
dense: true,
|
||||||
value: selected,
|
value: selected,
|
||||||
onChanged: (v) {
|
onChanged: (v) => setState(() {
|
||||||
setState(() {
|
|
||||||
if (v == true) {
|
if (v == true) {
|
||||||
_selectedRoleNames.add(role.name);
|
_selectedRoleNames.add(role.name);
|
||||||
} else {
|
} else {
|
||||||
_selectedRoleNames.remove(role.name);
|
_selectedRoleNames.remove(role.name);
|
||||||
}
|
}
|
||||||
});
|
}),
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
const SizedBox(height: 12),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (widget.allRoles.isNotEmpty)
|
||||||
UFPrimaryButton(
|
UFPrimaryButton(
|
||||||
label: 'Enregistrer les rôles',
|
label: 'Enregistrer les rôles',
|
||||||
onPressed: () {
|
onPressed: () => context.read<AdminUsersBloc>().add(
|
||||||
context.read<AdminUsersBloc>().add(
|
AdminUserRolesUpdateRequested(
|
||||||
AdminUserRolesUpdateRequested(widget.userId, _selectedRoleNames.toList()),
|
widget.userId, _selectedRoleNames.toList())),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
@@ -171,7 +243,7 @@ class _UserDetailContentState extends State<_UserDetailContent> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Permet à cet utilisateur (ex. admin d\'organisation) de voir « Mes organisations » et d\'accéder au dashboard de l\'organisation.',
|
'Permet à cet utilisateur (ex. admin d\'organisation) de voir « Mes organisations » et d\'accéder au dashboard de l\'organisation.',
|
||||||
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight),
|
style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
@@ -181,13 +253,14 @@ class _UserDetailContentState extends State<_UserDetailContent> {
|
|||||||
icon: const Icon(Icons.business, size: 18),
|
icon: const Icon(Icons.business, size: 18),
|
||||||
label: const Text('Associer à une organisation'),
|
label: const Text('Associer à une organisation'),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: AppColors.primaryGreen,
|
foregroundColor: AppColors.primary,
|
||||||
side: const BorderSide(color: AppColors.primaryGreen),
|
side: const BorderSide(color: AppColors.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
), // SingleChildScrollView
|
||||||
|
), // RefreshIndicator
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:file_picker/file_picker.dart';
|
|||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import '../../../../shared/design_system/tokens/app_colors.dart';
|
import '../../../../shared/design_system/tokens/app_colors.dart';
|
||||||
import '../../../../shared/design_system/tokens/color_tokens.dart';
|
import '../../../../shared/design_system/tokens/color_tokens.dart';
|
||||||
|
import '../../../../shared/design_system/tokens/module_colors.dart';
|
||||||
import '../../../../shared/design_system/tokens/spacing_tokens.dart';
|
import '../../../../shared/design_system/tokens/spacing_tokens.dart';
|
||||||
import '../../../../core/di/injection_container.dart';
|
import '../../../../core/di/injection_container.dart';
|
||||||
import '../../../../core/utils/logger.dart';
|
import '../../../../core/utils/logger.dart';
|
||||||
@@ -82,8 +83,10 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.lightBackground,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
body: Column(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildHeader(),
|
_buildHeader(),
|
||||||
_buildTabBar(),
|
_buildTabBar(),
|
||||||
@@ -99,27 +102,28 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Header harmonisé
|
/// Header harmonisé avec bouton retour
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: SpacingTokens.sm, vertical: SpacingTokens.xs),
|
margin: const EdgeInsets.symmetric(horizontal: SpacingTokens.sm, vertical: SpacingTokens.xs),
|
||||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: const LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: ColorTokens.primaryGradient,
|
colors: ModuleColors.backupGradient,
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusXl),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusXl),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: ColorTokens.primary.withOpacity(0.3),
|
color: ModuleColors.backup.withOpacity(0.3),
|
||||||
blurRadius: 20,
|
blurRadius: 20,
|
||||||
offset: const Offset(0, 8),
|
offset: const Offset(0, 8),
|
||||||
),
|
),
|
||||||
@@ -129,6 +133,19 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
// Bouton retour
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Navigator.of(context).maybePop(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.arrow_back_rounded, color: Colors.white, size: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -137,7 +154,7 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.backup,
|
Icons.backup,
|
||||||
color: Colors.white,
|
color: AppColors.onGradient,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -151,14 +168,14 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: AppColors.onGradient,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Gestion des sauvegardes système',
|
'Gestion des sauvegardes système',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.white.withOpacity(0.8),
|
color: AppColors.onGradient.withOpacity(0.8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -173,7 +190,7 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
onPressed: () => _createBackupNow(),
|
onPressed: () => _createBackupNow(),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.save,
|
Icons.save,
|
||||||
color: Colors.white,
|
color: AppColors.onGradient,
|
||||||
),
|
),
|
||||||
tooltip: 'Sauvegarde immédiate',
|
tooltip: 'Sauvegarde immédiate',
|
||||||
),
|
),
|
||||||
@@ -275,21 +292,21 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: Colors.white, size: 20),
|
Icon(icon, color: AppColors.onGradient, size: 20),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: AppColors.onGradient,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Colors.white.withOpacity(0.8),
|
color: AppColors.onGradient.withOpacity(0.8),
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -303,11 +320,11 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: AppColors.shadow,
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -315,9 +332,9 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
),
|
),
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
labelColor: AppColors.primaryGreen,
|
labelColor: AppColors.primary,
|
||||||
unselectedLabelColor: Colors.grey[600],
|
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
indicatorColor: AppColors.primaryGreen,
|
indicatorColor: AppColors.primary,
|
||||||
indicatorWeight: 3,
|
indicatorWeight: 3,
|
||||||
labelStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12),
|
labelStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12),
|
||||||
tabs: const [
|
tabs: const [
|
||||||
@@ -331,7 +348,15 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
|
|
||||||
/// Onglet sauvegardes
|
/// Onglet sauvegardes
|
||||||
Widget _buildBackupsTab(BackupState state) {
|
Widget _buildBackupsTab(BackupState state) {
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
|
color: ModuleColors.backup,
|
||||||
|
onRefresh: () async {
|
||||||
|
context.read<BackupBloc>()
|
||||||
|
..add(LoadBackups())
|
||||||
|
..add(LoadBackupConfig());
|
||||||
|
},
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -342,6 +367,7 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
const SizedBox(height: 80),
|
const SizedBox(height: 80),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,11 +384,11 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: AppColors.shadow,
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -373,14 +399,14 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.folder, color: AppColors.primaryGreen, size: 20),
|
const Icon(Icons.folder, color: AppColors.primary, size: 20),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'Sauvegardes disponibles',
|
'Sauvegardes disponibles',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.grey[800],
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -398,14 +424,14 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey[50],
|
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
backup['type'] == 'Auto' ? Icons.schedule : Icons.touch_app,
|
backup['type'] == 'Auto' ? Icons.schedule : Icons.touch_app,
|
||||||
color: backup['type'] == 'Auto' ? AppColors.primaryGreen : AppColors.success,
|
color: backup['type'] == 'Auto' ? AppColors.primary : AppColors.success,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -415,17 +441,19 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
backup['name']!,
|
backup['name']!,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: AppColors.textPrimaryLight,
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textPrimaryDark
|
||||||
|
: AppColors.textPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${backup['date']} • ${backup['size']}',
|
'${backup['date']} • ${backup['size']}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey[600],
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -438,7 +466,12 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
const PopupMenuItem(value: 'download', child: Text('Télécharger')),
|
const PopupMenuItem(value: 'download', child: Text('Télécharger')),
|
||||||
const PopupMenuItem(value: 'delete', child: Text('Supprimer')),
|
const PopupMenuItem(value: 'delete', child: Text('Supprimer')),
|
||||||
],
|
],
|
||||||
child: const Icon(Icons.more_vert, color: Colors.grey),
|
child: Icon(
|
||||||
|
Icons.more_vert,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textTertiary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -447,7 +480,12 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
|
|
||||||
/// Onglet planification
|
/// Onglet planification
|
||||||
Widget _buildScheduleTab() {
|
Widget _buildScheduleTab() {
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
|
color: ModuleColors.backup,
|
||||||
|
onRefresh: () async =>
|
||||||
|
context.read<BackupBloc>().add(LoadBackupConfig()),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -456,6 +494,7 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
const SizedBox(height: 80),
|
const SizedBox(height: 80),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,11 +503,11 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: AppColors.shadow,
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -479,14 +518,14 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.schedule, color: AppColors.primaryGreen, size: 20),
|
const Icon(Icons.schedule, color: AppColors.primary, size: 20),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'Configuration automatique',
|
'Configuration automatique',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.grey[800],
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -519,7 +558,12 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
|
|
||||||
/// Onglet restauration
|
/// Onglet restauration
|
||||||
Widget _buildRestoreTab() {
|
Widget _buildRestoreTab() {
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
|
color: ModuleColors.backup,
|
||||||
|
onRefresh: () async =>
|
||||||
|
context.read<BackupBloc>().add(LoadBackups()),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -528,6 +572,7 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
const SizedBox(height: 80),
|
const SizedBox(height: 80),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,11 +581,11 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: AppColors.shadow,
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -551,14 +596,14 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.restore, color: AppColors.primaryGreen, size: 20),
|
const Icon(Icons.restore, color: AppColors.primary, size: 20),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'Options de restauration',
|
'Options de restauration',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.grey[800],
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -568,7 +613,7 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
'Restaurer depuis un fichier',
|
'Restaurer depuis un fichier',
|
||||||
'Importer une sauvegarde externe',
|
'Importer une sauvegarde externe',
|
||||||
Icons.file_upload,
|
Icons.file_upload,
|
||||||
AppColors.primaryGreen,
|
AppColors.primary,
|
||||||
() => _restoreFromFile(),
|
() => _restoreFromFile(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -601,11 +646,11 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||||
Text(subtitle, style: TextStyle(fontSize: 12, color: Colors.grey[600])),
|
Text(subtitle, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Switch(value: value, onChanged: onChanged, activeColor: AppColors.primaryGreen),
|
Switch(value: value, onChanged: onChanged, activeColor: AppColors.primary),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -619,9 +664,9 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey[50],
|
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(color: Colors.grey[300]!),
|
border: Border.all(color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
child: DropdownButtonHideUnderline(
|
child: DropdownButtonHideUnderline(
|
||||||
child: DropdownButton<String>(
|
child: DropdownButton<String>(
|
||||||
@@ -656,11 +701,11 @@ class _BackupPageState extends State<BackupPage>
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(title, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: color)),
|
Text(title, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: color)),
|
||||||
Text(subtitle, style: TextStyle(fontSize: 12, color: Colors.grey[600])),
|
Text(subtitle, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Icon(Icons.arrow_forward_ios, color: Colors.grey[400], size: 16),
|
Icon(Icons.arrow_forward_ios, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
library event_detail_page;
|
library event_detail_page;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../../shared/design_system/tokens/module_colors.dart';
|
||||||
|
import '../../../../shared/design_system/tokens/color_tokens.dart';
|
||||||
import '../../../../shared/design_system/tokens/app_colors.dart';
|
import '../../../../shared/design_system/tokens/app_colors.dart';
|
||||||
|
import '../../../../shared/design_system/components/uf_app_bar.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../../../core/di/injection.dart';
|
import '../../../../core/di/injection.dart';
|
||||||
import '../../bloc/evenements_bloc.dart';
|
import '../../bloc/evenements_bloc.dart';
|
||||||
@@ -51,10 +54,9 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: UFAppBar(
|
||||||
title: const Text('Détails de l\'événement'),
|
title: "Détails de l'événement",
|
||||||
backgroundColor: AppColors.primaryGreen,
|
moduleGradient: ModuleColors.evenementsGradient,
|
||||||
foregroundColor: Colors.white,
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
@@ -62,9 +64,15 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: BlocBuilder<EvenementsBloc, EvenementsState>(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: BlocBuilder<EvenementsBloc, EvenementsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
|
color: ModuleColors.evenements,
|
||||||
|
onRefresh: _loadInscriptionStatus,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -76,9 +84,11 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
const SizedBox(height: 80), // Espace pour le bouton flottant
|
const SizedBox(height: 80), // Espace pour le bouton flottant
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
), // SingleChildScrollView
|
||||||
|
); // RefreshIndicator
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
floatingActionButton: _buildInscriptionButton(context),
|
floatingActionButton: _buildInscriptionButton(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -87,12 +97,9 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: ModuleColors.evenementsGradient,
|
||||||
AppColors.primaryGreen,
|
|
||||||
AppColors.primaryGreen.withOpacity(0.8),
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
@@ -191,7 +198,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: AppColors.primaryGreen, size: 20),
|
Icon(icon, color: ModuleColors.evenements, size: 20),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -201,7 +208,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
label,
|
label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey[600],
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
@@ -261,7 +268,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.location_on, color: AppColors.primaryGreen),
|
const Icon(Icons.location_on, color: ModuleColors.evenements),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -296,7 +303,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
'${widget.evenement.participantsActuels} inscrits',
|
'${widget.evenement.participantsActuels} inscrits',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.grey[600],
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -305,7 +312,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey[100],
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: const Row(
|
child: const Row(
|
||||||
@@ -335,14 +342,14 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
if (!isComplet) {
|
if (!isComplet) {
|
||||||
return FloatingActionButton.extended(
|
return FloatingActionButton.extended(
|
||||||
onPressed: () => _showInscriptionDialog(context, isInscrit),
|
onPressed: () => _showInscriptionDialog(context, isInscrit),
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: ModuleColors.evenements,
|
||||||
icon: const Icon(Icons.check),
|
icon: const Icon(Icons.check),
|
||||||
label: const Text('S\'inscrire'),
|
label: const Text('S\'inscrire'),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const FloatingActionButton.extended(
|
return const FloatingActionButton.extended(
|
||||||
onPressed: null,
|
onPressed: null,
|
||||||
backgroundColor: Colors.grey,
|
backgroundColor: AppColors.textTertiary,
|
||||||
icon: Icon(Icons.block),
|
icon: Icon(Icons.block),
|
||||||
label: Text('Complet'),
|
label: Text('Complet'),
|
||||||
);
|
);
|
||||||
@@ -425,17 +432,17 @@ class _EventDetailPageState extends State<EventDetailPage> {
|
|||||||
Color _getStatutColor(StatutEvenement statut) {
|
Color _getStatutColor(StatutEvenement statut) {
|
||||||
switch (statut) {
|
switch (statut) {
|
||||||
case StatutEvenement.planifie:
|
case StatutEvenement.planifie:
|
||||||
return AppColors.info;
|
return ColorTokens.info;
|
||||||
case StatutEvenement.confirme:
|
case StatutEvenement.confirme:
|
||||||
return AppColors.success;
|
return ColorTokens.success;
|
||||||
case StatutEvenement.enCours:
|
case StatutEvenement.enCours:
|
||||||
return AppColors.warning;
|
return ColorTokens.warningLight;
|
||||||
case StatutEvenement.termine:
|
case StatutEvenement.termine:
|
||||||
return AppColors.textSecondaryLight;
|
return ColorTokens.textSecondary;
|
||||||
case StatutEvenement.annule:
|
case StatutEvenement.annule:
|
||||||
return AppColors.error;
|
return ColorTokens.error;
|
||||||
case StatutEvenement.reporte:
|
case StatutEvenement.reporte:
|
||||||
return AppColors.brandGreen;
|
return ColorTokens.warning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -71,18 +71,39 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
}
|
}
|
||||||
if (state is NotificationsError) {
|
if (state is NotificationsError) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
|
SnackBar(content: Text(state.message), backgroundColor: AppColors.error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.lightBackground,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
body: Column(
|
appBar: UFAppBar(
|
||||||
children: [
|
title: 'Notifications',
|
||||||
_buildHeader(),
|
moduleGradient: ModuleColors.notificationsGradient,
|
||||||
_buildTabBar(),
|
actions: [
|
||||||
Expanded(
|
IconButton(
|
||||||
|
onPressed: () => _markAllAsRead(),
|
||||||
|
icon: const Icon(Icons.done_all, size: 20),
|
||||||
|
tooltip: 'Tout marquer comme lu',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
isScrollable: true,
|
||||||
|
labelColor: Colors.white,
|
||||||
|
unselectedLabelColor: Colors.white70,
|
||||||
|
indicatorColor: Colors.white,
|
||||||
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
|
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
||||||
|
tabs: const [
|
||||||
|
Tab(child: Text('FLUX')),
|
||||||
|
Tab(child: Text('RÉGLAGES')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
@@ -91,106 +112,13 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Header harmonisé avec le design system
|
// _buildHeader() et _buildTabBar() supprimés : gradient + TabBar migrés
|
||||||
Widget _buildHeader() {
|
// dans UFAppBar + UFAppBar.bottom (pattern Adhésions).
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [AppColors.brandGreen, AppColors.primaryGreen],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color(0x1A000000),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.2),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.notifications_none,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'NOTIFICATIONS',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
letterSpacing: 1.1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Restez connecté à votre réseau',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: Colors.white.withOpacity(0.9),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => _markAllAsRead(),
|
|
||||||
icon: const Icon(Icons.done_all, color: Colors.white, size: 20),
|
|
||||||
tooltip: 'Tout marquer comme lu',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Barre d'onglets
|
|
||||||
Widget _buildTabBar() {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.lightSurface,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: TabBar(
|
|
||||||
controller: _tabController,
|
|
||||||
labelColor: AppColors.primaryGreen,
|
|
||||||
unselectedLabelColor: AppColors.textSecondaryLight,
|
|
||||||
indicator: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
labelStyle: AppTypography.actionText.copyWith(fontSize: 12),
|
|
||||||
unselectedLabelStyle: AppTypography.bodyTextSmall.copyWith(fontSize: 12),
|
|
||||||
tabs: const [
|
|
||||||
Tab(text: 'FLUX'),
|
|
||||||
Tab(text: 'RÉGLAGES'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Onglet des notifications
|
/// Onglet des notifications
|
||||||
Widget _buildNotificationsTab() {
|
Widget _buildNotificationsTab() {
|
||||||
@@ -236,7 +164,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
child: Switch(
|
child: Switch(
|
||||||
value: _showOnlyUnread,
|
value: _showOnlyUnread,
|
||||||
onChanged: (value) => setState(() => _showOnlyUnread = value),
|
onChanged: (value) => setState(() => _showOnlyUnread = value),
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: AppColors.primary,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -262,22 +190,25 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
|
|
||||||
/// Chip de filtre
|
/// Chip de filtre
|
||||||
Widget _buildFilterChip(String label, bool isSelected) {
|
Widget _buildFilterChip(String label, bool isSelected) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final borderInactive= isDark ? AppColors.borderDark : AppColors.border;
|
||||||
|
final textInactive = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => setState(() => _selectedFilter = label),
|
onTap: () => setState(() => _selectedFilter = label),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected ? AppColors.primaryGreen.withOpacity(0.1) : Colors.transparent,
|
color: isSelected ? AppColors.primary.withOpacity(isDark ? 0.2 : 0.1) : Colors.transparent,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder,
|
color: isSelected ? AppColors.primary : borderInactive,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
label.toUpperCase(),
|
label.toUpperCase(),
|
||||||
style: AppTypography.badgeText.copyWith(
|
style: AppTypography.badgeText.copyWith(
|
||||||
color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight,
|
color: isSelected ? AppColors.primary : textInactive,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||||
),
|
),
|
||||||
@@ -290,17 +221,24 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
Widget _buildNotificationsList() {
|
Widget _buildNotificationsList() {
|
||||||
final notifications = _getFilteredNotifications();
|
final notifications = _getFilteredNotifications();
|
||||||
|
|
||||||
if (notifications.isEmpty) {
|
return RefreshIndicator(
|
||||||
return _buildEmptyState();
|
color: ModuleColors.notifications,
|
||||||
}
|
onRefresh: () async =>
|
||||||
|
context.read<NotificationsBloc>().add(const LoadNotifications()),
|
||||||
return ListView.builder(
|
child: notifications.isEmpty
|
||||||
|
? SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
child: _buildEmptyState(),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
itemCount: notifications.length,
|
itemCount: notifications.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final notification = notifications[index];
|
final notification = notifications[index];
|
||||||
return _buildNotificationCard(notification);
|
return _buildNotificationCard(notification);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,10 +248,10 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
Icon(
|
||||||
Icons.notifications_none_outlined,
|
Icons.notifications_none_outlined,
|
||||||
size: 40,
|
size: 40,
|
||||||
color: AppColors.textSecondaryLight,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
@@ -338,6 +276,10 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
final isRead = notification['isRead'] as bool;
|
final isRead = notification['isRead'] as bool;
|
||||||
final type = notification['type'] as String;
|
final type = notification['type'] as String;
|
||||||
final color = _getNotificationColor(type);
|
final color = _getNotificationColor(type);
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
|
||||||
|
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
|
final bgRead = isDark ? AppColors.surfaceVariantDark : AppColors.surface;
|
||||||
|
|
||||||
return CoreCard(
|
return CoreCard(
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
@@ -349,8 +291,8 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
MiniAvatar(
|
MiniAvatar(
|
||||||
fallbackText: _getNotificationIconSource(type),
|
fallbackText: _getNotificationIconSource(type),
|
||||||
size: 32,
|
size: 32,
|
||||||
backgroundColor: isRead ? AppColors.lightSurface : color.withOpacity(0.1),
|
backgroundColor: isRead ? bgRead : color.withOpacity(isDark ? 0.2 : 0.1),
|
||||||
iconColor: isRead ? AppColors.textSecondaryLight : color,
|
iconColor: isRead ? textSecondary : color,
|
||||||
isIcon: true,
|
isIcon: true,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -366,7 +308,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
notification['title'].toString().toUpperCase(),
|
notification['title'].toString().toUpperCase(),
|
||||||
style: AppTypography.actionText.copyWith(
|
style: AppTypography.actionText.copyWith(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: isRead ? AppColors.textSecondaryLight : AppColors.textPrimaryLight,
|
color: isRead ? textSecondary : textPrimary,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -382,7 +324,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
Text(
|
Text(
|
||||||
notification['message'],
|
notification['message'],
|
||||||
style: AppTypography.bodyTextSmall.copyWith(
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
color: isRead ? AppColors.textSecondaryLight : AppColors.textPrimaryLight,
|
color: isRead ? textSecondary : textPrimary,
|
||||||
fontWeight: isRead ? FontWeight.normal : FontWeight.w500,
|
fontWeight: isRead ? FontWeight.normal : FontWeight.w500,
|
||||||
),
|
),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
@@ -392,8 +334,8 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
InfoBadge(
|
InfoBadge(
|
||||||
text: 'NOUVEAU',
|
text: 'NOUVEAU',
|
||||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
backgroundColor: AppColors.primary.withOpacity(0.1),
|
||||||
textColor: AppColors.primaryGreen,
|
textColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -413,7 +355,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
child: Text('Supprimer', style: AppTypography.bodyTextSmall.copyWith(color: AppColors.error)),
|
child: Text('Supprimer', style: AppTypography.bodyTextSmall.copyWith(color: AppColors.error)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: const Icon(Icons.more_vert, size: 14, color: AppColors.textSecondaryLight),
|
child: Icon(Icons.more_vert, size: 14, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -545,7 +487,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
@@ -604,7 +546,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
child: Switch(
|
child: Switch(
|
||||||
value: value,
|
value: value,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: AppColors.primary,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -659,15 +601,15 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
Color _getNotificationColor(String type) {
|
Color _getNotificationColor(String type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'Membres':
|
case 'Membres':
|
||||||
return AppColors.primaryGreen;
|
return AppColors.primary;
|
||||||
case 'Événements':
|
case 'Événements':
|
||||||
return AppColors.success;
|
return AppColors.success;
|
||||||
case 'Organisations':
|
case 'Organisations':
|
||||||
return AppColors.primaryGreen;
|
return AppColors.primary;
|
||||||
case 'Système':
|
case 'Système':
|
||||||
return AppColors.warning;
|
return AppColors.warning;
|
||||||
default:
|
default:
|
||||||
return AppColors.textSecondaryLight;
|
return AppColors.textSecondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,7 +698,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text('ANNULER', style: AppTypography.actionText.copyWith(color: AppColors.textSecondaryLight)),
|
child: Text('ANNULER', style: AppTypography.actionText.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -769,7 +711,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
});
|
});
|
||||||
_showSuccessSnackBar('Flux marqué comme lu');
|
_showSuccessSnackBar('Flux marqué comme lu');
|
||||||
},
|
},
|
||||||
child: Text('CONFIRMER', style: AppTypography.actionText.copyWith(color: AppColors.primaryGreen)),
|
child: Text('CONFIRMER', style: AppTypography.actionText.copyWith(color: AppColors.primary)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -797,8 +739,8 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
_tabController.animateTo(1); // Aller à l'onglet Préférences
|
_tabController.animateTo(1); // Aller à l'onglet Préférences
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: const Text('Voir les préférences'),
|
child: const Text('Voir les préférences'),
|
||||||
),
|
),
|
||||||
@@ -831,8 +773,8 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
_showSuccessSnackBar('Notification supprimée');
|
_showSuccessSnackBar('Notification supprimée');
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: AppColors.error,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onError,
|
||||||
),
|
),
|
||||||
child: const Text('Supprimer'),
|
child: const Text('Supprimer'),
|
||||||
),
|
),
|
||||||
@@ -884,7 +826,7 @@ class _NotificationsPageState extends State<NotificationsPage>
|
|||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.warning,
|
backgroundColor: AppColors.warning,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: Text(notification['actionText']),
|
child: Text(notification['actionText']),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
library org_selector_page;
|
library org_selector_page;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../../shared/design_system/tokens/module_colors.dart';
|
||||||
|
import '../../../../shared/design_system/tokens/app_colors.dart';
|
||||||
|
import '../../../../shared/design_system/components/uf_app_bar.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../bloc/org_switcher_bloc.dart';
|
import '../../bloc/org_switcher_bloc.dart';
|
||||||
import '../../data/models/org_switcher_entry.dart';
|
import '../../data/models/org_switcher_entry.dart';
|
||||||
@@ -30,12 +33,14 @@ class _OrgSelectorPageState extends State<OrgSelectorPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: UFAppBar(
|
||||||
title: const Text('Choisir une organisation'),
|
title: 'Choisir une organisation',
|
||||||
|
moduleGradient: ModuleColors.organisationsGradient,
|
||||||
automaticallyImplyLeading: !widget.required,
|
automaticallyImplyLeading: !widget.required,
|
||||||
elevation: 0,
|
|
||||||
),
|
),
|
||||||
body: BlocConsumer<OrgSwitcherBloc, OrgSwitcherState>(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: BlocConsumer<OrgSwitcherBloc, OrgSwitcherState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is OrgSwitcherLoaded && widget.required && state.active != null) {
|
if (state is OrgSwitcherLoaded && widget.required && state.active != null) {
|
||||||
// Une org a été auto-sélectionnée, on peut continuer
|
// Une org a été auto-sélectionnée, on peut continuer
|
||||||
@@ -71,6 +76,7 @@ class _OrgSelectorPageState extends State<OrgSelectorPage> {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +109,12 @@ class _OrgList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: RefreshIndicator(
|
||||||
|
color: ModuleColors.organisations,
|
||||||
|
onRefresh: () async =>
|
||||||
|
context.read<OrgSwitcherBloc>().add(const OrgSwitcherLoadRequested()),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
itemCount: organisations.length,
|
itemCount: organisations.length,
|
||||||
separatorBuilder: (_, __) => const SizedBox(height: 8),
|
separatorBuilder: (_, __) => const SizedBox(height: 8),
|
||||||
@@ -116,7 +127,8 @@ class _OrgList extends StatelessWidget {
|
|||||||
onTap: () => onSelect(org),
|
onTap: () => onSelect(org),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
), // ListView.separated
|
||||||
|
), // RefreshIndicator
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -199,8 +211,8 @@ class _OrgCard extends StatelessWidget {
|
|||||||
_Chip(
|
_Chip(
|
||||||
org.statutMembre!,
|
org.statutMembre!,
|
||||||
color: org.statutMembre == 'ACTIF'
|
color: org.statutMembre == 'ACTIF'
|
||||||
? Colors.green.shade700
|
? AppColors.success
|
||||||
: Colors.orange.shade700,
|
: AppColors.warning,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -268,7 +280,7 @@ class _ErrorView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.cloud_off, size: 56, color: Colors.grey),
|
Icon(Icons.cloud_off, size: 56, color: AppColors.textTertiary),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(message, textAlign: TextAlign.center),
|
Text(message, textAlign: TextAlign.center),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -295,7 +307,7 @@ class _EmptyView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.business_outlined, size: 56, color: Colors.grey),
|
Icon(Icons.business_outlined, size: 56, color: AppColors.textTertiary),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Vous n\'êtes membre d\'aucune organisation active.',
|
'Vous n\'êtes membre d\'aucune organisation active.',
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.lightBackground,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: const UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'TYPES D\'ORGANISATIONS',
|
title: "Types d'Organisations",
|
||||||
|
moduleGradient: ModuleColors.organisationsGradient,
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
),
|
),
|
||||||
body: BlocConsumer<OrgTypesBloc, OrgTypesState>(
|
body: BlocConsumer<OrgTypesBloc, OrgTypesState>(
|
||||||
@@ -41,7 +42,7 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(state.message),
|
content: Text(state.message),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: AppColors.success,
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -49,7 +50,7 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(state.message),
|
content: Text(state.message),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: AppColors.error,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -86,7 +87,7 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
if (!isSuperAdmin) return const SizedBox.shrink();
|
if (!isSuperAdmin) return const SizedBox.shrink();
|
||||||
return FloatingActionButton.small(
|
return FloatingActionButton.small(
|
||||||
onPressed: () => _showTypeForm(context, null),
|
onPressed: () => _showTypeForm(context, null),
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
child: const Icon(Icons.add, color: Colors.white),
|
child: const Icon(Icons.add, color: Colors.white),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -103,10 +104,13 @@ 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.primary;
|
||||||
final authState = context.read<AuthBloc>().state;
|
final authState = context.read<AuthBloc>().state;
|
||||||
final isSuperAdmin = authState is AuthAuthenticated &&
|
final isSuperAdmin = authState is AuthAuthenticated &&
|
||||||
authState.effectiveRole == UserRole.superAdmin;
|
authState.effectiveRole == UserRole.superAdmin;
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
|
||||||
|
final textSecondary= isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
|
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: isOperating ? 0.6 : 1.0,
|
opacity: isOperating ? 0.6 : 1.0,
|
||||||
@@ -145,28 +149,28 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
),
|
),
|
||||||
if (type.estDefaut) ...[
|
if (type.estDefaut) ...[
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
const Icon(Icons.star_rounded, size: 13, color: Color(0xFFF59E0B)),
|
const Icon(Icons.star_rounded, size: 13, color: AppColors.warning),
|
||||||
],
|
],
|
||||||
if (type.estSysteme) ...[
|
if (type.estSysteme) ...[
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Icon(Icons.lock_outline, size: 12, color: Colors.grey[500]),
|
Icon(Icons.lock_outline, size: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
type.libelle,
|
type.libelle,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
color: AppColors.textPrimaryLight,
|
color: textPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (type.description != null && type.description!.isNotEmpty) ...[
|
if (type.description != null && type.description!.isNotEmpty) ...[
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
type.description!,
|
type.description!,
|
||||||
style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight),
|
style: TextStyle(fontSize: 11, color: textSecondary),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@@ -177,14 +181,14 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
if (isSuperAdmin && !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.textSecondary,
|
||||||
onPressed: () => _showTypeForm(context, type),
|
onPressed: () => _showTypeForm(context, type),
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.delete_outline, size: 16),
|
icon: const Icon(Icons.delete_outline, size: 16),
|
||||||
color: Colors.red[400],
|
color: AppColors.error,
|
||||||
onPressed: () => _confirmDelete(context, type),
|
onPressed: () => _confirmDelete(context, type),
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
@@ -208,18 +212,24 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.category_outlined, size: 48, color: Colors.grey[400]),
|
Icon(Icons.category_outlined, size: 48, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const Text(
|
Text(
|
||||||
'Aucun type défini',
|
'Aucun type défini',
|
||||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.textPrimaryLight),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textPrimaryDark
|
||||||
|
: AppColors.textPrimary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(
|
Text(
|
||||||
isSuperAdmin
|
isSuperAdmin
|
||||||
? 'Créez votre premier type d\'organisation'
|
? 'Créez votre premier type d\'organisation'
|
||||||
: 'Aucun type d\'organisation disponible',
|
: 'Aucun type d\'organisation disponible',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
if (isSuperAdmin) ...[
|
if (isSuperAdmin) ...[
|
||||||
@@ -229,8 +239,8 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
icon: const Icon(Icons.add, size: 16),
|
icon: const Icon(Icons.add, size: 16),
|
||||||
label: const Text('Créer un type'),
|
label: const Text('Créer un type'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
),
|
),
|
||||||
@@ -249,17 +259,26 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.error_outline, size: 40, color: Colors.red[400]),
|
Icon(Icons.error_outline, size: 40, color: AppColors.error),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const Text('Erreur de chargement', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w700)),
|
const Text('Erreur de chargement', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w700)),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(message, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight), textAlign: TextAlign.center),
|
Text(
|
||||||
|
message,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => context.read<OrgTypesBloc>().add(const LoadOrgTypes()),
|
onPressed: () => context.read<OrgTypesBloc>().add(const LoadOrgTypes()),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
),
|
),
|
||||||
@@ -303,8 +322,8 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
|
|||||||
bloc.add(DeleteOrgTypeEvent(type.id));
|
bloc.add(DeleteOrgTypeEvent(type.id));
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: AppColors.error,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onError,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||||
),
|
),
|
||||||
@@ -370,12 +389,16 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isEdit = widget.existing != null;
|
final isEdit = widget.existing != null;
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
|
||||||
|
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
|
final onSurface = Theme.of(context).colorScheme.onSurface;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||||
child: Form(
|
child: Form(
|
||||||
@@ -389,13 +412,13 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: 36,
|
width: 36,
|
||||||
height: 4,
|
height: 4,
|
||||||
decoration: BoxDecoration(color: Colors.grey[300], borderRadius: BorderRadius.circular(2)),
|
decoration: BoxDecoration(color: Theme.of(context).colorScheme.outline, borderRadius: BorderRadius.circular(2)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
Text(
|
Text(
|
||||||
isEdit ? 'Modifier le type' : 'Nouveau type d\'organisation',
|
isEdit ? 'Modifier le type' : 'Nouveau type d\'organisation',
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: AppColors.textPrimaryLight),
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: textPrimary),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
|
|
||||||
@@ -446,7 +469,7 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Color picker
|
// Color picker
|
||||||
const Text('Couleur', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.textSecondaryLight)),
|
Text('Couleur', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: textSecondary)),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
@@ -462,7 +485,7 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color,
|
color: color,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: selected ? Border.all(color: Colors.black, width: 2) : null,
|
border: selected ? Border.all(color: onSurface, width: 2) : null,
|
||||||
),
|
),
|
||||||
child: selected ? const Icon(Icons.check, size: 14, color: Colors.white) : null,
|
child: selected ? const Icon(Icons.check, size: 14, color: Colors.white) : null,
|
||||||
),
|
),
|
||||||
@@ -477,8 +500,8 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
|
|||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _submit,
|
onPressed: _submit,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import '../../bloc/organizations_event.dart';
|
|||||||
import '../../bloc/organizations_state.dart';
|
import '../../bloc/organizations_state.dart';
|
||||||
import '../../domain/repositories/organization_repository.dart';
|
import '../../domain/repositories/organization_repository.dart';
|
||||||
import 'edit_organization_page.dart';
|
import 'edit_organization_page.dart';
|
||||||
import '../../../../shared/design_system/tokens/app_colors.dart';
|
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||||
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
|
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
|
||||||
import '../../../../features/authentication/data/models/user_role.dart';
|
import '../../../../features/authentication/data/models/user_role.dart';
|
||||||
|
|
||||||
|
|
||||||
class OrganizationDetailPage extends StatefulWidget {
|
class OrganizationDetailPage extends StatefulWidget {
|
||||||
final String organizationId;
|
final String organizationId;
|
||||||
|
|
||||||
@@ -44,12 +45,10 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.lightBackground,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: AppBar(
|
appBar: UFAppBar(
|
||||||
backgroundColor: AppColors.brandGreen,
|
title: 'Détail Organisation',
|
||||||
foregroundColor: Colors.white,
|
moduleGradient: ModuleColors.organisationsGradient,
|
||||||
title: const Text('Détail Organisation'),
|
|
||||||
elevation: 0,
|
|
||||||
actions: [
|
actions: [
|
||||||
Builder(builder: (ctx) {
|
Builder(builder: (ctx) {
|
||||||
final authState = ctx.read<AuthBloc>().state;
|
final authState = ctx.read<AuthBloc>().state;
|
||||||
@@ -67,15 +66,17 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
return PopupMenuButton<String>(
|
return PopupMenuButton<String>(
|
||||||
onSelected: _handleMenuAction,
|
onSelected: _handleMenuAction,
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
const PopupMenuItem(value: 'activate', child: Row(children: [Icon(Icons.check_circle, color: AppColors.success), SizedBox(width: 8), Text('Activer')])),
|
const PopupMenuItem(value: 'activate', child: Row(children: [Icon(Icons.check_circle, color: AppColors.success), SizedBox(width: SpacingTokens.md), Text('Activer')])),
|
||||||
const PopupMenuItem(value: 'deactivate', child: Row(children: [Icon(Icons.pause_circle, color: AppColors.textSecondaryLight), SizedBox(width: 8), Text('Désactiver')])),
|
const PopupMenuItem(value: 'deactivate', child: Row(children: [Icon(Icons.pause_circle, color: AppColors.textSecondary), SizedBox(width: SpacingTokens.md), Text('Désactiver')])),
|
||||||
const PopupMenuItem(value: 'delete', child: Row(children: [Icon(Icons.delete, color: Colors.red), SizedBox(width: 8), Text('Supprimer')])),
|
const PopupMenuItem(value: 'delete', child: Row(children: [Icon(Icons.delete, color: AppColors.error), SizedBox(width: SpacingTokens.md), Text('Supprimer')])),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: BlocBuilder<OrganizationsBloc, OrganizationsState>(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: BlocBuilder<OrganizationsBloc, OrganizationsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is OrganizationLoading) return _buildLoading();
|
if (state is OrganizationLoading) return _buildLoading();
|
||||||
if (state is OrganizationLoaded) return _buildContent(state.organization);
|
if (state is OrganizationLoaded) return _buildContent(state.organization);
|
||||||
@@ -83,74 +84,82 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
return _buildEmpty();
|
return _buildEmpty();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(OrganizationModel org) {
|
Widget _buildContent(OrganizationModel org) {
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
padding: const EdgeInsets.all(12),
|
color: ModuleColors.organisations,
|
||||||
|
onRefresh: () async => context
|
||||||
|
.read<OrganizationsBloc>()
|
||||||
|
.add(LoadOrganizationById(widget.organizationId)),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(SpacingTokens.lg),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildHeaderCard(org),
|
_buildHeaderCard(org),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
_buildInfoCard(org),
|
_buildInfoCard(org),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
_buildContactCard(org),
|
_buildContactCard(org),
|
||||||
if (_hasAddress(org)) ...[const SizedBox(height: 8), _buildAddressCard(org)],
|
if (_hasAddress(org)) ...[const SizedBox(height: SpacingTokens.md), _buildAddressCard(org)],
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
_buildStatsCard(org),
|
_buildStatsCard(org),
|
||||||
if (_hasFinances(org)) ...[const SizedBox(height: 8), _buildFinancesCard(org)],
|
if (_hasFinances(org)) ...[const SizedBox(height: SpacingTokens.md), _buildFinancesCard(org)],
|
||||||
if (_hasMission(org)) ...[const SizedBox(height: 8), _buildMissionCard(org)],
|
if (_hasMission(org)) ...[const SizedBox(height: SpacingTokens.md), _buildMissionCard(org)],
|
||||||
if (_hasSupplementary(org)) ...[const SizedBox(height: 8), _buildSupplementaryCard(org)],
|
if (_hasSupplementary(org)) ...[const SizedBox(height: SpacingTokens.md), _buildSupplementaryCard(org)],
|
||||||
if (org.notes?.isNotEmpty == true) ...[const SizedBox(height: 8), _buildNotesCard(org)],
|
if (org.notes?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildNotesCard(org)],
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
_buildActionsCard(org),
|
_buildActionsCard(org),
|
||||||
],
|
],
|
||||||
),
|
), // Column
|
||||||
);
|
), // SingleChildScrollView
|
||||||
|
); // RefreshIndicator
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Header ─────────────────────────────────────────────────────────────────
|
// ── Header ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Widget _buildHeaderCard(OrganizationModel org) {
|
Widget _buildHeaderCard(OrganizationModel org) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(SpacingTokens.lg),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.brandGreen, AppColors.primaryGreen]),
|
gradient: const LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ModuleColors.organisationsDark, ModuleColors.organisations]),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))],
|
boxShadow: [BoxShadow(color: AppColors.shadowMedium, blurRadius: 8, offset: const Offset(0, 2))],
|
||||||
),
|
),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(SpacingTokens.lg),
|
||||||
decoration: BoxDecoration(color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8)),
|
decoration: BoxDecoration(color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd)),
|
||||||
child: const Icon(Icons.business_outlined, size: 24, color: Colors.white),
|
child: const Icon(Icons.business_outlined, size: 24, color: Colors.white),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: SpacingTokens.xl),
|
||||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(org.nom, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white)),
|
Text(org.nom, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white)),
|
||||||
if (org.nomCourt?.isNotEmpty == true) ...[
|
if (org.nomCourt?.isNotEmpty == true) ...[
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: SpacingTokens.xs),
|
||||||
Text(org.nomCourt!, style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(0.9))),
|
Text(org.nomCourt!, style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(0.9))),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
_buildWhiteBadge(org.typeOrganisationLibelle ?? org.typeOrganisation),
|
_buildWhiteBadge(org.typeOrganisationLibelle ?? org.typeOrganisation),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
_buildWhiteBadge(org.statutLibelle ?? org.statut.displayName),
|
_buildWhiteBadge(org.statutLibelle ?? org.statut.displayName),
|
||||||
]),
|
]),
|
||||||
])),
|
])),
|
||||||
]),
|
]),
|
||||||
if (org.description?.isNotEmpty == true) ...[
|
if (org.description?.isNotEmpty == true) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: SpacingTokens.lg),
|
||||||
Text(org.description!, style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(0.9), height: 1.4)),
|
Text(org.description!, style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(0.9), height: 1.4)),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
_buildBoolBadge(Icons.public, 'Public', org.organisationPublique),
|
_buildBoolBadge(Icons.public, 'Public', org.organisationPublique),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
_buildBoolBadge(Icons.person_add, 'Ouvert', org.accepteNouveauxMembres),
|
_buildBoolBadge(Icons.person_add, 'Ouvert', org.accepteNouveauxMembres),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
@@ -159,17 +168,19 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
|
|
||||||
Widget _buildWhiteBadge(String text) {
|
Widget _buildWhiteBadge(String text) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.md, vertical: 3),
|
||||||
decoration: BoxDecoration(color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(10)),
|
decoration: BoxDecoration(color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(SpacingTokens.radiusLg)),
|
||||||
child: Text(text, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.white)),
|
child: Text(text, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.white)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBoolBadge(IconData icon, String label, bool value) {
|
Widget _buildBoolBadge(IconData icon, String label, bool value) {
|
||||||
|
// Sur gradient : vert clair lisible pour TRUE, blanc translucide pour FALSE
|
||||||
|
final color = value ? AppColors.successUI : Colors.white.withOpacity(0.6);
|
||||||
return Row(mainAxisSize: MainAxisSize.min, children: [
|
return Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
Icon(icon, size: 12, color: value ? Colors.greenAccent : Colors.white60),
|
Icon(icon, size: 12, color: color),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: SpacingTokens.sm),
|
||||||
Text('$label: ${value ? 'Oui' : 'Non'}', style: TextStyle(fontSize: 11, color: value ? Colors.greenAccent : Colors.white60)),
|
Text('$label: ${value ? 'Oui' : 'Non'}', style: TextStyle(fontSize: 11, color: color)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,14 +189,14 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
Widget _buildInfoCard(OrganizationModel org) {
|
Widget _buildInfoCard(OrganizationModel org) {
|
||||||
return _buildCard('Informations générales', Icons.info_outline, [
|
return _buildCard('Informations générales', Icons.info_outline, [
|
||||||
if (org.dateFondation != null) _buildInfoRow(Icons.cake, 'Date de fondation', _formatDate(org.dateFondation)),
|
if (org.dateFondation != null) _buildInfoRow(Icons.cake, 'Date de fondation', _formatDate(org.dateFondation)),
|
||||||
if (org.dateFondation != null) const SizedBox(height: 10),
|
if (org.dateFondation != null) const SizedBox(height: SpacingTokens.md),
|
||||||
if (org.ancienneteAnnees > 0) _buildInfoRow(Icons.access_time, 'Ancienneté', '${org.ancienneteAnnees} an(s)'),
|
if (org.ancienneteAnnees > 0) _buildInfoRow(Icons.access_time, 'Ancienneté', '${org.ancienneteAnnees} an(s)'),
|
||||||
if (org.ancienneteAnnees > 0) const SizedBox(height: 10),
|
if (org.ancienneteAnnees > 0) const SizedBox(height: SpacingTokens.md),
|
||||||
if (org.numeroEnregistrement?.isNotEmpty == true) _buildInfoRow(Icons.assignment, 'N° d\'enregistrement', org.numeroEnregistrement!),
|
if (org.numeroEnregistrement?.isNotEmpty == true) _buildInfoRow(Icons.assignment, 'N° d\'enregistrement', org.numeroEnregistrement!),
|
||||||
if (org.numeroEnregistrement?.isNotEmpty == true) const SizedBox(height: 10),
|
if (org.numeroEnregistrement?.isNotEmpty == true) const SizedBox(height: SpacingTokens.md),
|
||||||
_buildInfoRow(Icons.calendar_today, 'Créé dans le système', _formatDate(org.dateCreation)),
|
_buildInfoRow(Icons.calendar_today, 'Créé dans le système', _formatDate(org.dateCreation)),
|
||||||
if (org.dateModification != null) ...[
|
if (org.dateModification != null) ...[
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
_buildInfoRow(Icons.edit_calendar, 'Dernière modification', _formatDate(org.dateModification)),
|
_buildInfoRow(Icons.edit_calendar, 'Dernière modification', _formatDate(org.dateModification)),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
@@ -204,11 +215,11 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
|
|
||||||
return _buildCard('Contact', Icons.contact_phone, [
|
return _buildCard('Contact', Icons.contact_phone, [
|
||||||
if (org.email?.isNotEmpty == true) _buildContactRow(Icons.email, 'Email', org.email!, onTap: () => _launchEmail(org.email!)),
|
if (org.email?.isNotEmpty == true) _buildContactRow(Icons.email, 'Email', org.email!, onTap: () => _launchEmail(org.email!)),
|
||||||
if (org.emailSecondaire?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildContactRow(Icons.alternate_email, 'Email secondaire', org.emailSecondaire!, onTap: () => _launchEmail(org.emailSecondaire!))],
|
if (org.emailSecondaire?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildContactRow(Icons.alternate_email, 'Email secondaire', org.emailSecondaire!, onTap: () => _launchEmail(org.emailSecondaire!))],
|
||||||
if (org.telephone?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildContactRow(Icons.phone, 'Téléphone', org.telephone!, onTap: () => _launchPhone(org.telephone!))],
|
if (org.telephone?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildContactRow(Icons.phone, 'Téléphone', org.telephone!, onTap: () => _launchPhone(org.telephone!))],
|
||||||
if (org.telephoneSecondaire?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildContactRow(Icons.phone_forwarded, 'Téléphone secondaire', org.telephoneSecondaire!, onTap: () => _launchPhone(org.telephoneSecondaire!))],
|
if (org.telephoneSecondaire?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildContactRow(Icons.phone_forwarded, 'Téléphone secondaire', org.telephoneSecondaire!, onTap: () => _launchPhone(org.telephoneSecondaire!))],
|
||||||
if (org.siteWeb?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildContactRow(Icons.web, 'Site web', org.siteWeb!, onTap: () => _launchWebsite(org.siteWeb!))],
|
if (org.siteWeb?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildContactRow(Icons.web, 'Site web', org.siteWeb!, onTap: () => _launchWebsite(org.siteWeb!))],
|
||||||
if (org.reseauxSociaux?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildInfoRow(Icons.share, 'Réseaux sociaux', org.reseauxSociaux!)],
|
if (org.reseauxSociaux?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.share, 'Réseaux sociaux', org.reseauxSociaux!)],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,10 +232,10 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
Widget _buildAddressCard(OrganizationModel org) {
|
Widget _buildAddressCard(OrganizationModel org) {
|
||||||
return _buildCard('Localisation', Icons.location_on, [
|
return _buildCard('Localisation', Icons.location_on, [
|
||||||
if (org.adresse?.isNotEmpty == true) _buildInfoRow(Icons.location_on, 'Adresse', org.adresse!),
|
if (org.adresse?.isNotEmpty == true) _buildInfoRow(Icons.location_on, 'Adresse', org.adresse!),
|
||||||
if (org.adresse?.isNotEmpty == true && (org.ville?.isNotEmpty == true)) const SizedBox(height: 10),
|
if (org.adresse?.isNotEmpty == true && (org.ville?.isNotEmpty == true)) const SizedBox(height: SpacingTokens.md),
|
||||||
if (org.ville?.isNotEmpty == true) _buildInfoRow(Icons.location_city, 'Ville', '${org.ville!}${org.codePostal?.isNotEmpty == true ? ' — ${org.codePostal}' : ''}'),
|
if (org.ville?.isNotEmpty == true) _buildInfoRow(Icons.location_city, 'Ville', '${org.ville!}${org.codePostal?.isNotEmpty == true ? ' — ${org.codePostal}' : ''}'),
|
||||||
if (org.region?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildInfoRow(Icons.map, 'Région', org.region!)],
|
if (org.region?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.map, 'Région', org.region!)],
|
||||||
if (org.pays?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildInfoRow(Icons.flag, 'Pays', org.pays!)],
|
if (org.pays?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.flag, 'Pays', org.pays!)],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,10 +244,10 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
Widget _buildStatsCard(OrganizationModel org) {
|
Widget _buildStatsCard(OrganizationModel org) {
|
||||||
return _buildCard('Statistiques', Icons.bar_chart, [
|
return _buildCard('Statistiques', Icons.bar_chart, [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Expanded(child: _buildStatItem(Icons.people, 'Membres', (_memberCount ?? org.nombreMembres).toString(), AppColors.primaryGreen)),
|
Expanded(child: _buildStatItem(Icons.people, 'Membres', (_memberCount ?? org.nombreMembres).toString(), ModuleColors.organisations)),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
Expanded(child: _buildStatItem(Icons.admin_panel_settings, 'Admins', org.nombreAdministrateurs.toString(), AppColors.brandGreen)),
|
Expanded(child: _buildStatItem(Icons.admin_panel_settings, 'Admins', org.nombreAdministrateurs.toString(), ModuleColors.organisationsDark)),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
Expanded(child: _buildStatItem(Icons.event, 'Événements', (org.nombreEvenements ?? 0).toString(), AppColors.success)),
|
Expanded(child: _buildStatItem(Icons.event, 'Événements', (org.nombreEvenements ?? 0).toString(), AppColors.success)),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
@@ -244,11 +255,11 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
|
|
||||||
Widget _buildStatItem(IconData icon, String label, String value, Color color) {
|
Widget _buildStatItem(IconData icon, String label, String value, Color color) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||||
decoration: BoxDecoration(color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(6), border: Border.all(color: color.withOpacity(0.15))),
|
decoration: BoxDecoration(color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), border: Border.all(color: color.withOpacity(0.15))),
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
Icon(icon, size: 20, color: color),
|
Icon(icon, size: 20, color: color),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: SpacingTokens.sm),
|
||||||
Text(value, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: color)),
|
Text(value, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: color)),
|
||||||
Text(label, style: TextStyle(fontSize: 11, color: color)),
|
Text(label, style: TextStyle(fontSize: 11, color: color)),
|
||||||
]),
|
]),
|
||||||
@@ -263,10 +274,10 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
Widget _buildFinancesCard(OrganizationModel org) {
|
Widget _buildFinancesCard(OrganizationModel org) {
|
||||||
return _buildCard('Finances', Icons.account_balance_wallet, [
|
return _buildCard('Finances', Icons.account_balance_wallet, [
|
||||||
_buildInfoRow(Icons.currency_exchange, 'Devise', org.devise),
|
_buildInfoRow(Icons.currency_exchange, 'Devise', org.devise),
|
||||||
if (org.budgetAnnuel != null) ...[const SizedBox(height: 10), _buildInfoRow(Icons.account_balance, 'Budget annuel', '${_formatMontant(org.budgetAnnuel)} ${org.devise}')],
|
if (org.budgetAnnuel != null) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.account_balance, 'Budget annuel', '${_formatMontant(org.budgetAnnuel)} ${org.devise}')],
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
_buildInfoRow(Icons.payments, 'Cotisation', org.cotisationObligatoire ? 'Obligatoire' : 'Facultative'),
|
_buildInfoRow(Icons.payments, 'Cotisation', org.cotisationObligatoire ? 'Obligatoire' : 'Facultative'),
|
||||||
if (org.cotisationObligatoire && org.montantCotisationAnnuelle != null) ...[const SizedBox(height: 10), _buildInfoRow(Icons.money, 'Montant annuel', '${_formatMontant(org.montantCotisationAnnuelle)} ${org.devise}')],
|
if (org.cotisationObligatoire && org.montantCotisationAnnuelle != null) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.money, 'Montant annuel', '${_formatMontant(org.montantCotisationAnnuelle)} ${org.devise}')],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +289,7 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
Widget _buildMissionCard(OrganizationModel org) {
|
Widget _buildMissionCard(OrganizationModel org) {
|
||||||
return _buildCard('Mission & Activités', Icons.flag, [
|
return _buildCard('Mission & Activités', Icons.flag, [
|
||||||
if (org.objectifs?.isNotEmpty == true) _buildTextBlock(Icons.track_changes, 'Objectifs', org.objectifs!),
|
if (org.objectifs?.isNotEmpty == true) _buildTextBlock(Icons.track_changes, 'Objectifs', org.objectifs!),
|
||||||
if (org.objectifs?.isNotEmpty == true && org.activitesPrincipales?.isNotEmpty == true) const SizedBox(height: 12),
|
if (org.objectifs?.isNotEmpty == true && org.activitesPrincipales?.isNotEmpty == true) const SizedBox(height: SpacingTokens.lg),
|
||||||
if (org.activitesPrincipales?.isNotEmpty == true) _buildTextBlock(Icons.work, 'Activités principales', org.activitesPrincipales!),
|
if (org.activitesPrincipales?.isNotEmpty == true) _buildTextBlock(Icons.work, 'Activités principales', org.activitesPrincipales!),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -291,7 +302,7 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
Widget _buildSupplementaryCard(OrganizationModel org) {
|
Widget _buildSupplementaryCard(OrganizationModel org) {
|
||||||
return _buildCard('Informations complémentaires', Icons.info, [
|
return _buildCard('Informations complémentaires', Icons.info, [
|
||||||
if (org.certifications?.isNotEmpty == true) _buildTextBlock(Icons.verified, 'Certifications / Agréments', org.certifications!),
|
if (org.certifications?.isNotEmpty == true) _buildTextBlock(Icons.verified, 'Certifications / Agréments', org.certifications!),
|
||||||
if (org.certifications?.isNotEmpty == true && org.partenaires?.isNotEmpty == true) const SizedBox(height: 12),
|
if (org.certifications?.isNotEmpty == true && org.partenaires?.isNotEmpty == true) const SizedBox(height: SpacingTokens.lg),
|
||||||
if (org.partenaires?.isNotEmpty == true) _buildTextBlock(Icons.handshake, 'Partenaires', org.partenaires!),
|
if (org.partenaires?.isNotEmpty == true) _buildTextBlock(Icons.handshake, 'Partenaires', org.partenaires!),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -299,20 +310,34 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
// ── Notes internes ──────────────────────────────────────────────────────────
|
// ── Notes internes ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Widget _buildNotesCard(OrganizationModel org) {
|
Widget _buildNotesCard(OrganizationModel org) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(SpacingTokens.lg),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFFFFBEB),
|
// Fond warning adaptatif (jaune clair light / surface sombre dark)
|
||||||
borderRadius: BorderRadius.circular(8),
|
color: isDark
|
||||||
border: Border.all(color: const Color(0xFFFCD34D), width: 1),
|
? AppColors.surfaceVariantDark
|
||||||
|
: AppColors.warningContainer,
|
||||||
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||||
|
border: Border.all(color: AppColors.warningUI, width: 1),
|
||||||
),
|
),
|
||||||
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
const Icon(Icons.sticky_note_2, size: 18, color: Color(0xFFF59E0B)),
|
const Icon(Icons.sticky_note_2, size: 18, color: AppColors.warningUI),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
const Text('Notes internes', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Color(0xFFF59E0B))),
|
Text('Notes internes',
|
||||||
const SizedBox(height: 4),
|
style: TextStyle(
|
||||||
Text(org.notes!, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, height: 1.4)),
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: isDark ? AppColors.warningUI : AppColors.warning,
|
||||||
|
)),
|
||||||
|
const SizedBox(height: SpacingTokens.sm),
|
||||||
|
Text(org.notes!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
height: 1.4,
|
||||||
|
)),
|
||||||
])),
|
])),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
@@ -337,15 +362,15 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
onPressed: _showEditPage,
|
onPressed: _showEditPage,
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
label: const Text('Modifier'),
|
label: const Text('Modifier'),
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryGreen, foregroundColor: Colors.white),
|
style: ElevatedButton.styleFrom(backgroundColor: ModuleColors.organisations, foregroundColor: AppColors.onPrimary),
|
||||||
)),
|
)),
|
||||||
if (isSuperAdmin) ...[
|
if (isSuperAdmin) ...[
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: SpacingTokens.lg),
|
||||||
Expanded(child: OutlinedButton.icon(
|
Expanded(child: OutlinedButton.icon(
|
||||||
onPressed: () => _showDeleteConfirmation(org),
|
onPressed: () => _showDeleteConfirmation(org),
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
label: const Text('Supprimer'),
|
label: const Text('Supprimer'),
|
||||||
style: OutlinedButton.styleFrom(foregroundColor: Colors.red, side: const BorderSide(color: Colors.red)),
|
style: OutlinedButton.styleFrom(foregroundColor: AppColors.error, side: const BorderSide(color: AppColors.error)),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
@@ -356,15 +381,19 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
|
|
||||||
Widget _buildCard(String title, IconData icon, List<Widget> children) {
|
Widget _buildCard(String title, IconData icon, List<Widget> children) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(SpacingTokens.lg),
|
||||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||||
|
border: Border.all(color: Theme.of(context).colorScheme.outlineVariant, width: 1),
|
||||||
|
),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(icon, size: 15, color: AppColors.primaryGreen),
|
Icon(icon, size: 15, color: ModuleColors.organisations),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primaryGreen)),
|
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: ModuleColors.organisations)),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
...children,
|
...children,
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
@@ -372,12 +401,12 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
|
|
||||||
Widget _buildInfoRow(IconData icon, String label, String value) {
|
Widget _buildInfoRow(IconData icon, String label, String value) {
|
||||||
return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Icon(icon, size: 18, color: AppColors.primaryGreen),
|
Icon(icon, size: 18, color: ModuleColors.organisations),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight, fontWeight: FontWeight.w500)),
|
Text(label, style: TextStyle(fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, fontWeight: FontWeight.w500)),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: SpacingTokens.xs),
|
||||||
Text(value, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, fontWeight: FontWeight.w600)),
|
Text(value, style: TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.w600)),
|
||||||
])),
|
])),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -385,29 +414,29 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
Widget _buildTextBlock(IconData icon, String label, String value) {
|
Widget _buildTextBlock(IconData icon, String label, String value) {
|
||||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(icon, size: 15, color: AppColors.primaryGreen),
|
Icon(icon, size: 15, color: ModuleColors.organisations),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textSecondaryLight, fontWeight: FontWeight.w600)),
|
Text(label, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant, fontWeight: FontWeight.w600)),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: SpacingTokens.sm),
|
||||||
Text(value, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, height: 1.5)),
|
Text(value, style: TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.onSurface, height: 1.5)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContactRow(IconData icon, String label, String value, {VoidCallback? onTap}) {
|
Widget _buildContactRow(IconData icon, String label, String value, {VoidCallback? onTap}) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
Icon(icon, size: 18, color: AppColors.primaryGreen),
|
Icon(icon, size: 18, color: ModuleColors.organisations),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight)),
|
Text(label, style: TextStyle(fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||||
Text(value, style: TextStyle(fontSize: 13, color: onTap != null ? AppColors.primaryGreen : AppColors.textPrimaryLight, fontWeight: FontWeight.w600, decoration: onTap != null ? TextDecoration.underline : null)),
|
Text(value, style: TextStyle(fontSize: 13, color: onTap != null ? ModuleColors.organisations : Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.w600, decoration: onTap != null ? TextDecoration.underline : null)),
|
||||||
])),
|
])),
|
||||||
if (onTap != null) const Icon(Icons.open_in_new, size: 14, color: AppColors.primaryGreen),
|
if (onTap != null) const Icon(Icons.open_in_new, size: 14, color: ModuleColors.organisations),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -416,28 +445,28 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
// ── États ────────────────────────────────────────────────────────────────────
|
// ── États ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Widget _buildLoading() => const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
Widget _buildLoading() => const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryGreen)),
|
CircularProgressIndicator(color: ModuleColors.organisations),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: SpacingTokens.xl),
|
||||||
Text('Chargement...', style: TextStyle(color: AppColors.textSecondaryLight)),
|
Text('Chargement...'),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
Widget _buildError(OrganizationsError state) => Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
Widget _buildError(OrganizationsError state) => Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
Icon(Icons.error_outline, size: 64, color: Colors.red.shade400),
|
Icon(Icons.error_outline, size: 64, color: Theme.of(context).colorScheme.error),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: SpacingTokens.xl),
|
||||||
Text(state.message, textAlign: TextAlign.center, style: const TextStyle(color: AppColors.textSecondaryLight)),
|
Text(state.message, textAlign: TextAlign.center, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
ElevatedButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: () => context.read<OrganizationsBloc>().add(LoadOrganizationById(widget.organizationId)),
|
onPressed: () => context.read<OrganizationsBloc>().add(LoadOrganizationById(widget.organizationId)),
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh, size: 18),
|
||||||
label: const Text('Réessayer'),
|
label: const Text('Réessayer'),
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryGreen, foregroundColor: Colors.white),
|
style: FilledButton.styleFrom(backgroundColor: ModuleColors.organisations),
|
||||||
),
|
),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
Widget _buildEmpty() => const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
Widget _buildEmpty() => Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
Icon(Icons.business_outlined, size: 64, color: AppColors.textSecondaryLight),
|
Icon(Icons.business_outlined, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: SpacingTokens.xl),
|
||||||
Text('Organisation non trouvée', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimaryLight)),
|
Text('Organisation non trouvée', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface)),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
// ── Actions ──────────────────────────────────────────────────────────────────
|
// ── Actions ──────────────────────────────────────────────────────────────────
|
||||||
@@ -475,8 +504,8 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
|
|||||||
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Annuler')),
|
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Annuler')),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () { Navigator.of(ctx).pop(); bloc.add(DeleteOrganization(widget.organizationId)); nav.pop(); },
|
onPressed: () { Navigator.of(ctx).pop(); bloc.add(DeleteOrganization(widget.organizationId)); nav.pop(); },
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
style: ElevatedButton.styleFrom(backgroundColor: AppColors.error),
|
||||||
child: const Text('Supprimer', style: TextStyle(color: Colors.white)),
|
child: const Text('Supprimer', style: TextStyle(color: AppColors.onPrimary)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||||
|
import '../../../../shared/design_system/tokens/color_tokens.dart';
|
||||||
import '../../../../shared/widgets/core_card.dart';
|
import '../../../../shared/widgets/core_card.dart';
|
||||||
import '../../../../shared/widgets/info_badge.dart';
|
import '../../../../shared/widgets/info_badge.dart';
|
||||||
import '../../../../shared/widgets/mini_avatar.dart';
|
import '../../../../shared/widgets/mini_avatar.dart';
|
||||||
|
import '../../../../core/config/environment.dart';
|
||||||
import '../../../../core/l10n/locale_provider.dart';
|
import '../../../../core/l10n/locale_provider.dart';
|
||||||
import '../../../../core/theme/theme_provider.dart';
|
import '../../../../core/theme/theme_provider.dart';
|
||||||
import '../../../authentication/presentation/bloc/auth_bloc.dart';
|
import '../../../authentication/presentation/bloc/auth_bloc.dart';
|
||||||
@@ -66,15 +68,22 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
// Infos app (chargées via package_info_plus)
|
// Infos app (chargées via package_info_plus)
|
||||||
String _appVersion = '...';
|
String _appVersion = '...';
|
||||||
String _platformInfo = '...';
|
String _platformInfo = '...';
|
||||||
|
String _osVersion = '...';
|
||||||
|
String _envInfo = '...';
|
||||||
// Tailles stockage (calculées au chargement)
|
// Tailles stockage (calculées au chargement)
|
||||||
String _cacheSize = '...';
|
String _cacheSize = '...';
|
||||||
String _imagesSize = '...';
|
String _imagesSize = '...';
|
||||||
String _offlineSize = '...';
|
String _offlineSize = '...';
|
||||||
|
// Options développeur (persistées via SharedPreferences)
|
||||||
|
bool _devMode = false;
|
||||||
|
bool _detailedLogs = false;
|
||||||
final List<String> _themes = ['Système', 'Clair', 'Sombre'];
|
final List<String> _themes = ['Système', 'Clair', 'Sombre'];
|
||||||
|
|
||||||
static const _keyNotifPush = 'notif_push';
|
static const _keyNotifPush = 'notif_push';
|
||||||
static const _keyNotifEmail = 'notif_email';
|
static const _keyNotifEmail = 'notif_email';
|
||||||
static const _keyNotifSon = 'notif_son';
|
static const _keyNotifSon = 'notif_son';
|
||||||
|
static const _keyDevMode = 'dev_mode';
|
||||||
|
static const _keyDetailedLogs = 'detailed_logs';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -136,26 +145,39 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'MON PROFIL',
|
title: 'Mon Profil',
|
||||||
backgroundColor: Theme.of(context).cardColor,
|
moduleGradient: ModuleColors.profilGradient,
|
||||||
foregroundColor: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? AppColors.textPrimaryDark
|
|
||||||
: AppColors.textPrimaryLight,
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(_isEditing ? Icons.save_outlined : Icons.edit_outlined, size: 20),
|
icon: Icon(_isEditing ? Icons.save_outlined : Icons.edit_outlined, size: 20),
|
||||||
onPressed: () => _isEditing ? _saveProfile() : _startEditing(),
|
onPressed: () => _isEditing ? _saveProfile() : _startEditing(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
isScrollable: true,
|
||||||
|
labelColor: Colors.white,
|
||||||
|
unselectedLabelColor: Colors.white70,
|
||||||
|
indicatorColor: Colors.white,
|
||||||
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
|
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
||||||
|
tabs: const [
|
||||||
|
Tab(child: Text('PERSO')),
|
||||||
|
Tab(child: Text('PRÉFÉRENCES')),
|
||||||
|
Tab(child: Text('SÉCURITÉ')),
|
||||||
|
Tab(child: Text('AVANCÉ')),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildHeader(),
|
Padding(
|
||||||
const SizedBox(height: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
_buildTabBar(),
|
child: _buildHeader(),
|
||||||
SizedBox(
|
),
|
||||||
height: 600, // Ajuster selon contenu ou utiliser NestedScrollView
|
Expanded(
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
@@ -169,6 +191,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +199,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
return BlocBuilder<ProfileBloc, ProfileState>(
|
return BlocBuilder<ProfileBloc, ProfileState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final scheme = Theme.of(context).colorScheme;
|
||||||
final membre = (state is ProfileLoaded)
|
final membre = (state is ProfileLoaded)
|
||||||
? state.membre
|
? state.membre
|
||||||
: (state is ProfileUpdated ? state.membre
|
: (state is ProfileUpdated ? state.membre
|
||||||
@@ -189,39 +213,39 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
Color badgeColor;
|
Color badgeColor;
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
badgeText = 'CHARGEMENT...';
|
badgeText = 'CHARGEMENT...';
|
||||||
badgeColor = AppColors.textSecondaryLight;
|
badgeColor = ColorTokens.textSecondary;
|
||||||
} else if (hasError) {
|
} else if (hasError) {
|
||||||
badgeText = 'ERREUR CHARGEMENT';
|
badgeText = 'ERREUR CHARGEMENT';
|
||||||
badgeColor = AppColors.error;
|
badgeColor = ColorTokens.error;
|
||||||
} else if (membre != null) {
|
} else if (membre != null) {
|
||||||
// Priorité au rôle s'il est disponible
|
// Priorité au rôle s'il est disponible
|
||||||
if (membre.role != null && membre.role!.isNotEmpty) {
|
if (membre.role != null && membre.role!.isNotEmpty) {
|
||||||
badgeText = membre.role!.replaceAll('_', ' ');
|
badgeText = membre.role!.replaceAll('_', ' ');
|
||||||
badgeColor = AppColors.primaryGreen;
|
badgeColor = ModuleColors.profil;
|
||||||
} else {
|
} else {
|
||||||
// Fallback sur le statut
|
// Fallback sur le statut
|
||||||
switch (membre.statut) {
|
switch (membre.statut) {
|
||||||
case StatutMembre.actif:
|
case StatutMembre.actif:
|
||||||
badgeText = membre.cotisationAJour ? 'MEMBRE ACTIF' : 'COTISATION EN RETARD';
|
badgeText = membre.cotisationAJour ? 'MEMBRE ACTIF' : 'COTISATION EN RETARD';
|
||||||
badgeColor = membre.cotisationAJour ? AppColors.success : AppColors.warning;
|
badgeColor = membre.cotisationAJour ? ColorTokens.success : ColorTokens.warning;
|
||||||
break;
|
break;
|
||||||
case StatutMembre.inactif:
|
case StatutMembre.inactif:
|
||||||
badgeText = 'INACTIF';
|
badgeText = 'INACTIF';
|
||||||
badgeColor = AppColors.textSecondaryLight;
|
badgeColor = ColorTokens.textSecondary;
|
||||||
break;
|
break;
|
||||||
case StatutMembre.suspendu:
|
case StatutMembre.suspendu:
|
||||||
badgeText = 'SUSPENDU';
|
badgeText = 'SUSPENDU';
|
||||||
badgeColor = AppColors.error;
|
badgeColor = ColorTokens.error;
|
||||||
break;
|
break;
|
||||||
case StatutMembre.enAttente:
|
case StatutMembre.enAttente:
|
||||||
badgeText = 'EN ATTENTE';
|
badgeText = 'EN ATTENTE';
|
||||||
badgeColor = AppColors.warning;
|
badgeColor = ColorTokens.warning;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
badgeText = 'CHARGEMENT...';
|
badgeText = 'CHARGEMENT...';
|
||||||
badgeColor = AppColors.textSecondaryLight;
|
badgeColor = ColorTokens.textSecondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ancienneté réelle
|
// Ancienneté réelle
|
||||||
@@ -274,7 +298,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_emailController.text.toLowerCase(),
|
_emailController.text.toLowerCase(),
|
||||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 11),
|
style: AppTypography.subtitleSmall.copyWith(fontSize: 11, color: scheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
@@ -291,7 +315,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Réessayer',
|
'Réessayer',
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: ModuleColors.profil,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
@@ -344,10 +368,11 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatItem(String label, String value) {
|
Widget _buildStatItem(String label, String value) {
|
||||||
|
final scheme = Theme.of(context).colorScheme;
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Text(value, style: AppTypography.headerSmall.copyWith(fontSize: 14)),
|
Text(value, style: AppTypography.headerSmall.copyWith(fontSize: 14)),
|
||||||
Text(label, style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
|
Text(label, style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold, color: scheme.onSurfaceVariant)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -388,35 +413,16 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Barre d'onglets
|
// _buildTabBar() supprimé : migré dans UFAppBar.bottom (pattern Adhésions)
|
||||||
Widget _buildTabBar() {
|
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
|
|
||||||
),
|
|
||||||
child: TabBar(
|
|
||||||
controller: _tabController,
|
|
||||||
labelColor: AppColors.primaryGreen,
|
|
||||||
unselectedLabelColor: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
|
|
||||||
indicatorColor: AppColors.primaryGreen,
|
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
|
||||||
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
|
||||||
tabs: const [
|
|
||||||
Tab(text: 'PERSO'),
|
|
||||||
Tab(text: 'PRÉF'),
|
|
||||||
Tab(text: 'SÉCU'),
|
|
||||||
Tab(text: 'AVANCÉ'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Onglet informations personnelles
|
/// Onglet informations personnelles
|
||||||
Widget _buildPersonalInfoTab() {
|
Widget _buildPersonalInfoTab() {
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
|
color: ModuleColors.profil,
|
||||||
|
onRefresh: () async =>
|
||||||
|
context.read<ProfileBloc>().add(const LoadMe()),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -533,8 +539,9 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
|
|
||||||
const SizedBox(height: 80),
|
const SizedBox(height: 80),
|
||||||
],
|
],
|
||||||
),
|
), // Column
|
||||||
);
|
), // SingleChildScrollView
|
||||||
|
); // RefreshIndicator
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Section d'informations
|
/// Section d'informations
|
||||||
@@ -551,7 +558,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
Icon(icon, color: ModuleColors.profil, size: 16),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
title.toUpperCase(),
|
title.toUpperCase(),
|
||||||
@@ -579,7 +586,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
int maxLines = 1,
|
int maxLines = 1,
|
||||||
String? hintText,
|
String? hintText,
|
||||||
}) {
|
}) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
@@ -587,35 +594,34 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight,
|
|
||||||
),
|
),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: label.toUpperCase(),
|
labelText: label.toUpperCase(),
|
||||||
labelStyle: AppTypography.subtitleSmall.copyWith(
|
labelStyle: AppTypography.subtitleSmall.copyWith(
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
|
color: scheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
prefixIcon: Icon(icon, color: enabled ? AppColors.primaryGreen : AppColors.textSecondaryLight, size: 16),
|
prefixIcon: Icon(icon, color: enabled ? ModuleColors.profil : ColorTokens.textSecondary, size: 16),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
fillColor: scheme.surface,
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
borderSide: BorderSide(color: isDark ? AppColors.darkBorder : AppColors.lightBorder),
|
borderSide: BorderSide(color: scheme.outlineVariant),
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
borderSide: BorderSide(color: isDark ? AppColors.darkBorder : AppColors.lightBorder),
|
borderSide: BorderSide(color: scheme.outlineVariant),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
borderSide: const BorderSide(color: AppColors.primaryGreen, width: 1),
|
borderSide: BorderSide(color: ModuleColors.profil, width: 1),
|
||||||
),
|
),
|
||||||
disabledBorder: OutlineInputBorder(
|
disabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
borderSide: BorderSide(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
|
borderSide: BorderSide(color: scheme.outlineVariant, width: 0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
@@ -631,15 +637,15 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
|
|
||||||
/// Boutons d'action
|
/// Boutons d'action
|
||||||
Widget _buildActionButtons() {
|
Widget _buildActionButtons() {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDark ? AppColors.darkSurface : Colors.white,
|
color: scheme.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: AppColors.shadow,
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -652,8 +658,8 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: _isLoading ? null : _cancelEditing,
|
onPressed: _isLoading ? null : _cancelEditing,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.grey[100],
|
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
foregroundColor: Colors.grey[700],
|
foregroundColor: AppColors.textSecondary,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
),
|
),
|
||||||
@@ -666,7 +672,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: _isLoading ? null : _saveProfile,
|
onPressed: _isLoading ? null : _saveProfile,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: ModuleColors.profil,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
@@ -689,7 +695,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: _startEditing,
|
onPressed: _startEditing,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: ModuleColors.profil,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
@@ -720,9 +726,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
[
|
[
|
||||||
Builder(
|
Builder(
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
final isDark = Theme.of(ctx).brightness == Brightness.dark;
|
final scheme = Theme.of(ctx).colorScheme;
|
||||||
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight;
|
|
||||||
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
@@ -738,18 +742,18 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.language, color: textSecondary, size: 22),
|
Icon(Icons.language, color: scheme.onSurfaceVariant, size: 22),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Langue', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600, color: textPrimary)),
|
Text('Langue', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600)),
|
||||||
Text('Actuellement : $_selectedLanguage', style: AppTypography.subtitleSmall.copyWith(color: textSecondary)),
|
Text('Actuellement : $_selectedLanguage', style: AppTypography.subtitleSmall.copyWith(color: scheme.onSurfaceVariant)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Icon(Icons.chevron_right, color: textSecondary, size: 20),
|
Icon(Icons.chevron_right, color: scheme.onSurfaceVariant, size: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -823,9 +827,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
[
|
[
|
||||||
Builder(
|
Builder(
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
final isDark = Theme.of(ctx).brightness == Brightness.dark;
|
final scheme = Theme.of(ctx).colorScheme;
|
||||||
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight;
|
|
||||||
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
@@ -837,18 +839,18 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.privacy_tip, color: textSecondary, size: 22),
|
Icon(Icons.privacy_tip, color: scheme.onSurfaceVariant, size: 22),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Gérer la confidentialité', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600, color: textPrimary)),
|
Text('Gérer la confidentialité', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600)),
|
||||||
Text('Visibilité, partage de données, suppression de compte', style: AppTypography.subtitleSmall.copyWith(color: textSecondary)),
|
Text('Visibilité, partage de données, suppression de compte', style: AppTypography.subtitleSmall.copyWith(color: scheme.onSurfaceVariant)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Icon(Icons.chevron_right, color: textSecondary, size: 20),
|
Icon(Icons.chevron_right, color: scheme.onSurfaceVariant, size: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -950,21 +952,21 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
'Télécharger mes données',
|
'Télécharger mes données',
|
||||||
'Exporter toutes vos données personnelles',
|
'Exporter toutes vos données personnelles',
|
||||||
Icons.download,
|
Icons.download,
|
||||||
AppColors.primaryGreen,
|
ModuleColors.profil,
|
||||||
() => _exportUserData(),
|
() => _exportUserData(),
|
||||||
),
|
),
|
||||||
_buildActionItem(
|
_buildActionItem(
|
||||||
'Déconnecter tous les appareils',
|
'Déconnecter tous les appareils',
|
||||||
'Fermer toutes les sessions actives',
|
'Fermer toutes les sessions actives',
|
||||||
Icons.logout,
|
Icons.logout,
|
||||||
AppColors.warning,
|
ColorTokens.warning,
|
||||||
() => _logoutAllDevices(),
|
() => _logoutAllDevices(),
|
||||||
),
|
),
|
||||||
_buildActionItem(
|
_buildActionItem(
|
||||||
'Supprimer mon compte',
|
'Supprimer mon compte',
|
||||||
'Action irréversible - toutes les données seront perdues',
|
'Action irréversible - toutes les données seront perdues',
|
||||||
Icons.delete_forever,
|
Icons.delete_forever,
|
||||||
Colors.red,
|
ColorTokens.error,
|
||||||
() => _showDeleteAccountDialog(),
|
() => _showDeleteAccountDialog(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1019,14 +1021,22 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
_buildSwitchPreference(
|
_buildSwitchPreference(
|
||||||
'Mode développeur',
|
'Mode développeur',
|
||||||
'Afficher les options de débogage',
|
'Afficher les options de débogage',
|
||||||
false,
|
_devMode,
|
||||||
(value) => _showSuccessSnackBar('Mode développeur ${value ? 'activé' : 'désactivé'}'),
|
(value) async {
|
||||||
|
setState(() => _devMode = value);
|
||||||
|
await _savePreference(_keyDevMode, value);
|
||||||
|
_showSuccessSnackBar('Mode développeur ${value ? 'activé' : 'désactivé'}');
|
||||||
|
},
|
||||||
),
|
),
|
||||||
_buildSwitchPreference(
|
_buildSwitchPreference(
|
||||||
'Logs détaillés',
|
'Logs détaillés',
|
||||||
'Enregistrer plus d\'informations de débogage',
|
'Logs réseau et erreurs — actif au prochain démarrage',
|
||||||
false,
|
_detailedLogs,
|
||||||
(value) => _showSuccessSnackBar('Logs détaillés ${value ? 'activés' : 'désactivés'}'),
|
(value) async {
|
||||||
|
setState(() => _detailedLogs = value);
|
||||||
|
await _savePreference(_keyDetailedLogs, value);
|
||||||
|
_showSuccessSnackBar('Logs détaillés ${value ? 'activés' : 'désactivés'} (prochain démarrage)');
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1041,6 +1051,9 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
[
|
[
|
||||||
_buildInfoItem('Version de l\'app', _appVersion),
|
_buildInfoItem('Version de l\'app', _appVersion),
|
||||||
_buildInfoItem('Plateforme', _platformInfo),
|
_buildInfoItem('Plateforme', _platformInfo),
|
||||||
|
_buildInfoItem('Système', _osVersion),
|
||||||
|
_buildInfoItem('Environnement', _envInfo),
|
||||||
|
if (_devMode) _buildInfoItem('API URL', AppConfig.apiBaseUrl),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -1054,9 +1067,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
[
|
[
|
||||||
Builder(
|
Builder(
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
final isDark = Theme.of(ctx).brightness == Brightness.dark;
|
final scheme = Theme.of(ctx).colorScheme;
|
||||||
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight;
|
|
||||||
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
@@ -1068,18 +1079,18 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.edit_note, color: textSecondary, size: 22),
|
Icon(Icons.edit_note, color: scheme.onSurfaceVariant, size: 22),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Envoyer des commentaires', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600, color: textPrimary)),
|
Text('Envoyer des commentaires', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600)),
|
||||||
Text('Suggestions, bugs ou idées d\'amélioration', style: AppTypography.subtitleSmall.copyWith(color: textSecondary)),
|
Text('Suggestions, bugs ou idées d\'amélioration', style: AppTypography.subtitleSmall.copyWith(color: scheme.onSurfaceVariant)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Icon(Icons.chevron_right, color: textSecondary, size: 20),
|
Icon(Icons.chevron_right, color: scheme.onSurfaceVariant, size: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1113,7 +1124,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
Icon(icon, color: ModuleColors.profil, size: 16),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
title.toUpperCase(),
|
title.toUpperCase(),
|
||||||
@@ -1159,7 +1170,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
List<String> options,
|
List<String> options,
|
||||||
Function(String?) onChanged,
|
Function(String?) onChanged,
|
||||||
) {
|
) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -1171,21 +1182,31 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
color: scheme.surface,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
|
border: Border.all(color: scheme.outlineVariant, width: 0.5),
|
||||||
),
|
),
|
||||||
child: DropdownButtonHideUnderline(
|
child: DropdownButtonHideUnderline(
|
||||||
child: DropdownButton<String>(
|
child: DropdownButton<String>(
|
||||||
value: value,
|
value: value,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
icon: const Icon(Icons.arrow_drop_down, color: AppColors.primaryGreen, size: 18),
|
dropdownColor: scheme.surface,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 12),
|
icon: Icon(Icons.arrow_drop_down, color: ModuleColors.profil, size: 18),
|
||||||
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: scheme.onSurface,
|
||||||
|
),
|
||||||
items: options.map((option) {
|
items: options.map((option) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: option,
|
value: option,
|
||||||
child: Text(option),
|
child: Text(
|
||||||
|
option,
|
||||||
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: scheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
@@ -1202,7 +1223,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
bool value,
|
bool value,
|
||||||
Function(bool) onChanged,
|
Function(bool) onChanged,
|
||||||
) {
|
) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -1217,7 +1238,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
subtitle,
|
subtitle,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
|
color: scheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1228,7 +1249,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
child: Switch(
|
child: Switch(
|
||||||
value: value,
|
value: value,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: ModuleColors.profil,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1242,21 +1263,20 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
IconData icon,
|
IconData icon,
|
||||||
VoidCallback onTap,
|
VoidCallback onTap,
|
||||||
) {
|
) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
color: scheme.surface,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
|
border: Border.all(color: scheme.outlineVariant, width: 0.5),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
Icon(icon, color: ModuleColors.profil, size: 16),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -1268,12 +1288,12 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: textSecondary),
|
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: scheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Icon(Icons.arrow_forward_ios, color: textSecondary, size: 12),
|
Icon(Icons.arrow_forward_ios, color: scheme.onSurfaceVariant, size: 12),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1287,19 +1307,18 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
IconData icon,
|
IconData icon,
|
||||||
bool isCurrentDevice,
|
bool isCurrentDevice,
|
||||||
) {
|
) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isCurrentDevice
|
color: isCurrentDevice
|
||||||
? AppColors.primaryGreen.withOpacity(0.05)
|
? ModuleColors.profil.withOpacity(0.05)
|
||||||
: (isDark ? AppColors.darkSurface : AppColors.lightSurface),
|
: scheme.surface,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isCurrentDevice
|
color: isCurrentDevice
|
||||||
? AppColors.primaryGreen.withOpacity(0.3)
|
? ModuleColors.profil.withOpacity(0.3)
|
||||||
: (isDark ? AppColors.darkBorder : AppColors.lightBorder),
|
: scheme.outlineVariant,
|
||||||
width: 0.5,
|
width: 0.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1307,7 +1326,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
color: isCurrentDevice ? AppColors.primaryGreen : textSecondary,
|
color: isCurrentDevice ? ModuleColors.profil : scheme.onSurfaceVariant,
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -1323,13 +1342,13 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
),
|
),
|
||||||
if (isCurrentDevice) ...[
|
if (isCurrentDevice) ...[
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const InfoBadge(text: 'ACTUEL', backgroundColor: AppColors.primaryGreen),
|
InfoBadge(text: 'ACTUEL', backgroundColor: ModuleColors.profil),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: textSecondary),
|
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: scheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1347,7 +1366,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
Color color,
|
Color color,
|
||||||
VoidCallback onTap,
|
VoidCallback onTap,
|
||||||
) {
|
) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
@@ -1374,7 +1393,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
subtitle,
|
subtitle,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
|
color: scheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1389,20 +1408,20 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
|
|
||||||
/// Élément de stockage
|
/// Élément de stockage
|
||||||
Widget _buildStorageItem(String title, String size, VoidCallback onTap) {
|
Widget _buildStorageItem(String title, String size, VoidCallback onTap) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
color: scheme.surface,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
|
border: Border.all(color: scheme.outlineVariant, width: 0.5),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.folder_outlined, color: AppColors.primaryGreen, size: 16),
|
Icon(Icons.folder_outlined, color: ModuleColors.profil, size: 16),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -1412,10 +1431,10 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
size,
|
size,
|
||||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
|
style: AppTypography.subtitleSmall.copyWith(fontSize: 10, color: scheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const Icon(Icons.clear, color: AppColors.error, size: 14),
|
Icon(Icons.clear, color: ColorTokens.error, size: 14),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1424,20 +1443,20 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
|
|
||||||
/// Élément d'information
|
/// Élément d'information
|
||||||
Widget _buildInfoItem(String title, String value) {
|
Widget _buildInfoItem(String title, String value) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
color: scheme.surface,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
|
border: Border.all(color: scheme.outlineVariant, width: 0.5),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
title.toUpperCase(),
|
title.toUpperCase(),
|
||||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 9, fontWeight: FontWeight.bold),
|
style: AppTypography.subtitleSmall.copyWith(fontSize: 9, fontWeight: FontWeight.bold, color: scheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@@ -1565,7 +1584,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Charger les préférences notifications depuis SharedPreferences
|
/// Charger les préférences depuis SharedPreferences
|
||||||
Future<void> _loadPreferences() async {
|
Future<void> _loadPreferences() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -1573,6 +1592,8 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
_notifPush = prefs.getBool(_keyNotifPush) ?? true;
|
_notifPush = prefs.getBool(_keyNotifPush) ?? true;
|
||||||
_notifEmail = prefs.getBool(_keyNotifEmail) ?? false;
|
_notifEmail = prefs.getBool(_keyNotifEmail) ?? false;
|
||||||
_notifSon = prefs.getBool(_keyNotifSon) ?? true;
|
_notifSon = prefs.getBool(_keyNotifSon) ?? true;
|
||||||
|
_devMode = prefs.getBool(_keyDevMode) ?? AppConfig.enableDebugMode;
|
||||||
|
_detailedLogs = prefs.getBool(_keyDetailedLogs) ?? AppConfig.enableLogging;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1588,7 +1609,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
final info = await PackageInfo.fromPlatform();
|
final info = await PackageInfo.fromPlatform();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_appVersion = '${info.version} (Build ${info.buildNumber})';
|
_appVersion = '${info.version} (build ${info.buildNumber})';
|
||||||
final os = kIsWeb
|
final os = kIsWeb
|
||||||
? 'Web'
|
? 'Web'
|
||||||
: Platform.isAndroid
|
: Platform.isAndroid
|
||||||
@@ -1597,6 +1618,10 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
? 'iOS'
|
? 'iOS'
|
||||||
: Platform.operatingSystem;
|
: Platform.operatingSystem;
|
||||||
_platformInfo = '$os · ${info.appName}';
|
_platformInfo = '$os · ${info.appName}';
|
||||||
|
_osVersion = kIsWeb
|
||||||
|
? 'Navigateur web'
|
||||||
|
: Platform.operatingSystemVersion;
|
||||||
|
_envInfo = AppConfig.environment.name.toUpperCase();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1684,7 +1709,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
confirmPassCtrl.dispose();
|
confirmPassCtrl.dispose();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: ModuleColors.profil,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
child: const Text('Modifier'),
|
child: const Text('Modifier'),
|
||||||
@@ -1782,7 +1807,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
_showFinalDeleteConfirmation();
|
_showFinalDeleteConfirmation();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: ColorTokens.error,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
child: const Text('Continuer'),
|
child: const Text('Continuer'),
|
||||||
@@ -1836,7 +1861,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
confirmCtrl.dispose();
|
confirmCtrl.dispose();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: ColorTokens.error,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
child: const Text('SUPPRIMER DÉFINITIVEMENT'),
|
child: const Text('SUPPRIMER DÉFINITIVEMENT'),
|
||||||
@@ -1953,7 +1978,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
message.toUpperCase(),
|
message.toUpperCase(),
|
||||||
style: AppTypography.actionText.copyWith(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
style: AppTypography.actionText.copyWith(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
backgroundColor: AppColors.success,
|
backgroundColor: ColorTokens.success,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||||
),
|
),
|
||||||
@@ -1967,7 +1992,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
message.toUpperCase(),
|
message.toUpperCase(),
|
||||||
style: AppTypography.actionText.copyWith(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
style: AppTypography.actionText.copyWith(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
backgroundColor: AppColors.error,
|
backgroundColor: ColorTokens.error,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
}
|
}
|
||||||
if (state is ReportsError) {
|
if (state is ReportsError) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(state.message), backgroundColor: Colors.orange),
|
SnackBar(content: Text(state.message), backgroundColor: AppColors.warning),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (state is ReportScheduled) {
|
if (state is ReportScheduled) {
|
||||||
@@ -74,18 +74,49 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.lightBackground,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
body: Column(
|
appBar: UFAppBar(
|
||||||
|
title: 'Rapports & Analytics',
|
||||||
|
moduleGradient: ModuleColors.rapportsGradient,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _showExportDialog(),
|
||||||
|
icon: const Icon(Icons.file_download_outlined, size: 20),
|
||||||
|
tooltip: 'Exporter',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
isScrollable: true,
|
||||||
|
labelColor: Colors.white,
|
||||||
|
unselectedLabelColor: Colors.white70,
|
||||||
|
indicatorColor: Colors.white,
|
||||||
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
|
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
||||||
|
tabs: const [
|
||||||
|
Tab(child: Text('GLOBAL')),
|
||||||
|
Tab(child: Text('MEMBRES')),
|
||||||
|
Tab(child: Text('ORGS')),
|
||||||
|
Tab(child: Text('EVENTS')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildHeader(),
|
|
||||||
_buildTabBar(),
|
|
||||||
if (state is ReportsLoading)
|
if (state is ReportsLoading)
|
||||||
const LinearProgressIndicator(
|
const LinearProgressIndicator(
|
||||||
minHeight: 2,
|
minHeight: 2,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryGreen),
|
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primary),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: RefreshIndicator(
|
||||||
|
color: ModuleColors.rapports,
|
||||||
|
onRefresh: () async => context
|
||||||
|
.read<ReportsBloc>()
|
||||||
|
.add(const LoadDashboardReports()),
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
@@ -96,162 +127,22 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHeader() {
|
// _buildHeader(), _buildHeaderStat() et _buildTabBar() supprimés :
|
||||||
return Container(
|
// titre + action Export migrés dans UFAppBar, TabBar dans UFAppBar.bottom.
|
||||||
width: double.infinity,
|
// Les KPIs sont toujours disponibles dans _buildKPICards() (onglet Global).
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: MediaQuery.of(context).padding.top + 12,
|
|
||||||
bottom: 16,
|
|
||||||
left: 12,
|
|
||||||
right: 12,
|
|
||||||
),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [
|
|
||||||
AppColors.primaryGreen,
|
|
||||||
AppColors.brandGreen,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
bottomLeft: Radius.circular(32),
|
|
||||||
bottomRight: Radius.circular(32),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'UnionFlow Analytics'.toUpperCase(),
|
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.8),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
letterSpacing: 1.2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
const Text(
|
|
||||||
'Rapports & Insights',
|
|
||||||
style: AppTypography.headerSmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.2),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: () => _showExportDialog(),
|
|
||||||
icon: const Icon(Icons.file_download_outlined, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildHeaderStat(
|
|
||||||
'Membres',
|
|
||||||
_statsMembres['total']?.toString() ?? '...',
|
|
||||||
Icons.people_outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: _buildHeaderStat(
|
|
||||||
'Organisations',
|
|
||||||
_statsMembres['totalOrganisations']?.toString() ?? '...',
|
|
||||||
Icons.business_outlined,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: _buildHeaderStat(
|
|
||||||
'Événements',
|
|
||||||
_statsEvenements['total']?.toString() ?? '...',
|
|
||||||
Icons.event_outlined,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildHeaderStat(String label, String value, IconData icon) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.15),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
border: Border.all(color: Colors.white.withOpacity(0.2)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Icon(icon, color: Colors.white, size: 18),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: AppTypography.headerSmall.copyWith(fontSize: 18),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
label.toUpperCase(),
|
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.7),
|
|
||||||
fontSize: 8,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTabBar() {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.lightBackground,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)),
|
|
||||||
),
|
|
||||||
child: TabBar(
|
|
||||||
controller: _tabController,
|
|
||||||
labelColor: AppColors.primaryGreen,
|
|
||||||
unselectedLabelColor: AppColors.textSecondaryLight,
|
|
||||||
indicatorColor: AppColors.primaryGreen,
|
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
|
||||||
dividerColor: Colors.transparent,
|
|
||||||
labelStyle: AppTypography.badgeText.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
tabs: const [
|
|
||||||
Tab(text: 'GLOBAL'),
|
|
||||||
Tab(text: 'MEMBRES'),
|
|
||||||
Tab(text: 'ORGS'),
|
|
||||||
Tab(text: 'EVENTS'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildOverviewTab() {
|
Widget _buildOverviewTab() {
|
||||||
return ListView(
|
return ListView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
children: [
|
children: [
|
||||||
_buildKPICards(),
|
_buildKPICards(),
|
||||||
@@ -282,7 +173,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildKPICard('Cotisations', totalCotisations, Icons.payments_outlined, AppColors.brandGreen)),
|
Expanded(child: _buildKPICard('Cotisations', totalCotisations, Icons.payments_outlined, AppColors.primaryDark)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: _buildKPICard('Événements', totalEvenements, Icons.event_available_outlined, AppColors.warning)),
|
Expanded(child: _buildKPICard('Événements', totalEvenements, Icons.event_available_outlined, AppColors.warning)),
|
||||||
],
|
],
|
||||||
@@ -332,7 +223,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.analytics_outlined, color: AppColors.primaryGreen, size: 20),
|
const Icon(Icons.analytics_outlined, color: AppColors.primary, size: 20),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'Évolution de l\'Activité'.toUpperCase(),
|
'Évolution de l\'Activité'.toUpperCase(),
|
||||||
@@ -341,26 +232,29 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Container(
|
Builder(builder: (context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
return Container(
|
||||||
height: 180,
|
height: 180,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBorder.withOpacity(0.1),
|
color: (isDark ? AppColors.borderDark : AppColors.border).withOpacity(0.15),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: const Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.auto_graph_outlined, color: AppColors.textSecondaryLight, size: 40),
|
Icon(Icons.auto_graph_outlined,
|
||||||
SizedBox(height: 12),
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
Text(
|
size: 40),
|
||||||
'Visualisation graphique en préparation',
|
const SizedBox(height: 12),
|
||||||
style: AppTypography.subtitleSmall,
|
Text('Visualisation graphique en préparation',
|
||||||
),
|
style: AppTypography.subtitleSmall),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -400,19 +294,19 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBorder.withOpacity(0.05),
|
color: AppColors.border.withOpacity(0.05),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)),
|
border: Border.all(color: AppColors.border.withOpacity(0.1)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Icon(icon, color: AppColors.primaryGreen, size: 20),
|
child: Icon(icon, color: AppColors.primary, size: 20),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -425,7 +319,11 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Icon(Icons.chevron_right_outlined, color: AppColors.textSecondaryLight, size: 20),
|
Icon(Icons.chevron_right_outlined,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary,
|
||||||
|
size: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -436,6 +334,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
/// Onglet membres
|
/// Onglet membres
|
||||||
Widget _buildMembersTab() {
|
Widget _buildMembersTab() {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -473,9 +372,9 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildStatItem('Total', total)),
|
Expanded(child: _buildStatItem('Total', total)),
|
||||||
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
|
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
|
||||||
Expanded(child: _buildStatItem('Nouveaux', nouveaux)),
|
Expanded(child: _buildStatItem('Nouveaux', nouveaux)),
|
||||||
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
|
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
|
||||||
Expanded(child: _buildStatItem('Actifs %', actifs)),
|
Expanded(child: _buildStatItem('Actifs %', actifs)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -512,6 +411,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
/// Onglet organisations
|
/// Onglet organisations
|
||||||
Widget _buildOrganizationsTab() {
|
Widget _buildOrganizationsTab() {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -537,7 +437,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.business_center_outlined, color: AppColors.primaryGreen, size: 20),
|
const Icon(Icons.business_center_outlined, color: AppColors.primary, size: 20),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(
|
Text(
|
||||||
'Indicateurs Organisations'.toUpperCase(),
|
'Indicateurs Organisations'.toUpperCase(),
|
||||||
@@ -549,9 +449,9 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildStatItem('Total', total)),
|
Expanded(child: _buildStatItem('Total', total)),
|
||||||
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
|
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
|
||||||
Expanded(child: _buildStatItem('Actives', actives)),
|
Expanded(child: _buildStatItem('Actives', actives)),
|
||||||
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
|
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
|
||||||
Expanded(child: _buildStatItem('Membres moy.', moy)),
|
Expanded(child: _buildStatItem('Membres moy.', moy)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -568,7 +468,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.folder_shared_outlined, color: AppColors.primaryGreen, size: 20),
|
const Icon(Icons.folder_shared_outlined, color: AppColors.primary, size: 20),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(
|
Text(
|
||||||
'Rapports Structures'.toUpperCase(),
|
'Rapports Structures'.toUpperCase(),
|
||||||
@@ -588,6 +488,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
/// Onglet événements
|
/// Onglet événements
|
||||||
Widget _buildEventsTab() {
|
Widget _buildEventsTab() {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -625,9 +526,9 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildStatItem('Total', total)),
|
Expanded(child: _buildStatItem('Total', total)),
|
||||||
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
|
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
|
||||||
Expanded(child: _buildStatItem('À Venir', venir)),
|
Expanded(child: _buildStatItem('À Venir', venir)),
|
||||||
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
|
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
|
||||||
Expanded(child: _buildStatItem('Part. moyenne', participation)),
|
Expanded(child: _buildStatItem('Part. moyenne', participation)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -667,7 +568,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: AppTypography.headerSmall.copyWith(color: AppColors.primaryGreen, fontWeight: FontWeight.bold),
|
style: AppTypography.headerSmall.copyWith(color: AppColors.primary, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
@@ -688,19 +589,19 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBorder.withOpacity(0.05),
|
color: AppColors.border.withOpacity(0.05),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)),
|
border: Border.all(color: AppColors.border.withOpacity(0.1)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Icon(icon, color: AppColors.primaryGreen, size: 20),
|
child: Icon(icon, color: AppColors.primary, size: 20),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -713,7 +614,11 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Icon(Icons.file_download_outlined, color: AppColors.textSecondaryLight, size: 20),
|
Icon(Icons.file_download_outlined,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary,
|
||||||
|
size: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -752,7 +657,7 @@ class _ReportsPageState extends State<ReportsPage>
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
context.read<ReportsBloc>().add(GenerateReportRequested('export', format: _selectedFormat));
|
context.read<ReportsBloc>().add(GenerateReportRequested('export', format: _selectedFormat));
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryGreen, foregroundColor: Colors.white),
|
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primary, foregroundColor: AppColors.onPrimary),
|
||||||
child: const Text('Exporter'),
|
child: const Text('Exporter'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -28,18 +28,19 @@ class _DemandeAideDetailPageState extends State<DemandeAideDetailPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: const UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'DÉTAIL DEMANDE',
|
title: 'Détail Demande',
|
||||||
backgroundColor: AppColors.surface,
|
moduleGradient: ModuleColors.solidariteGradient,
|
||||||
foregroundColor: AppColors.textPrimaryLight,
|
|
||||||
),
|
),
|
||||||
body: BlocConsumer<SolidarityBloc, SolidarityState>(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: BlocConsumer<SolidarityBloc, SolidarityState>(
|
||||||
listenWhen: (prev, curr) => prev.status != curr.status,
|
listenWhen: (prev, curr) => prev.status != curr.status,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.status == SolidarityStatus.error && state.message != null) {
|
if (state.status == SolidarityStatus.error && state.message != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(state.message!), backgroundColor: Colors.red),
|
SnackBar(content: Text(state.message!), backgroundColor: AppColors.error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -55,7 +56,7 @@ class _DemandeAideDetailPageState extends State<DemandeAideDetailPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.error_outline, size: 64, color: Colors.grey),
|
const Icon(Icons.error_outline, size: 64, color: AppColors.textTertiary),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Demande introuvable',
|
'Demande introuvable',
|
||||||
@@ -65,7 +66,12 @@ class _DemandeAideDetailPageState extends State<DemandeAideDetailPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
|
color: ModuleColors.solidarite,
|
||||||
|
onRefresh: () async =>
|
||||||
|
context.read<SolidarityBloc>().add(LoadDemandeAideById(widget.demandeId)),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -97,10 +103,12 @@ class _DemandeAideDetailPageState extends State<DemandeAideDetailPage> {
|
|||||||
_ActionsSection(demande: d, isGestionnaire: _isGestionnaire()),
|
_ActionsSection(demande: d, isGestionnaire: _isGestionnaire()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
), // SingleChildScrollView
|
||||||
|
); // RefreshIndicator
|
||||||
},
|
},
|
||||||
),
|
), // BlocConsumer
|
||||||
);
|
), // SafeArea
|
||||||
|
); // Scaffold
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isGestionnaire() {
|
bool _isGestionnaire() {
|
||||||
@@ -136,7 +144,7 @@ class _InfoCard extends StatelessWidget {
|
|||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
color: AppColors.textSecondaryLight,
|
color: AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
|
|||||||
@@ -76,17 +76,16 @@ class _DemandesAidePageState extends State<DemandesAidePage>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'SOLIDARITÉ',
|
title: 'Solidarité',
|
||||||
backgroundColor: AppColors.surface,
|
moduleGradient: ModuleColors.solidariteGradient,
|
||||||
foregroundColor: AppColors.textPrimaryLight,
|
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
onTap: _loadTab,
|
onTap: _loadTab,
|
||||||
labelColor: AppColors.primaryGreen,
|
labelColor: Colors.white,
|
||||||
unselectedLabelColor: AppColors.textSecondaryLight,
|
unselectedLabelColor: Colors.white70,
|
||||||
indicatorColor: AppColors.primaryGreen,
|
indicatorColor: Colors.white,
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
||||||
tabs: const [
|
tabs: const [
|
||||||
@@ -125,7 +124,13 @@ class _DemandesAidePageState extends State<DemandesAidePage>
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.volunteer_activism_outlined, size: 32, color: AppColors.lightBorder),
|
Icon(
|
||||||
|
Icons.volunteer_activism_outlined,
|
||||||
|
size: 32,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.borderDark
|
||||||
|
: AppColors.border,
|
||||||
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text('Aucune demande', style: AppTypography.subtitleSmall),
|
Text('Aucune demande', style: AppTypography.subtitleSmall),
|
||||||
],
|
],
|
||||||
@@ -204,7 +209,7 @@ class _DemandeCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildStatutBadge(demande.statut),
|
_buildStatutBadge(context, demande.statut),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -217,7 +222,7 @@ class _DemandeCard extends StatelessWidget {
|
|||||||
Text('MONTANT DEMANDÉ', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
|
Text('MONTANT DEMANDÉ', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
|
||||||
Text(
|
Text(
|
||||||
currencyFormat.format(demande.montantDemande ?? 0),
|
currencyFormat.format(demande.montantDemande ?? 0),
|
||||||
style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primaryGreen),
|
style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primary),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -235,7 +240,7 @@ class _DemandeCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatutBadge(String? statut) {
|
Widget _buildStatutBadge(BuildContext context, String? statut) {
|
||||||
Color color;
|
Color color;
|
||||||
switch (statut) {
|
switch (statut) {
|
||||||
case 'APPROUVEE':
|
case 'APPROUVEE':
|
||||||
@@ -246,10 +251,12 @@ class _DemandeCard extends StatelessWidget {
|
|||||||
break;
|
break;
|
||||||
case 'EN_ATTENTE':
|
case 'EN_ATTENTE':
|
||||||
case 'SOUMISE':
|
case 'SOUMISE':
|
||||||
color = AppColors.brandGreenLight;
|
color = AppColors.primaryLight;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
color = AppColors.textSecondaryLight;
|
color = Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary;
|
||||||
}
|
}
|
||||||
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
|
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user