feat(100): bandeau dashboard générique, icônes rôle/email, footer go_router, user fromJson défensif

- Bandeau générique DashboardBandeau (logo | onglets | capsule utilisateur)
- Capsule: icône rôle (admin/gestionnaire/parent/AM) + Prénom Nom + menu (email avec icône, Profil, Paramètres, Déconnexion)
- Migration admin, gestionnaire, parent, AM vers DashboardBandeau
- Écran AM (page blanche), route /am-dashboard
- Routes /privacy et /legal, footer avec context.push
- AppUser.fromJson: id/email/role null-safe
- Suppression DashboardAppBarAdmin et dashboard_app_bar.dart

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
MARTIN Julien 2026-02-25 19:50:53 +01:00
parent defa438edf
commit 4339e1e53d
10 changed files with 504 additions and 346 deletions

View File

@ -22,6 +22,9 @@ import '../screens/home/home_screen.dart';
import '../screens/administrateurs/admin_dashboardScreen.dart'; import '../screens/administrateurs/admin_dashboardScreen.dart';
import '../screens/gestionnaire/gestionnaire_dashboard_screen.dart'; import '../screens/gestionnaire/gestionnaire_dashboard_screen.dart';
import '../screens/home/parent_screen/ParentDashboardScreen.dart'; import '../screens/home/parent_screen/ParentDashboardScreen.dart';
import '../screens/am/am_dashboard_screen.dart';
import '../screens/legal/privacy_page.dart';
import '../screens/legal/legal_page.dart';
import '../screens/unknown_screen.dart'; import '../screens/unknown_screen.dart';
// --- Provider Instances --- // --- Provider Instances ---
@ -64,7 +67,16 @@ class AppRouter {
), ),
GoRoute( GoRoute(
path: '/am-dashboard', path: '/am-dashboard',
builder: (BuildContext context, GoRouterState state) => const HomeScreen(), builder: (BuildContext context, GoRouterState state) =>
const AmDashboardScreen(),
),
GoRoute(
path: '/privacy',
builder: (BuildContext context, GoRouterState state) => const PrivacyPage(),
),
GoRoute(
path: '/legal',
builder: (BuildContext context, GoRouterState state) => const LegalPage(),
), ),
// --- Parent Registration Flow --- // --- Parent Registration Flow ---

View File

