feat(unionflow): ajout Spec-Kit, constitution, mission mutuelles

- Config Spec-Kit pour Spec-Driven Development
- CONSTITUTION.md + .specify/memory/constitution.md
- Commandes Cursor /speckit.*, règles projet
- Mission: associations + mutuelles d'épargne et de financement
- .gitignore: versionner config spec-kit unionflow

Made-with: Cursor
This commit is contained in:
dahoud
2026-02-27 14:41:07 +00:00
parent 144b68f8e7
commit b1957c1c81
631 changed files with 104070 additions and 0 deletions

View File

@@ -0,0 +1,275 @@
import 'package:flutter/material.dart';
/// Dashboard simple pour Membre Actif
class ActiveMemberDashboard extends StatelessWidget {
const ActiveMemberDashboard({super.key});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête de bienvenue
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF00B894), Color(0xFF00CEC9)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Bonjour !',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'Bienvenue sur votre espace membre',
style: TextStyle(
color: Colors.white70,
fontSize: 16,
),
),
],
),
),
const SizedBox(height: 24),
// Statistiques rapides
const Text(
'Mes Statistiques',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
childAspectRatio: 1.2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: [
_buildStatCard(
icon: Icons.event_available,
value: '12',
title: 'Événements',
color: const Color(0xFF00B894),
),
_buildStatCard(
icon: Icons.volunteer_activism,
value: '3',
title: 'Solidarité',
color: const Color(0xFF00CEC9),
),
_buildStatCard(
icon: Icons.payment,
value: 'À jour',
title: 'Cotisations',
color: const Color(0xFF0984E3),
),
_buildStatCard(
icon: Icons.star,
value: '4.8',
title: 'Engagement',
color: const Color(0xFFE17055),
),
],
),
const SizedBox(height: 24),
// Actions rapides
const Text(
'Actions Rapides',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
childAspectRatio: 1.5,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: [
_buildActionCard(
icon: Icons.event,
title: 'Créer Événement',
color: const Color(0xFF00B894),
onTap: () {},
),
_buildActionCard(
icon: Icons.volunteer_activism,
title: 'Demande Aide',
color: const Color(0xFF00CEC9),
onTap: () {},
),
_buildActionCard(
icon: Icons.account_circle,
title: 'Mon Profil',
color: const Color(0xFF0984E3),
onTap: () {},
),
_buildActionCard(
icon: Icons.message,
title: 'Contacter',
color: const Color(0xFFE17055),
onTap: () {},
),
],
),
const SizedBox(height: 24),
// Activités récentes
const Text(
'Activités Récentes',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Card(
child: Column(
children: [
_buildActivityItem(
icon: Icons.check_circle,
title: 'Participation confirmée',
subtitle: 'Assemblée Générale - Il y a 2h',
color: const Color(0xFF00B894),
),
const Divider(height: 1),
_buildActivityItem(
icon: Icons.payment,
title: 'Cotisation payée',
subtitle: 'Décembre 2024 - Il y a 1j',
color: const Color(0xFF0984E3),
),
const Divider(height: 1),
_buildActivityItem(
icon: Icons.event,
title: 'Événement créé',
subtitle: 'Sortie ski de fond - Il y a 3j',
color: const Color(0xFF00CEC9),
),
],
),
),
],
),
);
}
Widget _buildStatCard({
required IconData icon,
required String value,
required String title,
required Color color,
}) {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 32),
const SizedBox(height: 8),
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildActionCard({
required IconData icon,
required String title,
required Color color,
required VoidCallback onTap,
}) {
return Card(
elevation: 2,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 28),
const SizedBox(height: 8),
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
Widget _buildActivityItem({
required IconData icon,
required String title,
required String subtitle,
required Color color,
}) {
return ListTile(
leading: CircleAvatar(
backgroundColor: color.withOpacity(0.1),
child: Icon(icon, color: color, size: 20),
),
title: Text(
title,
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text(subtitle),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
);
}
}

View File

@@ -0,0 +1,767 @@
/// Dashboard Consultant - Interface Limitée
/// Interface spécialisée pour consultants externes
library consultant_dashboard;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../profile/presentation/pages/profile_page_wrapper.dart';
import '../../../../help/presentation/pages/help_support_page.dart';
import '../../../../notifications/presentation/pages/notifications_page_wrapper.dart';
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
/// Dashboard pour Consultant Externe
class ConsultantDashboard extends StatefulWidget {
const ConsultantDashboard({super.key});
@override
State<ConsultantDashboard> createState() => _ConsultantDashboardState();
}
class _ConsultantDashboardState extends State<ConsultantDashboard> {
int _selectedIndex = 0;
final List<String> _consultantSections = [
'Mes Projets',
'Contacts',
'Profil',
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF8F9FA),
appBar: AppBar(
title: Text(
'Consultant - ${_consultantSections[_selectedIndex]}',
style: const TextStyle(
color: Color(0xFF6C5CE7),
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
backgroundColor: Colors.white,
elevation: 2,
centerTitle: false,
actions: [
// Notifications consultant
IconButton(
icon: const Icon(Icons.notifications_outlined, color: Color(0xFF6C5CE7)),
onPressed: () => _showConsultantNotifications(),
tooltip: 'Mes notifications',
),
// Menu consultant
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, color: Color(0xFF6C5CE7)),
onSelected: (value) {
switch (value) {
case 'profile':
_editProfile();
break;
case 'contact':
_contactSupport();
break;
case 'help':
_showHelp();
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'profile',
child: Row(
children: [
Icon(Icons.person, size: 20, color: Color(0xFF6C5CE7)),
SizedBox(width: 12),
Text('Mon Profil'),
],
),
),
const PopupMenuItem(
value: 'contact',
child: Row(
children: [
Icon(Icons.support_agent, size: 20, color: Color(0xFF6C5CE7)),
SizedBox(width: 12),
Text('Support'),
],
),
),
const PopupMenuItem(
value: 'help',
child: Row(
children: [
Icon(Icons.help, size: 20, color: Color(0xFF6C5CE7)),
SizedBox(width: 12),
Text('Aide'),
],
),
),
],
),
],
),
drawer: _buildConsultantDrawer(),
body: Stack(
children: [
_buildSelectedContent(),
// Navigation rapide consultant
Positioned(
bottom: 20,
left: 20,
right: 20,
child: _buildConsultantQuickNavigation(),
),
],
),
);
}
/// Drawer de navigation consultant
Widget _buildConsultantDrawer() {
return Drawer(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF6C5CE7),
Color(0xFF5A4FCF),
Color(0xFF4834D4),
],
),
),
child: Column(
children: [
// Header consultant
Container(
padding: const EdgeInsets.fromLTRB(20, 60, 20, 20),
child: Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 2,
),
),
child: const Icon(
Icons.business_center,
color: Colors.white,
size: 30,
),
),
const SizedBox(width: 16),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Sophie Martin',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
'Consultant IT',
style: TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
],
),
),
],
),
),
// Menu de navigation
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: _consultantSections.length,
itemBuilder: (context, index) {
final isSelected = _selectedIndex == index;
return Container(
margin: const EdgeInsets.symmetric(vertical: 2),
decoration: BoxDecoration(
color: isSelected
? Colors.white.withOpacity(0.2)
: Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: ListTile(
leading: Icon(
_getConsultantSectionIcon(index),
color: Colors.white,
size: 22,
),
title: Text(
_consultantSections[index],
style: TextStyle(
color: Colors.white,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
),
),
onTap: () {
setState(() => _selectedIndex = index);
Navigator.pop(context);
},
),
);
},
),
),
// Footer avec déconnexion
Container(
padding: const EdgeInsets.all(16),
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pop();
context.read<AuthBloc>().add(const AuthLogoutRequested());
},
icon: const Icon(Icons.logout, size: 16),
label: const Text('Déconnexion'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white.withOpacity(0.2),
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 40),
),
),
),
],
),
),
);
}
/// Icône pour chaque section consultant
IconData _getConsultantSectionIcon(int index) {
switch (index) {
case 0: return Icons.work;
case 1: return Icons.contacts;
case 2: return Icons.person;
default: return Icons.work;
}
}
/// Contenu de la section sélectionnée
Widget _buildSelectedContent() {
switch (_selectedIndex) {
case 0:
return _buildProjectsContent();
case 1:
return _buildContactsContent();
case 2:
return _buildProfileContent();
default:
return _buildProjectsContent();
}
}
/// Mes Projets - Vue des projets assignés
Widget _buildProjectsContent() {
return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header projets
_buildProjectsHeader(),
const SizedBox(height: 20),
// Projets actifs
_buildActiveProjects(),
const SizedBox(height: 20),
// Tâches en cours
_buildCurrentTasks(),
const SizedBox(height: 20),
// Statistiques consultant
_buildConsultantStats(),
],
),
);
}
/// Placeholder pour les autres sections
Widget _buildContactsContent() {
return const Center(
child: Text(
'Contacts\n(En développement)',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
);
}
Widget _buildProfileContent() {
return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
const Text(
'Mon Profil',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _editProfile,
icon: const Icon(Icons.edit, size: 20),
label: const Text('Éditer mon profil'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6C5CE7),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
),
),
],
),
),
],
),
);
}
/// Header projets
Widget _buildProjectsHeader() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Row(
children: [
Icon(Icons.work, color: Color(0xFF6C5CE7), size: 24),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mes Projets Assignés',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
'3 projets actifs',
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
],
),
),
],
),
);
}
/// Projets actifs
Widget _buildActiveProjects() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Projets Actifs',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
_buildProjectCard(
'Refonte Site Web',
'Développement frontend',
'75%',
const Color(0xFF00B894),
),
const SizedBox(height: 8),
_buildProjectCard(
'App Mobile',
'Interface utilisateur',
'45%',
const Color(0xFF0984E3),
),
const SizedBox(height: 8),
_buildProjectCard(
'API Backend',
'Architecture serveur',
'90%',
const Color(0xFFE17055),
),
],
);
}
/// Widget pour une carte de projet
Widget _buildProjectCard(String title, String description, String progress, Color color) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.folder, color: color, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
Text(
description,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
],
),
),
Text(
progress,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
LinearProgressIndicator(
value: double.parse(progress.replaceAll('%', '')) / 100,
backgroundColor: color.withOpacity(0.2),
valueColor: AlwaysStoppedAnimation<Color>(color),
),
],
),
);
}
/// Tâches en cours
Widget _buildCurrentTasks() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Tâches du Jour',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
_buildTaskItem('Révision code frontend', true),
const SizedBox(height: 8),
_buildTaskItem('Réunion client 15h', false),
const SizedBox(height: 8),
_buildTaskItem('Tests unitaires', false),
],
),
),
],
);
}
/// Widget pour un élément de tâche
Widget _buildTaskItem(String task, bool completed) {
return Row(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: completed ? const Color(0xFF6C5CE7) : Colors.transparent,
border: Border.all(color: const Color(0xFF6C5CE7), width: 2),
borderRadius: BorderRadius.circular(4),
),
child: completed
? const Icon(Icons.check, color: Colors.white, size: 14)
: null,
),
const SizedBox(width: 12),
Expanded(
child: Text(
task,
style: TextStyle(
fontSize: 14,
decoration: completed ? TextDecoration.lineThrough : null,
color: completed ? Colors.grey[600] : Colors.black,
),
),
),
],
);
}
/// Statistiques consultant
Widget _buildConsultantStats() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Mes Statistiques',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildStatCard('Projets', '3', Icons.work, const Color(0xFF6C5CE7)),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatCard('Tâches', '12', Icons.task, const Color(0xFF00B894)),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildStatCard('Heures', '156h', Icons.schedule, const Color(0xFF0984E3)),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatCard('Éval.', '4.8/5', Icons.star, const Color(0xFFFDAB00)),
),
],
),
],
);
}
/// Widget pour une carte de statistique
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(height: 8),
Text(
value,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
title,
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
),
],
),
);
}
/// Navigation rapide consultant
Widget _buildConsultantQuickNavigation() {
return Container(
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 5),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildNavItem(Icons.work, 'Projets', 0),
_buildNavItem(Icons.contacts, 'Contacts', 1),
_buildNavItem(Icons.person, 'Profil', 2),
],
),
);
}
/// Widget pour un élément de navigation
Widget _buildNavItem(IconData icon, String label, int index) {
final isSelected = _selectedIndex == index;
return GestureDetector(
onTap: () => setState(() => _selectedIndex = index),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelected
? const Color(0xFF6C5CE7).withOpacity(0.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
size: 18,
color: isSelected
? const Color(0xFF6C5CE7)
: Colors.grey[600],
),
),
const SizedBox(height: 2),
Text(
label,
style: TextStyle(
fontSize: 9,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected
? const Color(0xFF6C5CE7)
: Colors.grey[600],
),
),
],
),
),
);
}
// Méthodes d'action
void _showConsultantNotifications() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const NotificationsPageWrapper(),
),
);
}
void _editProfile() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ProfilePageWrapper(),
),
);
}
void _contactSupport() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const HelpSupportPage(),
),
);
}
void _showHelp() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const HelpSupportPage(),
),
);
}
}

