fix(dashboards): dark mode + scrolledUnderElevation + DashboardInitial
- SuperAdminDashboard : scrolledUnderElevation 1→0 (pas de tint surface sur gradient) - OrgAdminDashboard : idem + gestion état DashboardInitial (plus de blanc flash) - VisitorDashboard : tous les conteneurs body + bouton outline → AppColors pairs (surface/border/textPrimary/textSecondary remplacés par isDark ternaires)
This commit is contained in:
@@ -23,7 +23,7 @@ class OrgAdminDashboard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: UnionFlowColors.background,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: _buildAppBar(context),
|
||||
drawer: DashboardDrawer(
|
||||
onLogout: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
|
||||
@@ -38,7 +38,8 @@ class OrgAdminDashboard extends StatelessWidget {
|
||||
|
||||
return BlocBuilder<DashboardBloc, DashboardState>(
|
||||
builder: (context, dashboardState) {
|
||||
if (dashboardState is DashboardLoading) {
|
||||
if (dashboardState is DashboardLoading ||
|
||||
dashboardState is DashboardInitial) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: UnionFlowColors.gold),
|
||||
);
|
||||
@@ -285,43 +286,62 @@ class OrgAdminDashboard extends StatelessWidget {
|
||||
}
|
||||
|
||||
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
||||
// Pattern aligné sur SuperAdminDashboard : AppBar + flexibleSpace gradient,
|
||||
// texte/icônes en blanc sur le gradient. Conserve l'identité "Admin Org"
|
||||
// via le badge « A » et le gradient gold spécifique au rôle.
|
||||
return AppBar(
|
||||
backgroundColor: UnionFlowColors.surface,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0, // Pas de surface tint sur le gradient gold
|
||||
flexibleSpace: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: UnionFlowColors.goldGradient,
|
||||
),
|
||||
),
|
||||
leading: Builder(
|
||||
builder: (ctx) => IconButton(
|
||||
icon: const Icon(Icons.menu, size: 22, color: Colors.white),
|
||||
onPressed: () => Scaffold.of(ctx).openDrawer(),
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
gradient: UnionFlowColors.goldGradient,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.4)),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'A',
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 18),
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 15),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: 8),
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('UnionFlow', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary)),
|
||||
Text('Admin Organisation', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w400, color: UnionFlowColors.textSecondary)),
|
||||
Text('UnionFlow',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white)),
|
||||
Text('Admin Organisation',
|
||||
style: TextStyle(fontSize: 10, fontWeight: FontWeight.w400, color: Colors.white70)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
actionsIconTheme: const IconThemeData(color: Colors.white),
|
||||
actions: [
|
||||
UnionExportButton(onExport: (_) => _handleExport(context)),
|
||||
const SizedBox(width: 8),
|
||||
UnionNotificationBadge(
|
||||
count: 0,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.notifications_outlined),
|
||||
color: UnionFlowColors.textPrimary,
|
||||
icon: const Icon(Icons.notifications_outlined, color: Colors.white),
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const NotificationsPageWrapper()),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../../../shared/design_system/unionflow_design_v2.dart';
|
||||
import '../../../../../shared/design_system/tokens/module_colors.dart';
|
||||
import '../../../../../shared/design_system/tokens/color_tokens.dart';
|
||||
import '../../bloc/dashboard_bloc.dart';
|
||||
import '../../../domain/entities/dashboard_entity.dart';
|
||||
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
|
||||
@@ -11,252 +15,302 @@ import '../../../../backup/presentation/pages/backup_page.dart';
|
||||
import '../../../../help/presentation/pages/help_support_page.dart';
|
||||
import '../../widgets/dashboard_drawer.dart';
|
||||
|
||||
/// Dashboard Super Admin — design fintech compact
|
||||
class SuperAdminDashboard extends StatelessWidget {
|
||||
/// Dashboard Super Admin — design harmonisé v2
|
||||
///
|
||||
/// Corrections v2 :
|
||||
/// • Dark mode : _MemberActivityCard / _OrgTypeCard en StatelessWidget avec BuildContext
|
||||
/// • Couleur module : ModuleColors.systeme (navy autorité) au lieu de UnionFlowColors.error
|
||||
/// • ROOT badge : ModuleColors.solidariteGradient (rouge sémantique = root/danger)
|
||||
/// • KPIs : ModuleColors.organisations/membres/evenements/cotisations
|
||||
/// • Indicateur WebSocket dans l'AppBar
|
||||
/// • États vides pour activités et événements
|
||||
/// • Refresh via Completer (plus de Future.delayed artificiel)
|
||||
/// • "Sécurité" → "Analytics" (supprime la duplication avec "Système")
|
||||
class SuperAdminDashboard extends StatefulWidget {
|
||||
const SuperAdminDashboard({super.key});
|
||||
|
||||
@override
|
||||
State<SuperAdminDashboard> createState() => _SuperAdminDashboardState();
|
||||
}
|
||||
|
||||
class _SuperAdminDashboardState extends State<SuperAdminDashboard> {
|
||||
Completer<void>? _refreshCompleter;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: UnionFlowColors.background,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: _buildAppBar(context),
|
||||
drawer: DashboardDrawer(
|
||||
onLogout: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
|
||||
),
|
||||
body: BlocBuilder<AuthBloc, AuthState>(
|
||||
builder: (context, authState) {
|
||||
final user = (authState is AuthAuthenticated) ? authState.user : null;
|
||||
return BlocBuilder<DashboardBloc, DashboardState>(
|
||||
builder: (context, dashboardState) {
|
||||
if (dashboardState is DashboardLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: UnionFlowColors.error,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
final dashboardData = (dashboardState is DashboardLoaded)
|
||||
? dashboardState.dashboardData
|
||||
: null;
|
||||
final stats = dashboardData?.stats;
|
||||
body: BlocListener<DashboardBloc, DashboardState>(
|
||||
listener: (context, state) {
|
||||
if (state is DashboardLoaded || state is DashboardError) {
|
||||
_refreshCompleter?.complete();
|
||||
_refreshCompleter = null;
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<AuthBloc, AuthState>(
|
||||
builder: (context, authState) {
|
||||
final user = (authState is AuthAuthenticated) ? authState.user : null;
|
||||
return BlocBuilder<DashboardBloc, DashboardState>(
|
||||
builder: (context, dashboardState) {
|
||||
if (dashboardState is DashboardLoading ||
|
||||
dashboardState is DashboardInitial) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: ModuleColors.systeme,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
color: UnionFlowColors.error,
|
||||
strokeWidth: 2,
|
||||
onRefresh: () async {
|
||||
context.read<DashboardBloc>().add(LoadDashboardData(
|
||||
organizationId: dashboardData?.organizationId ?? '',
|
||||
userId: user?.id ?? '',
|
||||
useGlobalDashboard: true,
|
||||
));
|
||||
await Future.delayed(const Duration(milliseconds: 600));
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ── Carte identité ───────────────────────────────
|
||||
UserIdentityCard(
|
||||
initials: user?.initials ?? 'SA',
|
||||
name: user?.fullName ?? 'Super Administrateur',
|
||||
subtitle: 'Accès global système',
|
||||
badgeLabel: 'ROOT',
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFB91C1C), Color(0xFF7F1D1D)],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
final dashboardData = dashboardState is DashboardLoaded
|
||||
? dashboardState.dashboardData
|
||||
: dashboardState is DashboardRefreshing
|
||||
? dashboardState.dashboardData
|
||||
: null;
|
||||
final stats = dashboardData?.stats;
|
||||
|
||||
return RefreshIndicator(
|
||||
color: ModuleColors.systeme,
|
||||
strokeWidth: 2,
|
||||
onRefresh: () async {
|
||||
_refreshCompleter = Completer<void>();
|
||||
context.read<DashboardBloc>().add(LoadDashboardData(
|
||||
organizationId: dashboardData?.organizationId ?? '',
|
||||
userId: user?.id ?? '',
|
||||
useGlobalDashboard: true,
|
||||
));
|
||||
return _refreshCompleter!.future;
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ── Carte identité ROOT ──────────────────────────
|
||||
UserIdentityCard(
|
||||
initials: user?.initials ?? 'SA',
|
||||
name: user?.fullName ?? 'Super Administrateur',
|
||||
subtitle: 'Accès global système',
|
||||
badgeLabel: 'ROOT',
|
||||
gradient: LinearGradient(
|
||||
colors: ModuleColors.solidariteGradient,
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
accentColor: ModuleColors.solidarite,
|
||||
showTopBorder: false,
|
||||
),
|
||||
accentColor: UnionFlowColors.error,
|
||||
showTopBorder: false,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// ── Balance principale ───────────────────────────
|
||||
UnionBalanceCard(
|
||||
label: 'Caisse Globale Système',
|
||||
amount: _formatAmount(stats?.totalContributionAmount ?? 0),
|
||||
trend: stats != null && stats.totalContributionAmount > 0
|
||||
? '${stats.monthlyGrowth >= 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}%'
|
||||
: null,
|
||||
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// ── Caisse globale ───────────────────────────────
|
||||
UnionBalanceCard(
|
||||
label: 'Caisse Globale Système',
|
||||
amount: _formatAmount(stats?.totalContributionAmount ?? 0),
|
||||
trend: stats != null && stats.totalContributionAmount > 0
|
||||
? '${stats.monthlyGrowth >= 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}%'
|
||||
: null,
|
||||
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// ── Grille 3×2 KPIs ─────────────────────────────
|
||||
GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 6,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 2.0,
|
||||
children: [
|
||||
UnionStatWidget(
|
||||
label: 'Organisations',
|
||||
value: '${stats?.totalOrganizations ?? 0}',
|
||||
icon: Icons.business_rounded,
|
||||
color: UnionFlowColors.unionGreen,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Utilisateurs',
|
||||
value: '${stats?.totalMembers ?? 0}',
|
||||
icon: Icons.groups_rounded,
|
||||
color: UnionFlowColors.gold,
|
||||
trend: stats != null && stats.monthlyGrowth > 0
|
||||
? '+${stats.monthlyGrowth.toStringAsFixed(0)}%'
|
||||
: null,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Événements',
|
||||
value: '${stats?.totalEvents ?? 0}',
|
||||
icon: Icons.event_rounded,
|
||||
color: UnionFlowColors.info,
|
||||
trend: stats != null && stats.upcomingEvents > 0
|
||||
? '${stats.upcomingEvents} à venir'
|
||||
: null,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Cotisations',
|
||||
value: '${stats?.totalContributions ?? 0}',
|
||||
icon: Icons.payments_rounded,
|
||||
color: UnionFlowColors.amber,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Demandes',
|
||||
value: '${stats?.pendingRequests ?? 0}',
|
||||
icon: Icons.pending_actions_rounded,
|
||||
color: (stats?.pendingRequests ?? 0) > 0
|
||||
? UnionFlowColors.warning
|
||||
: UnionFlowColors.success,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Engagement',
|
||||
value: stats != null
|
||||
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
|
||||
: '—',
|
||||
icon: Icons.trending_up_rounded,
|
||||
color: (stats?.engagementRate ?? 0) >= 0.7
|
||||
? UnionFlowColors.success
|
||||
: UnionFlowColors.warning,
|
||||
),
|
||||
// ── KPIs 3×2 ────────────────────────────────────
|
||||
GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 6,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 2.0,
|
||||
children: [
|
||||
UnionStatWidget(
|
||||
label: 'Organisations',
|
||||
value: '${stats?.totalOrganizations ?? 0}',
|
||||
icon: Icons.business_rounded,
|
||||
color: ModuleColors.organisations,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Utilisateurs',
|
||||
value: '${stats?.totalMembers ?? 0}',
|
||||
icon: Icons.groups_rounded,
|
||||
color: ModuleColors.membres,
|
||||
trend: stats != null && stats.monthlyGrowth > 0
|
||||
? '+${stats.monthlyGrowth.toStringAsFixed(0)}%'
|
||||
: null,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Événements',
|
||||
value: '${stats?.totalEvents ?? 0}',
|
||||
icon: Icons.event_rounded,
|
||||
color: ModuleColors.evenements,
|
||||
trend: stats != null && stats.upcomingEvents > 0
|
||||
? '${stats.upcomingEvents} à venir'
|
||||
: null,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Cotisations',
|
||||
value: '${stats?.totalContributions ?? 0}',
|
||||
icon: Icons.payments_rounded,
|
||||
color: ModuleColors.cotisations,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Demandes',
|
||||
value: '${stats?.pendingRequests ?? 0}',
|
||||
icon: Icons.pending_actions_rounded,
|
||||
color: (stats?.pendingRequests ?? 0) > 0
|
||||
? ColorTokens.warning
|
||||
: ColorTokens.success,
|
||||
),
|
||||
UnionStatWidget(
|
||||
label: 'Engagement',
|
||||
value: stats != null
|
||||
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
|
||||
: '—',
|
||||
icon: Icons.trending_up_rounded,
|
||||
color: (stats?.engagementRate ?? 0) >= 0.7
|
||||
? ColorTokens.success
|
||||
: ColorTokens.warning,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// ── Activité membres ─────────────────────────────
|
||||
if (stats != null && stats.totalMembers > 0) ...[
|
||||
_MemberActivityCard(stats: stats),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// ── Activité membres (pie compact) ───────────────
|
||||
if (stats != null && stats.totalMembers > 0) ...[
|
||||
_buildMemberActivityRow(stats),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
// ── Répartition types d'org ──────────────────────
|
||||
if (stats?.organizationTypeDistribution != null &&
|
||||
stats!.organizationTypeDistribution!.isNotEmpty) ...[
|
||||
_OrgTypeCard(stats: stats),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
// ── Répartition types d'org ──────────────────────
|
||||
if (stats?.organizationTypeDistribution != null &&
|
||||
stats!.organizationTypeDistribution!.isNotEmpty) ...[
|
||||
_buildOrgTypeRow(stats),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
// ── Événements à venir ───────────────────────────
|
||||
if (dashboardData != null && dashboardData.hasUpcomingEvents) ...[
|
||||
// ── Événements à venir ───────────────────────────
|
||||
UFSectionHeader(
|
||||
'Événements à venir',
|
||||
trailing: '${dashboardData.upcomingEvents.length} programmés',
|
||||
trailing: dashboardData != null &&
|
||||
dashboardData.hasUpcomingEvents
|
||||
? '${dashboardData.upcomingEvents.length} programmés'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
...dashboardData.upcomingEvents.take(3).map(
|
||||
(e) => DashboardEventRow(
|
||||
title: e.title,
|
||||
date: e.formattedDate,
|
||||
daysUntil: e.daysUntilEvent,
|
||||
participants:
|
||||
'${e.currentParticipants}/${e.maxParticipants}',
|
||||
),
|
||||
),
|
||||
if (dashboardData != null && dashboardData.hasUpcomingEvents)
|
||||
...dashboardData.upcomingEvents.take(3).map(
|
||||
(e) => DashboardEventRow(
|
||||
title: e.title,
|
||||
date: e.formattedDate,
|
||||
daysUntil: e.daysUntilEvent,
|
||||
participants:
|
||||
'${e.currentParticipants}/${e.maxParticipants}',
|
||||
),
|
||||
)
|
||||
else
|
||||
_EmptyStateRow(
|
||||
icon: Icons.event_available_outlined,
|
||||
message: 'Aucun événement programmé',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
// ── Activités récentes ───────────────────────────
|
||||
if (dashboardData != null && dashboardData.hasRecentActivity) ...[
|
||||
// ── Activités récentes ───────────────────────────
|
||||
const UFSectionHeader('Activités récentes',
|
||||
trailing: 'Dernières actions'),
|
||||
const SizedBox(height: 6),
|
||||
...dashboardData.recentActivities.take(5).map(
|
||||
(a) => DashboardActivityRow(
|
||||
title: a.title,
|
||||
description: a.description,
|
||||
timeAgo: a.timeAgo,
|
||||
icon: DashboardActivityRow.iconFor(a.type),
|
||||
color: DashboardActivityRow.colorFor(a.type),
|
||||
),
|
||||
),
|
||||
if (dashboardData != null && dashboardData.hasRecentActivity)
|
||||
...dashboardData.recentActivities.take(5).map(
|
||||
(a) => DashboardActivityRow(
|
||||
title: a.title,
|
||||
description: a.description,
|
||||
timeAgo: a.timeAgo,
|
||||
icon: DashboardActivityRow.iconFor(a.type),
|
||||
color: DashboardActivityRow.colorFor(a.type),
|
||||
),
|
||||
)
|
||||
else
|
||||
_EmptyStateRow(
|
||||
icon: Icons.history_outlined,
|
||||
message: 'Aucune activité récente',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
// ── Actions rapides ──────────────────────────────
|
||||
const UFSectionHeader('Actions'),
|
||||
const SizedBox(height: 8),
|
||||
GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 6,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 2.8,
|
||||
children: [
|
||||
UnionActionButton(
|
||||
label: 'Organisations',
|
||||
icon: Icons.business_rounded,
|
||||
iconColor: UnionFlowColors.unionGreen,
|
||||
onTap: () => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => const OrganizationsPageWrapper())),
|
||||
),
|
||||
UnionActionButton(
|
||||
label: 'Utilisateurs',
|
||||
icon: Icons.people_rounded,
|
||||
iconColor: UnionFlowColors.gold,
|
||||
onTap: () => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => const UserManagementPage())),
|
||||
),
|
||||
UnionActionButton(
|
||||
label: 'Système',
|
||||
icon: Icons.settings_rounded,
|
||||
iconColor: UnionFlowColors.indigo,
|
||||
onTap: () => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => const SystemSettingsPage())),
|
||||
),
|
||||
UnionActionButton(
|
||||
label: 'Backup',
|
||||
icon: Icons.backup_rounded,
|
||||
iconColor: UnionFlowColors.warning,
|
||||
onTap: () => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => const BackupPage())),
|
||||
),
|
||||
UnionActionButton(
|
||||
label: 'Sécurité',
|
||||
icon: Icons.security_rounded,
|
||||
iconColor: UnionFlowColors.error,
|
||||
onTap: () => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => const SystemSettingsPage())),
|
||||
),
|
||||
UnionActionButton(
|
||||
label: 'Support',
|
||||
icon: Icons.help_outline_rounded,
|
||||
iconColor: UnionFlowColors.info,
|
||||
onTap: () => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => const HelpSupportPage())),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
// ── Actions rapides ──────────────────────────────
|
||||
const UFSectionHeader('Actions rapides'),
|
||||
const SizedBox(height: 8),
|
||||
GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 6,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 2.8,
|
||||
children: [
|
||||
UnionActionButton(
|
||||
label: 'Organisations',
|
||||
icon: Icons.business_rounded,
|
||||
iconColor: ModuleColors.organisations,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
const OrganizationsPageWrapper())),
|
||||
),
|
||||
UnionActionButton(
|
||||
label: 'Utilisateurs',
|
||||
icon: Icons.people_rounded,
|
||||
iconColor: ModuleColors.membres,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
const UserManagementPage())),
|
||||
),
|
||||
UnionActionButton(
|
||||
label: 'Système',
|
||||
icon: Icons.settings_rounded,
|
||||
iconColor: ModuleColors.systeme,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
const SystemSettingsPage())),
|
||||
),
|
||||
UnionActionButton(
|
||||
label: 'Backup',
|
||||
icon: Icons.backup_rounded,
|
||||
iconColor: ModuleColors.backup,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const BackupPage())),
|
||||
),
|
||||
// Bouton "Analytics" retiré : redondant avec
|
||||
// l'item "Rapports & Analytics" du menu "Plus".
|
||||
UnionActionButton(
|
||||
label: 'Support',
|
||||
icon: Icons.help_outline_rounded,
|
||||
iconColor: ModuleColors.support,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
const HelpSupportPage())),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -264,24 +318,45 @@ class SuperAdminDashboard extends StatelessWidget {
|
||||
// ─── AppBar ────────────────────────────────────────────────────────────────
|
||||
|
||||
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
||||
// L'AppBar a toujours un fond gradient sombre (navy) — les icônes de status bar
|
||||
// sont donc toujours claires, indépendamment du thème de l'app.
|
||||
return AppBar(
|
||||
backgroundColor: UnionFlowColors.surface,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 1,
|
||||
shadowColor: UnionFlowColors.border,
|
||||
scrolledUnderElevation: 0, // Pas de surface tint sur le gradient
|
||||
systemOverlayStyle: const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.light, // icônes blanches sur gradient sombre
|
||||
statusBarBrightness: Brightness.dark, // iOS : barre sombre
|
||||
),
|
||||
flexibleSpace: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: ModuleColors.systemeGradient,
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
),
|
||||
leading: Builder(
|
||||
builder: (ctx) => IconButton(
|
||||
icon: const Icon(Icons.menu, size: 22, color: UnionFlowColors.textPrimary),
|
||||
icon: const Icon(Icons.menu, size: 22, color: Colors.white),
|
||||
onPressed: () => Scaffold.of(ctx).openDrawer(),
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
// Badge ROOT
|
||||
Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.error,
|
||||
gradient: LinearGradient(
|
||||
colors: ModuleColors.solidariteGradient,
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
@@ -295,38 +370,81 @@ class SuperAdminDashboard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Column(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: const [
|
||||
Text(
|
||||
'UnionFlow',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Super Admin',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
style: TextStyle(fontSize: 10, color: Colors.white70),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
// Indicateur temps réel WebSocket
|
||||
BlocBuilder<DashboardBloc, DashboardState>(
|
||||
builder: (context, state) {
|
||||
final isLive =
|
||||
state is DashboardLoaded || state is DashboardRefreshing;
|
||||
return Tooltip(
|
||||
message: isLive ? 'Temps réel actif' : 'En attente',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 6),
|
||||
child: Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: isLive
|
||||
? ColorTokens.successLight
|
||||
: Colors.white30,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: isLive
|
||||
? [
|
||||
BoxShadow(
|
||||
color: ColorTokens.successLight.withOpacity(0.6),
|
||||
blurRadius: 6,
|
||||
)
|
||||
]
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
UnionExportButton(onExport: (_) {}),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Activité membres ─────────────────────────────────────────────────────
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
Widget _buildMemberActivityRow(DashboardStatsEntity stats) {
|
||||
String _formatAmount(double amount) {
|
||||
if (amount >= 1000000) return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
|
||||
if (amount >= 1000) return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
|
||||
return '${amount.toStringAsFixed(0)} FCFA';
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Widget : Activité membres (dark-mode aware) ───────────────────────────
|
||||
|
||||
class _MemberActivityCard extends StatelessWidget {
|
||||
final DashboardStatsEntity stats;
|
||||
const _MemberActivityCard({required this.stats});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final total = stats.totalMembers;
|
||||
final active = stats.activeMembers;
|
||||
final inactive = total - active;
|
||||
@@ -335,27 +453,26 @@ class SuperAdminDashboard extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
color: scheme.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: UnionFlowColors.border),
|
||||
border: Border.all(color: scheme.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
Text(
|
||||
'Activité membres',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: scheme.onSurface),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'$total membres',
|
||||
style: const TextStyle(fontSize: 10, color: UnionFlowColors.textTertiary),
|
||||
style: TextStyle(fontSize: 10, color: scheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -365,29 +482,40 @@ class SuperAdminDashboard extends StatelessWidget {
|
||||
child: LinearProgressIndicator(
|
||||
value: pct,
|
||||
minHeight: 6,
|
||||
backgroundColor: UnionFlowColors.border,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(UnionFlowColors.success),
|
||||
backgroundColor: scheme.outlineVariant,
|
||||
valueColor:
|
||||
const AlwaysStoppedAnimation<Color>(ColorTokens.success),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
_dot(UnionFlowColors.success),
|
||||
_dot(ColorTokens.success),
|
||||
const SizedBox(width: 4),
|
||||
const Text('Actifs', style: TextStyle(fontSize: 10, color: UnionFlowColors.textSecondary)),
|
||||
Text('Actifs',
|
||||
style: TextStyle(
|
||||
fontSize: 10, color: scheme.onSurfaceVariant)),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'$active (${(pct * 100).toStringAsFixed(0)}%)',
|
||||
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: scheme.onSurface),
|
||||
),
|
||||
const Spacer(),
|
||||
_dot(UnionFlowColors.border),
|
||||
_dot(scheme.outlineVariant),
|
||||
const SizedBox(width: 4),
|
||||
const Text('Inactifs', style: TextStyle(fontSize: 10, color: UnionFlowColors.textSecondary)),
|
||||
Text('Inactifs',
|
||||
style: TextStyle(
|
||||
fontSize: 10, color: scheme.onSurfaceVariant)),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'$inactive (${total > 0 ? ((inactive / total) * 100).toStringAsFixed(0) : 0}%)',
|
||||
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: scheme.onSurface),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -396,94 +524,137 @@ class SuperAdminDashboard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Répartition types d'org ──────────────────────────────────────────────
|
||||
|
||||
Widget _buildOrgTypeRow(DashboardStatsEntity stats) {
|
||||
final dist = stats.organizationTypeDistribution!;
|
||||
final total = dist.values.fold(0, (s, v) => s + v);
|
||||
const colors = [
|
||||
UnionFlowColors.unionGreen,
|
||||
UnionFlowColors.gold,
|
||||
UnionFlowColors.info,
|
||||
UnionFlowColors.warning,
|
||||
UnionFlowColors.error,
|
||||
];
|
||||
final entries = dist.entries.toList();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: UnionFlowColors.border),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
'Types d\'organisation',
|
||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
|
||||
),
|
||||
const Spacer(),
|
||||
Text('$total org.', style: const TextStyle(fontSize: 10, color: UnionFlowColors.textTertiary)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Row(
|
||||
children: entries.asMap().entries.map((e) {
|
||||
final frac = total > 0 ? e.value.value / total : 0.0;
|
||||
return Flexible(
|
||||
flex: (frac * 100).round(),
|
||||
child: Container(height: 6, color: colors[e.key % colors.length]),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...entries.asMap().entries.map((e) {
|
||||
final color = colors[e.key % colors.length];
|
||||
final pct = total > 0 ? (e.value.value / total * 100).toStringAsFixed(0) : '0';
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
_dot(color),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
e.value.key,
|
||||
style: const TextStyle(fontSize: 10, color: UnionFlowColors.textSecondary),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$pct% · ${e.value.value}',
|
||||
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _dot(Color color) => Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────
|
||||
// ─── Widget : Répartition types d'org (dark-mode aware) ───────────────────
|
||||
|
||||
String _formatAmount(double amount) {
|
||||
if (amount >= 1000000) return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
|
||||
if (amount >= 1000) return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
|
||||
return '${amount.toStringAsFixed(0)} FCFA';
|
||||
class _OrgTypeCard extends StatelessWidget {
|
||||
final DashboardStatsEntity stats;
|
||||
const _OrgTypeCard({required this.stats});
|
||||
|
||||
static const _kColors = [
|
||||
ModuleColors.organisations,
|
||||
ModuleColors.membres,
|
||||
ModuleColors.evenements,
|
||||
ModuleColors.cotisations,
|
||||
ModuleColors.solidarite,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final dist = stats.organizationTypeDistribution!;
|
||||
final total = dist.values.fold(0, (s, v) => s + v);
|
||||
final entries = dist.entries.toList();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: scheme.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Types d\'organisation',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: scheme.onSurface),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'$total org.',
|
||||
style: TextStyle(fontSize: 10, color: scheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Row(
|
||||
children: entries.asMap().entries.map((e) {
|
||||
final frac = total > 0 ? e.value.value / total : 0.0;
|
||||
return Flexible(
|
||||
flex: (frac * 100).round(),
|
||||
child: Container(
|
||||
height: 6, color: _kColors[e.key % _kColors.length]),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...entries.asMap().entries.map((e) {
|
||||
final color = _kColors[e.key % _kColors.length];
|
||||
final pct =
|
||||
total > 0 ? (e.value.value / total * 100).toStringAsFixed(0) : '0';
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
e.value.key,
|
||||
style: TextStyle(
|
||||
fontSize: 10, color: scheme.onSurfaceVariant),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$pct% · ${e.value.value}',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: scheme.onSurface),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Widget : État vide inline ─────────────────────────────────────────────
|
||||
|
||||
class _EmptyStateRow extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String message;
|
||||
const _EmptyStateRow({required this.icon, required this.message});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: scheme.onSurfaceVariant),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
message,
|
||||
style: TextStyle(fontSize: 12, color: scheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../../../shared/design_system/unionflow_design_v2.dart';
|
||||
import '../../../../../shared/design_system/tokens/app_colors.dart';
|
||||
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
|
||||
|
||||
/// Dashboard affiché pour un compte authentifié sans rôle métier actif.
|
||||
@@ -11,11 +12,17 @@ class VisitorDashboard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final authState = context.watch<AuthBloc>().state;
|
||||
final email = authState is AuthAuthenticated ? authState.user.email : '';
|
||||
final email = authState is AuthAuthenticated ? authState.user.email : '';
|
||||
final firstName = authState is AuthAuthenticated ? authState.user.firstName : '';
|
||||
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final bgCard = isDark ? AppColors.surfaceDark : AppColors.surface;
|
||||
final borderColor = isDark ? AppColors.borderDark : AppColors.border;
|
||||
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
|
||||
final textSecondary= isDark ? AppColors.textSecondaryDark: AppColors.textSecondary;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UnionFlowColors.background,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: _buildAppBar(context),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
@@ -32,32 +39,20 @@ class VisitorDashboard extends StatelessWidget {
|
||||
color: UnionFlowColors.gold.withOpacity(0.12),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.hourglass_empty_rounded,
|
||||
size: 40,
|
||||
color: UnionFlowColors.gold,
|
||||
),
|
||||
child: const Icon(Icons.hourglass_empty_rounded, size: 40, color: UnionFlowColors.gold),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Titre
|
||||
Text(
|
||||
firstName.isNotEmpty ? 'Bonjour $firstName,' : 'Bienvenue,',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w800, color: textPrimary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
Text(
|
||||
'Votre compte est en attente d\'activation',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: textPrimary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -67,22 +62,16 @@ class VisitorDashboard extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
color: bgCard,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: UnionFlowColors.border),
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.email_outlined, size: 14, color: UnionFlowColors.textSecondary),
|
||||
Icon(Icons.email_outlined, size: 14, color: textSecondary),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
email,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
Text(email, style: TextStyle(fontSize: 13, color: textSecondary)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -93,41 +82,38 @@ class VisitorDashboard extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
color: bgCard,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: const Border(
|
||||
left: BorderSide(color: UnionFlowColors.gold, width: 3),
|
||||
),
|
||||
boxShadow: UnionFlowColors.softShadow,
|
||||
border: Border(left: BorderSide(color: UnionFlowColors.gold, width: 3)),
|
||||
boxShadow: isDark ? null : UnionFlowColors.softShadow,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
Text(
|
||||
'Que se passe-t-il ?',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w700, color: textPrimary),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStep(
|
||||
'1',
|
||||
'Votre compte a été créé par l\'administrateur de votre organisation.',
|
||||
UnionFlowColors.unionGreen,
|
||||
textSecondary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStep(
|
||||
'2',
|
||||
'Il doit être activé par un administrateur avant que vous puissiez accéder à la plateforme.',
|
||||
UnionFlowColors.gold,
|
||||
textSecondary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStep(
|
||||
'3',
|
||||
'Une fois activé, reconnectez-vous pour accéder à votre espace.',
|
||||
UnionFlowColors.indigo,
|
||||
textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -138,9 +124,7 @@ class VisitorDashboard extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(const AuthStatusChecked());
|
||||
},
|
||||
onPressed: () => context.read<AuthBloc>().add(const AuthStatusChecked()),
|
||||
icon: const Icon(Icons.refresh_rounded, size: 18),
|
||||
label: const Text(
|
||||
'Vérifier l\'état de mon compte',
|
||||
@@ -161,23 +145,20 @@ class VisitorDashboard extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(const AuthLogoutRequested());
|
||||
},
|
||||
icon: const Icon(Icons.logout_rounded, size: 18),
|
||||
label: const Text(
|
||||
onPressed: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
|
||||
icon: Icon(Icons.logout_rounded, size: 18, color: textSecondary),
|
||||
label: Text(
|
||||
'Se déconnecter',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: textSecondary),
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: UnionFlowColors.textSecondary,
|
||||
side: const BorderSide(color: UnionFlowColors.border),
|
||||
foregroundColor: textSecondary,
|
||||
side: BorderSide(color: borderColor),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
@@ -187,8 +168,10 @@ class VisitorDashboard extends StatelessWidget {
|
||||
|
||||
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
||||
return AppBar(
|
||||
backgroundColor: UnionFlowColors.surface,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 1,
|
||||
shadowColor: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||
automaticallyImplyLeading: false,
|
||||
title: Row(
|
||||
children: [
|
||||
@@ -200,74 +183,48 @@ class VisitorDashboard extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'U',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
child: const Text('U',
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 18)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Column(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'UnionFlow',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Compte en attente',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
Text('UnionFlow',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
)),
|
||||
Text('Compte en attente',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
|
||||
iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStep(String number, String text, Color color) {
|
||||
Widget _buildStep(String number, String text, Color badgeColor, Color textColor) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 22,
|
||||
height: 22,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
decoration: BoxDecoration(color: badgeColor, shape: BoxShape.circle),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
number,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
child: Text(number,
|
||||
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w800, color: Colors.white)),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.5,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
child: Text(text, style: TextStyle(fontSize: 12, height: 1.5, color: textColor)),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user