Finalização de detalhes e pequenas adições em dashboards de alunos e professores

This commit is contained in:
2026-05-18 22:48:27 +01:00
parent c0ade9ef76
commit 7f12f3eb1f
58 changed files with 1347 additions and 1065 deletions

View File

@@ -23,6 +23,8 @@ class AnalyticsPage extends StatefulWidget {
class _AnalyticsPageState extends State<AnalyticsPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final _classSearchController = TextEditingController();
String _classSearchQuery = '';
List<ClassStats> _classStats = [];
bool _loading = true;
String? _selectedClassId;
@@ -38,6 +40,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
@override
void dispose() {
_tabController.dispose();
_classSearchController.dispose();
super.dispose();
}
@@ -46,7 +49,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
final user = AuthService.currentUser;
if (user == null) return;
// Obter disciplinas do professor
// Obter turmas do professor
final classesSnapshot = await FirebaseFirestore.instance
.collection('classes')
.where('teacherId', isEqualTo: user.uid)
@@ -85,7 +88,6 @@ class _AnalyticsPageState extends State<AnalyticsPage>
@override
Widget build(BuildContext context) {
final themeExtras = AppThemeExtras.of(context);
final cs = Theme.of(context).colorScheme;
return PopScope(
canPop: false,
@@ -94,9 +96,9 @@ class _AnalyticsPageState extends State<AnalyticsPage>
context.go('/teacher-dashboard');
},
child: Scaffold(
backgroundColor: cs.surface,
backgroundColor: Colors.transparent,
resizeToAvoidBottomInset: false,
body: Container(
body: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
@@ -106,6 +108,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
),
),
child: SafeArea(
bottom: false,
child: Column(
children: [
// Header
@@ -137,7 +140,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
),
const SizedBox(height: 4),
Text(
'Acompanhe o desempenho das disciplinas',
'Acompanhe o desempenho das turmas',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.8),
fontSize: 16,
@@ -163,7 +166,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
indicatorColor: Colors.white,
indicatorWeight: 2,
tabs: const [
Tab(text: 'Disciplinas'),
Tab(text: 'Turmas'),
Tab(text: 'Alunos'),
],
),
@@ -205,7 +208,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
),
const SizedBox(height: 16),
Text(
'Nenhuma disciplina encontrada',
'Nenhuma turma encontrada',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.7),
fontSize: 18,
@@ -213,7 +216,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
),
const SizedBox(height: 8),
Text(
'Crie disciplinas para ver as analytics aqui',
'Crie turmas para ver as analytics aqui',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.5),
fontSize: 14,
@@ -224,47 +227,131 @@ class _AnalyticsPageState extends State<AnalyticsPage>
);
}
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Overview Cards
Row(
children: [
Expanded(
child: _buildOverviewCard(
'Total de Alunos',
'${_classStats.fold(0, (sum, stats) => sum + stats.totalStudents)}',
Icons.people,
Colors.blue,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildOverviewCard(
'Alunos Ativos',
'${_classStats.fold(0, (sum, stats) => sum + stats.activeStudents)}',
Icons.trending_up,
Colors.green,
),
),
],
),
const SizedBox(height: 20),
final filtered = _classSearchQuery.isEmpty
? _classStats
: _classStats
.where(
(s) => s.className.toLowerCase().contains(_classSearchQuery),
)
.toList();
// Class Cards
..._classStats.map(
(stats) => Padding(
padding: const EdgeInsets.only(bottom: 16),
child: ClassAnalyticsCard(
classStats: stats,
onTap: () => _showClassStudents(stats),
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,
),
),
),
],
),
],
);
}
@@ -281,7 +368,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
),
const SizedBox(height: 16),
Text(
'Seleciona uma disciplina',
'Seleciona uma turma',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.7),
fontSize: 18,
@@ -289,7 +376,7 @@ class _AnalyticsPageState extends State<AnalyticsPage>
),
const SizedBox(height: 8),
Text(
'Clica numa disciplina no separador "Disciplinas" para ver os alunos',
'Clica numa turma no separador "Turmas" para ver os alunos',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.5),
fontSize: 14,