View File

@@ -0,0 +1,915 @@
/// Dashboard Gestionnaire RH - Interface Ressources Humaines
/// Outils spécialisés pour la gestion des employés et RH
library hr_manager_dashboard;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
import '../../../../notifications/presentation/pages/notifications_page_wrapper.dart';
import '../../../../settings/presentation/pages/system_settings_page.dart';
import '../../../../reports/presentation/pages/reports_page_wrapper.dart';
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
/// Dashboard spécialisé pour Gestionnaire RH
///
/// Fonctionnalités RH :
/// - Gestion des employés
/// - Recrutement et onboarding
/// - Évaluations de performance
/// - Congés et absences
/// - Reporting RH
/// - Formation et développement
class HRManagerDashboard extends StatefulWidget {
const HRManagerDashboard({super.key});
@override
State<HRManagerDashboard> createState() => _HRManagerDashboardState();
}
class _HRManagerDashboardState extends State<HRManagerDashboard>
with TickerProviderStateMixin {
late TabController _tabController;
int _selectedIndex = 0;
final List<String> _hrSections = [
'Vue d\'ensemble',
'Employés',
'Recrutement',
'Évaluations',
'Congés',
'Formation',
];
@override
void initState() {
super.initState();
_tabController = TabController(length: _hrSections.length, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF8F9FA),
appBar: AppBar(
title: Text(
'RH Manager - ${_hrSections[_selectedIndex]}',
style: const TextStyle(
color: Color(0xFF00B894),
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
backgroundColor: Colors.white,
elevation: 2,
centerTitle: false,
actions: [
// Recherche employés
IconButton(
icon: const Icon(Icons.search, color: Color(0xFF00B894)),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MembersPageWrapper(),
),
);
},
tooltip: 'Rechercher employés',
),
// Notifications RH
IconButton(
icon: const Icon(Icons.notifications_outlined, color: Color(0xFF00B894)),
onPressed: () => _showHRNotifications(),
tooltip: 'Notifications RH',
),
// Menu RH
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, color: Color(0xFF00B894)),
onSelected: (value) {
switch (value) {
case 'reports':
_generateHRReports();
break;
case 'settings':
_openHRSettings();
break;
case 'export':
_exportHRData();
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'reports',
child: Row(
children: [
Icon(Icons.assessment, size: 20, color: Color(0xFF00B894)),
SizedBox(width: 12),
Text('Rapports RH'),
],
),
),
const PopupMenuItem(
value: 'settings',
child: Row(
children: [
Icon(Icons.settings, size: 20, color: Color(0xFF00B894)),
SizedBox(width: 12),
Text('Paramètres RH'),
],
),
),
const PopupMenuItem(
value: 'export',
child: Row(
children: [
Icon(Icons.download, size: 20, color: Color(0xFF00B894)),
SizedBox(width: 12),
Text('Exporter données'),
],
),
),
],
),
],
),
drawer: _buildHRDrawer(),
body: Stack(
children: [
_buildSelectedContent(),
// Navigation rapide RH
Positioned(
bottom: 20,
left: 20,
right: 20,
child: _buildHRQuickNavigation(),
),
],
),
);
}
/// Drawer de navigation RH
Widget _buildHRDrawer() {
return Drawer(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF00B894),
Color(0xFF00A085),
Color(0xFF008B75),
],
),
),
child: Column(
children: [
// Header RH
Container(
padding: const EdgeInsets.fromLTRB(20, 60, 20, 20),
child: Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 2,
),
),
child: const Icon(
Icons.people,
color: Colors.white,
size: 30,
),
),
const SizedBox(width: 16),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Gestionnaire RH',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
'Ressources Humaines',
style: TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
],
),
),
],
),
),
// Menu de navigation
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: _hrSections.length,
itemBuilder: (context, index) {
final isSelected = _selectedIndex == index;
return Container(
margin: const EdgeInsets.symmetric(vertical: 2),
decoration: BoxDecoration(
color: isSelected
? Colors.white.withOpacity(0.2)
: Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: ListTile(
leading: Icon(
_getHRSectionIcon(index),
color: Colors.white,
size: 22,
),
title: Text(
_hrSections[index],
style: TextStyle(
color: Colors.white,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
),
),
onTap: () {
setState(() => _selectedIndex = index);
Navigator.pop(context);
},
),
);
},
),
),
// Footer avec déconnexion
Container(
padding: const EdgeInsets.all(16),
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pop();
context.read<AuthBloc>().add(const AuthLogoutRequested());
},
icon: const Icon(Icons.logout, size: 16),
label: const Text('Déconnexion'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white.withOpacity(0.2),
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 40),
),
),
),
],
),
),
);
}
/// Icône pour chaque section RH
IconData _getHRSectionIcon(int index) {
switch (index) {
case 0: return Icons.dashboard;
case 1: return Icons.people;
case 2: return Icons.person_add;
case 3: return Icons.star_rate;
case 4: return Icons.event_busy;
case 5: return Icons.school;
default: return Icons.dashboard;
}
}
/// Contenu de la section sélectionnée
Widget _buildSelectedContent() {
switch (_selectedIndex) {
case 0:
return _buildOverviewContent();
case 1:
return _buildEmployeesContent();
case 2:
return _buildRecruitmentContent();
case 3:
return _buildEvaluationsContent();
case 4:
return _buildLeavesContent();
case 5:
return _buildTrainingContent();
default:
return _buildOverviewContent();
}
}
/// Vue d'ensemble RH
Widget _buildOverviewContent() {
return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header avec statut RH
_buildHRStatusHeader(),
const SizedBox(height: 20),
// KPIs RH
_buildHRKPIsSection(),
const SizedBox(height: 20),
// Actions rapides RH
_buildHRQuickActions(),
const SizedBox(height: 20),
// Alertes RH importantes
_buildHRAlerts(),
],
),
);
}
/// Placeholder pour les autres sections
Widget _buildEmployeesContent() {
return const Center(
child: Text(
'Gestion des Employés\n(En développement)',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
);
}
Widget _buildRecruitmentContent() {
return const Center(
child: Text(
'Recrutement\n(En développement)',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
);
}
Widget _buildEvaluationsContent() {
return const Center(
child: Text(
'Évaluations\n(En développement)',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
);
}
Widget _buildLeavesContent() {
return const Center(
child: Text(
'Congés et Absences\n(En développement)',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
);
}
Widget _buildTrainingContent() {
return const Center(
child: Text(
'Formation\n(En développement)',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
);
}
/// Header avec statut RH
Widget _buildHRStatusHeader() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF00B894), Color(0xFF00A085)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: const Color(0xFF00B894).withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Département RH Actif',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'Dernière sync: ${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}',
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 12,
),
),
],
),
),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.people,
color: Colors.white,
size: 28,
),
),
],
),
);
}
/// Section KPIs RH
Widget _buildHRKPIsSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Indicateurs RH',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildHRKPICard(
'Employés Actifs',
'247',
'+12 ce mois',
Icons.people,
const Color(0xFF00B894),
),
),
const SizedBox(width: 12),
Expanded(
child: _buildHRKPICard(
'Candidatures',
'34',
'+8 cette semaine',
Icons.person_add,
const Color(0xFF0984E3),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildHRKPICard(
'En Congé',
'18',
'7.3% de l\'effectif',
Icons.event_busy,
const Color(0xFFFDAB00),
),
),
const SizedBox(width: 12),
Expanded(
child: _buildHRKPICard(
'Évaluations',
'156',
'89% complétées',
Icons.star_rate,
const Color(0xFFE17055),
),
),
],
),
],
);
}
/// Widget pour une carte KPI RH
Widget _buildHRKPICard(
String title,
String value,
String subtitle,
IconData icon,
Color color,
) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: color,
size: 20,
),
),
const Spacer(),
],
),
const SizedBox(height: 12),
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 2),
Text(
subtitle,
style: TextStyle(
fontSize: 10,
color: Colors.grey[600],
),
),
],
),
);
}
/// Actions rapides RH
Widget _buildHRQuickActions() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Actions Rapides',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 1.5,
children: [
_buildHRActionCard(
'Nouveau Employé',
Icons.person_add,
const Color(0xFF00B894),
() => _addNewEmployee(),
),
_buildHRActionCard(
'Demandes Congés',
Icons.event_busy,
const Color(0xFFFDAB00),
() => _viewLeaveRequests(),
),
_buildHRActionCard(
'Évaluations',
Icons.star_rate,
const Color(0xFFE17055),
() => _viewEvaluations(),
),
_buildHRActionCard(
'Recrutement',
Icons.work,
const Color(0xFF0984E3),
() => _viewRecruitment(),
),
],
),
],
);
}
/// Widget pour une action RH
Widget _buildHRActionCard(
String title,
IconData icon,
Color color,
VoidCallback onTap,
) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.2)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: color,
size: 24,
),
),
const SizedBox(height: 8),
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 12,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
/// Alertes RH importantes
Widget _buildHRAlerts() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Alertes Importantes',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
_buildHRAlertItem(
'Évaluations en retard',
'12 évaluations annuelles à finaliser',
Icons.warning,
const Color(0xFFE17055),
),
const SizedBox(height: 8),
_buildHRAlertItem(
'Congés à approuver',
'5 demandes de congé en attente',
Icons.pending_actions,
const Color(0xFFFDAB00),
),
const SizedBox(height: 8),
_buildHRAlertItem(
'Nouveaux candidats',
'8 candidatures reçues cette semaine',
Icons.person_add,
const Color(0xFF0984E3),
),
],
);
}
/// Widget pour un élément d'alerte RH
Widget _buildHRAlertItem(
String title,
String message,
IconData icon,
Color color,
) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Row(
children: [
Icon(icon, color: color, size: 20),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 12,
color: color,
),
),
Text(
message,
style: TextStyle(
fontSize: 11,
color: Colors.grey[700],
),
),
],
),
),
],
),
);
}
/// Navigation rapide RH
Widget _buildHRQuickNavigation() {
return Container(
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 5),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildHRNavItem(Icons.dashboard, 'Vue', 0),
_buildHRNavItem(Icons.people, 'Employés', 1),
_buildHRNavItem(Icons.person_add, 'Recrutement', 2),
_buildHRNavItem(Icons.star_rate, 'Évaluations', 3),
_buildHRNavItem(Icons.event_busy, 'Congés', 4),
],
),
);
}
/// Widget pour un élément de navigation RH
Widget _buildHRNavItem(IconData icon, String label, int index) {
final isSelected = _selectedIndex == index;
return GestureDetector(
onTap: () => setState(() => _selectedIndex = index),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelected
? const Color(0xFF00B894).withOpacity(0.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
size: 18,
color: isSelected
? const Color(0xFF00B894)
: Colors.grey[600],
),
),
const SizedBox(height: 2),
Text(
label,
style: TextStyle(
fontSize: 9,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected
? const Color(0xFF00B894)
: Colors.grey[600],
),
),
],
),
),
);
}
// Méthodes d'action
void _showHRNotifications() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const NotificationsPageWrapper(),
),
);
}
void _generateHRReports() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ReportsPageWrapper(),
),
);
}
void _openHRSettings() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const SystemSettingsPage(),
),
);
}
void _exportHRData() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ReportsPageWrapper(),
),
);
}
void _addNewEmployee() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Ajouter employé - Fonctionnalité à implémenter'),
backgroundColor: Color(0xFF00B894),
),
);
}
void _viewLeaveRequests() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Demandes de congé - Fonctionnalité à implémenter'),
backgroundColor: Color(0xFFFDAB00),
),
);
}
void _viewEvaluations() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Évaluations - Fonctionnalité à implémenter'),
backgroundColor: Color(0xFFE17055),
),
);
}
void _viewRecruitment() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Recrutement - Fonctionnalité à implémenter'),
backgroundColor: Color(0xFF0984E3),
),
);
}
}