@ -41,9 +41,9 @@ class AppUser {
relaisJson is Map<String, dynamic> ? relaisJson : <String, dynamic>{}; relaisJson is Map<String, dynamic> ? relaisJson : <String, dynamic>{};
return AppUser( return AppUser(
id: json['id'] as String, id: (json['id'] as String?) ?? '',
email: json['email'] as String, email: (json['email'] as String?) ?? '',
role: json['role'] as String, role: (json['role'] as String?) ?? '',
createdAt: json['cree_le'] != null createdAt: json['cree_le'] != null
? DateTime.parse(json['cree_le'] as String) ? DateTime.parse(json['cree_le'] as String)
: (json['createdAt'] != null : (json['createdAt'] != null

View File

@ -1,9 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/configuration_service.dart'; import 'package:p_tits_pas/services/configuration_service.dart';
import 'package:p_tits_pas/widgets/admin/dashboard_admin.dart';
import 'package:p_tits_pas/widgets/admin/parametres_panel.dart'; import 'package:p_tits_pas/widgets/admin/parametres_panel.dart';
import 'package:p_tits_pas/widgets/admin/user_management_panel.dart'; import 'package:p_tits_pas/widgets/admin/user_management_panel.dart';
import 'package:p_tits_pas/widgets/app_footer.dart'; import 'package:p_tits_pas/widgets/app_footer.dart';
import 'package:p_tits_pas/widgets/admin/dashboard_admin.dart'; import 'package:p_tits_pas/widgets/dashboard/dashboard_bandeau.dart';
class AdminDashboardScreen extends StatefulWidget { class AdminDashboardScreen extends StatefulWidget {
const AdminDashboardScreen({super.key}); const AdminDashboardScreen({super.key});
@ -14,6 +17,7 @@ class AdminDashboardScreen extends StatefulWidget {
class _AdminDashboardScreenState extends State<AdminDashboardScreen> { class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
bool? _setupCompleted; bool? _setupCompleted;
AppUser? _user;
int mainTabIndex = 0; int mainTabIndex = 0;
int settingsSubIndex = 0; int settingsSubIndex = 0;
@ -31,9 +35,11 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
Future<void> _loadSetupStatus() async { Future<void> _loadSetupStatus() async {
try { try {
final completed = await ConfigurationService.getSetupStatus(); final completed = await ConfigurationService.getSetupStatus();
final user = await AuthService.getCurrentUser();
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_setupCompleted = completed; _setupCompleted = completed;
_user = user;
if (!completed) mainTabIndex = 1; if (!completed) mainTabIndex = 1;
}); });
} catch (e) { } catch (e) {
@ -68,17 +74,29 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(60.0), preferredSize: const Size.fromHeight(60.0),
child: Container( child: DashboardBandeau(
decoration: BoxDecoration( tabItems: [
border: Border( DashboardTabItem(
bottom: BorderSide(color: Colors.grey.shade300), label: 'Gestion des utilisateurs',
), enabled: _setupCompleted!,
),
child: DashboardAppBarAdmin(
selectedIndex: mainTabIndex,
onTabChange: onMainTabChange,
setupCompleted: _setupCompleted!,
), ),
const DashboardTabItem(label: 'Paramètres'),
],
selectedTabIndex: mainTabIndex,
onTabSelected: onMainTabChange,
userDisplayName: _user?.fullName.isNotEmpty == true
? _user!.fullName
: 'Admin',
userEmail: _user?.email,
userRole: _user?.role,
onProfileTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Modification du profil à venir')),
);
},
onSettingsTap: () => onMainTabChange(1),
onLogout: () {},
showLogoutConfirmation: true,
), ),
), ),
body: Column( body: Column(

View File

@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/widgets/app_footer.dart';
import 'package:p_tits_pas/widgets/dashboard/dashboard_bandeau.dart';
/// Dashboard assistante maternelle page blanche avec bandeau générique.
/// Contenu détaillé à venir.
class AmDashboardScreen extends StatefulWidget {
const AmDashboardScreen({super.key});
@override
State<AmDashboardScreen> createState() => _AmDashboardScreenState();
}
class _AmDashboardScreenState extends State<AmDashboardScreen> {
int selectedTabIndex = 0;
AppUser? _user;
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
final user = await AuthService.getCurrentUser();
if (mounted) setState(() => _user = user);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(60.0),
child: DashboardBandeau(
tabItems: const [
DashboardTabItem(label: 'Mon tableau de bord'),
DashboardTabItem(label: 'Paramètres'),
],
selectedTabIndex: selectedTabIndex,
onTabSelected: (index) => setState(() => selectedTabIndex = index),
userDisplayName: _user?.fullName.isNotEmpty == true
? _user!.fullName
: 'Assistante maternelle',
userEmail: _user?.email,
userRole: _user?.role,
onProfileTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Modification du profil à venir')),
);
},
onSettingsTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Paramètres à venir')),
);
},
onLogout: () {},
showLogoutConfirmation: true,
),
),
body: Column(
children: [
Expanded(
child: Center(
child: Text(
'Dashboard AM à venir',
style: Theme.of(context).textTheme.titleLarge,
),
),
),
const AppFooter(),
],
),
);
}
}

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/widgets/admin/dashboard_admin.dart'; import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/widgets/admin/user_management_panel.dart'; import 'package:p_tits_pas/widgets/admin/user_management_panel.dart';
import 'package:p_tits_pas/widgets/app_footer.dart'; import 'package:p_tits_pas/widgets/app_footer.dart';
import 'package:p_tits_pas/widgets/dashboard/dashboard_bandeau.dart';
/// Dashboard gestionnaire même shell que l'admin, sans onglet Paramètres. /// Dashboard gestionnaire même shell que l'admin, sans onglet Paramètres.
/// Réutilise [UserManagementPanel]. /// Réutilise [UserManagementPanel].
@ -14,24 +16,47 @@ class GestionnaireDashboardScreen extends StatefulWidget {
} }
class _GestionnaireDashboardScreenState extends State<GestionnaireDashboardScreen> { class _GestionnaireDashboardScreenState extends State<GestionnaireDashboardScreen> {
AppUser? _user;
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
final user = await AuthService.getCurrentUser();
if (mounted) setState(() => _user = user);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(60.0), preferredSize: const Size.fromHeight(60.0),
child: Container( child: DashboardBandeau(
decoration: BoxDecoration( tabItems: const [
border: Border( DashboardTabItem(label: 'Gestion des utilisateurs'),
bottom: BorderSide(color: Colors.grey.shade300), ],
), selectedTabIndex: 0,
), onTabSelected: (_) {},
child: DashboardAppBarAdmin( userDisplayName: _user?.fullName.isNotEmpty == true
selectedIndex: 0, ? _user!.fullName
onTabChange: (_) {}, : 'Gestionnaire',
showSettingsTab: false, userEmail: _user?.email,
roleLabel: 'Gestionnaire', userRole: _user?.role,
setupCompleted: true, onProfileTap: () {
), ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Modification du profil à venir')),
);
},
onSettingsTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Paramètres à venir')),
);
},
onLogout: () {},
showLogoutConfirmation: true,
), ),
), ),
body: Column( body: Column(

View File

@ -1,11 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/controllers/parent_dashboard_controller.dart'; import 'package:p_tits_pas/controllers/parent_dashboard_controller.dart';
import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/dashboardService.dart'; import 'package:p_tits_pas/services/dashboardService.dart';
import 'package:p_tits_pas/widgets/app_footer.dart'; import 'package:p_tits_pas/widgets/app_footer.dart';
import 'package:p_tits_pas/widgets/dashbord_parent/app_layout.dart';
import 'package:p_tits_pas/widgets/dashbord_parent/children_sidebar.dart'; import 'package:p_tits_pas/widgets/dashbord_parent/children_sidebar.dart';
import 'package:p_tits_pas/widgets/dashbord_parent/dashboard_app_bar.dart';
import 'package:p_tits_pas/widgets/dashbord_parent/wid_dashbord.dart'; import 'package:p_tits_pas/widgets/dashbord_parent/wid_dashbord.dart';
import 'package:p_tits_pas/widgets/dashboard/dashboard_bandeau.dart';
import 'package:p_tits_pas/widgets/main_content_area.dart'; import 'package:p_tits_pas/widgets/main_content_area.dart';
import 'package:p_tits_pas/widgets/messaging_sidebar.dart'; import 'package:p_tits_pas/widgets/messaging_sidebar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -19,6 +20,7 @@ class ParentDashboardScreen extends StatefulWidget {
class _ParentDashboardScreenState extends State<ParentDashboardScreen> { class _ParentDashboardScreenState extends State<ParentDashboardScreen> {
int selectedIndex = 0; int selectedIndex = 0;
AppUser? _user;
void onTabChange(int index) { void onTabChange(int index) {
setState(() { setState(() {
@ -29,12 +31,18 @@ class _ParentDashboardScreenState extends State<ParentDashboardScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadUser();
// Initialiser les données du dashboard // Initialiser les données du dashboard
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<ParentDashboardController>().initDashboard(); context.read<ParentDashboardController>().initDashboard();
}); });
} }
Future<void> _loadUser() async {
final user = await AuthService.getCurrentUser();
if (mounted) setState(() => _user = user);
}
Widget _getBody() { Widget _getBody() {
switch (selectedIndex) { switch (selectedIndex) {
case 0: case 0:
@ -53,29 +61,43 @@ class _ParentDashboardScreenState extends State<ParentDashboardScreen> {
return ChangeNotifierProvider( return ChangeNotifierProvider(
create: (context) => ParentDashboardController(DashboardService())..initDashboard(), create: (context) => ParentDashboardController(DashboardService())..initDashboard(),
child: Scaffold( child: Scaffold(
appBar: PreferredSize(preferredSize: const Size.fromHeight(60.0), appBar: PreferredSize(
child: Container( preferredSize: const Size.fromHeight(60.0),
decoration: BoxDecoration( child: DashboardBandeau(
border: Border( tabItems: const [
bottom: BorderSide(color: Colors.grey.shade300), DashboardTabItem(label: 'Mon tableau de bord'),
), DashboardTabItem(label: 'Trouver une nounou'),
), DashboardTabItem(label: 'Paramètres'),
child: DashboardAppBar( ],
selectedIndex: selectedIndex, selectedTabIndex: selectedIndex,
onTabChange: onTabChange, onTabSelected: onTabChange,
), userDisplayName: _user?.fullName.isNotEmpty == true
? _user!.fullName
: 'Parent',
userEmail: _user?.email,
userRole: _user?.role,
onProfileTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Modification du profil à venir')),
);
},
onSettingsTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Paramètres à venir')),
);
},
onLogout: () {},
showLogoutConfirmation: true,
), ),
), ),
body: Column( body: Column(
children: [ children: [
Expanded (child: _getBody(), Expanded(child: _getBody()),
),
const AppFooter(), const AppFooter(),
], ],
), ),
) ),
// body: _buildResponsiveBody(context, controller),
// footer: const AppFooter(),
); );
} }

View File

@ -1,143 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:p_tits_pas/services/auth_service.dart';
/// Barre du dashboard admin : onglets Gestion des utilisateurs | Paramètres + déconnexion.
/// Pour le dashboard gestionnaire : [showSettingsTab] = false, [roleLabel] = 'Gestionnaire'.
class DashboardAppBarAdmin extends StatelessWidget
implements PreferredSizeWidget {
final int selectedIndex;
final ValueChanged<int> onTabChange;
final bool setupCompleted;
final bool showSettingsTab;
final String roleLabel;
const DashboardAppBarAdmin({
Key? key,
required this.selectedIndex,
required this.onTabChange,
this.setupCompleted = true,
this.showSettingsTab = true,
this.roleLabel = 'Admin',
}) : super(key: key);
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10);
@override
Widget build(BuildContext context) {
return AppBar(
elevation: 0,
automaticallyImplyLeading: false,
title: Row(
children: [
const SizedBox(width: 24),
Image.asset(
'assets/images/logo.png',
height: 40,
fit: BoxFit.contain,
),
Expanded(
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildNavItem(context, 'Gestion des utilisateurs', 0,
enabled: setupCompleted),
if (showSettingsTab) ...[
const SizedBox(width: 24),
_buildNavItem(context, 'Paramètres', 1, enabled: true),
],
],
),
),
),
],
),
actions: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Center(
child: Text(
roleLabel,
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
Padding(
padding: const EdgeInsets.only(right: 16),
child: TextButton(
onPressed: () => _handleLogout(context),
style: TextButton.styleFrom(
backgroundColor: const Color(0xFF9CC5C0),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Se déconnecter'),
),
),
],
);
}
Widget _buildNavItem(BuildContext context, String title, int index,
{bool enabled = true}) {
final bool isActive = index == selectedIndex;
return InkWell(
onTap: enabled ? () => onTabChange(index) : null,
child: Opacity(
opacity: enabled ? 1.0 : 0.5,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent,
borderRadius: BorderRadius.circular(20),
border: isActive ? null : Border.all(color: Colors.black26),
),
child: Text(
title,
style: TextStyle(
color: isActive ? Colors.white : Colors.black,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
fontSize: 14,
),
),
),
),
);
}
void _handleLogout(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Déconnexion'),
content: const Text('Êtes-vous sûr de vouloir vous déconnecter ?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () async {
Navigator.pop(context);
await AuthService.logout();
if (context.mounted) context.go('/login');
},
child: const Text('Déconnecter'),
),
],
),
);
}
}
/// Sous-barre : Gestionnaires | Parents | Assistantes maternelles | [Administrateurs]. /// Sous-barre : Gestionnaires | Parents | Assistantes maternelles | [Administrateurs].
/// [subTabCount] = 3 pour masquer l'onglet Administrateurs (dashboard gestionnaire). /// [subTabCount] = 3 pour masquer l'onglet Administrateurs (dashboard gestionnaire).

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:p_tits_pas/models/m_dashbord/child_model.dart'; import 'package:p_tits_pas/models/m_dashbord/child_model.dart';
import 'package:p_tits_pas/services/bug_report_service.dart'; import 'package:p_tits_pas/services/bug_report_service.dart';
@ -185,13 +186,11 @@ class AppFooter extends StatelessWidget {
} }
void _handleLegalNotices(BuildContext context) { void _handleLegalNotices(BuildContext context) {
// Handle legal notices action context.push('/legal');
Navigator.pushNamed(context, '/legal');
} }
void _handlePrivacyPolicy(BuildContext context) { void _handlePrivacyPolicy(BuildContext context) {
// Handle privacy policy action context.push('/privacy');
Navigator.pushNamed(context, '/privacy');
} }
void _handleContactSupport(BuildContext context) { void _handleContactSupport(BuildContext context) {

View File

@ -0,0 +1,299 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:p_tits_pas/services/auth_service.dart';
/// Item d'onglet pour le bandeau (label + enabled).
class DashboardTabItem {
final String label;
final bool enabled;
const DashboardTabItem({
required this.label,
this.enabled = true,
});
}
/// Icône associée au rôle utilisateur (alignée sur le panneau admin).
IconData _iconForRole(String? role) {
if (role == null || role.isEmpty) return Icons.person_outline;
final r = role.toLowerCase();
if (r == 'super_admin') return Icons.verified_user_outlined;
if (r == 'admin' || r == 'administrateur') return Icons.manage_accounts_outlined;
if (r == 'gestionnaire') return Icons.assignment_ind_outlined;
if (r == 'parent') return Icons.supervisor_account_outlined;
if (r == 'assistante_maternelle') return Icons.face;
return Icons.person_outline;
}
/// Bandeau générique type Gitea : icône | onglets | capsule (Prénom Nom ) menu (email, Profil, Paramètres, Déconnexion).
class DashboardBandeau extends StatelessWidget implements PreferredSizeWidget {
final Widget? leading;
final List<DashboardTabItem> tabItems;
final int selectedTabIndex;
final ValueChanged<int> onTabSelected;
final String userDisplayName;
final String? userEmail;
/// Rôle de l'utilisateur pour afficher l'icône correspondante (même que panneau admin).
final String? userRole;
final VoidCallback? onProfileTap;
final VoidCallback? onSettingsTap;
final VoidCallback? onLogout;
final bool showLogoutConfirmation;
final bool bottomBorder;
final double? preferredHeight;
const DashboardBandeau({
super.key,
this.leading,
required this.tabItems,
required this.selectedTabIndex,
required this.onTabSelected,
required this.userDisplayName,
this.userEmail,
this.userRole,
this.onProfileTap,
this.onSettingsTap,
this.onLogout,
this.showLogoutConfirmation = true,
this.bottomBorder = true,
this.preferredHeight,
});
@override
Size get preferredSize =>
Size.fromHeight(preferredHeight ?? (kToolbarHeight + 10));
Widget _defaultLeading() {
return Image.asset(
'assets/images/logo.png',
height: 40,
fit: BoxFit.contain,
);
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).appBarTheme.backgroundColor ?? Colors.white,
border: bottomBorder
? Border(bottom: BorderSide(color: Colors.grey.shade300))
: null,
),
child: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
title: Row(
children: [
const SizedBox(width: 24),
leading ?? _defaultLeading(),
Expanded(
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < tabItems.length; i++) ...[
if (i > 0) const SizedBox(width: 24),
_buildNavItem(
context,
title: tabItems[i].label,
index: i,
enabled: tabItems[i].enabled,
),
],
],
),
),
),
],
),
actions: [
_buildUserCapsule(context),
],
),
);
}
Widget _buildNavItem(
BuildContext context, {
required String title,
required int index,
bool enabled = true,
}) {
final isActive = index == selectedTabIndex;
return InkWell(
onTap: enabled ? () => onTabSelected(index) : null,
child: Opacity(
opacity: enabled ? 1.0 : 0.5,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent,
borderRadius: BorderRadius.circular(20),
border: isActive ? null : Border.all(color: Colors.black26),
),
child: Text(
title,
style: TextStyle(
color: isActive ? Colors.white : Colors.black,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
fontSize: 14,
),
),
),
),
);
}
Widget _buildUserCapsule(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 16),
child: PopupMenuButton<String?>(
offset: const Offset(0, 45),
position: PopupMenuPosition.under,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
onSelected: (value) {
switch (value) {
case 'profile':
onProfileTap?.call();
break;
case 'settings':
onSettingsTap?.call();
break;
case 'logout':
_handleLogout(context);
break;
}
},
itemBuilder: (context) {
final entries = <PopupMenuEntry<String?>>[];
if (userEmail != null && userEmail!.isNotEmpty) {
entries.add(
PopupMenuItem<String?>(
enabled: false,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.email_outlined, size: 16, color: Colors.grey.shade700),
const SizedBox(width: 8),
Flexible(
child: Text(
userEmail!,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
entries.add(const PopupMenuDivider());
}
if (onProfileTap != null) {
entries.add(
const PopupMenuItem<String?>(
value: 'profile',
child: ListTile(
leading: Icon(Icons.person_outline, size: 20),
title: Text('Modification du profil'),
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
),
),
);
}
if (onSettingsTap != null) {
entries.add(
const PopupMenuItem<String?>(
value: 'settings',
child: ListTile(
leading: Icon(Icons.settings_outlined, size: 20),
title: Text('Paramètres'),
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
),
),
);
}
if (onLogout != null) {
if (entries.isNotEmpty) entries.add(const PopupMenuDivider());
entries.add(
const PopupMenuItem<String?>(
value: 'logout',
child: ListTile(
leading: Icon(Icons.logout, size: 20),
title: Text('Déconnexion'),
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
),
),
);
}
return entries;
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.grey.shade300),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(_iconForRole(userRole), size: 18, color: Colors.grey.shade700),
const SizedBox(width: 6),
Text(
userDisplayName,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
const SizedBox(width: 4),
Icon(Icons.keyboard_arrow_down, size: 20, color: Colors.grey.shade700),
],
),
),
),
);
}
void _handleLogout(BuildContext context) {
if (showLogoutConfirmation) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Déconnexion'),
content: const Text(
'Êtes-vous sûr de vouloir vous déconnecter ?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () async {
Navigator.pop(ctx);
onLogout?.call();
await AuthService.logout();
if (context.mounted) context.go('/login');
},
child: const Text('Déconnecter'),
),
],
),
);
} else {
onLogout?.call();
AuthService.logout().then((_) {
if (context.mounted) context.go('/login');
});
}
}
}

