import 'dart:async'; import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:intl/intl.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'dashboard.dart'; import 'akun.dart'; import '../widgets/fade_in_up.dart'; class HistoryModel { final String key; final String time; final String date; final double suhu; final double alkohol; final String status; final String jenisTape; HistoryModel({ required this.key, required this.time, required this.date, required this.suhu, required this.alkohol, required this.status, required this.jenisTape, }); factory HistoryModel.fromMap(String key, Map data) { // Ambil waktu sekarang sebagai cadangan (fallback) DateTime sekarang = DateTime.now(); String jamSkrg = "${sekarang.hour.toString().padLeft(2, '0')}:${sekarang.minute.toString().padLeft(2, '0')}"; String tglSkrg = "${sekarang.day.toString().padLeft(2, '0')}/${sekarang.month.toString().padLeft(2, '0')}/${sekarang.year}"; // Logika Parsing Tanggal dari Firebase String dateFromDb = data['tanggal']?.toString() ?? tglSkrg; // Jika format dari DB adalah YYYY-MM-DD, ubah ke DD/MM/YYYY agar filter kalender jalan if (dateFromDb.contains("-")) { try { List p = dateFromDb.split("-"); if (p[0].length == 4) { // YYYY-MM-DD dateFromDb = "${p[2]}/${p[1]}/${p[0]}"; } } catch (e) { dateFromDb = tglSkrg; } } String timeStr = data['jam']?.toString() ?? jamSkrg; // Ubah format desimal jam (misal "27.18 Jam") menjadi "HH:MM:SS" if (timeStr.toLowerCase().contains("jam")) { String numStr = timeStr.replaceAll(RegExp(r'[^0-9.]'), ''); double? val = double.tryParse(numStr); if (val != null) { int totalDetik = (val * 3600).toInt(); int j = totalDetik ~/ 3600; int m = (totalDetik % 3600) ~/ 60; int s = totalDetik % 60; timeStr = "${j.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}"; } } String statusStr = data['status']?.toString() ?? "Done"; String lowerStatus = statusStr.toLowerCase(); if (lowerStatus.contains("optimal") || lowerStatus.contains("awal") || lowerStatus.contains("menjelang matang")) { statusStr = "Belum Matang"; } return HistoryModel( key: key, time: timeStr, date: dateFromDb, suhu: (data['suhu'] as num?)?.toDouble() ?? 0.0, alkohol: (data['alkohol'] as num?)?.toDouble() ?? 0.0, status: statusStr, jenisTape: data['jenisTape'] != null ? (data['jenisTape'].toString().contains("Singkong") ? "Tape Singkong" : "Tape Ketan") : "Tape Ketan", ); } } class HistoryPage extends StatefulWidget { const HistoryPage({super.key}); @override State createState() => _HistoryPageState(); } class _HistoryPageState extends State { final DatabaseReference ref = FirebaseDatabase.instance.ref("history"); String selectedCategory = "All"; DateTime activeDate = DateTime.now(); bool _localeReady = false; @override void initState() { super.initState(); initializeDateFormatting('id', null).then((_) { if (mounted) setState(() => _localeReady = true); }); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { if (!_localeReady) return const Scaffold(body: Center(child: CircularProgressIndicator())); return Scaffold( extendBody: true, body: Stack( children: [ Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, stops: [0.0, 0.4, 0.7, 1.0], colors: [ Color(0xFFE8F5E9), Color(0xFFFFFDF0), Color(0xFFF1F8E9), Color(0xFFE8F5E9), ], ), ), ), Column( children: [ FadeInUp(delay: 0, child: _buildAppBar()), FadeInUp(delay: 150, child: _buildHorizontalCalendar()), FadeInUp(delay: 300, child: _buildCategoryFilter()), Expanded(child: FadeInUp(delay: 450, child: _buildHistoryList())), ], ), ], ), bottomNavigationBar: _buildBottomNavbar(), ); } Widget _buildBottomNavbar() { return Container( decoration: const BoxDecoration( color: Color(0xFFE8F5E9), borderRadius: BorderRadius.vertical(top: Radius.circular(30)), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 10, offset: Offset(0, -2), ), ], ), child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), child: BottomNavigationBar( currentIndex: 1, onTap: (index) { if (index == 0) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const DashboardPage()), ); } else if (index == 2) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const AkunScreen()), ); } }, showSelectedLabels: false, showUnselectedLabels: false, backgroundColor: Colors.transparent, elevation: 0, type: BottomNavigationBarType.fixed, items: [ BottomNavigationBarItem( icon: _navIcon(Icons.home_filled, 0), label: 'Home', ), BottomNavigationBarItem( icon: _navIcon(Icons.history, 1), label: 'History', ), BottomNavigationBarItem( icon: _navIcon(Icons.people_alt_rounded, 2), label: 'Profile', ), ], ), ), ); } Widget _navIcon(IconData icon, int index) { bool isSelected = index == 1; return AnimatedContainer( duration: const Duration(milliseconds: 300), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), gradient: isSelected ? const LinearGradient( colors: [Color(0xFF2E7D32), Color(0xFF4CAF50)], ) : null, ), child: Icon( icon, color: isSelected ? Colors.white : const Color(0xFF81C784), size: 24, ), ); } Widget _buildAppBar() { return SafeArea( bottom: false, child: Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 10), // Padding seperti di pesan.dart child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.arrow_back_ios_new, size: 20), onPressed: () => Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const DashboardPage()), ), ), const Expanded( child: Text( "Fermentation History", textAlign: TextAlign.center, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), IconButton( icon: const Icon(Icons.calendar_month, color: Color(0xFF2E7D32)), onPressed: () async { DateTime? picked = await showDatePicker( context: context, initialDate: activeDate, firstDate: DateTime(2020), lastDate: DateTime(2100), ); if (picked != null && picked != activeDate) { setState(() { activeDate = picked; }); } }, ), ], ), ), ); } Widget _buildHorizontalCalendar() { return Container( height: 90, margin: const EdgeInsets.symmetric(vertical: 15), // Jarak seperti di pesan.dart child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 15), itemCount: 7, itemBuilder: (context, index) { DateTime date = activeDate.add(Duration(days: index - 3)); bool isSelected = date.day == activeDate.day && date.month == activeDate.month; return GestureDetector( onTap: () => setState(() => activeDate = date), child: AnimatedContainer( duration: const Duration(milliseconds: 300), width: 70, margin: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: isSelected ? const Color(0xFF2E7D32) : Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: isSelected ? [ BoxShadow( color: const Color(0xFF2E7D32).withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 5), ), ] : [], border: isSelected ? null : Border.all(color: Colors.grey.withOpacity(0.1)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( DateFormat('MMM', 'id').format(date), style: TextStyle( color: isSelected ? Colors.white : Colors.black54, fontSize: 12, ), ), Text( date.day.toString(), style: TextStyle( color: isSelected ? Colors.white : Colors.black, fontSize: 20, fontWeight: FontWeight.bold, ), ), Text( DateFormat('E', 'id').format(date), style: TextStyle( color: isSelected ? Colors.white : Colors.black54, fontSize: 12, ), ), ], ), ), ); }, ), ); } Widget _buildCategoryFilter() { return Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( children: [ _filterTab("All"), _filterTab("Tape Ketan"), _filterTab("Tape Singkong"), ], ), ), ); } Widget _filterTab(String label) { bool isSelected = selectedCategory == label; return GestureDetector( onTap: () => setState(() => selectedCategory = label), child: Container( margin: const EdgeInsets.only(right: 12), padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 12), decoration: BoxDecoration( color: isSelected ? const Color(0xFF2E7D32) : const Color(0xFFE8F5E9), borderRadius: BorderRadius.circular(15), ), child: Text( label, style: TextStyle( color: isSelected ? Colors.white : const Color(0xFF2E7D32), fontWeight: FontWeight.bold, ), ), ), ); } Widget _buildHistoryList() { return StreamBuilder( stream: ref.onValue, builder: (context, AsyncSnapshot snapshot) { if (!snapshot.hasData || snapshot.data!.snapshot.value == null) return const Center(child: Text("No history available")); Map data = snapshot.data!.snapshot.value as Map; List list = []; // Format pembanding sesuai dengan format di model (dd/MM/yyyy) String filterDateString = DateFormat('dd/MM/yyyy').format(activeDate); data.forEach((key, value) { final item = HistoryModel.fromMap(key, value); // Memastikan pencocokan tanggal akurat if (item.date == filterDateString && (selectedCategory == "All" || item.jenisTape == selectedCategory)) { list.add(item); } }); // Urutkan dari yang terbaru (berdasarkan key Firebase atau jam) list.sort((a, b) => b.time.compareTo(a.time)); if (list.isEmpty) return const Center(child: Text("No data for this date")); return ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 20), itemCount: list.length, itemBuilder: (context, index) => _buildHistoryCard(list[index]), ); }, ); } Widget _buildHistoryCard(HistoryModel item) { bool isAlert = item.status.toLowerCase().contains("matang") && !item.status.toLowerCase().contains("belum"); return Container( margin: const EdgeInsets.only(bottom: 15), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 10, offset: const Offset(0, 5), ), ], border: Border.all(color: Colors.grey.withOpacity(0.05)), ), child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.jenisTape, style: const TextStyle(color: Colors.black45, fontSize: 13), ), Text( "Suhu: ${item.suhu}°C", style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( "Alkohol: ${item.jenisTape.contains('Singkong') ? item.alkohol.toStringAsFixed(1) : item.alkohol.toStringAsFixed(2)}%", style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 10), Row( children: [ const Icon( Icons.access_time_filled, size: 16, color: Color(0xFF2E7D32), ), const SizedBox(width: 5), Text( item.time, style: const TextStyle( color: Color(0xFF2E7D32), fontWeight: FontWeight.w600, ), ), const SizedBox(width: 15), const Icon( Icons.calendar_month, size: 16, color: Color(0xFF2E7D32), ), const SizedBox(width: 5), Text( item.date, style: const TextStyle( color: Color(0xFF2E7D32), fontWeight: FontWeight.w600, ), ), ], ), ], ), Positioned( right: 0, bottom: 0, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: const Color(0xFFE8F5E9), borderRadius: BorderRadius.circular(10), ), child: Text( item.status, style: TextStyle( color: isAlert ? Colors.orange : const Color(0xFF2E7D32), fontSize: 11, fontWeight: FontWeight.bold, ), ), ), ), ], ), ); } }