View File

@@ -0,0 +1,230 @@
/// Dashboard Modérateur - Management Hub Focalisé
/// Outils de modération et gestion partielle
library moderator_dashboard;
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../widgets/dashboard_widgets.dart';
/// Dashboard Management Hub pour Modérateur
class ModeratorDashboard extends StatelessWidget {
const ModeratorDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar Modérateur
SliverAppBar(
expandedHeight: 160,
floating: false,
pinned: true,
backgroundColor: const Color(0xFFE17055), // Orange focus
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Management Hub',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFE17055), Color(0xFFD63031)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Center(
child: Icon(Icons.manage_accounts, color: Colors.white, size: 60),
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Métriques modération
_buildModerationMetrics(),
const SizedBox(height: SpacingTokens.xl),
// Actions modération
_buildModerationActions(),
const SizedBox(height: SpacingTokens.xl),
// Tâches en attente
_buildPendingTasks(),
const SizedBox(height: SpacingTokens.xl),
// Activité récente
_buildRecentActivity(),
],
),
),
),
],
),
);
}
Widget _buildModerationMetrics() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Métriques de Modération',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardStatsGrid(
stats: const [
DashboardStat(
icon: Icons.flag,
value: '12',
title: 'Signalements',
color: Color(0xFFE17055),
),
DashboardStat(
icon: Icons.pending_actions,
value: '8',
title: 'En Attente',
color: Color(0xFFD63031),
),
DashboardStat(
icon: Icons.check_circle,
value: '45',
title: 'Résolus',
color: Color(0xFF00B894),
),
DashboardStat(
icon: Icons.people,
value: '156',
title: 'Membres',
color: Color(0xFF0984E3),
),
],
onStatTap: (type) {},
),
],
);
}
Widget _buildModerationActions() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions de Modération',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardQuickActionsGrid(
children: [
DashboardQuickAction(
icon: Icons.gavel,
title: 'Modérer',
color: const Color(0xFFE17055),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.person_remove,
title: 'Suspendre',
color: const Color(0xFFD63031),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.message,
title: 'Communiquer',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.report,
title: 'Rapport',
color: const Color(0xFF6C5CE7),
onTap: () {},
),
],
),
],
);
}
Widget _buildPendingTasks() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Tâches en Attente',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Column(
children: [
ListTile(
leading: CircleAvatar(
backgroundColor: Color(0xFFFFE0E0),
child: Icon(Icons.flag, color: Color(0xFFD63031)),
),
title: Text('Contenu inapproprié signalé'),
subtitle: Text('Commentaire sur événement'),
trailing: Text('Urgent'),
),
Divider(height: 1),
ListTile(
leading: CircleAvatar(
backgroundColor: Color(0xFFFFF3E0),
child: Icon(Icons.person_add, color: Color(0xFFE17055)),
),
title: Text('Demande d\'adhésion'),
subtitle: Text('Marie Dubois'),
trailing: Text('2j'),
),
],
),
),
],
);
}
Widget _buildRecentActivity() {
return const DashboardRecentActivitySection(
children: [
DashboardActivity(
title: 'Signalement traité',
subtitle: 'Contenu supprimé',
icon: Icons.check_circle,
color: Color(0xFF00B894),
time: 'Il y a 1h',
),
DashboardActivity(
title: 'Membre suspendu',
subtitle: 'Violation des règles',
icon: Icons.person_remove,
color: Color(0xFFD63031),
time: 'Il y a 3h',
),
],
);
}
}

