import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:typed_data'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:pdf/pdf.dart'; class PendapatanPage extends StatefulWidget { final String token; const PendapatanPage({super.key, required this.token}); @override State createState() => _PendapatanPageState(); } class _PendapatanPageState extends State { List bookings = []; bool isLoading = false; final Color primaryColor = const Color(0xFFF06292); final Color backgroundColor = const Color(0xFFFFF6F9); @override void initState() { super.initState(); fetchBookings(); } Future fetchBookings() async { setState(() => isLoading = true); try { final response = await http.get( Uri.parse('http://angeliasalon.my.id/api/bookings'), headers: { 'Authorization': 'Bearer ${widget.token}', 'Accept': 'application/json', }, ); if (response.statusCode == 200) { final data = json.decode(response.body); setState(() { bookings = data; isLoading = false; }); } else { throw Exception('Gagal memuat data booking'); } } catch (e) { setState(() => isLoading = false); _showErrorSnackBar('Error: $e'); } } void _showErrorSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: Colors.red.shade600), ); } Map> groupBookingsByMonth() { Map> result = {}; for (var booking in bookings) { final tanggal = booking['tanggal_booking'] ?? '0000-00-00'; final bulan = tanggal.substring(0, 7); // yyyy-MM result.putIfAbsent(bulan, () => []); result[bulan]!.add(booking); } return result; } int getTotalPendapatanPerMonth(List bookingsInMonth) { int total = 0; for (var booking in bookingsInMonth) { total += (booking['service']?['price'] ?? 0) as int; } return total; } String formatRupiah(int angka) { return 'Rp ${angka.toString().replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (m) => '${m[1]}.', )}'; } String getNamaBulan(String bulanAngka) { const namaBulan = { '01': 'Januari', '02': 'Februari', '03': 'Maret', '04': 'April', '05': 'Mei', '06': 'Juni', '07': 'Juli', '08': 'Agustus', '09': 'September', '10': 'Oktober', '11': 'November', '12': 'Desember', }; try { final parts = bulanAngka.split('-'); // yyyy-MM if (parts.length == 2) { final tahun = parts[0]; final bulan = namaBulan[parts[1]] ?? parts[1]; return '$bulan $tahun'; } return bulanAngka; } catch (_) { return bulanAngka; } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: backgroundColor, appBar: AppBar( title: const Text('Pendapatan'), backgroundColor: primaryColor, foregroundColor: Colors.white, ), body: isLoading ? const Center(child: CircularProgressIndicator()) : bookings.isEmpty ? const Center(child: Text('Tidak ada data booking')) : RefreshIndicator( onRefresh: fetchBookings, child: ListView( padding: const EdgeInsets.all(12), children: [ ...groupBookingsByMonth().entries.map((entry) { final bulan = entry.key; final bookingsInMonth = entry.value; final total = getTotalPendapatanPerMonth(bookingsInMonth); return Card( margin: const EdgeInsets.symmetric(vertical: 8), elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: ListTile( title: Text( 'Bulan: ${getNamaBulan(bulan)}', style: TextStyle( fontWeight: FontWeight.bold, color: primaryColor, ), ), subtitle: Text('Total: ${formatRupiah(total)}'), trailing: ElevatedButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (_) => DetailPendapatanPage( bulan: bulan, bookingsInMonth: bookingsInMonth, primaryColor: primaryColor, ), ), ); }, style: ElevatedButton.styleFrom( backgroundColor: primaryColor, foregroundColor: Colors.white, ), child: const Text('Detail'), ), ), ); }), ], ), ), ); } } class DetailPendapatanPage extends StatelessWidget { final String bulan; final List bookingsInMonth; final Color primaryColor; const DetailPendapatanPage({ super.key, required this.bulan, required this.bookingsInMonth, required this.primaryColor, }); String formatRupiah(int angka) { return 'Rp ${angka.toString().replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (m) => '${m[1]}.', )}'; } String formatTanggal(String tanggal) { try { final parts = tanggal.split('-'); if (parts.length == 3) { return '${parts[2]}-${parts[1]}-${parts[0]}'; } return tanggal; } catch (_) { return tanggal; } } String getNamaBulan(String bulanAngka) { const namaBulan = { '01': 'Januari', '02': 'Februari', '03': 'Maret', '04': 'April', '05': 'Mei', '06': 'Juni', '07': 'Juli', '08': 'Agustus', '09': 'September', '10': 'Oktober', '11': 'November', '12': 'Desember', }; try { final parts = bulanAngka.split('-'); // yyyy-MM if (parts.length == 2) { final tahun = parts[0]; final bulan = namaBulan[parts[1]] ?? parts[1]; return '$bulan $tahun'; } return bulanAngka; } catch (_) { return bulanAngka; } } Future generatePdf(BuildContext context) async { final pdf = pw.Document(); final groupedByDate = >{}; for (var booking in bookingsInMonth) { final tanggal = booking['tanggal_booking'] ?? 'Tidak diketahui'; groupedByDate.putIfAbsent(tanggal, () => []); groupedByDate[tanggal]!.add(booking); } int totalBulan = 0; pdf.addPage( pw.MultiPage( pageFormat: PdfPageFormat.a4, build: (context) { List widgets = [ pw.Text( 'Laporan Pendapatan Bulan ${getNamaBulan(bulan)}', style: pw.TextStyle(fontSize: 18, fontWeight: pw.FontWeight.bold), ), pw.SizedBox(height: 10), ]; for (var entry in groupedByDate.entries) { final tanggal = entry.key; final layananMap = >{}; for (var booking in entry.value) { final nama = booking['service']?['name'] ?? 'Tidak diketahui'; layananMap.putIfAbsent(nama, () => []); layananMap[nama]!.add(booking); } int totalTanggal = 0; layananMap.forEach((_, list) { final price = list[0]['service']?['price'] ?? 0; totalTanggal += (price as int) * list.length; }); totalBulan += totalTanggal; widgets.addAll([ pw.Text( formatTanggal(tanggal), style: pw.TextStyle(fontWeight: pw.FontWeight.bold), ), pw.SizedBox(height: 4), ...layananMap.entries.map((e) { final nama = e.key; final list = e.value; final harga = list[0]['service']?['price'] ?? 0; return pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('$nama (${list.length}x)'), pw.Text(formatRupiah((harga as int) * list.length)), ], ); }), pw.SizedBox(height: 6), pw.Container( alignment: pw.Alignment.centerRight, child: pw.Text( 'Total: ${formatRupiah(totalTanggal)}', style: pw.TextStyle(fontWeight: pw.FontWeight.bold), ), ), pw.SizedBox(height: 10), pw.Divider(), ]); } // Tambahkan total keseluruhan di akhir widgets.addAll([ pw.SizedBox(height: 16), pw.Divider(), pw.Container( alignment: pw.Alignment.centerRight, child: pw.Text( 'Total Pendapatan Bulan Ini: ${formatRupiah(totalBulan)}', style: pw.TextStyle(fontSize: 16, fontWeight: pw.FontWeight.bold), ), ), ]); return widgets; }, ), ); return pdf.save(); } @override Widget build(BuildContext context) { final groupedByDate = >{}; for (var booking in bookingsInMonth) { final tanggal = booking['tanggal_booking'] ?? 'Tidak diketahui'; groupedByDate.putIfAbsent(tanggal, () => []); groupedByDate[tanggal]!.add(booking); } return Scaffold( appBar: AppBar( title: Text('Detail Bulan ${getNamaBulan(bulan)}'), backgroundColor: primaryColor, foregroundColor: Colors.white, actions: [ IconButton( icon: const Icon(Icons.picture_as_pdf), onPressed: () async { final pdfData = await generatePdf(context); await Printing.layoutPdf(onLayout: (format) async => pdfData); }, ), ], ), body: ListView( padding: const EdgeInsets.all(16), children: groupedByDate.entries.map((entry) { final tanggal = entry.key; final layananMap = >{}; for (var booking in entry.value) { final nama = booking['service']?['name'] ?? 'Tidak diketahui'; layananMap.putIfAbsent(nama, () => []); layananMap[nama]!.add(booking); } int totalTanggal = 0; layananMap.forEach((_, list) { final price = list[0]['service']?['price'] ?? 0; totalTanggal += (price as int) * list.length; }); return Card( margin: const EdgeInsets.only(bottom: 16), elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( formatTanggal(tanggal), style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: primaryColor, ), ), const SizedBox(height: 8), ...layananMap.entries.map((e) { final nama = e.key; final list = e.value; final harga = list[0]['service']?['price'] ?? 0; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('$nama (${list.length}x)'), Text(formatRupiah((harga as int) * list.length)), ], ); }), const SizedBox(height: 6), Align( alignment: Alignment.centerRight, child: Text( 'Total: ${formatRupiah(totalTanggal)}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ], ), ), ); }).toList(), ), ); } }