refactoring
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user