209 lines
7.4 KiB
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),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|