refactoring

This commit is contained in:
dahoud
2026-03-31 09:14:47 +00:00
parent 9bfffeeebe
commit 5383df6dcb
200 changed files with 11192 additions and 7063 deletions

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'more_page.dart';
@@ -13,8 +14,8 @@ import '../../features/events/presentation/pages/events_page_wrapper.dart';
import '../../features/dashboard/presentation/bloc/dashboard_bloc.dart';
import '../di/injection.dart';
/// Layout principal avec navigation hybride
/// Bottom Navigation pour les sections principales + Drawer pour fonctions avancées
/// Layout principal — Navigation hybride
/// Pill navigation (style Material 3) au bas de l'écran
class MainNavigationLayout extends StatefulWidget {
const MainNavigationLayout({super.key});
@@ -27,18 +28,20 @@ class _MainNavigationLayoutState extends State<MainNavigationLayout> {
List<Widget>? _cachedPages;
UserRole? _lastRole;
String? _lastUserId;
String? _lastOrgId;
/// Obtient le dashboard approprié selon le rôle de l'utilisateur
Widget _getDashboardForRole(UserRole role, String userId, String? orgId) {
// Admin d'organisation sans orgId (organizationContexts vide) : charger /mes puis dashboard
if (role == UserRole.orgAdmin && (orgId == null || orgId.isEmpty)) {
return OrgAdminDashboardLoader(userId: userId);
}
// Pour SUPER_ADMIN sans org: forcer les stats globales (toutes organisations)
final isGlobalAdmin = role == UserRole.superAdmin && (orgId == null || orgId.isEmpty);
return BlocProvider<DashboardBloc>(
create: (context) => getIt<DashboardBloc>()
..add(LoadDashboardData(
organizationId: orgId ?? '',
userId: userId,
useGlobalDashboard: isGlobalAdmin,
)),
child: _buildDashboardView(role),
);
@@ -65,21 +68,21 @@ class _MainNavigationLayoutState extends State<MainNavigationLayout> {
}
}
/// Obtient les pages et les met en cache pour éviter les rebuilds inutiles
List<Widget> _getPages(UserRole role, String userId, String? orgId) {
if (_cachedPages != null && _lastRole == role && _lastUserId == userId) {
if (_cachedPages != null && _lastRole == role && _lastUserId == userId && _lastOrgId == orgId) {
return _cachedPages!;
}
debugPrint('🔄 [MainNavigationLayout] Initialisation des pages (Role: $role, User: $userId)');
debugPrint('🔄 [MainNavigationLayout] Init pages (Role: $role, User: $userId)');
_lastRole = role;
_lastUserId = userId;
final canManageMembers = role.hasLevelOrAbove(UserRole.hrManager);
_lastOrgId = orgId;
_cachedPages = [
_getDashboardForRole(role, userId, orgId),
if (canManageMembers) const MembersPageWrapper(),
if (role.hasLevelOrAbove(UserRole.hrManager))
MembersPageWrapper(
organisationId: role == UserRole.orgAdmin ? orgId : null,
),
const EventsPageWrapper(),
const MorePage(),
];
@@ -96,74 +99,36 @@ class _MainNavigationLayoutState extends State<MainNavigationLayout> {
);
}
final orgId = state.user.organizationContexts.isNotEmpty
? state.user.organizationContexts.first.organizationId
final orgId = state.user.organizationContexts.isNotEmpty
? state.user.organizationContexts.first.organizationId
: null;
final pages = _getPages(state.effectiveRole, state.user.id, orgId);
final safeIndex = _selectedIndex >= pages.length ? 0 : _selectedIndex;
return Scaffold(
backgroundColor: ColorTokens.background,
body: SafeArea(
top: true, // Respecte le StatusBar
bottom: false, // Le BottomNavigationBar gère son propre SafeArea
child: IndexedStack(
index: safeIndex,
children: pages,
),
// Construire la liste des items de navigation selon le rôle
final navItems = _buildNavItems(state.effectiveRole);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
bottomNavigationBar: SafeArea(
top: false,
child: Container(
decoration: const BoxDecoration(
color: ColorTokens.surface,
boxShadow: [
BoxShadow(
color: ColorTokens.shadow,
blurRadius: 8,
offset: Offset(0, -2),
),
],
child: Scaffold(
backgroundColor: ColorTokens.background,
body: SafeArea(
top: true,
bottom: false,
child: IndexedStack(
index: safeIndex,
children: pages,
),
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: safeIndex,
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
backgroundColor: ColorTokens.surface,
selectedItemColor: ColorTokens.primary,
unselectedItemColor: ColorTokens.onSurfaceVariant,
selectedLabelStyle: TypographyTokens.labelSmall.copyWith(
fontWeight: FontWeight.w600,
),
unselectedLabelStyle: TypographyTokens.labelSmall,
elevation: 0, // Géré par le Container
items: [
const BottomNavigationBarItem(
icon: Icon(Icons.dashboard_outlined),
activeIcon: Icon(Icons.dashboard),
label: 'Dashboard',
),
if (state.effectiveRole.hasLevelOrAbove(UserRole.hrManager))
const BottomNavigationBarItem(
icon: Icon(Icons.people_outline),
activeIcon: Icon(Icons.people),
label: 'Membres',
),
const BottomNavigationBarItem(
icon: Icon(Icons.event_outlined),
activeIcon: Icon(Icons.event),
label: 'Événements',
),
const BottomNavigationBarItem(
icon: Icon(Icons.more_horiz_outlined),
activeIcon: Icon(Icons.more_horiz),
label: 'Plus',
),
],
),
bottomNavigationBar: SafeArea(
top: false,
child: _PillNavigationBar(
items: navItems,
selectedIndex: safeIndex,
onItemTap: (i) => setState(() => _selectedIndex = i),
),
),
),
@@ -171,4 +136,146 @@ class _MainNavigationLayoutState extends State<MainNavigationLayout> {
},
);
}
List<_NavItem> _buildNavItems(UserRole role) {
return [
const _NavItem(
icon: Icons.dashboard_outlined,
activeIcon: Icons.dashboard_rounded,
label: 'Dashboard',
),
if (role.hasLevelOrAbove(UserRole.hrManager))
const _NavItem(
icon: Icons.people_outline_rounded,
activeIcon: Icons.people_rounded,
label: 'Membres',
),
const _NavItem(
icon: Icons.event_outlined,
activeIcon: Icons.event_rounded,
label: 'Événements',
),
const _NavItem(
icon: Icons.more_horiz_rounded,
activeIcon: Icons.more_horiz_rounded,
label: 'Plus',
),
];
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Pill Navigation Bar — Material 3 style
// ─────────────────────────────────────────────────────────────────────────────
class _NavItem {
const _NavItem({
required this.icon,
required this.activeIcon,
required this.label,
});
final IconData icon;
final IconData activeIcon;
final String label;
}
class _PillNavigationBar extends StatelessWidget {
const _PillNavigationBar({
required this.items,
required this.selectedIndex,
required this.onItemTap,
});
final List<_NavItem> items;
final int selectedIndex;
final ValueChanged<int> onItemTap;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: ColorTokens.surface,
boxShadow: [
BoxShadow(
color: ColorTokens.shadow,
blurRadius: 12,
offset: const Offset(0, -2),
),
],
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(items.length, (i) {
return _PillNavItem(
item: items[i],
isSelected: i == selectedIndex,
onTap: () => onItemTap(i),
);
}),
),
);
}
}
class _PillNavItem extends StatelessWidget {
const _PillNavItem({
required this.item,
required this.isSelected,
required this.onTap,
});
final _NavItem item;
final bool isSelected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
padding: EdgeInsets.symmetric(
horizontal: isSelected ? 16 : 14,
vertical: 8,
),
decoration: BoxDecoration(
color: isSelected
? ColorTokens.primary.withOpacity(0.12)
: Colors.transparent,
borderRadius: BorderRadius.circular(24),
),
child: isSelected
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
item.activeIcon,
size: 22,
color: ColorTokens.primary,
),
const SizedBox(width: 6),
Text(
item.label,
style: const TextStyle(
fontFamily: 'Inter',
fontSize: 12,
fontWeight: FontWeight.w700,
color: ColorTokens.primary,
letterSpacing: 0.2,
),
),
],
)
: Icon(
item.icon,
size: 22,
color: ColorTokens.navigationUnselected,
),
),
);
}
}