Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4007d7010e | ||
|
|
4261667198 | ||
|
|
15e2d8d850 | ||
|
|
34a516d509 |
@@ -1,11 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:p_tits_pas/models/couple_garde.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';
|
||||
import 'package:p_tits_pas/services/couple_garde_service.dart';
|
||||
import 'package:p_tits_pas/widgets/quotidien/couple_selector_bandeau.dart';
|
||||
import 'package:p_tits_pas/widgets/quotidien/quotidien_shell.dart';
|
||||
import 'package:p_tits_pas/widgets/quotidien/quotidien_theme.dart';
|
||||
import 'package:p_tits_pas/screens/home/parent_screen/agenda_absences_stub.dart';
|
||||
|
||||
/// Dashboard assistante maternelle – page blanche avec bandeau générique.
|
||||
/// Contenu détaillé à venir.
|
||||
/// Dashboard assistante maternelle – coquille 3 colonnes quotidien (#169).
|
||||
/// Colonne gauche : sélecteur de couple enfant–parent(s) (#170).
|
||||
/// Métier cartes / blog / messagerie : tickets C/D/E.
|
||||
class AmDashboardScreen extends StatefulWidget {
|
||||
const AmDashboardScreen({super.key});
|
||||
|
||||
@@ -14,13 +19,19 @@ class AmDashboardScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AmDashboardScreenState extends State<AmDashboardScreen> {
|
||||
int selectedTabIndex = 0;
|
||||
QuotidienNavSection _section = QuotidienNavSection.liaison;
|
||||
AppUser? _user;
|
||||
|
||||
List<CoupleGarde> _couples = const [];
|
||||
String? _selectedCoupleId;
|
||||
bool _couplesLoading = true;
|
||||
String? _couplesError;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadUser();
|
||||
_loadCouples();
|
||||
}
|
||||
|
||||
Future<void> _loadUser() async {
|
||||
@@ -28,51 +39,152 @@ class _AmDashboardScreenState extends State<AmDashboardScreen> {
|
||||
if (mounted) setState(() => _user = user);
|
||||
}
|
||||
|
||||
Future<void> _loadCouples() async {
|
||||
setState(() {
|
||||
_couplesLoading = true;
|
||||
_couplesError = null;
|
||||
});
|
||||
try {
|
||||
// TODO: Pour l'instant on utilise le service générique de test (CoupleGardeService.getCouplesGarde).
|
||||
// Dans le futur, l'API pour l'AM (enfants_accueillis) fournira la vraie liste.
|
||||
final res = await CoupleGardeService.getCouplesGarde();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_couples = res.couples;
|
||||
_selectedCoupleId = res.coupleCourant?.id;
|
||||
_couplesLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_couplesError = e.toString().replaceFirst('Exception: ', '');
|
||||
_couplesLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectCouple(CoupleGarde couple) async {
|
||||
if (couple.id == _selectedCoupleId) return;
|
||||
setState(() => _selectedCoupleId = couple.id);
|
||||
try {
|
||||
final res = await CoupleGardeService.definirCoupleCourant(couple.id);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_couples = res.couples;
|
||||
_selectedCoupleId = res.coupleCourant?.id ?? couple.id;
|
||||
});
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Impossible de changer d'enfant : "
|
||||
'${e.toString().replaceFirst('Exception: ', '')}',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String get _displayName {
|
||||
final n = _user?.fullName.trim() ?? '';
|
||||
if (n.isNotEmpty) return n;
|
||||
final email = _user?.email.trim() ?? '';
|
||||
if (email.isNotEmpty) return email.split('@').first;
|
||||
return 'Assistante maternelle';
|
||||
}
|
||||
|
||||
void _soon(String label) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('$label — à venir')),
|
||||
);
|
||||
}
|
||||
|
||||
@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,
|
||||
),
|
||||
return QuotidienShell(
|
||||
selectedSection: _section,
|
||||
onSectionSelected: (s) => setState(() => _section = s),
|
||||
userDisplayName: _displayName,
|
||||
userEmail: _user?.email,
|
||||
onProfileTap: () => _soon('Profil'),
|
||||
onSettingsTap: () => _soon('Paramètres'),
|
||||
// L'AM n'a pas de bouton "Recherche AM" dans le bandeau
|
||||
onSearchAmTap: null,
|
||||
leftColumn: _LeftColumn(
|
||||
couples: _couples,
|
||||
selectedCoupleId: _selectedCoupleId,
|
||||
loading: _couplesLoading,
|
||||
error: _couplesError,
|
||||
onRetry: _loadCouples,
|
||||
onCoupleSelected: _selectCouple,
|
||||
),
|
||||
body: Column(
|
||||
centerColumn: const QuotidienColumnPlaceholder(
|
||||
title: 'Blog',
|
||||
subtitle:
|
||||
'Fil du quotidien (affichage par défaut)\n(à brancher — ticket #179).',
|
||||
icon: Icons.auto_stories_outlined,
|
||||
),
|
||||
rightColumn: const QuotidienColumnPlaceholder(
|
||||
title: 'Messagerie',
|
||||
subtitle:
|
||||
'Mess. Parents · Mess. RPE\n(à brancher — ticket #185).',
|
||||
icon: Icons.chat_bubble_outline,
|
||||
),
|
||||
agendaBody: AgendaAbsencesStub(placementId: _selectedCoupleId),
|
||||
contratBody: const QuotidienStubPage(
|
||||
title: 'Contrat',
|
||||
message: 'Contrat — contenu à venir (stub #187).',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LeftColumn extends StatelessWidget {
|
||||
final List<CoupleGarde> couples;
|
||||
final String? selectedCoupleId;
|
||||
final bool loading;
|
||||
final String? error;
|
||||
final VoidCallback onRetry;
|
||||
final ValueChanged<CoupleGarde> onCoupleSelected;
|
||||
|
||||
const _LeftColumn({
|
||||
required this.couples,
|
||||
required this.selectedCoupleId,
|
||||
required this.loading,
|
||||
required this.error,
|
||||
required this.onRetry,
|
||||
required this.onCoupleSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Dashboard AM – à venir',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
CoupleSelectorBandeau(
|
||||
mode: CoupleBandeauMode.assistanteMaternelle,
|
||||
couples: couples,
|
||||
selectedCoupleId: selectedCoupleId,
|
||||
loading: loading,
|
||||
errorMessage: error,
|
||||
onRetry: onRetry,
|
||||
onCoupleSelected: onCoupleSelected,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const Expanded(
|
||||
child: QuotidienColumnPlaceholder(
|
||||
title: 'Cartes',
|
||||
subtitle:
|
||||
'Absences, congés AM, sorties à valider\n(à brancher — ticket #174).',
|
||||
icon: Icons.style_outlined,
|
||||
),
|
||||
),
|
||||
const AppFooter(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,16 @@ class _CoupleCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Parent mode: enfant | AM
|
||||
// AM mode: enfant | parent(s)
|
||||
final isParentMode = mode == CoupleBandeauMode.parent;
|
||||
final fallbackRoleIcon = isParentMode ? Icons.volunteer_activism : Icons.person_outline;
|
||||
final fallbackRoleName = isParentMode ? 'Nounou' : 'Parent';
|
||||
|
||||
// TODO: In AM mode, we should ideally display "parent1+parent2 empilés" or centered.
|
||||
// For now, CoupleGarde only gives us `am` (which in AM mode might just represent one parent, or the system needs to feed both parents here).
|
||||
// Assuming backend will populate `am` field with the parent data when called by AM.
|
||||
|
||||
return Container(
|
||||
height: 90,
|
||||
decoration: BoxDecoration(
|
||||
@@ -175,14 +185,8 @@ class _CoupleCard extends StatelessWidget {
|
||||
photoUrl: couple.am.photoUrl,
|
||||
name: (couple.am.prenom != null && couple.am.prenom!.isNotEmpty)
|
||||
? couple.am.prenom!
|
||||
: couple.am.displayName(
|
||||
fallback: mode == CoupleBandeauMode.parent
|
||||
? 'Nounou'
|
||||
: 'Parent',
|
||||
),
|
||||
fallbackIcon: mode == CoupleBandeauMode.parent
|
||||
? Icons.volunteer_activism
|
||||
: Icons.person_outline,
|
||||
: couple.am.displayName(fallback: fallbackRoleName),
|
||||
fallbackIcon: fallbackRoleIcon,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -183,15 +183,16 @@ class _UserMenu extends StatelessWidget {
|
||||
title: Text('Profil'),
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 'search_am',
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Icon(Icons.search, size: 20),
|
||||
title: Text('Recherche AM'),
|
||||
if (onSearchAmTap != null)
|
||||
const PopupMenuItem(
|
||||
value: 'search_am',
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Icon(Icons.search, size: 20),
|
||||
title: Text('Recherche AM'),
|
||||
),
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 'settings',
|
||||
child: ListTile(
|
||||
|
||||
Reference in New Issue
Block a user