Files
LearnIT/lib/features/analytics/presentation/pages/analytics_page.dart

469 lines
15 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:go_router/go_router.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../../../../core/services/auth_service.dart';
import '../../../../core/services/gamification_service.dart';
import '../../../../core/models/class_stats.dart';
import '../../../../core/models/achievement.dart';
import '../widgets/class_analytics_card.dart';
import '../widgets/class_students_inline_widget.dart';
import '../widgets/create_achievement_dialog.dart';
/// Analytics page for teachers with class breakdowns and rankings
class AnalyticsPage extends StatefulWidget {
const AnalyticsPage({super.key});
@override
State<AnalyticsPage> createState() => _AnalyticsPageState();
}
class _AnalyticsPageState extends State<AnalyticsPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final _classSearchController = TextEditingController();
String _classSearchQuery = '';
List<ClassStats> _classStats = [];
bool _loading = true;
String? _selectedClassId;
String? _selectedClassName;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_loadClassStats();
}
@override
void dispose() {
_tabController.dispose();
_classSearchController.dispose();
super.dispose();
}
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;
// Forçar atualização para obter dados mais recentes
final stats = await GamificationService.getClassStats(
classId,
forceRefresh: true,
);
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;
});
}
}
}
@override
Widget build(BuildContext context) {
final themeExtras = AppThemeExtras.of(context);
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (didPop) return;
context.go('/teacher-dashboard');
},
child: Scaffold(
backgroundColor: Colors.transparent,
resizeToAvoidBottomInset: false,
body: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: themeExtras.dashboardBackgroundGradient,
stops: themeExtras.dashboardGradientStops,
),
),
child: SafeArea(
top: false,
bottom: false,
child: Column(
children: [
// Header
Container(
padding: const EdgeInsets.only(
left: 24,
right: 24,
bottom: 28,
top: 52,
),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () => context.go('/teacher-dashboard'),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Analytics',
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'Acompanhe o desempenho das turmas',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.8),
fontSize: 16,
),
),
],
),
),
IconButton(
icon: const Icon(Icons.add, color: Colors.white),
onPressed: _showCreateAchievementDialog,
tooltip: 'Criar Conquista',
),
],
),
const SizedBox(height: 20),
TabBar(
controller: _tabController,
labelColor: Colors.white,
unselectedLabelColor: Colors.white.withValues(
alpha: 0.7,
),
indicatorColor: Colors.white,
indicatorWeight: 2,
tabs: const [
Tab(text: 'Turmas'),
Tab(text: 'Alunos'),
],
),
],
),
),
// Content
Expanded(
child: TabBarView(
controller: _tabController,
children: [_buildClassesTab(), _buildStudentsTab()],
),
),
],
),
),
),
),
);
}
Widget _buildClassesTab() {
if (_loading) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
}
if (_classStats.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.analytics_outlined,
size: 64,
color: Colors.white.withValues(alpha: 0.5),
),
const SizedBox(height: 16),
Text(
'Nenhuma turma encontrada',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.7),
fontSize: 18,
),
),
const SizedBox(height: 8),
Text(
'Crie turmas para ver as analytics aqui',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.5),
fontSize: 14,
),
),
],
),
);
}
final filtered = _classSearchQuery.isEmpty
? _classStats
: _classStats
.where(
(s) => s.className.toLowerCase().contains(_classSearchQuery),
)
.toList();
return CustomScrollView(
slivers: [
SliverPadding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
sliver: SliverToBoxAdapter(
child: Column(
children: [
// Overview Cards
Center(
child: _buildOverviewCard(
'Total de Alunos',
'${_classStats.fold(0, (sum, s) => sum + s.totalStudents)}',
Icons.people,
Colors.blue,
),
),
const SizedBox(height: 20),
// Search bar
Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.white.withValues(alpha: 0.2),
),
),
child: Row(
children: [
Icon(
Icons.search,
color: Colors.white.withValues(alpha: 0.7),
size: 20,
),
const SizedBox(width: 10),
Expanded(
child: Theme(
data: ThemeData.dark().copyWith(
textSelectionTheme: const TextSelectionThemeData(
cursorColor: Colors.white,
selectionColor: Colors.white24,
selectionHandleColor: Colors.white,
),
),
child: TextField(
controller: _classSearchController,
onChanged: (v) => setState(
() => _classSearchQuery = v.trim().toLowerCase(),
),
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
cursorColor: Colors.white,
decoration: InputDecoration(
hintText: 'Pesquisar turma…',
hintStyle: TextStyle(
color: Colors.white.withValues(alpha: 0.5),
fontSize: 14,
),
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
),
),
),
if (_classSearchQuery.isNotEmpty)
GestureDetector(
onTap: () {
_classSearchController.clear();
setState(() => _classSearchQuery = '');
},
child: Icon(
Icons.close,
color: Colors.white.withValues(alpha: 0.7),
size: 18,
),
),
],
),
),
const SizedBox(height: 20),
],
),
),
),
if (filtered.isEmpty)
SliverFillRemaining(
hasScrollBody: false,
child: Center(
child: Text(
'Nenhuma turma encontrada',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.6),
fontSize: 15,
),
),
),
)
else
SliverPadding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ClassAnalyticsCard(
classStats: filtered[index],
onTap: () => _showClassStudents(filtered[index]),
),
childCount: filtered.length,
),
),
),
],
);
}
Widget _buildStudentsTab() {
if (_selectedClassId == null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.people_outline,
size: 64,
color: Colors.white.withValues(alpha: 0.5),
),
const SizedBox(height: 16),
Text(
'Seleciona uma turma',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.7),
fontSize: 18,
),
),
const SizedBox(height: 8),
Text(
'Clica numa turma no separador "Turmas" para ver os alunos',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.5),
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
),
);
}
return ClassStudentsInlineWidget(
classId: _selectedClassId!,
className: _selectedClassName ?? '',
);
}
Widget _buildOverviewCard(
String title,
String value,
IconData icon,
Color color,
) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withValues(alpha: 0.2)),
),
child: Column(
children: [
Icon(icon, color: color, size: 32),
const SizedBox(height: 12),
Text(
value,
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
title,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.8),
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
),
).animate().scale(duration: 600.ms, curve: Curves.elasticOut);
}
void _showClassStudents(ClassStats stats) {
setState(() {
_selectedClassId = stats.classId;
_selectedClassName = stats.className;
});
_tabController.animateTo(1);
}
void _showCreateAchievementDialog() {
showDialog(
context: context,
builder: (context) => CreateAchievementDialog(
onAchievementCreated: (achievement) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Conquista "${achievement.name}" criada com sucesso!',
),
backgroundColor: Colors.green,
),
);
},
),
);
}
}