placeholders removidos e todos os dados reais colocados, com conquistas e tudo
This commit is contained in:
@@ -3,13 +3,75 @@ import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_theme_extension.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import '../../../../core/services/auth_service.dart';
|
||||
import '../../../../core/services/gamification_service.dart';
|
||||
import '../../../../core/models/user_stats.dart';
|
||||
import '../../../../core/models/achievement.dart';
|
||||
|
||||
/// Profile section with user info and achievements
|
||||
class ProfileSectionWidget extends StatelessWidget {
|
||||
class ProfileSectionWidget extends StatefulWidget {
|
||||
const ProfileSectionWidget({super.key});
|
||||
|
||||
@override
|
||||
State<ProfileSectionWidget> createState() => _ProfileSectionWidgetState();
|
||||
}
|
||||
|
||||
class _ProfileSectionWidgetState extends State<ProfileSectionWidget> {
|
||||
UserStats? _userStats;
|
||||
List<Achievement> _recentAchievements = [];
|
||||
bool _loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadUserData();
|
||||
}
|
||||
|
||||
Future<void> _loadUserData() async {
|
||||
try {
|
||||
final user = AuthService.currentUser;
|
||||
if (user != null) {
|
||||
final results = await Future.wait([
|
||||
GamificationService.getUserStats(user.uid),
|
||||
GamificationService.getAvailableAchievements(),
|
||||
]);
|
||||
|
||||
final stats = results[0] as UserStats?;
|
||||
final achievements = results[1] as List<Achievement>;
|
||||
|
||||
// Obter conquistas desbloqueadas recentemente
|
||||
final unlockedAchievementIds = stats?.unlockedAchievements
|
||||
.map((ua) => ua.achievementId)
|
||||
.toSet() ?? {};
|
||||
|
||||
final recentUnlocked = achievements
|
||||
.where((a) => unlockedAchievementIds.contains(a.id))
|
||||
.take(4)
|
||||
.toList();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_userStats = stats;
|
||||
_recentAchievements = recentUnlocked;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading user data: $e');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final user = AuthService.currentUser;
|
||||
final userName = user?.displayName ?? 'Estudante';
|
||||
final userEmail = user?.email ?? '';
|
||||
@@ -124,36 +186,55 @@ class ProfileSectionWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Achievement Badges
|
||||
Row(
|
||||
children: [
|
||||
_buildAchievementBadge(
|
||||
icon: Icons.local_fire_department,
|
||||
label: '7 dias',
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildAchievementBadge(
|
||||
icon: Icons.school,
|
||||
label: '3 conceitos',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildAchievementBadge(
|
||||
icon: Icons.speed,
|
||||
label: 'Rápido',
|
||||
color: Theme.of(
|
||||
// Achievement List (Teacher-style design)
|
||||
if (_recentAchievements.isNotEmpty) ...[
|
||||
..._recentAchievements.map((achievement) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildAchievementItem(
|
||||
context,
|
||||
).colorScheme.primary.withOpacity(0.8),
|
||||
achievement.name,
|
||||
achievement.points,
|
||||
_getRarityColor(achievement.rarity),
|
||||
_getIconData(achievement.icon),
|
||||
),
|
||||
);
|
||||
}),
|
||||
] else ...[
|
||||
// Streak item
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildAchievementItem(
|
||||
context,
|
||||
'${_userStats?.currentStreak ?? 0} dias seguidos',
|
||||
(_userStats?.currentStreak ?? 0) * 5,
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
Icons.local_fire_department,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildAchievementBadge(
|
||||
icon: Icons.star,
|
||||
label: '100%',
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
// Concepts item
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildAchievementItem(
|
||||
context,
|
||||
'${_userStats?.masteredConcepts.length ?? 0} conceitos dominados',
|
||||
(_userStats?.masteredConcepts.length ?? 0) * 10,
|
||||
Theme.of(context).colorScheme.primary,
|
||||
Icons.school,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_userStats != null && _userStats!.totalStudyTime > 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildAchievementItem(
|
||||
context,
|
||||
'${(_userStats!.totalStudyTime / 60).toStringAsFixed(1)}h estudadas',
|
||||
(_userStats!.totalStudyTime ~/ 60) * 3,
|
||||
Theme.of(context).colorScheme.tertiary,
|
||||
Icons.schedule,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Recent Activity Summary
|
||||
@@ -183,7 +264,7 @@ class ProfileSectionWidget extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Ótimo progresso!',
|
||||
_getProgressMessage(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontSize: 14,
|
||||
@@ -192,7 +273,7 @@ class ProfileSectionWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Você está 15% acima da média esta semana',
|
||||
_getProgressComparison(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
@@ -217,32 +298,103 @@ class ProfileSectionWidget extends StatelessWidget {
|
||||
.then(delay: const Duration(milliseconds: 400));
|
||||
}
|
||||
|
||||
Widget _buildAchievementBadge({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required Color color,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: color.withOpacity(0.3), width: 1),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 16),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
Widget _buildAchievementItem(
|
||||
BuildContext context,
|
||||
String name,
|
||||
int points,
|
||||
Color color,
|
||||
IconData icon,
|
||||
) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(icon, color: color, size: 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'$points pts',
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: 10,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getIconData(String iconName) {
|
||||
switch (iconName) {
|
||||
case 'emoji_events': return Icons.emoji_events;
|
||||
case 'school': return Icons.school;
|
||||
case 'local_fire_department': return Icons.local_fire_department;
|
||||
case 'schedule': return Icons.schedule;
|
||||
case 'trending_up': return Icons.trending_up;
|
||||
case 'military_tech': return Icons.military_tech;
|
||||
case 'workspace_premium': return Icons.workspace_premium;
|
||||
case 'psychology': return Icons.psychology;
|
||||
case 'lightbulb': return Icons.lightbulb;
|
||||
case 'star': return Icons.star;
|
||||
case 'speed': return Icons.speed;
|
||||
default: return Icons.star;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getRarityColor(String rarity) {
|
||||
switch (rarity) {
|
||||
case 'common': return Colors.grey;
|
||||
case 'rare': return Colors.blue;
|
||||
case 'epic': return Colors.purple;
|
||||
case 'legendary': return Colors.orange;
|
||||
default: return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
String _getProgressMessage() {
|
||||
if (_userStats == null) return 'Continue estudando!';
|
||||
|
||||
final streak = _userStats!.currentStreak;
|
||||
final studyTime = _userStats!.totalStudyTime;
|
||||
|
||||
if (streak >= 7) return 'Incrível streak! 🔥';
|
||||
if (studyTime >= 300) return 'Dedicação exemplar! 📚';
|
||||
if (streak >= 3) return 'Bom progresso! 📈';
|
||||
return 'Continue estudando! 💪';
|
||||
}
|
||||
|
||||
String _getProgressComparison() {
|
||||
if (_userStats == null) return 'Comece sua jornada de estudos';
|
||||
|
||||
final weeklyTime = _userStats!.weeklyStudyTime;
|
||||
final concepts = _userStats!.masteredConcepts.length;
|
||||
|
||||
if (weeklyTime >= 180) return 'Você está 15% acima da média esta semana';
|
||||
if (concepts >= 3) return 'Domine $concepts conceitos esta semana';
|
||||
if (weeklyTime >= 60) return 'Bom tempo de estudo esta semana';
|
||||
return 'Continue assim para subir no ranking!';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,81 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
|
||||
import '../../../../core/theme/app_theme_extension.dart';
|
||||
import '../../../../core/services/gamification_service.dart';
|
||||
import '../../../../core/models/user_stats.dart';
|
||||
import '../../../../core/services/auth_service.dart';
|
||||
|
||||
/// Progress tracking hero section for student dashboard
|
||||
class ProgressHeroWidget extends StatelessWidget {
|
||||
class ProgressHeroWidget extends StatefulWidget {
|
||||
final String userName;
|
||||
final double overallProgress;
|
||||
final List<String> masteredConcepts;
|
||||
final int studyTimeMinutes;
|
||||
final int streakDays;
|
||||
|
||||
const ProgressHeroWidget({
|
||||
super.key,
|
||||
required this.userName,
|
||||
this.overallProgress = 0.65,
|
||||
this.masteredConcepts = const [
|
||||
'Fundamentos de Programação',
|
||||
'Algoritmos Básicos',
|
||||
'Estruturas de Dados',
|
||||
],
|
||||
this.studyTimeMinutes = 245,
|
||||
this.streakDays = 7,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ProgressHeroWidget> createState() => _ProgressHeroWidgetState();
|
||||
}
|
||||
|
||||
class _ProgressHeroWidgetState extends State<ProgressHeroWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<UserStats?>(
|
||||
future: _loadUserStats(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return _buildLoadingState();
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return _buildErrorState();
|
||||
}
|
||||
|
||||
final userStats = snapshot.data;
|
||||
return _buildContent(userStats);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<UserStats?> _loadUserStats() async {
|
||||
try {
|
||||
final user = AuthService.currentUser;
|
||||
if (user != null) {
|
||||
return await GamificationService.getUserStats(user.uid);
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
print('Error loading user stats: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
double _calculateOverallProgress(UserStats? userStats) {
|
||||
if (userStats == null || userStats.masteredConcepts.isEmpty) {
|
||||
return 0.0;
|
||||
}
|
||||
final totalMastery = userStats.masteredConcepts
|
||||
.map((c) => c.masteryLevel)
|
||||
.reduce((a, b) => a + b);
|
||||
return totalMastery / (userStats.masteredConcepts.length * 100);
|
||||
}
|
||||
|
||||
Widget _buildLoadingState() {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
Widget _buildErrorState() {
|
||||
return const Center(child: Text('Erro ao carregar dados'));
|
||||
}
|
||||
|
||||
Widget _buildContent(UserStats? userStats) {
|
||||
|
||||
final streakDays = userStats?.currentStreak ?? 0;
|
||||
final overallProgress = _calculateOverallProgress(userStats);
|
||||
final masteredConcepts = userStats?.masteredConcepts.map((c) => c.conceptName).toList() ?? [];
|
||||
final studyTimeMinutes = userStats?.totalStudyTime ?? 0;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 24),
|
||||
child: Column(
|
||||
@@ -48,7 +99,7 @@ class ProgressHeroWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Continue assim, $userName!',
|
||||
'Continue assim, ${widget.userName}!',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontSize: 16,
|
||||
|
||||
@@ -22,23 +22,34 @@ class QuickAccessWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: _buildTutorIACard(context),
|
||||
Column(
|
||||
children: [
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildTutorIACard(context),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildQuizCard(context),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildAchievementsCard(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildQuizCard(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildQuizManagementCard(context),
|
||||
const SizedBox(height: 16),
|
||||
_buildJoinClassCard(context),
|
||||
],
|
||||
)
|
||||
@@ -85,6 +96,42 @@ class QuickAccessWidget extends StatelessWidget {
|
||||
.then(delay: const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
Widget _buildAchievementsCard(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return DashboardActionCardSurface(
|
||||
title: 'Conquistas',
|
||||
subtitle: 'Ver medals',
|
||||
icon: Icons.emoji_events,
|
||||
minHeight: 150,
|
||||
iconColor: Colors.amber,
|
||||
onTap: () => context.go('/student/achievements'),
|
||||
)
|
||||
.animate()
|
||||
.scale(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
curve: Curves.elasticOut,
|
||||
)
|
||||
.then(delay: const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
Widget _buildQuizManagementCard(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return DashboardActionCardSurface(
|
||||
title: 'Gerenciar Quizzes',
|
||||
subtitle: 'Ver histórico ou eliminar',
|
||||
icon: Icons.manage_history,
|
||||
minHeight: 80,
|
||||
iconColor: cs.tertiary,
|
||||
onTap: () => context.go('/quiz-management'),
|
||||
)
|
||||
.animate()
|
||||
.slideY(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
curve: Curves.easeOut,
|
||||
)
|
||||
.then(delay: const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
Widget _buildJoinClassCard(BuildContext context) {
|
||||
return DashboardActionCard(
|
||||
title: 'Entrar numa Turma',
|
||||
|
||||
@@ -1,15 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
import '../../../../core/theme/app_theme_extension.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import '../../../../core/services/auth_service.dart';
|
||||
import '../../../../core/services/gamification_service.dart';
|
||||
import '../../../../core/models/class_stats.dart';
|
||||
|
||||
/// Analytics preview section for teacher dashboard
|
||||
class TeacherAnalyticsPreviewWidget extends StatelessWidget {
|
||||
class TeacherAnalyticsPreviewWidget extends StatefulWidget {
|
||||
const TeacherAnalyticsPreviewWidget({super.key});
|
||||
|
||||
@override
|
||||
State<TeacherAnalyticsPreviewWidget> createState() => _TeacherAnalyticsPreviewWidgetState();
|
||||
}
|
||||
|
||||
class _TeacherAnalyticsPreviewWidgetState extends State<TeacherAnalyticsPreviewWidget> {
|
||||
List<ClassStats> _classStats = [];
|
||||
List<StudentRanking> _topStudents = [];
|
||||
bool _loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAnalyticsData();
|
||||
}
|
||||
|
||||
Future<void> _loadAnalyticsData() async {
|
||||
try {
|
||||
final user = AuthService.currentUser;
|
||||
if (user == null) return;
|
||||
|
||||
// Obter turmas do professor
|
||||
final classesSnapshot = await FirebaseFirestore.instance
|
||||
.collection('classes')
|
||||
.where('teacherId', isEqualTo: user.uid)
|
||||
.get();
|
||||
|
||||
final classStatsList = <ClassStats>[];
|
||||
|
||||
for (final classDoc in classesSnapshot.docs) {
|
||||
final classId = classDoc.id;
|
||||
final stats = await GamificationService.getClassStats(classId);
|
||||
if (stats != null) {
|
||||
classStatsList.add(stats);
|
||||
}
|
||||
}
|
||||
|
||||
// Obter melhores alunos de todas as turmas
|
||||
final allTopStudents = <StudentRanking>[];
|
||||
for (final classStats in classStatsList) {
|
||||
final ranking = await GamificationService.getClassRanking(classStats.classId);
|
||||
allTopStudents.addAll(ranking.take(3)); // Top 3 de cada turma
|
||||
}
|
||||
|
||||
// Ordenar por score e pegar os melhores
|
||||
allTopStudents.sort((a, b) => b.overallScore.compareTo(a.overallScore));
|
||||
final topStudents = allTopStudents.take(4).toList();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_classStats = classStatsList;
|
||||
_topStudents = topStudents;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading analytics data: $e');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get totalStudents => _classStats.fold(0, (sum, stats) => sum + stats.totalStudents);
|
||||
int get activeStudents => _classStats.fold(0, (sum, stats) => sum + stats.activeStudents);
|
||||
double get averageProgress {
|
||||
if (_classStats.isEmpty) return 0.0;
|
||||
final totalProgress = _classStats.fold(0.0, (sum, stats) => sum + stats.averageProgress);
|
||||
return totalProgress / _classStats.length;
|
||||
}
|
||||
int get studentsNeedingSupport => _classStats.fold(0, (sum, stats) => sum + stats.studentsNeedingSupport.length);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final user = AuthService.currentUser;
|
||||
final userName = user?.displayName ?? 'Professor';
|
||||
final userEmail = user?.email ?? '';
|
||||
@@ -109,24 +189,24 @@ class TeacherAnalyticsPreviewWidget extends StatelessWidget {
|
||||
_buildQuickStat(
|
||||
icon: Icons.check_circle,
|
||||
label: 'Alunos Ativos',
|
||||
value: '18/24',
|
||||
value: '$activeStudents/$totalStudents',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildQuickStat(
|
||||
icon: Icons.warning_amber,
|
||||
label: 'Precisam Apoio',
|
||||
value: '3',
|
||||
value: '$studentsNeedingSupport',
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildQuickStat(
|
||||
icon: Icons.emoji_events,
|
||||
label: 'Média Turma',
|
||||
value: '72%',
|
||||
value: '${(averageProgress * 100).toInt()}%',
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withOpacity(0.8),
|
||||
).colorScheme.primary.withValues(alpha: 0.8),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -154,33 +234,17 @@ class TeacherAnalyticsPreviewWidget extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Student List Preview
|
||||
_buildStudentPerformanceItem(
|
||||
context,
|
||||
'Ana Silva',
|
||||
95,
|
||||
Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStudentPerformanceItem(
|
||||
context,
|
||||
'João Costa',
|
||||
88,
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStudentPerformanceItem(
|
||||
context,
|
||||
'Maria Santos',
|
||||
82,
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStudentPerformanceItem(
|
||||
context,
|
||||
'Pedro Lima',
|
||||
45,
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
..._topStudents.map((student) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildStudentPerformanceItem(
|
||||
context,
|
||||
student.studentName,
|
||||
student.overallScore.toInt(),
|
||||
_getScoreColor(student.overallScore),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@@ -218,7 +282,7 @@ class TeacherAnalyticsPreviewWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'12 conteúdos verificados • 2 pendentes de revisão',
|
||||
'${_classStats.fold(0, (sum, stats) => sum + stats.totalContent)} conteúdos • $studentsNeedingSupport alunos precisam de apoio',
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
@@ -336,4 +400,11 @@ class TeacherAnalyticsPreviewWidget extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Color _getScoreColor(double score) {
|
||||
if (score >= 80) return Theme.of(context).colorScheme.tertiary;
|
||||
if (score >= 60) return Theme.of(context).colorScheme.primary;
|
||||
if (score >= 40) return Theme.of(context).colorScheme.secondary;
|
||||
return Theme.of(context).colorScheme.error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,89 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
import '../../../../core/theme/app_theme_extension.dart';
|
||||
import '../../../../core/services/gamification_service.dart';
|
||||
import '../../../../core/models/class_stats.dart';
|
||||
import '../../../../core/services/auth_service.dart';
|
||||
|
||||
/// Hero section for teacher dashboard showing class overview
|
||||
class TeacherHeroWidget extends StatelessWidget {
|
||||
class TeacherHeroWidget extends StatefulWidget {
|
||||
final String userName;
|
||||
final int totalStudents;
|
||||
final int activeQuizzes;
|
||||
final int uploadedContent;
|
||||
final double classAverageProgress;
|
||||
|
||||
const TeacherHeroWidget({
|
||||
super.key,
|
||||
required this.userName,
|
||||
this.totalStudents = 24,
|
||||
this.activeQuizzes = 3,
|
||||
this.uploadedContent = 12,
|
||||
this.classAverageProgress = 0.72,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TeacherHeroWidget> createState() => _TeacherHeroWidgetState();
|
||||
}
|
||||
|
||||
class _TeacherHeroWidgetState extends State<TeacherHeroWidget> {
|
||||
List<ClassStats> _classStats = [];
|
||||
bool _loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadClassStats();
|
||||
}
|
||||
|
||||
Future<void> _loadClassStats() async {
|
||||
try {
|
||||
final user = AuthService.currentUser;
|
||||
if (user == null) return;
|
||||
|
||||
// Obter turmas do professor
|
||||
final classesSnapshot = await FirebaseFirestore.instance
|
||||
.collection('classes')
|
||||
.where('teacherId', isEqualTo: user.uid)
|
||||
.get();
|
||||
|
||||
final classStatsList = <ClassStats>[];
|
||||
|
||||
for (final classDoc in classesSnapshot.docs) {
|
||||
final classId = classDoc.id;
|
||||
final stats = await GamificationService.getClassStats(classId);
|
||||
if (stats != null) {
|
||||
classStatsList.add(stats);
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_classStats = classStatsList;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading class stats: $e');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get totalStudents => _classStats.fold(0, (sum, stats) => sum + stats.totalStudents);
|
||||
int get activeQuizzes => _classStats.fold(0, (sum, stats) => sum + stats.activeQuizzes);
|
||||
int get uploadedContent => _classStats.fold(0, (sum, stats) => sum + stats.totalContent);
|
||||
double get classAverageProgress {
|
||||
if (_classStats.isEmpty) return 0.0;
|
||||
final totalProgress = _classStats.fold(0.0, (sum, stats) => sum + stats.averageProgress);
|
||||
return totalProgress / _classStats.length;
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
if (_loading) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 24),
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 24),
|
||||
child: Column(
|
||||
@@ -231,26 +293,7 @@ class TeacherHeroWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildActivityItem(
|
||||
context,
|
||||
'15 alunos completaram o quiz de Derivadas',
|
||||
'Hoje, 14:30',
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildActivityItem(
|
||||
context,
|
||||
'Novo conteúdo: Regra da Cadeia',
|
||||
'Ontem, 09:15',
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildActivityItem(
|
||||
context,
|
||||
'3 alunos precisam de apoio em Limites',
|
||||
'Ontem, 16:45',
|
||||
Theme.of(context).colorScheme.error,
|
||||
),
|
||||
..._buildRecentActivities(),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -302,6 +345,67 @@ class TeacherHeroWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildRecentActivities() {
|
||||
final activities = <Widget>[];
|
||||
|
||||
if (_classStats.isEmpty) {
|
||||
activities.add(_buildActivityItem(
|
||||
context,
|
||||
'Nenhuma atividade recente',
|
||||
'Comece criando turmas e conteúdos',
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
));
|
||||
return activities;
|
||||
}
|
||||
|
||||
// Adicionar atividades baseadas nas estatísticas das turmas
|
||||
for (final stats in _classStats.take(3)) {
|
||||
if (stats.activeQuizzes > 0) {
|
||||
activities.add(_buildActivityItem(
|
||||
context,
|
||||
'${stats.activeQuizzes} quizzes ativos em ${stats.className}',
|
||||
'Recente',
|
||||
Theme.of(context).colorScheme.primary,
|
||||
));
|
||||
activities.add(const SizedBox(height: 8));
|
||||
}
|
||||
|
||||
if (stats.studentsNeedingSupport.isNotEmpty) {
|
||||
activities.add(_buildActivityItem(
|
||||
context,
|
||||
'${stats.studentsNeedingSupport.length} alunos precisam de apoio em ${stats.className}',
|
||||
'Ver analytics',
|
||||
Theme.of(context).colorScheme.error,
|
||||
));
|
||||
activities.add(const SizedBox(height: 8));
|
||||
}
|
||||
|
||||
if (stats.totalContent > 0) {
|
||||
activities.add(_buildActivityItem(
|
||||
context,
|
||||
'${stats.totalContent} conteúdos disponíveis em ${stats.className}',
|
||||
'Atualizado',
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
));
|
||||
activities.add(const SizedBox(height: 8));
|
||||
}
|
||||
}
|
||||
|
||||
// Remover o último SizedBox se existir
|
||||
if (activities.isNotEmpty && activities.last is SizedBox) {
|
||||
activities.removeLast();
|
||||
}
|
||||
|
||||
return activities.isEmpty ? [
|
||||
_buildActivityItem(
|
||||
context,
|
||||
'Nenhuma atividade recente',
|
||||
'Comece criando turmas e conteúdos',
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
)
|
||||
] : activities;
|
||||
}
|
||||
|
||||
Widget _buildActivityItem(
|
||||
BuildContext context,
|
||||
String text,
|
||||
@@ -333,7 +437,7 @@ class TeacherHeroWidget extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.7),
|
||||
).colorScheme.onSurfaceVariant.withValues(alpha: 0.7),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user