import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:get/get.dart'; import 'package:praresi/presentation/routes/app_routes.dart'; import 'package:praresi/utils/pdf_export_daily_a4.dart'; import 'package:praresi/utils/pdf_export_daily_a4_multi.dart'; import 'package:praresi/utils/pdf_export_daily_a5.dart'; import 'package:praresi/utils/pdf_export_daily_a5_multi.dart'; import 'package:praresi/utils/pdf_export_daily_a6.dart'; import '../controllers/riwayat_controller.dart'; class RiwayatView extends StatefulWidget { const RiwayatView({super.key}); @override State createState() => _RiwayatViewState(); } class _RiwayatViewState extends State { DateTime? startDate; DateTime? endDate; final riwayatC = Get.put(RiwayatController(), permanent: true); @override void initState() { super.initState(); final now = DateTime.now(); startDate = DateTime(now.year, now.month, 1); endDate = DateTime(now.year, now.month + 1, 0, 23, 59, 59); riwayatC.fetchResiData(); // 🔹 ambil data default (1 bulan terakhir) } Future _selectDate(bool isStart) async { final now = DateTime.now(); final picked = await showDatePicker( context: context, initialDate: isStart ? (startDate ?? DateTime(now.year, now.month, 1)) : (endDate ?? DateTime(now.year, now.month + 1, 0)), firstDate: DateTime(2023), lastDate: DateTime(2100), builder: (context, child) => Theme( data: Theme.of(context).copyWith( colorScheme: const ColorScheme.light( primary: Color(0xFF1976D2), onPrimary: Colors.white, onSurface: Colors.black, ), ), child: child!, ), ); if (picked != null) { setState(() { if (isStart) { startDate = DateTime(picked.year, picked.month, picked.day); } else { // ⏰ tambahkan jam agar mencakup full hari terakhir endDate = DateTime(picked.year, picked.month, picked.day, 23, 59, 59); } }); // 🔹 Kalau dua-duanya sudah terisi, ambil data if (startDate != null && endDate != null) { riwayatC.fetchResiData(start: startDate, end: endDate); } } } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; return Scaffold( body: Container( width: size.width, height: size.height, decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFF1976D2), Color(0xFFE3F2FD)], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), child: SafeArea( child: Column( children: [ // 🔹 Card filter tanggal Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Expanded( child: _dateInputCard( label: "Mulai dari", date: startDate, onTap: () => _selectDate(true), ), ), const SizedBox(width: 12), Expanded( child: _dateInputCard( label: "Hingga", date: endDate, onTap: () => _selectDate(false), ), ), ], ), ), // 🔹 Garis pembatas putih dari kiri ke kanan const Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Divider( color: Colors.white, // warna garis thickness: 1, // ketebalan garis ), ), // 🔹 Daftar data dari Firestore Expanded( child: Obx(() { if (riwayatC.isLoading.value) { return const Center( child: CircularProgressIndicator(color: Color(0xFF1976D2)), ); } if (riwayatC.dailySummary.isEmpty) { return const Center( child: Text( "Belum ada data resi", style: TextStyle(color: Colors.black54, fontSize: 16), ), ); } return SingleChildScrollView( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 10), child: Column( children: riwayatC.dailySummary.map((data) { return Padding( padding: const EdgeInsets.only(bottom: 15), child: _dailySummaryCard( day: data['day'], date: data['date'], total: data['total'], cod: data['cod'], nonCod: data['nonCod'], income: data['income'], ), ); }).toList(), ), ); }), ), ], ), ), ), ); } // 🔹 Widget input tanggal Widget _dateInputCard({ required String label, required DateTime? date, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: Colors.grey.shade300), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle( fontSize: 12, color: Colors.grey, fontWeight: FontWeight.w500, )), const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( date == null ? "Pilih tanggal" : DateFormat('dd MMM yyyy', 'id_ID').format(date), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), const Icon(Icons.calendar_today, color: Color(0xFF1976D2), size: 18), ], ), ], ), ), ); } // 🔹 Widget tampilan per hari (sesuai desain kamu) Widget _dailySummaryCard({ required String day, required String date, required int total, required int cod, required int nonCod, required int income, }) { return GestureDetector( onTap: () async { final controller = Get.find(); try { // ubah format tanggal "21 Oktober 2025" ke DateTime final rawDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date); // tampilkan snackbar loading sementara Get.snackbar( 'Memuat', 'Mengambil data detail...', showProgressIndicator: true, isDismissible: false, snackPosition: SnackPosition.BOTTOM, ); // ambil data dulu await controller.fetchResiByDate(rawDate); // tutup snackbar Get.closeAllSnackbars(); // navigasi ke halaman detail (pakai named route) Get.toNamed( AppRoutes.detailRiwayat, arguments: { 'day': day, 'date': date, }, ); } catch (e) { Get.closeAllSnackbars(); Get.snackbar('Error', 'Gagal membuka detail: $e'); } }, child: Container( width: double.infinity, padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 8, offset: const Offset(0, 5), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 🔹 Baris header: hari & tanggal + ikon print di kanan atas Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "$day, $date", style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, ), ), IconButton( icon: const Icon(Icons.download, color: Color(0xFF1976D2)), tooltip: "Unduh data hari ini", onPressed: () { _showPrintFormatDialog(context, day, date, riwayatC); }, ), ], ), const Divider(), const SizedBox(height: 5), // 🔸 Total paket dan COD/NON-COD Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( flex: 2, child: Container( height: 90, margin: const EdgeInsets.only(right: 10), decoration: BoxDecoration( border: Border.all(color: const Color(0xFF1976D2), width: 2), borderRadius: BorderRadius.circular(14), ), child: Center( child: Text( "Total \n$total", textAlign: TextAlign.center, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1976D2), ), ), ), ), ), Expanded( flex: 2, child: Column( children: [ // 🔹 Card COD InkWell( onTap: () async { final rawDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date); final pendapatan = await riwayatC.getPendapatanHarian(rawDate); final totalCod = pendapatan['cod'] ?? 0.0; Get.defaultDialog( title: "Pendapatan COD", titleStyle: const TextStyle( color: Color(0xFFFF9800), fontWeight: FontWeight.bold, fontSize: 18, ), contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 16), radius: 14, content: Column( children: [ Icon(Icons.local_shipping, color: const Color(0xFFFF9800), size: 48), const SizedBox(height: 12), Text( "Tanggal: ${DateFormat('d MMMM yyyy', 'id_ID').format(rawDate)}", style: const TextStyle(fontSize: 14), ), const SizedBox(height: 8), Text( "Rp ${NumberFormat('#,###', 'id_ID').format(totalCod)}", style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xFFFF9800), ), ), ], ), confirm: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFF9800), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 10), ), onPressed: () => Get.back(), child: const Text( "Tutup", style: TextStyle(color: Colors.white), ), ), ); }, child: Container( height: 40, margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( border: Border.all(color: const Color(0xFFFF9800), width: 2), borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( "COD: $cod", style: const TextStyle( color: Color(0xFFFF9800), fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ), ), // 🔹 Card Non COD InkWell( onTap: () async { final rawDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date); final pendapatan = await riwayatC.getPendapatanHarian(rawDate); final totalNonCod = pendapatan['nonCod'] ?? 0.0; Get.defaultDialog( title: "Pendapatan Non COD", titleStyle: const TextStyle( color: Color(0xFF4CAF50), fontWeight: FontWeight.bold, fontSize: 18, ), contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 16), radius: 14, content: Column( children: [ Icon(Icons.account_balance_wallet, color: const Color(0xFF4CAF50), size: 48), const SizedBox(height: 12), Text( "Tanggal: ${DateFormat('d MMMM yyyy', 'id_ID').format(rawDate)}", style: const TextStyle(fontSize: 14), ), const SizedBox(height: 8), Text( "Rp ${NumberFormat('#,###', 'id_ID').format(totalNonCod)}", style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xFF4CAF50), ), ), ], ), confirm: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF4CAF50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 10), ), onPressed: () => Get.back(), child: const Text( "Tutup", style: TextStyle(color: Colors.white), ), ), ); }, child: Container( height: 40, decoration: BoxDecoration( border: Border.all(color: const Color(0xFF4CAF50), width: 2), borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( "Non COD: $nonCod", style: const TextStyle( color: Color(0xFF4CAF50), fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ), ), ], ), ), ], ), const SizedBox(height: 10), // 🔸 Pendapatan Container( width: double.infinity, height: 40, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade400, width: 1.2), borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( "Rp ${NumberFormat('#,###', 'id_ID').format(income)}", style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), ), ], ), ), ); } void _showPrintFormatDialog(BuildContext context, String day, String date, dynamic controller,) { Get.bottomSheet( Container( padding: const EdgeInsets.all(20), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Wrap( children: [ const Center( child: Text( "Pilih Format Unduh", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 20), ListTile( leading: const Icon(Icons.picture_as_pdf, color: Colors.blue), title: const Text("Format A4"), onTap: () async { Get.back(); try { // 🔹 Parse tanggal final parsedDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date); // 🔹 Ambil data harian await controller.fetchResiByDate(parsedDate); // 🔹 Export PDF (1 halaman = 1 data) final path = await PdfExportDailyA4_onedata.exportDailyToA4_onedata( controller.dailyDetail, parsedDate, ); Get.snackbar( "Berhasil", "PDF disimpan di: $path", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green.shade100, ); } catch (e) { Get.snackbar( "Error", "Gagal mencetak data: $e", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red.shade100, ); } }, ), ListTile( leading: const Icon(Icons.picture_as_pdf, color: Colors.blue), title: const Text("Format A5"), onTap: () async { Get.back(); try { // 🔹 Parse tanggal "25 Oktober 2025" ke DateTime final parsedDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date); // 🔹 Ambil data harian await controller.fetchResiByDate(parsedDate); // 🔹 Cetak ke PDF format A4 final path = await PdfExportDailyA5_onedata.exportDailyToA5_onedata( controller.dailyDetail, parsedDate, ); Get.snackbar( "Berhasil", "PDF disimpan di: $path", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green.shade100, ); } catch (e) { Get.snackbar( "Error", "Gagal mencetak data: $e", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red.shade100, ); } }, ), ListTile( leading: const Icon(Icons.picture_as_pdf, color: Colors.blue), title: const Text("Format A6"), onTap: () async { Get.back(); try { // 🔹 Parse tanggal "25 Oktober 2025" ke DateTime final parsedDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date); // 🔹 Ambil data harian await controller.fetchResiByDate(parsedDate); // 🔹 Cetak ke PDF format A4 final path = await PdfExportDailyA6.exportDailyToA6( controller.dailyDetail, parsedDate, ); Get.snackbar( "Berhasil", "PDF disimpan di: $path", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green.shade100, ); } catch (e) { Get.snackbar( "Error", "Gagal mengunduh data: $e", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red.shade100, ); } }, ), ListTile( leading: const Icon(Icons.picture_as_pdf, color: Colors.blue), title: const Text("Format A4 (Multi)"), onTap: () async { Get.back(); try { // 🔹 Parse tanggal "25 Oktober 2025" ke DateTime final parsedDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date); // 🔹 Ambil data harian await controller.fetchResiByDate(parsedDate); // 🔹 Cetak ke PDF format A4 final path = await PdfExportDailyA4.exportDailyToA4( controller.dailyDetail, parsedDate, ); Get.snackbar( "Berhasil", "PDF disimpan di: $path", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green.shade100, ); } catch (e) { Get.snackbar( "Error", "Gagal mencetak data: $e", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red.shade100, ); } }, ), ListTile( leading: const Icon(Icons.picture_as_pdf, color: Colors.blue), title: const Text("Format A5 (Multi)"), onTap: () async { Get.back(); try { // 🔹 Parse tanggal "25 Oktober 2025" ke DateTime final parsedDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date); // 🔹 Ambil data harian await controller.fetchResiByDate(parsedDate); // 🔹 Cetak ke PDF format A4 final path = await PdfExportDailyA5.exportDailyToA5( controller.dailyDetail, parsedDate, ); Get.snackbar( "Berhasil", "PDF disimpan di: $path", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green.shade100, ); } catch (e) { Get.snackbar( "Error", "Gagal mencetak data: $e", snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red.shade100, ); } }, ), ], ), ), ); } }