View File

@ -1,156 +0,0 @@
import 'package:flutter/material.dart';
class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
final int selectedIndex;
final ValueChanged<int> onTabChange;
const DashboardAppBar({Key? key, required this.selectedIndex, required this.onTabChange}) : super(key: key);
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10);
@override
Widget build(BuildContext context) {
final isMobile = MediaQuery.of(context).size.width < 768;
return AppBar(
// backgroundColor: Colors.white,
elevation: 0,
title: Row(
children: [
// Logo de la ville
// Container(
// height: 32,
// width: 32,
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(8),
// ),
// child: const Icon(
// Icons.location_city,
// color: Color(0xFF9CC5C0),
// size: 20,
// ),
// ),
SizedBox(width: MediaQuery.of(context).size.width * 0.19),
const Text(
"P'tit Pas",
style: TextStyle(
color: Color(0xFF9CC5C0),
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
// Navigation principale
_buildNavItem(context, 'Mon tableau de bord', 0),
const SizedBox(width: 24),
_buildNavItem(context, 'Trouver une nounou', 1),
const SizedBox(width: 24),
_buildNavItem(context, 'Paramètres', 2),
],
),
actions: isMobile
? [_buildMobileMenu(context)]
: [
// Nom de l'utilisateur
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Center(
child: Text(
'Jean Dupont',
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
// Bouton déconnexion
Padding(
padding: const EdgeInsets.only(right: 16),
child: TextButton(
onPressed: () => _handleLogout(context),
style: TextButton.styleFrom(
backgroundColor: const Color(0xFF9CC5C0),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Se déconnecter'),
),
),
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
],
);
}
Widget _buildNavItem(BuildContext context, String title, int index) {
final bool isActive = index == selectedIndex;
return InkWell(
onTap: () => onTabChange(index),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent,
borderRadius: BorderRadius.circular(20),
border: isActive ? null : Border.all(color: Colors.black26),
),
child: Text(
title,
style: TextStyle(
color: isActive ? Colors.white : Colors.black,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
fontSize: 14,
),
),
),
);
}
Widget _buildMobileMenu(BuildContext context) {
return PopupMenuButton<int>(
icon: const Icon(Icons.menu, color: Colors.white),
onSelected: (value) {
if (value == 3) {
_handleLogout(context);
}
},
itemBuilder: (context) => [
const PopupMenuItem(value: 0, child: Text("Mon tableau de bord")),
const PopupMenuItem(value: 1, child: Text("Trouver une nounou")),
const PopupMenuItem(value: 2, child: Text("Paramètres")),
const PopupMenuDivider(),
const PopupMenuItem(value: 3, child: Text("Se déconnecter")),
],
);
}
void _handleLogout(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Déconnexion'),
content: const Text('Êtes-vous sûr de vouloir vous déconnecter ?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
// TODO: Implémenter la logique de déconnexion
},
child: const Text('Déconnecter'),
),
],
),
);
}
}