262 lines
8.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/theme/app_theme.dart';
import '../../models/attendance.dart';
import '../../providers/attendance_provider.dart';
class AttendanceScreen extends ConsumerWidget {
const AttendanceScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
const filter = AttendanceFilter();
final attendanceAsync = ref.watch(attendanceProvider(filter));
return Scaffold(
appBar: AppBar(
title: const Text('Attendance'),
),
body: RefreshIndicator(
onRefresh: () async {
ref.invalidate(attendanceProvider(filter));
await ref.read(attendanceProvider(filter).future);
},
child: attendanceAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, _) => Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline_rounded, size: 48, color: AppTheme.errorColor),
const SizedBox(height: 12),
const Text('Failed to load attendance',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text(err.toString(),
textAlign: TextAlign.center,
style: const TextStyle(color: AppTheme.textSecondary, fontSize: 13)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => ref.refresh(attendanceProvider(filter)),
child: const Text('Retry'),
),
],
),
),
),
data: (data) => _buildAttendanceBody(context, data),
),
),
);
}
Widget _buildAttendanceBody(BuildContext context, AttendanceData data) {
final summary = data.summary;
final records = data.records;
return ListView(
padding: const EdgeInsets.all(16),
children: [
// Attendance Overview Card
Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
const Text(
'Cumulative Attendance',
style: TextStyle(
fontSize: 14,
color: AppTheme.textSecondary,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Text(
'${summary.percentage.toStringAsFixed(1)}%',
style: TextStyle(
fontSize: 44,
fontWeight: FontWeight.bold,
color: summary.percentage >= 75
? AppTheme.successColor
: (summary.percentage >= 60 ? AppTheme.warningColor : AppTheme.errorColor),
),
),
const SizedBox(height: 6),
Text(
summary.percentage >= 75
? 'Eligibility: Met (Eligible for exams)'
: 'Eligibility: Shortage (< 75% required)',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: summary.percentage >= 75 ? AppTheme.successColor : AppTheme.errorColor,
),
),
const Divider(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatCol('Present', summary.present, AppTheme.successColor),
_buildStatCol('Absent', summary.absent, AppTheme.errorColor),
_buildStatCol('Late', summary.late, AppTheme.warningColor),
_buildStatCol('Leave', summary.medicalLeave, Colors.purple),
_buildStatCol('Duty', summary.onDuty, Colors.blue),
],
),
],
),
),
),
const SizedBox(height: 20),
// Session History Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Attendance History',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
Text(
'${records.length} sessions recorded',
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
],
),
const SizedBox(height: 10),
if (records.isEmpty)
Container(
padding: const EdgeInsets.symmetric(vertical: 40),
alignment: Alignment.center,
child: const Column(
children: [
Icon(Icons.event_busy, size: 40, color: AppTheme.textMuted),
SizedBox(height: 8),
Text(
'No attendance sessions recorded yet',
style: TextStyle(color: AppTheme.textSecondary),
),
],
),
)
else
...records.map((r) => _buildRecordCard(r)),
],
);
}
Widget _buildStatCol(String label, int count, Color color) {
return Column(
children: [
Text(
'$count',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(height: 2),
Text(
label,
style: const TextStyle(
fontSize: 11,
color: AppTheme.textSecondary,
),
),
],
);
}
Widget _buildRecordCard(AttendanceRecord record) {
Color badgeColor;
Color textColor;
String label;
switch (record.status.toLowerCase()) {
case 'present':
badgeColor = AppTheme.successColor.withOpacity(0.12);
textColor = AppTheme.successColor;
label = 'PRESENT';
break;
case 'absent':
badgeColor = AppTheme.errorColor.withOpacity(0.12);
textColor = AppTheme.errorColor;
label = 'ABSENT';
break;
case 'late':
badgeColor = AppTheme.warningColor.withOpacity(0.12);
textColor = AppTheme.warningColor;
label = 'LATE';
break;
case 'medical_leave':
badgeColor = Colors.purple.withOpacity(0.12);
textColor = Colors.purple;
label = 'MEDICAL LEAVE';
break;
case 'on_duty':
badgeColor = Colors.blue.withOpacity(0.12);
textColor = Colors.blue;
label = 'ON DUTY';
break;
default:
badgeColor = Colors.grey.withOpacity(0.12);
textColor = Colors.grey;
label = record.status.toUpperCase();
}
return Card(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Icon(Icons.calendar_today_rounded, size: 16, color: AppTheme.textSecondary),
const SizedBox(width: 8),
Text(
record.date,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: badgeColor,
borderRadius: BorderRadius.circular(6),
),
child: Text(
label,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: textColor,
),
),
),
],
),
),
);
}
}