import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/sale.dart'; import '../services/export_service.dart'; import '../services/printer_service.dart'; import '../state/app_state.dart'; import '../utils/colors.dart'; import '../utils/format.dart'; import '../utils/rupiah_input.dart'; class RiwayatScreen extends StatefulWidget { const RiwayatScreen({super.key}); @override State createState() => _RiwayatScreenState(); } class _RiwayatScreenState extends State { DateTimeRange? _dateRange; String _paymentFilter = 'all'; final PageController _reportPageController = PageController(viewportFraction: 0.85); int _reportPage = 0; @override Widget build(BuildContext context) { final state = context.watch(); if (state.loading) { return const Center(child: CircularProgressIndicator(color: kAccent)); } final now = DateTime.now(); final isAdmin = state.isAdmin; List filteredSales; if (isAdmin) { if (_dateRange != null) { final start = DateTime(_dateRange!.start.year, _dateRange!.start.month, _dateRange!.start.day); final end = DateTime(_dateRange!.end.year, _dateRange!.end.month, _dateRange!.end.day + 1); filteredSales = state.sales .where((s) => s.date .isAfter(start.subtract(const Duration(seconds: 1))) && s.date.isBefore(end)) .toList(); } else { filteredSales = state.sales .where((s) => s.date.year == now.year && s.date.month == now.month) .toList(); } } else { filteredSales = state.sales .where((s) => s.date.year == now.year && s.date.month == now.month && s.date.day == now.day) .toList(); } if (_paymentFilter == 'unpaid') { filteredSales = filteredSales.where((s) => s.paymentStatus != 'paid' && !s.isCancelled).toList(); } else if (_paymentFilter == 'paid') { filteredSales = filteredSales.where((s) => s.paymentStatus == 'paid' && !s.isCancelled).toList(); } final displayedSales = filteredSales.take(state.visibleSalesCount).toList(); final currentMonth = DateTime(now.year, now.month); final previousMonth = DateTime(now.year, now.month - 1); final currentMonthSales = state.sales.where((s) { final saleMonth = DateTime(s.date.year, s.date.month); return saleMonth == currentMonth && !s.isCancelled; }).toList(); final previousMonthSales = state.sales.where((s) { final saleMonth = DateTime(s.date.year, s.date.month); return saleMonth == previousMonth && !s.isCancelled; }).toList(); final currentRevenue = currentMonthSales.fold(0.0, (sum, s) => sum + s.total); final previousRevenue = previousMonthSales.fold(0.0, (sum, s) => sum + s.total); final revenueChange = previousRevenue > 0 ? ((currentRevenue - previousRevenue) / previousRevenue) * 100 : (currentRevenue > 0 ? 100 : 0); final currentProfit = currentMonthSales.fold(0.0, (sum, s) => sum + (s.total - s.totalCost)); final previousProfit = previousMonthSales.fold(0.0, (sum, s) => sum + (s.total - s.totalCost)); final profitChange = previousProfit > 0 ? ((currentProfit - previousProfit) / previousProfit) * 100 : (currentProfit > 0 ? 100 : 0); final currentTransactions = currentMonthSales.length; final previousTransactions = previousMonthSales.length; final transactionChange = previousTransactions > 0 ? ((currentTransactions - previousTransactions) / previousTransactions) * 100 : (currentTransactions > 0 ? 100 : 0); final today = DateTime(now.year, now.month, now.day); final yesterday = DateTime(now.year, now.month, now.day - 1); final todaySales = state.sales.where((s) { final saleDay = DateTime(s.date.year, s.date.month, s.date.day); return saleDay == today && !s.isCancelled; }).toList(); final yesterdaySales = state.sales.where((s) { final saleDay = DateTime(s.date.year, s.date.month, s.date.day); return saleDay == yesterday && !s.isCancelled; }).toList(); final todayRevenue = todaySales.fold(0.0, (sum, s) => sum + s.total); final yesterdayRevenue = yesterdaySales.fold(0.0, (sum, s) => sum + s.total); final revenueDayChange = yesterdayRevenue > 0 ? ((todayRevenue - yesterdayRevenue) / yesterdayRevenue) * 100 : (todayRevenue > 0 ? 100 : 0); final reports = <_Report>[ if (isAdmin) ...[ _Report( 'Pendapatan Bulan Ini', formatRupiah(currentRevenue), Icons.trending_up, kSuccess, change: revenueChange.toDouble(), compareValue: formatRupiah(previousRevenue), compareLabel: 'Bulan Lalu', ), _Report( 'Laba Kotor Bulan Ini', formatRupiah(currentProfit), Icons.attach_money, kAccent, change: profitChange.toDouble(), compareValue: formatRupiah(previousProfit), compareLabel: 'Bulan Lalu', ), _Report( 'Transaksi Bulan Ini', '$currentTransactions', Icons.receipt_long, kAccent, change: transactionChange.toDouble(), compareValue: '$previousTransactions', compareLabel: 'Bulan Lalu', ), ] else ...[ _Report( 'Transaksi Hari Ini', '$currentTransactions', Icons.receipt_long, kAccent, change: transactionChange.toDouble(), compareValue: '$previousTransactions', compareLabel: 'Kemarin', ), _Report( 'Pendapatan Hari Ini', formatRupiah(todayRevenue), Icons.today_outlined, kSuccess, change: revenueDayChange.toDouble(), compareValue: formatRupiah(yesterdayRevenue), compareLabel: 'Kemarin', ), ], ]; return Padding( padding: const EdgeInsets.all(12), child: Column( children: [ if (isAdmin) Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( _dateRange == null ? 'Semua data' : '${formatTanggal(_dateRange!.start)} - ${formatTanggal(_dateRange!.end)}', style: const TextStyle(color: kMuted, fontSize: 13), ), ), ], ), const SizedBox(height: 8), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ TextButton.icon( onPressed: () async { final messenger = ScaffoldMessenger.of(context); try { final picked = await showDateRangePicker( context: context, firstDate: DateTime(2024), lastDate: DateTime.now(), initialDateRange: _dateRange, ); if (mounted && picked != null) { setState(() => _dateRange = picked); context.read().resetPagination(); } } catch (e) { if (mounted) { messenger.showSnackBar( SnackBar( content: Text('Gagal memilih tanggal: $e'), backgroundColor: kDanger, ), ); } } }, icon: const Icon(Icons.date_range_outlined, size: 18), label: const Text('Pilih Rentang Tanggal'), ), if (_dateRange != null) TextButton.icon( onPressed: () { try { setState(() => _dateRange = null); context.read().resetPagination(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal reset filter: $e'), backgroundColor: kDanger, ), ); } } }, icon: const Icon(Icons.close, size: 18), label: const Text('Reset'), ), IconButton( icon: const Icon(Icons.picture_as_pdf_outlined, size: 18), onPressed: () async { final messenger = ScaffoldMessenger.of(context); try { if (filteredSales.isEmpty) { if (mounted) { messenger.showSnackBar( const SnackBar( content: Text('Tidak ada data untuk export'), backgroundColor: kDanger, ), ); } return; } final exportService = ExportService(); final path = await exportService.exportToPdf( filteredSales .map((s) => { 'date': s.date, 'id': s.id.toUpperCase(), 'total': formatRupiah(s.total), 'paid': formatRupiah(s.paid), 'change': formatRupiah(s.change), 'items': s.items.length, 'discount': s.discount, }) .toList(), 'Dutra Store', _dateRange, ); if (path != null && mounted) { await exportService.shareFile(path); } } catch (e) { if (mounted) { messenger.showSnackBar( SnackBar( content: Text('Gagal export PDF: $e'), backgroundColor: kDanger, ), ); } } }, tooltip: 'Export PDF', ), ], ), ), ], ), ), Column( children: [ SizedBox( height: 90, child: PageView.builder( controller: _reportPageController, onPageChanged: (i) => setState(() => _reportPage = i), itemCount: reports.length, itemBuilder: (context, i) { final r = reports[i]; return Padding( padding: EdgeInsets.only( right: i < reports.length - 1 ? 8 : 0, ), child: _ReportCard( report: r, isActive: i == _reportPage, ), ); }, ), ), const SizedBox(height: 2), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(reports.length, (i) { return AnimatedContainer( duration: const Duration(milliseconds: 220), width: _reportPage == i ? 12 : 5, height: 5, margin: const EdgeInsets.symmetric(horizontal: 2), decoration: BoxDecoration( color: _reportPage == i ? reports[i].color : kBorder, borderRadius: BorderRadius.circular(999), ), ); }), ), ], ), const SizedBox(height: 6), if (isAdmin) _buildUnpaidSummary(state.sales, () => setState(() => _paymentFilter = 'unpaid')), const SizedBox(height: 6), Wrap( spacing: 6, runSpacing: 6, children: [ _PaymentFilterChip( label: 'Semua', selected: _paymentFilter == 'all', onTap: () => setState(() => _paymentFilter = 'all'), ), _PaymentFilterChip( label: 'Belum Lunas', selected: _paymentFilter == 'unpaid', onTap: () => setState(() => _paymentFilter = 'unpaid'), isWarning: true, ), _PaymentFilterChip( label: 'Sudah Lunas', selected: _paymentFilter == 'paid', onTap: () => setState(() => _paymentFilter = 'paid'), isSuccess: true, ), ], ), const SizedBox(height: 8), Expanded( child: filteredSales.isEmpty ? Center( child: Column( mainAxisSize: MainAxisSize.min, children: const [ Icon(Icons.receipt_long_outlined, size: 48, color: kMuted), SizedBox(height: 8), Text('Belum ada transaksi', style: TextStyle(color: kMuted)), ], ), ) : RefreshIndicator( onRefresh: () async { context.read().recordManualRefresh(); await context.read().refresh(); }, color: kAccent, child: ListView.separated( itemCount: displayedSales.length + (state.hasMoreSales ? 1 : 0), separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, i) { if (i >= displayedSales.length) { return Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: FilledButton.icon( onPressed: () => state.loadMoreSales(), icon: const Icon(Icons.expand_more, size: 18), label: Text('Muat lebih banyak (${filteredSales.length - state.visibleSalesCount} tersisa)'), ), ); } final sale = displayedSales[i]; final isCancelled = sale.isCancelled; return Card( color: isCancelled ? kDanger.withValues(alpha: 0.08) : null, child: InkWell( onTap: () => _showSaleDetail(context, sale), borderRadius: BorderRadius.circular(kRadius), child: Padding( padding: const EdgeInsets.all(12), child: Row( children: [ CircleAvatar( radius: 18, backgroundColor: isCancelled ? kDanger.withValues(alpha: 0.12) : kSuccess.withValues(alpha: 0.12), child: Icon( isCancelled ? Icons.cancel_outlined : Icons.check_circle_outline, color: isCancelled ? kDanger : kSuccess, size: 20, ), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( formatRupiah(sale.total), style: TextStyle( fontSize: 15, fontWeight: FontWeight.w800, decoration: isCancelled ? TextDecoration.lineThrough : null, color: isCancelled ? kMuted : null, ), ), const SizedBox(height: 2), Text( '${formatTanggal(sale.date)} ${formatWaktu(sale.date)} • ${sale.items.length} item', style: const TextStyle(fontSize: 11, color: kMuted), ), if (isCancelled) ...[ const SizedBox(height: 2), Text( 'Dibatalkan', style: const TextStyle( fontSize: 10, color: kDanger, fontWeight: FontWeight.w600, ), ), ], ], ), ), const SizedBox(width: 6), Text( '#${sale.id.toUpperCase()}', style: const TextStyle(fontSize: 10, color: kMuted), overflow: TextOverflow.ellipsis, ), ], ), ), ), ); }, ), ), ), ], ), ); } void _showSaleDetail(BuildContext context, Sale sale) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (sheetContext) => DraggableScrollableSheet( initialChildSize: 0.7, minChildSize: 0.5, maxChildSize: 0.92, builder: (_, controller) => _buildSaleDetailBottomSheet( context, sheetContext, sale, controller, ), ), ); } Widget _buildSaleDetailBottomSheet( BuildContext parentContext, BuildContext sheetContext, Sale sale, ScrollController controller) { final state = parentContext.read(); final isAdmin = state.isAdmin; final isCancelled = sale.isCancelled; return Container( decoration: const BoxDecoration( color: kSurface, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), border: Border(top: BorderSide(color: kBorder)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( margin: const EdgeInsets.only(top: 12), width: 40, height: 4, decoration: BoxDecoration( color: kBorder, borderRadius: BorderRadius.circular(4), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('#${sale.id.toUpperCase()}', style: const TextStyle(fontSize: 10, color: kMuted), overflow: TextOverflow.ellipsis), Text( 'Kasir: ${state.getProfileName(sale.userId)}', style: const TextStyle(color: kMuted, fontSize: 11), ), ], ), ), if (isCancelled) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2), decoration: BoxDecoration( color: kDanger.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), ), child: const Text( 'DIBATALKAN', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w700, color: kDanger), ), ), ], ), ), const SizedBox(height: 2), Text( '${formatTanggal(sale.date)} ${formatWaktu(sale.date)}', style: const TextStyle(color: kMuted, fontSize: 12), ), const Divider(height: 24), Expanded( child: SingleChildScrollView( controller: controller, padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...sale.items.map((it) => Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text('${it.name} x${it.qty}', style: const TextStyle(fontSize: 14)), ), Text(formatRupiah(it.subtotal), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600)), ], ), )), if (sale.discount > 0) Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Diskon', style: TextStyle(color: kMuted)), Text('-${formatRupiah(sale.discount)}', style: const TextStyle(color: kMuted)), ], ), ), const Divider(height: 24), _receiptRow('Total', formatRupiah(sale.total), true), _receiptRow('Bayar', formatRupiah(sale.paid), false), if (sale.paymentStatus == 'unpaid' || sale.paymentStatus == 'partial') _receiptRow('Sisa', formatRupiah(sale.dueAmount), false), _receiptRow('Kembali', formatRupiah(sale.change), false), _receiptRow('Metode', _paymentMethodLabel(sale.paymentMethod), false), _receiptRow('Status', sale.paymentStatus == 'paid' ? 'Lunas' : 'Belum Lunas', false), if (sale.splitPayment != null && sale.splitPayment!.isNotEmpty) ...sale.splitPayment!.entries.map((e) { final label = e.key == 'tunai' ? 'Tunai' : e.key == 'transfer' ? 'Bank' : 'QRIS'; return _receiptRow('Split $label', formatRupiah(e.value), false); }), if (sale.totalCost > 0) ...[ const Divider(height: 24), _receiptRow('Harga Pokok', formatRupiah(sale.totalCost), false), _receiptRow('Laba Kotor', formatRupiah(sale.total - sale.totalCost), true), ], const SizedBox(height: 16), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: isCancelled ? null : () async { final messenger = ScaffoldMessenger.of(context); final prefs = await SharedPreferences.getInstance(); final storeName = prefs.getString('store_name') ?? 'Toko Grosir'; final err = await printerService.printSale( sale, storeName, kasirName: state.getProfileName(sale.userId), whatsapp: prefs.getString('store_whatsapp'), notes: prefs.getString('store_notes'), ); if (mounted) { messenger.showSnackBar( SnackBar( content: Text(err == null ? 'Struk dikirim ke printer' : 'Gagal cetak: $err'), backgroundColor: err == null ? kSuccess : kDanger, ), ); } }, icon: const Icon(Icons.print_outlined), label: const Text('Cetak Struk'), ), ), if (isAdmin && !isCancelled && sale.paymentStatus != 'paid') ...[ const SizedBox(height: 4), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: () async { final payCtrl = TextEditingController(); final result = await showDialog( context: parentContext, builder: (ctx) => AlertDialog( title: Text(sale.paymentStatus == 'unpaid' ? 'Terima Pembayaran' : 'Bayar Sisa'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Total: ${formatRupiah(sale.total)}'), Text('Sudah bayar: ${formatRupiah(sale.paid)}'), Text('Sisa: ${formatRupiah(sale.dueAmount)}'), const SizedBox(height: 12), TextField( controller: payCtrl, keyboardType: TextInputType.number, inputFormatters: [RupiahInputFormatter()], decoration: const InputDecoration( labelText: 'Nominal Pembayaran', prefixText: 'Rp ', ), ), ], ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Batal')), FilledButton( onPressed: () { final val = (RupiahInputFormatter.getRawValue(payCtrl.text) ?? 0).toDouble(); if (val <= 0) return; Navigator.pop(ctx, val); }, style: FilledButton.styleFrom(backgroundColor: kSuccess), child: const Text('Simpan'), ), ], ), ); if (result != null && result > 0) { final err = await state.recordPayment(sale.id, result); if (mounted) { final messenger = ScaffoldMessenger.of(parentContext); messenger.showSnackBar( SnackBar( content: Text(err == null ? 'Pembayaran berhasil dicatat' : 'Gagal: $err'), backgroundColor: err == null ? kSuccess : kDanger, ), ); } } }, icon: const Icon(Icons.payments_outlined, color: Colors.white), label: Text(sale.paymentStatus == 'unpaid' ? 'Terima Pembayaran' : 'Bayar Sisa'), style: FilledButton.styleFrom( backgroundColor: kSuccess, foregroundColor: const Color(0xFF06210F), ), ), ), ], if (isAdmin && !isCancelled) ...[ const SizedBox(height: 4), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: () async { final messenger = ScaffoldMessenger.of(parentContext); final confirm = await showDialog( context: parentContext, builder: (ctx) => AlertDialog( icon: Icon(Icons.cancel_outlined, color: kDanger, size: 28), title: const Text('Batalkan Transaksi?'), content: const Text( 'Stok akan dikembalikan secara otomatis. Tindakan ini tidak dapat dibatalkan.', ), actions: [ SizedBox( width: double.infinity, child: OutlinedButton( onPressed: () => Navigator.pop(ctx, false), style: OutlinedButton.styleFrom( foregroundColor: kMuted, side: const BorderSide(color: kBorder), ), child: const Text('Batal'), ), ), const SizedBox(height: 8), SizedBox( width: double.infinity, child: FilledButton( onPressed: () => Navigator.pop(ctx, true), style: FilledButton.styleFrom( backgroundColor: kDanger, ), child: const Text('Batalkan'), ), ), ], ), ); if (confirm == true && mounted) { final err = await state.cancelSale(sale.id); if (mounted) { Navigator.pop(sheetContext); messenger.showSnackBar( SnackBar( content: Text(err == null ? 'Transaksi dibatalkan, stok dipulihkan' : 'Gagal membatalkan: $err'), backgroundColor: err == null ? kSuccess : kDanger, ), ); } } }, icon: const Icon(Icons.cancel_outlined, color: kDanger), label: const Text('Batalkan Transaksi'), style: OutlinedButton.styleFrom( foregroundColor: kDanger, side: const BorderSide(color: kDanger), ), ), ), ], ], ), ), ), ], ), ); } Widget _receiptRow(String label, String value, bool bold) { return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle( color: bold ? kText : kMuted, fontWeight: bold ? FontWeight.w700 : FontWeight.normal)), Text(value, style: TextStyle( fontSize: bold ? 17 : 14, fontWeight: bold ? FontWeight.w800 : FontWeight.w600, color: bold ? kAccent : kText, )), ], ), ); } } class _ReportCard extends StatelessWidget { final _Report report; final bool isActive; const _ReportCard({required this.report, required this.isActive}); @override Widget build(BuildContext context) { final r = report; return AnimatedContainer( duration: const Duration(milliseconds: 220), curve: Curves.easeInOut, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: kSurface2, borderRadius: BorderRadius.circular(kRadius), border: Border.all( color: isActive ? r.color.withValues(alpha: 0.4) : kBorder, ), ), child: isActive ? Row( children: [ Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( color: r.color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Icon(r.icon, color: r.color, size: 14), ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( r.value, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w800, color: r.color, ), ), Text( r.label, style: const TextStyle(fontSize: 10, color: kMuted), ), if (r.change != null) ...[ const SizedBox(height: 1), Text( '${r.change! >= 0 ? '↑' : '↓'} ${r.change!.abs().toStringAsFixed(1)}% vs ${r.compareLabel}${r.compareValue != null ? " • ${r.compareValue}" : ""}', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w600, color: r.change! >= 0 ? kSuccess : kDanger, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ], ), ), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: r.color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Icon(r.icon, color: r.color, size: 11), ), const SizedBox(height: 2), Text( r.value, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w800), ), Text( r.label, style: const TextStyle(fontSize: 8, color: kMuted), ), ], ), ); } } Widget _buildUnpaidSummary(List sales, VoidCallback onTap) { final unpaid = sales.where((s) => s.paymentStatus != 'paid' && !s.isCancelled).toList(); if (unpaid.isEmpty) return const SizedBox.shrink(); final totalDue = unpaid.fold(0.0, (sum, s) => sum + s.dueAmount); return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: kAccent.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(kRadius), border: Border.all(color: kAccent.withValues(alpha: 0.2)), ), child: Row( children: [ Icon(Icons.warning_amber_rounded, color: kAccent, size: 20), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text('${unpaid.length} Transaksi Belum Lunas', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)), Text('Total sisa: ${formatRupiah(totalDue)}', style: TextStyle(fontSize: 11, color: kMuted)), ], ), ), TextButton.icon( onPressed: onTap, icon: const Icon(Icons.visibility_outlined, size: 16), label: const Text('Lihat'), ), ], ), ); } class _PaymentFilterChip extends StatelessWidget { final String label; final bool selected; final VoidCallback onTap; final bool isWarning; final bool isSuccess; const _PaymentFilterChip({ required this.label, required this.selected, required this.onTap, this.isWarning = false, this.isSuccess = false, }); @override Widget build(BuildContext context) { final color = isWarning ? kAccent : isSuccess ? kSuccess : kMuted; return FilterChip( label: Text(label, style: const TextStyle(fontSize: 12)), selected: selected, onSelected: (_) => onTap(), selectedColor: color.withValues(alpha: 0.15), checkmarkColor: color, labelStyle: TextStyle(color: selected ? color : kMuted, fontSize: 12), side: BorderSide(color: selected ? color : kBorder), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), ); } } String _paymentMethodLabel(String method) { switch (method) { case 'transfer': return 'Bank'; case 'qris': return 'QRIS'; case 'split': return 'Split Payment'; default: return 'Tunai'; } } class _Report { final String label; final String value; final IconData icon; final Color color; final double? change; final String? compareValue; final String? compareLabel; const _Report( this.label, this.value, this.icon, this.color, { this.change, this.compareValue, this.compareLabel, }); }