diff --git a/creche_app/lib/features/home/home_dashboard.dart b/creche_app/lib/features/home/home_dashboard.dart deleted file mode 100644 index a29e8d6..0000000 --- a/creche_app/lib/features/home/home_dashboard.dart +++ /dev/null @@ -1,741 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:go_router/go_router.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; -import 'package:intl/intl.dart'; -import '/core/auth_provider.dart'; -import '/models/profile.dart'; -import '/models/child.dart'; -import '/models/daily_access_approval.dart'; - -class HomeDashboard extends ConsumerWidget { - const HomeDashboard({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final profileAsync = ref.watch(currentProfileProvider); - - return profileAsync.when( - data: (profile) { - if (profile == null) { - return const Scaffold( - body: Center(child: Text('Perfil não encontrado')), - ); - } - switch (profile.role) { - case 'principal': - case 'admin': - return _AdminDashboard(profile: profile); - case 'teacher': - return _TeacherDashboard(profile: profile); - case 'parent': - return _ParentDashboard(profile: profile); - default: - return const Scaffold( - body: Center(child: Text('Role desconhecido'))); - } - }, - loading: () => - const Scaffold(body: Center(child: CircularProgressIndicator())), - error: (e, _) => Scaffold(body: Center(child: Text('Erro: $e'))), - ); - } -} - -// ─────────────── ADMIN DASHBOARD ─────────────── -class _AdminDashboard extends ConsumerWidget { - final Profile profile; - const _AdminDashboard({required this.profile}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final supabase = Supabase.instance.client; - - return Scaffold( - backgroundColor: const Color(0xFF0D1117), - appBar: AppBar( - backgroundColor: const Color(0xFF161B22), - title: Row( - children: [ - Image.asset('assets/logo.png', height: 36, - errorBuilder: (_, __, ___) => - const Icon(Icons.child_care, color: Color(0xFF4FC3F7))), - const SizedBox(width: 10), - const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Sementes do Futuro', - style: TextStyle( - color: Color(0xFF4FC3F7), - fontSize: 14, - fontWeight: FontWeight.bold)), - Text('Dashboard Admin', - style: - TextStyle(color: Color(0xFF888888), fontSize: 11)), - ], - ), - ], - ), - actions: [ - IconButton( - icon: const Icon(Icons.notifications_outlined, - color: Color(0xFF4FC3F7)), - onPressed: () => context.go('/announcements'), - ), - IconButton( - icon: const Icon(Icons.settings_outlined, - color: Color(0xFF4FC3F7)), - onPressed: () => context.go('/settings'), - ), - ], - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Olá, ${profile.fullName.split(' ').first}! 👋', - style: const TextStyle( - color: Colors.white, - fontSize: 22, - fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Text(DateFormat('EEEE, d MMMM yyyy', 'pt_PT').format(DateTime.now()), - style: const TextStyle(color: Color(0xFF888888), fontSize: 13)), - const SizedBox(height: 24), - - // Quick actions - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _QuickAction( - icon: Icons.add_circle, - label: 'Diário', - onTap: () => context.go('/new-diary')), - _QuickAction( - icon: Icons.check_circle, - label: 'Presença', - onTap: () => context.go('/attendance')), - _QuickAction( - icon: Icons.attach_money, - label: 'Pagamentos', - onTap: () => context.go('/payments')), - _QuickAction( - icon: Icons.campaign, - label: 'Avisos', - onTap: () => context.go('/announcements')), - ], - ), - const SizedBox(height: 24), - - // Cards de estatísticas - const Text('Visão Geral', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold)), - const SizedBox(height: 12), - GridView.count( - crossAxisCount: 2, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 1.5, - children: const [ - _StatCard(title: 'Crianças Hoje', value: '–', icon: Icons.child_care, color: Color(0xFF4FC3F7)), - _StatCard(title: 'Presença', value: '–%', icon: Icons.check_circle, color: Color(0xFFA5D6A7)), - _StatCard(title: 'Pendentes', value: '–', icon: Icons.payment, color: Color(0xFFFFCC02)), - _StatCard(title: 'Avisos', value: '–', icon: Icons.campaign, color: Color(0xFFFF7043)), - ], - ), - const SizedBox(height: 24), - - // Aprovações pendentes - const Text('Pedidos de Acesso Hoje', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold)), - const SizedBox(height: 12), - StreamBuilder>>( - stream: supabase - .from('daily_access_approvals') - .stream(primaryKey: ['id']).eq('status', 'pending'), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator( - color: Color(0xFF4FC3F7))); - } - final approvals = snapshot.data!; - if (approvals.isEmpty) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: const Color(0xFF161B22), - borderRadius: BorderRadius.circular(12), - ), - child: const Row( - children: [ - Icon(Icons.check_circle, color: Color(0xFFA5D6A7)), - SizedBox(width: 8), - Text('Nenhum pedido pendente', - style: TextStyle(color: Color(0xFF888888))), - ], - ), - ); - } - return ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: approvals.length, - itemBuilder: (context, index) { - final approval = - DailyAccessApproval.fromMap(approvals[index]); - return _ApprovalCard( - approval: approval, - onApprove: () => _approve(supabase, approval.id), - onReject: () => _reject(supabase, approval.id), - ); - }, - ); - }, - ), - const SizedBox(height: 80), - ], - ), - ), - bottomNavigationBar: _AdminBottomNav(), - floatingActionButton: FloatingActionButton.extended( - backgroundColor: const Color(0xFF4FC3F7), - icon: const Icon(Icons.person_add, color: Colors.white), - label: - const Text('Nova Criança', style: TextStyle(color: Colors.white)), - onPressed: () => context.go('/child/new'), - ), - ); - } - - Future _approve(SupabaseClient supabase, String id) async { - await supabase.from('daily_access_approvals').update({ - 'status': 'approved', - 'approved_at': DateTime.now().toIso8601String(), - 'approved_by': supabase.auth.currentUser!.id, - }).eq('id', id); - } - - Future _reject(SupabaseClient supabase, String id) async { - await supabase - .from('daily_access_approvals') - .update({'status': 'rejected'}).eq('id', id); - } -} - -// ─────────────── TEACHER DASHBOARD ─────────────── -class _TeacherDashboard extends ConsumerWidget { - final Profile profile; - const _TeacherDashboard({required this.profile}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final supabase = Supabase.instance.client; - - return Scaffold( - backgroundColor: const Color(0xFF0D1117), - appBar: AppBar( - backgroundColor: const Color(0xFF161B22), - title: const Text('Minha Turma', - style: TextStyle(color: Color(0xFF4FC3F7))), - actions: [ - IconButton( - icon: const Icon(Icons.chat_outlined, color: Color(0xFF4FC3F7)), - onPressed: () => context.go('/chat'), - ), - IconButton( - icon: - const Icon(Icons.person_outline, color: Color(0xFF4FC3F7)), - onPressed: () => context.go('/profile'), - ), - ], - ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'Olá, ${profile.fullName.split(' ').first}! 👋', - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold), - ), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: Text('Crianças da tua turma hoje:', - style: TextStyle(color: Color(0xFF888888), fontSize: 14)), - ), - const SizedBox(height: 12), - Expanded( - child: StreamBuilder>>( - stream: supabase - .from('children') - .stream(primaryKey: ['id']).eq('teacher_id', profile.id), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center( - child: - CircularProgressIndicator(color: Color(0xFF4FC3F7))); - } - final children = - snapshot.data!.map(Child.fromMap).toList(); - return ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 16), - itemCount: children.length, - itemBuilder: (context, index) { - final child = children[index]; - return _ChildCard( - child: child, - onTap: () => context.go('/child/${child.id}'), - ); - }, - ); - }, - ), - ), - ], - ), - floatingActionButton: FloatingActionButton.extended( - backgroundColor: const Color(0xFF4FC3F7), - icon: const Icon(Icons.add, color: Colors.white), - label: - const Text('Novo Diário', style: TextStyle(color: Colors.white)), - onPressed: () => context.go('/new-diary'), - ), - bottomNavigationBar: _TeacherBottomNav(), - ); - } -} - -// ─────────────── PARENT DASHBOARD ─────────────── -class _ParentDashboard extends ConsumerWidget { - final Profile profile; - const _ParentDashboard({required this.profile}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - backgroundColor: const Color(0xFF0D1117), - appBar: AppBar( - backgroundColor: const Color(0xFF161B22), - title: Image.asset('assets/logo.png', height: 36, - errorBuilder: (_, __, ___) => - const Icon(Icons.child_care, color: Color(0xFF4FC3F7))), - centerTitle: true, - actions: [ - IconButton( - icon: - const Icon(Icons.notifications_outlined, color: Color(0xFF4FC3F7)), - onPressed: () => context.go('/announcements'), - ), - ], - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: const Color(0xFF161B22), - borderRadius: BorderRadius.circular(16), - ), - child: Row( - children: [ - CircleAvatar( - radius: 30, - backgroundColor: const Color(0xFF4FC3F7), - child: Text( - profile.fullName.isNotEmpty - ? profile.fullName[0].toUpperCase() - : 'P', - style: const TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold), - ), - ), - const SizedBox(width: 16), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Bem-vindo(a)!', - style: TextStyle( - color: Color(0xFF888888), fontSize: 12)), - Text(profile.fullName, - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold)), - ], - ), - ], - ), - ), - const SizedBox(height: 24), - const Text('Ações Rápidas', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold)), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: _ActionButton( - icon: Icons.book_outlined, - label: 'Ver Diário', - onTap: () => context.go('/children'), - ), - ), - const SizedBox(width: 12), - Expanded( - child: _ActionButton( - icon: Icons.restaurant_menu, - label: 'Cardápio', - onTap: () => context.go('/menu'), - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: _ActionButton( - icon: Icons.medication, - label: 'Medicação', - onTap: () => context.go('/medication'), - ), - ), - const SizedBox(width: 12), - Expanded( - child: _ActionButton( - icon: Icons.chat_outlined, - label: 'Falar c/ Educadora', - onTap: () => context.go('/chat'), - ), - ), - ], - ), - ], - ), - ), - bottomNavigationBar: _ParentBottomNav(), - ); - } -} - -// ─────────────── WIDGETS AUXILIARES ─────────────── - -class _QuickAction extends StatelessWidget { - final IconData icon; - final String label; - final VoidCallback onTap; - const _QuickAction( - {required this.icon, required this.label, required this.onTap}); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: const Color(0xFF161B22), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: const Color(0xFF333366)), - ), - child: Icon(icon, color: const Color(0xFF4FC3F7), size: 26), - ), - const SizedBox(height: 6), - Text(label, - style: const TextStyle(color: Color(0xFF888888), fontSize: 12)), - ], - ), - ); - } -} - -class _StatCard extends StatelessWidget { - final String title; - final String value; - final IconData icon; - final Color color; - const _StatCard( - {required this.title, - required this.value, - required this.icon, - required this.color}); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: const Color(0xFF161B22), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: color.withOpacity(0.3)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, color: color, size: 22), - const SizedBox(height: 4), - FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Text(value, - style: TextStyle( - color: color, - fontSize: 20, - fontWeight: FontWeight.bold)), - ), - Text(title, - style: const TextStyle( - color: Color(0xFF888888), fontSize: 10), - overflow: TextOverflow.ellipsis), - ], - ), - ); - } -} - -class _ApprovalCard extends StatelessWidget { - final DailyAccessApproval approval; - final VoidCallback onApprove; - final VoidCallback onReject; - const _ApprovalCard( - {required this.approval, - required this.onApprove, - required this.onReject}); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: const Color(0xFF161B22), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFFFCC02).withOpacity(0.3)), - ), - child: Row( - children: [ - const Icon(Icons.person_outline, color: Color(0xFF4FC3F7)), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Funcionário: ${approval.userId.substring(0, 8)}...', - style: const TextStyle(color: Colors.white, fontSize: 13)), - Text( - 'IP: ${approval.ipAddress ?? 'N/A'} • ${DateFormat('HH:mm').format(approval.approvalDate)}', - style: const TextStyle( - color: Color(0xFF888888), fontSize: 11)), - ], - ), - ), - IconButton( - icon: const Icon(Icons.check_circle, color: Color(0xFFA5D6A7)), - onPressed: onApprove, - tooltip: 'Aprovar', - ), - IconButton( - icon: const Icon(Icons.cancel, color: Colors.red), - onPressed: onReject, - tooltip: 'Rejeitar', - ), - ], - ), - ); - } -} - -class _ChildCard extends StatelessWidget { - final Child child; - final VoidCallback onTap; - const _ChildCard({required this.child, required this.onTap}); - - String get _moodEmoji { - switch (child.mood) { - case 'happy': return '😊'; - case 'sad': return '😟'; - case 'sick': return '🤒'; - case 'excited': return '😃'; - default: return '😐'; - } - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Container( - margin: const EdgeInsets.only(bottom: 10), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: const Color(0xFF161B22), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: const Color(0xFF333366)), - ), - child: Row( - children: [ - CircleAvatar( - radius: 26, - backgroundImage: child.photoUrl != null - ? NetworkImage(child.photoUrl!) - : null, - backgroundColor: const Color(0xFF4FC3F7).withOpacity(0.2), - child: child.photoUrl == null - ? const Icon(Icons.child_care, - color: Color(0xFF4FC3F7), size: 28) - : null, - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(child.fullName, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 15)), - Text('${child.age} anos', - style: const TextStyle( - color: Color(0xFF888888), fontSize: 12)), - ], - ), - ), - Text(_moodEmoji, style: const TextStyle(fontSize: 28)), - const SizedBox(width: 8), - const Icon(Icons.chevron_right, color: Color(0xFF888888)), - ], - ), - ), - ); - } -} - -class _ActionButton extends StatelessWidget { - final IconData icon; - final String label; - final VoidCallback onTap; - const _ActionButton( - {required this.icon, required this.label, required this.onTap}); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12), - decoration: BoxDecoration( - color: const Color(0xFF161B22), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: const Color(0xFF333366)), - ), - child: Column( - children: [ - Icon(icon, color: const Color(0xFF4FC3F7), size: 28), - const SizedBox(height: 8), - Text(label, - style: - const TextStyle(color: Colors.white, fontSize: 13), - textAlign: TextAlign.center), - ], - ), - ), - ); - } -} - -// Bottom Navs -class _AdminBottomNav extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BottomNavigationBar( - backgroundColor: const Color(0xFF161B22), - selectedItemColor: const Color(0xFF4FC3F7), - unselectedItemColor: const Color(0xFF888888), - type: BottomNavigationBarType.fixed, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Início'), - BottomNavigationBarItem(icon: Icon(Icons.child_care), label: 'Crianças'), - BottomNavigationBarItem(icon: Icon(Icons.check_box), label: 'Presença'), - BottomNavigationBarItem(icon: Icon(Icons.people), label: 'Utilizadores'), - BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Perfil'), - ], - onTap: (i) { - final routes = ['/home', '/children', '/attendance', '/users', '/profile']; - context.go(routes[i]); - }, - ); - } -} - -class _TeacherBottomNav extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BottomNavigationBar( - backgroundColor: const Color(0xFF161B22), - selectedItemColor: const Color(0xFF4FC3F7), - unselectedItemColor: const Color(0xFF888888), - type: BottomNavigationBarType.fixed, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Início'), - BottomNavigationBarItem(icon: Icon(Icons.child_care), label: 'Crianças'), - BottomNavigationBarItem(icon: Icon(Icons.book), label: 'Diários'), - BottomNavigationBarItem(icon: Icon(Icons.chat), label: 'Chat'), - BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Perfil'), - ], - onTap: (i) { - final routes = ['/home', '/children', '/new-diary', '/chat', '/profile']; - context.go(routes[i]); - }, - ); - } -} - -class _ParentBottomNav extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BottomNavigationBar( - backgroundColor: const Color(0xFF161B22), - selectedItemColor: const Color(0xFF4FC3F7), - unselectedItemColor: const Color(0xFF888888), - type: BottomNavigationBarType.fixed, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Início'), - BottomNavigationBarItem(icon: Icon(Icons.child_care), label: 'Filhos'), - BottomNavigationBarItem(icon: Icon(Icons.restaurant_menu), label: 'Cardápio'), - BottomNavigationBarItem(icon: Icon(Icons.chat), label: 'Chat'), - BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Perfil'), - ], - onTap: (i) { - final routes = ['/home', '/children', '/menu', '/chat', '/profile']; - context.go(routes[i]); - }, - ); - } -}