View File

@@ -0,0 +1,11 @@
/// Export de tous les dashboards spécifiques par rôle
/// Facilite l'importation des dashboards dans l'application
library role_dashboards;
// Dashboards spécifiques par rôle
export 'super_admin_dashboard.dart';
export 'org_admin_dashboard.dart';
export 'moderator_dashboard.dart';
export 'active_member_dashboard.dart';
export 'simple_member_dashboard.dart';
export 'visitor_dashboard.dart';

View File

@@ -0,0 +1,360 @@
/// Dashboard Membre Simple - Personal Space Minimaliste
/// Interface simplifiée pour accès basique
library simple_member_dashboard;
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../widgets/dashboard_widgets.dart';
/// Dashboard Personal Space pour Membre Simple
class SimpleMemberDashboard extends StatelessWidget {
const SimpleMemberDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar Membre Simple
SliverAppBar(
expandedHeight: 140,
floating: false,
pinned: true,
backgroundColor: const Color(0xFF00CEC9), // Teal simple
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Mon Espace',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF00CEC9), Color(0xFF00B3B3)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Center(
child: Icon(Icons.person, color: Colors.white, size: 50),
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profil personnel
_buildPersonalProfile(),
const SizedBox(height: SpacingTokens.xl),
// Mes informations
_buildMyInfo(),
const SizedBox(height: SpacingTokens.xl),
// Actions simples
_buildSimpleActions(),
const SizedBox(height: SpacingTokens.xl),
// Événements publics
_buildPublicEvents(),
const SizedBox(height: SpacingTokens.xl),
// Mon historique
_buildMyHistory(),
],
),
),
),
],
),
);
}
Widget _buildPersonalProfile() {
return Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.lg),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
const CircleAvatar(
radius: 35,
backgroundColor: Color(0xFF00CEC9),
child: Icon(Icons.person, color: Colors.white, size: 35),
),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pierre Dupont',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
'Membre depuis 6 mois',
style: TypographyTokens.bodyMedium.copyWith(
color: ColorTokens.textSecondary,
),
),
const SizedBox(height: SpacingTokens.sm),
Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF00CEC9).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: Text(
'Membre Simple',
style: TypographyTokens.bodySmall.copyWith(
color: const Color(0xFF00CEC9),
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
);
}
Widget _buildMyInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mes Informations',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
const DashboardStatsGrid(
stats: [
DashboardStat(
icon: Icons.payment,
value: 'À jour',
title: 'Cotisations',
color: Color(0xFF00B894),
),
DashboardStat(
icon: Icons.event,
value: '2',
title: 'Événements',
color: Color(0xFF00CEC9),
),
DashboardStat(
icon: Icons.account_circle,
value: '100%',
title: 'Profil',
color: Color(0xFF0984E3),
),
DashboardStat(
icon: Icons.notifications,
value: '3',
title: 'Notifications',
color: Color(0xFFE17055),
),
],
),
],
);
}
Widget _buildSimpleActions() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions Disponibles',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardQuickActionsGrid(
children: [
DashboardQuickAction(
icon: Icons.edit,
title: 'Modifier Profil',
color: const Color(0xFF00CEC9),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.payment,
title: 'Mes Cotisations',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.event,
title: 'Événements',
color: const Color(0xFF00B894),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.help,
title: 'Aide',
color: const Color(0xFFE17055),
onTap: () {},
),
],
),
],
);
}
Widget _buildPublicEvents() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Événements Disponibles',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF00B894).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: const Icon(
Icons.event,
color: Color(0xFF00B894),
),
),
title: const Text('Assemblée Générale'),
subtitle: const Text('15 décembre • 19h00'),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF00B894).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: const Text(
'Public',
style: TextStyle(
color: Color(0xFF00B894),
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
const Divider(height: 1),
ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF00CEC9).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: const Icon(
Icons.celebration,
color: Color(0xFF00CEC9),
),
),
title: const Text('Soirée de Noël'),
subtitle: const Text('22 décembre • 20h00'),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF00CEC9).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: const Text(
'Public',
style: TextStyle(
color: Color(0xFF00CEC9),
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
],
);
}
Widget _buildMyHistory() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mon Historique',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
const DashboardRecentActivitySection(
children: [
DashboardActivity(
title: 'Cotisation payée',
subtitle: 'Décembre 2024',
icon: Icons.payment,
color: Color(0xFF00B894),
time: 'Il y a 1j',
),
DashboardActivity(
title: 'Profil mis à jour',
subtitle: 'Informations personnelles',
icon: Icons.edit,
color: Color(0xFF00CEC9),
time: 'Il y a 1 sem',
),
DashboardActivity(
title: 'Inscription événement',
subtitle: 'Assemblée Générale',
icon: Icons.event,
color: Color(0xFF0984E3),
time: 'Il y a 2 sem',
),
],
),
],
);
}
}

