import 'package:flutter/material.dart'; import 'models/sensor_reading.dart'; import 'package:green/services/api_service.dart'; import 'package:intl/intl.dart'; import 'package:excel/excel.dart'; import 'package:file_saver/file_saver.dart'; import 'dart:typed_data';// Kalau belum ada import 'package:flutter/foundation.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:pdf/pdf.dart'; import 'package:printing/printing.dart'; import 'dart:typed_data'; import 'package:intl/intl.dart'; class HistoryScreen extends StatefulWidget { @override _HistoryScreenState createState() => _HistoryScreenState(); } class _HistoryScreenState extends State { final ApiService _apiService = ApiService(); late Future _futureData; DateTime _selectedDate = DateTime.now(); bool _isLoading = false; double _rataRataPPM = 0.0; int _jumlahData = 0; DailySensorData? _lastFetchedData; @override void initState() { super.initState(); _loadTodayData(); } Future _loadTodayData() async { setState(() { _selectedDate = DateTime.now(); _isLoading = true; }); try { _futureData = _apiService.getTodayData(); final data = await _futureData; if (mounted) { double totalPPM = 0; int count = 0; for (var reading in data.data) { if (reading.ppm != null) { totalPPM += reading.ppm!.toDouble(); count++; } } setState(() { _jumlahData = data.data.length; _rataRataPPM = count == 0 ? 0.0 : totalPPM / count; _lastFetchedData = data; }); } } finally { if (mounted) { setState(() => _isLoading = false); } } } Future _loadDataByDate(DateTime date) async { setState(() { _selectedDate = date; _isLoading = true; }); try { _futureData = _apiService.getDataByDate(date); final data = await _futureData; if (mounted) { double totalPPM = 0; int count = 0; for (var reading in data.data) { if (reading.ppm != null) { totalPPM += reading.ppm!.toDouble(); count++; } } setState(() { _jumlahData = data.data.length; _rataRataPPM = count == 0 ? 0.0 : totalPPM / count; _lastFetchedData = data; }); } } catch (e) { print('Error loading data: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to load data: ${e.toString()}')), ); } rethrow; } finally { if (mounted) { setState(() => _isLoading = false); } } } Future _exportDailyHistoryToPdf(DailySensorData data) async { final pdf = pw.Document(); final formattedDate = DateFormat('dd MMMM yyyy').format(_selectedDate); pdf.addPage( pw.MultiPage( build: (context) => [ pw.Text('Riwayat Sensor - $formattedDate', style: pw.TextStyle(fontSize: 18, fontWeight: pw.FontWeight.bold)), pw.SizedBox(height: 16), pw.Table.fromTextArray( headers: ['Waktu', 'Suhu (°C)', 'PPM', 'Jarak Air (cm)'], data: data.data.map((reading) { return [ DateFormat('HH:mm:ss').format(reading.createdAt), reading.temperature.toString(), reading.ppm.toString(), reading.waterLevel.toString(), ]; }).toList(), headerStyle: pw.TextStyle( fontWeight: pw.FontWeight.bold, color: PdfColors.white, ), headerDecoration: pw.BoxDecoration(color: PdfColors.blue), cellAlignment: pw.Alignment.centerLeft, cellPadding: const pw.EdgeInsets.symmetric(vertical: 6, horizontal: 4), ), ], ), ); final pdfBytes = await pdf.save(); await Printing.sharePdf( bytes: pdfBytes, filename: 'Riwayat_Sensor_${DateFormat('yyyyMMdd').format(_selectedDate)}.pdf', ); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('PDF berhasil diunduh')), ); } Future _selectDate(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: _selectedDate, firstDate: DateTime(2020), lastDate: DateTime.now(), ); if (picked != null && picked != _selectedDate) { _loadDataByDate(picked); } } Future _exportDailyHistoryToExcel(DailySensorData data) async { print('Jumlah data yang akan diexport: ${data.data.length}'); final excel = Excel.createExcel(); final sheet = excel['HistorySensor']; // Style untuk header final headerStyle = CellStyle( bold: true, fontColorHex: '#FFFFFF', backgroundColorHex: '#2196F3', horizontalAlign: HorizontalAlign.Center, ); // Style untuk baris genap (zebra) final zebraStyle = CellStyle( backgroundColorHex: '#F2F2F2', // abu terang horizontalAlign: HorizontalAlign.Left, ); // Header row final headers = ['Waktu', 'Suhu (°C)', 'PPM', 'Jarak Air (cm)']; sheet.appendRow(headers); // Terapkan style ke header for (int col = 0; col < headers.length; col++) { final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: 0)); cell.cellStyle = headerStyle; } // Data rows for (int i = 0; i < data.data.length; i++) { final reading = data.data[i]; final rowIndex = i + 1; // karena header di rowIndex = 0 // Tambah data sheet.appendRow([ DateFormat('HH:mm:ss').format(reading.createdAt), reading.temperature, reading.ppm, reading.waterLevel, ]); // Terapkan zebra style ke baris genap if (i % 2 == 0) { for (int col = 0; col < 4; col++) { final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: rowIndex)); cell.cellStyle = zebraStyle; } } } final excelBytes = excel.encode(); if (excelBytes == null) return; await FileSaver.instance.saveFile( name: 'Riwayat_Sensor_${DateFormat('yyyyMMdd').format(_selectedDate)}', bytes: Uint8List.fromList(excelBytes), ext: 'xlsx', ); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Berhasil mengunduh file Excel')), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('History Sensor'), actions: [ IconButton( icon: Icon(Icons.calendar_today), onPressed: () => _selectDate(context), tooltip: 'Pilih Tanggal', ), IconButton( icon: Icon(Icons.download), tooltip: 'Export Data', onPressed: () { if (_lastFetchedData == null) return; if (kIsWeb) { _exportDailyHistoryToExcel(_lastFetchedData!); } else { _exportDailyHistoryToPdf(_lastFetchedData!); } }, ), ], ), body: Column( children: [ _buildDateHeader(), if (_isLoading) LinearProgressIndicator() else SizedBox(height: 2), Expanded( child: _buildDataContent(), ), ], ), floatingActionButton: FloatingActionButton( onPressed: _loadTodayData, tooltip: 'Refresh Data Hari Ini', child: Icon(Icons.refresh), ), ); } Widget _buildDateHeader() { return Container( padding: EdgeInsets.all(16), color: Colors.blue[50], child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Rata-Rata PPM (Parts Per Millions):', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), Text( _rataRataPPM.toStringAsFixed(2), // tampilkan dua angka desimal style: TextStyle( fontSize: 19, color: Colors.blue[900], fontWeight: FontWeight.bold, ), ), // TextButton( // onPressed: () => _selectDate(context), // child: Text( // DateFormat('dd MMMM yyyy').format(_selectedDate), // style: TextStyle( // fontSize: 16, // color: Colors.blue[700], // fontWeight: FontWeight.bold, // ), // ), // ), ], ), ); } Widget _buildDataContent() { return FutureBuilder( future: _futureData, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting && !_isLoading) { return Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return _buildErrorWidget(snapshot.error.toString()); } else if (!snapshot.hasData || snapshot.data!.data.isEmpty) { return _buildEmptyWidget(); } return _buildDataList(snapshot.data!); }, ); } Widget _buildDataList(DailySensorData data) { return RefreshIndicator( onRefresh: () => _loadDataByDate(_selectedDate), child: ListView.builder( padding: EdgeInsets.symmetric(vertical: 8), itemCount: data.data.length, itemBuilder: (context, index) { final reading = data.data[index]; return _buildReadingCard(reading); }, ), ); } Widget _buildReadingCard(SensorReading reading) { return Card( margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Waktu: ${_formatTime(reading.createdAt)}', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.blue[700], ), ), Text( 'ID: ${reading.id}', style: TextStyle(color: Colors.grey), ), ], ), SizedBox(height: 12), _buildDataRow(Icons.thermostat, 'Suhu', '${reading.temperature}°C'), _buildDataRow(Icons.eco, 'PPM', '${reading.ppm}'), _buildDataRow(Icons.water_drop, 'Jarak Air', '${reading.waterLevel} cm'), ], ), ), ); } Widget _buildDataRow(IconData icon, String label, String value) { return Padding( padding: EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Icon(icon, size: 20, color: Colors.blue[600]), SizedBox(width: 12), Text('$label: ', style: TextStyle(fontWeight: FontWeight.bold)), Text(value), ], ), ); } Widget _buildErrorWidget(String error) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, color: Colors.red, size: 48), SizedBox(height: 16), Text( 'Gagal memuat data', style: TextStyle(fontSize: 18, color: Colors.red), ), SizedBox(height: 8), Padding( padding: EdgeInsets.symmetric(horizontal: 32), child: Text( error, textAlign: TextAlign.center, style: TextStyle(color: Colors.grey), ), ), SizedBox(height: 16), ElevatedButton( onPressed: () => _loadDataByDate(_selectedDate), child: Text('Coba Lagi'), ), ], ), ); } Widget _buildEmptyWidget() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.history, size: 48, color: Colors.grey), SizedBox(height: 16), Text( 'Tidak ada data untuk tanggal ini', style: TextStyle(fontSize: 16, color: Colors.grey), ), SizedBox(height: 8), TextButton( onPressed: () => _loadDataByDate(_selectedDate), child: Text('Refresh Data'), ), ], ), ); } String _formatTime(DateTime time) { return DateFormat('HH:mm:ss').format(time); } }