import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:intl/intl.dart'; import '../../../app/theme/app_colors.dart'; import '../../../core/widgets/progress_bar.dart'; import '../../../data/local/hive_boxes.dart'; import '../../../data/local/models/daily_worship_log.dart'; import '../../../data/local/models/checklist_item.dart'; class LaporanScreen extends ConsumerStatefulWidget { const LaporanScreen({super.key}); @override ConsumerState createState() => _LaporanScreenState(); } class _LaporanScreenState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); _tabController.addListener(() => setState(() {})); } @override void dispose() { _tabController.dispose(); super.dispose(); } /// Get the last 7 days' point data. List<_DayData> _getWeeklyData() { final logBox = Hive.box(HiveBoxes.worshipLogs); final now = DateTime.now(); final data = <_DayData>[]; for (int i = 6; i >= 0; i--) { final date = now.subtract(Duration(days: i)); final key = DateFormat('yyyy-MM-dd').format(date); final log = logBox.get(key); data.add(_DayData( label: DateFormat('E').format(date).substring(0, 3), value: (log?.totalPoints ?? 0).toDouble(), // Use points instead of % isToday: i == 0, )); } return data; } /// Get average points for the week. double _weekAverage(List<_DayData> data) { if (data.isEmpty) return 0; final sum = data.fold(0, (s, d) => s + d.value); return sum / data.length; } /// Find best and worst performing items. _InsightPair _getInsights() { final logBox = Hive.box(HiveBoxes.worshipLogs); final now = DateTime.now(); final completionCounts = {}; final totalCounts = {}; int daysChecked = 0; for (int i = 0; i < 7; i++) { final date = now.subtract(Duration(days: i)); final key = DateFormat('yyyy-MM-dd').format(date); final log = logBox.get(key); if (log != null && log.totalItems > 0) { daysChecked++; // Fardhu totalCounts['fardhu'] = (totalCounts['fardhu'] ?? 0) + 5; int completedFardhu = log.shalatLogs.values.where((l) => l.completed).length; completionCounts['fardhu'] = (completionCounts['fardhu'] ?? 0) + completedFardhu; // Rawatib int rawatibTotal = 0; int rawatibCompleted = 0; for (var sLog in log.shalatLogs.values) { if (sLog.qabliyah != null) { rawatibTotal++; if (sLog.qabliyah!) rawatibCompleted++; } if (sLog.badiyah != null) { rawatibTotal++; if (sLog.badiyah!) rawatibCompleted++; } } if (rawatibTotal > 0) { totalCounts['rawatib'] = (totalCounts['rawatib'] ?? 0) + rawatibTotal; completionCounts['rawatib'] = (completionCounts['rawatib'] ?? 0) + rawatibCompleted; } // Tilawah if (log.tilawahLog != null) { totalCounts['tilawah'] = (totalCounts['tilawah'] ?? 0) + 1; if (log.tilawahLog!.isCompleted) { completionCounts['tilawah'] = (completionCounts['tilawah'] ?? 0) + 1; } } // Dzikir if (log.dzikirLog != null) { totalCounts['dzikir'] = (totalCounts['dzikir'] ?? 0) + 2; int dCompleted = (log.dzikirLog!.pagi ? 1 : 0) + (log.dzikirLog!.petang ? 1 : 0); completionCounts['dzikir'] = (completionCounts['dzikir'] ?? 0) + dCompleted; } // Puasa if (log.puasaLog != null) { totalCounts['puasa'] = (totalCounts['puasa'] ?? 0) + 1; if (log.puasaLog!.completed) { completionCounts['puasa'] = (completionCounts['puasa'] ?? 0) + 1; } } } } if (daysChecked == 0 || totalCounts.isEmpty) { return _InsightPair( best: _InsightItem(title: 'Sholat Fardhu', percent: 0), worst: _InsightItem(title: 'Belum Ada Data', percent: 0), ); } String bestId = totalCounts.keys.first; String worstId = totalCounts.keys.first; double bestRate = -1.0; double worstRate = 2.0; for (final id in totalCounts.keys) { final total = totalCounts[id]!; final completed = completionCounts[id] ?? 0; final rate = completed / total; if (rate > bestRate) { bestRate = rate; bestId = id; } if (rate < worstRate) { worstRate = rate; worstId = id; } } final idToTitle = { 'fardhu': 'Sholat Fardhu', 'rawatib': 'Sholat Rawatib', 'tilawah': 'Tilawah Quran', 'dzikir': 'Dzikir Harian', 'puasa': 'Puasa Sunnah', }; return _InsightPair( best: _InsightItem( title: idToTitle[bestId] ?? bestId, percent: (bestRate * 100).round(), ), worst: _InsightItem( title: idToTitle[worstId] ?? worstId, percent: (worstRate * 100).round(), ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; final weekData = _getWeeklyData(); final avgPercent = _weekAverage(weekData); final insights = _getInsights(); return Scaffold( appBar: AppBar( title: const Text('Laporan Kualitas Ibadah'), centerTitle: false, actions: [ IconButton( onPressed: () {}, icon: const Icon(Icons.notifications_outlined), ), IconButton( onPressed: () => context.push('/settings'), icon: const Icon(Icons.settings_outlined), ), const SizedBox(width: 8), ], ), body: Column( children: [ // ── Tab Bar ── Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: isDark ? AppColors.primary.withValues(alpha: 0.1) : AppColors.cream, ), ), ), child: TabBar( controller: _tabController, labelColor: AppColors.primary, unselectedLabelColor: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, indicatorColor: AppColors.primary, indicatorWeight: 3, labelStyle: const TextStyle( fontWeight: FontWeight.w700, fontSize: 14, ), tabs: const [ Tab(text: 'Mingguan'), Tab(text: 'Bulanan'), Tab(text: 'Tahunan'), ], ), ), // ── Tab Content ── Expanded( child: TabBarView( controller: _tabController, children: [ _buildWeeklyView(context, isDark, weekData, avgPercent, insights), _buildComingSoon(context, 'Bulanan'), _buildComingSoon(context, 'Tahunan'), ], ), ), ], ), ); } Widget _buildWeeklyView( BuildContext context, bool isDark, List<_DayData> weekData, double avgPercent, _InsightPair insights, ) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Completion Card ── Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(20), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.1) : AppColors.cream, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Poin Rata-Rata Harian', style: TextStyle( fontSize: 13, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), ), child: const Icon(Icons.stars, color: AppColors.primary, size: 18), ), ], ), const SizedBox(height: 4), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${avgPercent.round()} pt', style: const TextStyle( fontSize: 36, fontWeight: FontWeight.w800, height: 1.1, ), ), ], ), const SizedBox(height: 20), // ── Bar Chart ── SizedBox( height: 140, child: Builder( builder: (context) { final maxPts = weekData.map((d) => d.value).fold(0.0, (a, b) => a > b ? a : b).clamp(50.0, 300.0); return Row( crossAxisAlignment: CrossAxisAlignment.end, children: weekData.map((d) { final ratio = (d.value / maxPts).clamp(0.05, 1.0); return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Flexible( child: Container( width: double.infinity, height: 120 * ratio, decoration: BoxDecoration( color: d.isToday ? AppColors.primary : AppColors.primary .withValues(alpha: 0.3 + ratio * 0.4), borderRadius: BorderRadius.circular(6), ), ), ), const SizedBox(height: 8), Text( d.label, style: TextStyle( fontSize: 10, fontWeight: d.isToday ? FontWeight.w700 : FontWeight.w400, color: d.isToday ? AppColors.primary : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight), ), ), ], ), ), ); }).toList(), ); } ), ), ], ), ), const SizedBox(height: 24), // ── Insights ── Text('Wawasan', style: Theme.of(context) .textTheme .titleMedium ?.copyWith(fontWeight: FontWeight.w700)), const SizedBox(height: 12), // Best performing _insightCard( context, isDark, icon: Icons.star, iconBg: AppColors.primary.withValues(alpha: 0.15), iconColor: AppColors.primary, label: 'PALING RAJIN', title: insights.best.title, percent: insights.best.percent, percentColor: AppColors.primary, ), const SizedBox(height: 10), // Needs improvement _insightCard( context, isDark, icon: Icons.trending_up, iconBg: const Color(0xFFFFF3E0), iconColor: Colors.orange, label: 'PERLU DITINGKATKAN', title: insights.worst.title, percent: insights.worst.percent, percentColor: Colors.orange, ), const SizedBox(height: 24), // ── Motivational Quote ── Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? AppColors.primary.withValues(alpha: 0.08) : const Color(0xFFF5F9F0), borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '❝', style: TextStyle( fontSize: 32, color: AppColors.primary, height: 0.8, ), ), const SizedBox(height: 4), Text( '"Amal yang paling dicintai Allah adalah yang paling konsisten, meskipun sedikit."', style: TextStyle( fontSize: 15, fontStyle: FontStyle.italic, height: 1.5, color: isDark ? Colors.white : Colors.black87, ), ), const SizedBox(height: 12), Text( '— Shahih Bukhari', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ), const SizedBox(height: 24), ], ), ); } Widget _insightCard( BuildContext context, bool isDark, { required IconData icon, required Color iconBg, required Color iconColor, required String label, required String title, required int percent, required Color percentColor, }) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(16), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.1) : AppColors.cream, ), ), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: iconBg, borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: iconColor, size: 22), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, letterSpacing: 1.5, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), const SizedBox(height: 2), Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), ], ), ), Text( '$percent%', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w800, color: percentColor, ), ), ], ), ); } Widget _buildComingSoon(BuildContext context, String period) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.bar_chart, size: 48, color: AppColors.primary.withValues(alpha: 0.3)), const SizedBox(height: 12), Text( 'Laporan $period', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), const SizedBox(height: 4), Text( 'Segera hadir', style: TextStyle( color: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight), ), ], ), ); } } class _DayData { final String label; final double value; final bool isToday; _DayData({required this.label, required this.value, this.isToday = false}); } class _InsightItem { final String title; final int percent; _InsightItem({required this.title, required this.percent}); } class _InsightPair { final _InsightItem best; final _InsightItem worst; _InsightPair({required this.best, required this.worst}); }