View File

@@ -0,0 +1,554 @@
/// Dashboard Visiteur - Landing Experience Accueillante
/// Interface publique pour découvrir l'organisation
library visitor_dashboard;
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/tokens/color_tokens.dart';
import '../../../../../shared/design_system/tokens/radius_tokens.dart';
import '../../../../../shared/design_system/tokens/spacing_tokens.dart';
import '../../../../../shared/design_system/tokens/typography_tokens.dart';
/// Dashboard Landing Experience pour Visiteur
class VisitorDashboard extends StatelessWidget {
const VisitorDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar Visiteur
SliverAppBar(
expandedHeight: 200,
floating: false,
pinned: true,
backgroundColor: const Color(0xFF6C5CE7), // Indigo accueillant
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Découvrir UnionFlow',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF6C5CE7), Color(0xFF5A4FCF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Stack(
children: [
// Motif d'accueil
Positioned.fill(
child: CustomPaint(
painter: _WelcomePatternPainter(),
),
),
const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.waving_hand, color: Colors.white, size: 60),
SizedBox(height: 8),
Text(
'Bienvenue !',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Message d'accueil
_buildWelcomeMessage(),
const SizedBox(height: SpacingTokens.xl),
// À propos de l'organisation
_buildAboutOrganization(),
const SizedBox(height: SpacingTokens.xl),
// Événements publics
_buildPublicEvents(),
const SizedBox(height: SpacingTokens.xl),
// Comment rejoindre
_buildHowToJoin(),
const SizedBox(height: SpacingTokens.xl),
// Contact
_buildContactInfo(),
],
),
),
),
],
),
);
}
Widget _buildWelcomeMessage() {
return Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF6C5CE7), Color(0xFF5A4FCF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(RadiusTokens.lg),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.info_outline, color: Colors.white, size: 30),
const SizedBox(width: SpacingTokens.sm),
Expanded(
child: Text(
'Découvrez notre communauté',
style: TypographyTokens.headlineMedium.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: SpacingTokens.md),
Text(
'Bienvenue sur UnionFlow ! Explorez notre organisation, découvrez nos événements publics et apprenez comment nous rejoindre.',
style: TypographyTokens.bodyLarge.copyWith(
color: Colors.white.withOpacity(0.9),
),
),
const SizedBox(height: SpacingTokens.md),
ElevatedButton(
onPressed: () => _onJoinNow(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF6C5CE7),
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.lg,
vertical: SpacingTokens.md,
),
),
child: const Text(
'Nous Rejoindre',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
],
),
);
}
Widget _buildAboutOrganization() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'À Propos de Nous',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7).withOpacity(0.1),
borderRadius: BorderRadius.circular(30),
),
child: const Icon(
Icons.business,
color: Color(0xFF6C5CE7),
size: 30,
),
),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Association des Développeurs',
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
'Communauté tech passionnée',
style: TypographyTokens.bodyMedium.copyWith(
color: ColorTokens.textSecondary,
),
),
],
),
),
],
),
const SizedBox(height: SpacingTokens.md),
const Text(
'Nous sommes une association dynamique qui rassemble les passionnés de technologie. Notre mission est de favoriser l\'apprentissage, le partage de connaissances et l\'entraide dans le domaine du développement.',
style: TypographyTokens.bodyMedium,
),
const SizedBox(height: SpacingTokens.md),
// Statistiques publiques
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildPublicStat('156', 'Membres'),
_buildPublicStat('24', 'Événements/an'),
_buildPublicStat('5', 'Ans d\'existence'),
],
),
],
),
),
],
);
}
Widget _buildPublicStat(String value, String label) {
return Column(
children: [
Text(
value,
style: TypographyTokens.headlineMedium.copyWith(
color: const Color(0xFF6C5CE7),
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.textSecondary,
),
),
],
);
}
Widget _buildPublicEvents() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Événements Publics',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF00B894).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('15', style: TextStyle(fontWeight: FontWeight.bold)),
Text('DÉC', style: TextStyle(fontSize: 10)),
],
),
),
title: const Text('Assemblée Générale Publique'),
subtitle: const Text('Salle communale • 19h00 • Gratuit'),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF00B894).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: const Text(
'OUVERT',
style: TextStyle(
color: Color(0xFF00B894),
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
),
const Divider(height: 1),
ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('20', style: TextStyle(fontWeight: FontWeight.bold)),
Text('DÉC', style: TextStyle(fontSize: 10)),
],
),
),
title: const Text('Conférence Tech Trends 2025'),
subtitle: const Text('Amphithéâtre Université • 14h00 • Gratuit'),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: const Text(
'OUVERT',
style: TextStyle(
color: Color(0xFF6C5CE7),
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
],
);
}
Widget _buildHowToJoin() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Comment Nous Rejoindre',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
_buildJoinStep('1', 'Créer un compte', 'Inscription gratuite en 2 minutes'),
const SizedBox(height: SpacingTokens.md),
_buildJoinStep('2', 'Compléter le profil', 'Partagez vos centres d\'intérêt'),
const SizedBox(height: SpacingTokens.md),
_buildJoinStep('3', 'Validation', 'Approbation par nos modérateurs'),
const SizedBox(height: SpacingTokens.lg),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _onStartRegistration(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6C5CE7),
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.md),
),
child: const Text(
'Commencer l\'inscription',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
],
);
}
Widget _buildJoinStep(String number, String title, String description) {
return Row(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7),
borderRadius: BorderRadius.circular(15),
),
child: Center(
child: Text(
number,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TypographyTokens.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
Text(
description,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.textSecondary,
),
),
],
),
),
],
);
}
Widget _buildContactInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Nous Contacter',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Column(
children: [
ListTile(
leading: Icon(Icons.email, color: Color(0xFF6C5CE7)),
title: Text('Email'),
subtitle: Text('contact@association-dev.fr'),
contentPadding: EdgeInsets.zero,
),
ListTile(
leading: Icon(Icons.phone, color: Color(0xFF6C5CE7)),
title: Text('Téléphone'),
subtitle: Text('+33 1 23 45 67 89'),
contentPadding: EdgeInsets.zero,
),
ListTile(
leading: Icon(Icons.location_on, color: Color(0xFF6C5CE7)),
title: Text('Adresse'),
subtitle: Text('123 Rue de la Tech, 75001 Paris'),
contentPadding: EdgeInsets.zero,
),
],
),
),
],
);
}
// === CALLBACKS ===
void _onJoinNow() {
// Navigation vers l'inscription
}
void _onStartRegistration() {
// Démarrer le processus d'inscription
}
}
/// Painter pour le motif d'accueil
class _WelcomePatternPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.1)
..strokeWidth = 1
..style = PaintingStyle.stroke;
// Dessiner des cercles concentriques
for (int i = 1; i <= 5; i++) {
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
i * size.width / 10,
paint,
);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}