import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/app_theme.dart'; import '../providers/monitoring_provider.dart'; class MonitoringChart extends ConsumerWidget { const MonitoringChart({super.key}); String _getRangeLabel(ChartRange range, List dailyData) { if (dailyData.isEmpty) return '-'; final first = dailyData.first.date; final months = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nov', 'Des']; if (range == ChartRange.daily) { return '${first.day} ${months[first.month - 1]} ${first.year}'; } else { return '${months[first.month - 1]} ${first.year}'; } } @override Widget build(BuildContext context, WidgetRef ref) { ref.listen(chartOffsetProvider, (previous, next) { if (next != previous) { final currentRange = ref.read(chartRangeProvider); ref.read(monitoringProvider.notifier).fetchHistoryForOffset( range: currentRange, offset: next, ); } }); ref.listen(chartRangeProvider, (previous, next) { if (next != previous) { Future.microtask(() { ref.read(chartOffsetProvider.notifier).state = 0; if (ref.read(chartOffsetProvider) == 0) { ref.read(monitoringProvider.notifier).fetchHistoryForOffset( range: next, offset: 0, ); } }); } }); final range = ref.watch(chartRangeProvider); final type = ref.watch(chartTypeProvider); final offset = ref.watch(chartOffsetProvider); final dailyData = ref.watch(dailyAverageProvider); final values = dailyData .map((d) => type == ChartType.suhu ? d.suhu : d.kelembapan) .whereType() .toList(); final maxY = values.isEmpty ? 100.0 : (values.reduce((a, b) => a > b ? a : b) + 15).ceilToDouble(); final barColor = type == ChartType.suhu ? Colors.deepOrange : Colors.teal; Widget chart = BarChart( BarChartData( alignment: BarChartAlignment.spaceBetween, maxY: maxY, gridData: FlGridData( show: true, drawVerticalLine: false, horizontalInterval: (maxY / 4).ceilToDouble(), getDrawingHorizontalLine: (_) => FlLine( color: Colors.grey.withValues(alpha: 0.1), strokeWidth: 1, ), ), borderData: FlBorderData(show: false), barTouchData: BarTouchData( enabled: false, // Matikan interaksi sentuh karena angka sudah muncul di atas touchTooltipData: BarTouchTooltipData( tooltipBgColor: Colors.transparent, tooltipPadding: const EdgeInsets.only(bottom: 0), tooltipMargin: 2, getTooltipItem: (group, groupIndex, rod, rodIndex) { if (rod.toY == 0) return null; // Sembunyikan untuk batang kosong final val = rod.toY.round(); return BarTooltipItem( '$val', TextStyle( color: barColor, fontWeight: FontWeight.bold, fontSize: 11, ), ); }, ), ), titlesData: FlTitlesData( topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 36, getTitlesWidget: (value, _) { final i = value.toInt(); if (i < 0 || i >= dailyData.length) { return const SizedBox(); } final date = dailyData[i].date; final label = range == ChartRange.daily ? '${date.hour.toString().padLeft(2, '0')}:00' : date.day.toString(); return Padding( padding: const EdgeInsets.only(top: 6), child: Text( label, style: const TextStyle( fontSize: 11, color: Colors.grey), ), ); }, ), ), ), barGroups: List.generate(dailyData.length, (i) { final v = type == ChartType.suhu ? dailyData[i].suhu : dailyData[i].kelembapan; final val = v != null ? v.roundToDouble() : 0.0; return BarChartGroupData( x: i, showingTooltipIndicators: v != null ? [0] : [], // Selalu tampilkan angka jika ada data barRods: [ BarChartRodData( toY: val, width: range == ChartRange.daily ? 16 : 12, borderRadius: BorderRadius.circular(6), color: v == null ? Colors.grey.shade200 : barColor, backDrawRodData: BackgroundBarChartRodData( show: true, toY: maxY, color: Colors.grey.shade100, // Background bar semu ), ), ], ); }), ), ); if (range == ChartRange.monthly || range == ChartRange.daily) { chart = SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( width: dailyData.length * (range == ChartRange.daily ? 40.0 : 40.0), child: chart, ), ); } return Container( padding: const EdgeInsets.all(20), decoration: AppTheme.cardDecoration(radius: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Monitoring Chart', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: barColor.withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: Text( type == ChartType.suhu ? '°C (Suhu)' : '% (Kelembapan)', style: TextStyle( fontSize: 12, color: barColor, fontWeight: FontWeight.bold), ), ), ], ), const SizedBox(height: 16), // Navigasi Waktu (Kalender) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( onPressed: () => ref.read(chartOffsetProvider.notifier).state--, icon: const Icon(Icons.chevron_left, color: AppTheme.textSecondary), padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), Text( _getRangeLabel(range, dailyData), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), IconButton( onPressed: offset < 0 ? () => ref.read(chartOffsetProvider.notifier).state++ : null, // Disable jika sudah di minggu/bulan ini icon: Icon( Icons.chevron_right, color: offset < 0 ? AppTheme.textSecondary : Colors.grey.shade300 ), padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), ], ), const SizedBox(height: 30), // Beri jarak extra untuk angka di atas bar SizedBox(height: 220, child: chart), ], ), ); } }