Eliminar creche_app/lib/features/home/home_dashboard.dart

This commit is contained in:
Alberto 2026-03-11 19:25:04 +00:00
parent f2f795cf38
commit 7efc7e3c9e
1 changed files with 0 additions and 741 deletions

View File

@ -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<List<Map<String, dynamic>>>(
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<void> _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<void> _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<List<Map<String, dynamic>>>(
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]);
},
);
}
}