TKK_E32230206/lib/features/iot/widgets/monitoring_chart.dart

209 lines
7.4 KiB
Dart

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<DailyAverage> 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) {
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<double>()
.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),
],
),
);
}
}