import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/theme/app_theme.dart'; import '../../models/fee.dart'; import '../../providers/fee_provider.dart'; class FeesScreen extends ConsumerStatefulWidget { const FeesScreen({super.key}); @override ConsumerState createState() => _FeesScreenState(); } class _FeesScreenState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } String _formatCurrency(double amount, String symbol) { return '$symbol${amount.toStringAsFixed(2)}'; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Fee Management'), bottom: TabBar( controller: _tabController, labelColor: AppTheme.primaryColor, unselectedLabelColor: AppTheme.textSecondary, indicatorColor: AppTheme.primaryColor, indicatorWeight: 3, tabs: const [ Tab(text: 'Invoices & Summary'), Tab(text: 'Payment History'), ], ), ), body: TabBarView( controller: _tabController, children: [ _InvoicesAndSummaryTab(formatCurrency: _formatCurrency), _PaymentHistoryTab(formatCurrency: _formatCurrency), ], ), ); } } class _InvoicesAndSummaryTab extends ConsumerWidget { final String Function(double, String) formatCurrency; const _InvoicesAndSummaryTab({required this.formatCurrency}); @override Widget build(BuildContext context, WidgetRef ref) { final summaryAsync = ref.watch(feeSummaryProvider); final invoicesAsync = ref.watch(feeInvoicesProvider); final activeFilter = ref.watch(feeStatusFilterProvider); return RefreshIndicator( onRefresh: () async { ref.invalidate(feeSummaryProvider); ref.invalidate(feeInvoicesProvider); await Future.wait([ ref.read(feeSummaryProvider.future), ref.read(feeInvoicesProvider.future), ]); }, child: ListView( padding: const EdgeInsets.all(16), children: [ // Fee Summary Section summaryAsync.when( loading: () => const Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 24), child: CircularProgressIndicator(), ), ), error: (err, _) => Card( color: Colors.red.shade50, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ const Icon(Icons.error_outline, color: AppTheme.errorColor), const SizedBox(width: 12), Expanded( child: Text( 'Failed to load fee summary: $err', style: const TextStyle(color: AppTheme.errorColor, fontSize: 13), ), ), TextButton( onPressed: () => ref.refresh(feeSummaryProvider), child: const Text('Retry'), ), ], ), ), ), data: (summary) => _buildSummaryCard(context, summary), ), const SizedBox(height: 16), // Invoices Header & Filter Chips Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Fee Invoices', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), if (activeFilter != null) TextButton( onPressed: () => ref.read(feeStatusFilterProvider.notifier).state = null, child: const Text('Clear Filter', style: TextStyle(fontSize: 12)), ), ], ), const SizedBox(height: 8), // Filters SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildFilterChip(ref, 'All', null, activeFilter == null), const SizedBox(width: 8), _buildFilterChip(ref, 'Unpaid', 'unpaid', activeFilter == 'unpaid'), const SizedBox(width: 8), _buildFilterChip(ref, 'Partial', 'partial', activeFilter == 'partial'), const SizedBox(width: 8), _buildFilterChip(ref, 'Overdue', 'overdue', activeFilter == 'overdue'), const SizedBox(width: 8), _buildFilterChip(ref, 'Paid', 'paid', activeFilter == 'paid'), ], ), ), const SizedBox(height: 12), // Invoices List invoicesAsync.when( loading: () => const Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 40), child: CircularProgressIndicator(), ), ), error: (err, _) => Card( color: Colors.red.shade50, child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ const Icon(Icons.error_outline, color: AppTheme.errorColor, size: 36), const SizedBox(height: 8), Text( 'Failed to load invoices: $err', textAlign: TextAlign.center, style: const TextStyle(color: AppTheme.errorColor, fontSize: 13), ), const SizedBox(height: 12), ElevatedButton( onPressed: () => ref.refresh(feeInvoicesProvider), child: const Text('Retry'), ), ], ), ), ), data: (pageResult) { if (pageResult.invoices.isEmpty) { return _buildEmptyState( icon: Icons.receipt_long_outlined, message: activeFilter != null ? 'No invoices match the "$activeFilter" filter.' : 'No fee invoices found for your account.', ); } return Column( children: pageResult.invoices.map((inv) => _buildInvoiceCard(context, inv)).toList(), ); }, ), ], ), ); } Widget _buildFilterChip(WidgetRef ref, String label, String? value, bool isSelected) { return ChoiceChip( label: Text(label), selected: isSelected, selectedColor: AppTheme.primaryColor.withValues(alpha: 0.15), labelStyle: TextStyle( color: isSelected ? AppTheme.primaryColor : AppTheme.textSecondary, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontSize: 12, ), onSelected: (_) { ref.read(feeStatusFilterProvider.notifier).state = value; }, ); } Widget _buildSummaryCard(BuildContext context, FeeSummary summary) { final sym = summary.currencySymbol; Color statusColor; String statusLabel; switch (summary.paymentStatus) { case 'fully_paid': case 'paid': statusColor = AppTheme.successColor; statusLabel = 'Fully Paid'; break; case 'overdue': statusColor = AppTheme.errorColor; statusLabel = 'Overdue'; break; case 'partial': statusColor = AppTheme.warningColor; statusLabel = 'Partially Paid'; break; default: statusColor = summary.outstandingBalance > 0 ? AppTheme.primaryLight : AppTheme.successColor; statusLabel = summary.outstandingBalance > 0 ? 'Pending' : 'No Dues'; } return Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppTheme.primaryColor, AppTheme.primaryColor.withValues(alpha: 0.85), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppTheme.primaryColor.withValues(alpha: 0.25), blurRadius: 10, offset: const Offset(0, 4), ), ], ), padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Financial Overview', style: TextStyle( color: Colors.white70, fontSize: 14, fontWeight: FontWeight.w500, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.25), borderRadius: BorderRadius.circular(20), border: Border.all(color: statusColor, width: 1), ), child: Text( statusLabel, style: TextStyle( color: statusColor == AppTheme.errorColor ? Colors.red.shade200 : Colors.white, fontSize: 11, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 12), Text( formatCurrency(summary.outstandingBalance, sym), style: const TextStyle( color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold, ), ), const Text( 'Outstanding Balance', style: TextStyle( color: Colors.white70, fontSize: 13, ), ), const Divider(color: Colors.white24, height: 24), Row( children: [ Expanded( child: _buildMetricCol('Assessed', formatCurrency(summary.totalFees, sym)), ), Expanded( child: _buildMetricCol('Paid', formatCurrency(summary.totalPaid, sym)), ), Expanded( child: _buildMetricCol( 'Overdue', formatCurrency(summary.overdueAmount, sym), isAlert: summary.overdueAmount > 0, ), ), ], ), if (summary.nextDueDate != null) ...[ const SizedBox(height: 16), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.event_outlined, color: Colors.white70, size: 16), const SizedBox(width: 8), Text( 'Next Due Date: ${summary.nextDueDate}', style: const TextStyle(color: Colors.white, fontSize: 12), ), ], ), ), ], ], ), ); } Widget _buildMetricCol(String label, String value, {bool isAlert = false}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle(color: Colors.white60, fontSize: 11), ), const SizedBox(height: 4), Text( value, style: TextStyle( color: isAlert ? Colors.amber.shade300 : Colors.white, fontSize: 14, fontWeight: FontWeight.w600, ), ), ], ); } Widget _buildInvoiceCard(BuildContext context, FeeInvoice invoice) { final sym = invoice.currencySymbol; Color statusColor; String statusText; switch (invoice.paymentStatus) { case 'paid': statusColor = AppTheme.successColor; statusText = 'Paid'; break; case 'overdue': statusColor = AppTheme.errorColor; statusText = 'Overdue'; break; case 'partial': statusColor = AppTheme.warningColor; statusText = 'Partial'; break; default: statusColor = AppTheme.textSecondary; statusText = 'Unpaid'; } return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: const BorderSide(color: AppTheme.borderLight), ), child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () => _showInvoiceDetailSheet(context, invoice.id), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( invoice.invoiceNumber, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 15, color: AppTheme.textPrimary, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(6), border: Border.all(color: statusColor.withValues(alpha: 0.5)), ), child: Text( statusText, style: TextStyle( color: statusColor, fontWeight: FontWeight.bold, fontSize: 11, ), ), ), ], ), const SizedBox(height: 6), Text( invoice.title, style: const TextStyle( color: AppTheme.textSecondary, fontSize: 13, ), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Total Amount', style: TextStyle(fontSize: 11, color: AppTheme.textMuted), ), const SizedBox(height: 2), Text( formatCurrency(invoice.amount, sym), style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( 'Paid', style: TextStyle(fontSize: 11, color: AppTheme.textMuted), ), const SizedBox(height: 2), Text( formatCurrency(invoice.amountPaid, sym), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppTheme.successColor, ), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ const Text( 'Due', style: TextStyle(fontSize: 11, color: AppTheme.textMuted), ), const SizedBox(height: 2), Text( formatCurrency(invoice.amountDue, sym), style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: invoice.isOverdue ? AppTheme.errorColor : AppTheme.textPrimary, ), ), ], ), ], ), if (invoice.dueDate != null) ...[ const Divider(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( Icons.schedule_rounded, size: 14, color: invoice.isOverdue ? AppTheme.errorColor : AppTheme.textMuted, ), const SizedBox(width: 4), Text( 'Due: ${invoice.dueDate}', style: TextStyle( fontSize: 12, color: invoice.isOverdue ? AppTheme.errorColor : AppTheme.textSecondary, fontWeight: invoice.isOverdue ? FontWeight.bold : FontWeight.normal, ), ), ], ), const Row( children: [ Text( 'View Details', style: TextStyle( fontSize: 12, color: AppTheme.primaryLight, fontWeight: FontWeight.w600, ), ), Icon(Icons.chevron_right, size: 16, color: AppTheme.primaryLight), ], ), ], ), ], ], ), ), ), ); } void _showInvoiceDetailSheet(BuildContext context, int invoiceId) { showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (_) => _InvoiceDetailBottomSheet(invoiceId: invoiceId, formatCurrency: formatCurrency), ); } Widget _buildEmptyState({required IconData icon, required String message}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 54, color: AppTheme.textMuted), const SizedBox(height: 12), Text( message, textAlign: TextAlign.center, style: const TextStyle(color: AppTheme.textSecondary, fontSize: 14), ), ], ), ); } } class _InvoiceDetailBottomSheet extends ConsumerWidget { final int invoiceId; final String Function(double, String) formatCurrency; const _InvoiceDetailBottomSheet({ required this.invoiceId, required this.formatCurrency, }); @override Widget build(BuildContext context, WidgetRef ref) { final detailAsync = ref.watch(feeInvoiceDetailProvider(invoiceId)); return DraggableScrollableSheet( initialChildSize: 0.75, minChildSize: 0.5, maxChildSize: 0.95, expand: false, builder: (_, scrollController) { return detailAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48, color: AppTheme.errorColor), const SizedBox(height: 12), Text('Failed to load invoice details: $err', textAlign: TextAlign.center), const SizedBox(height: 16), ElevatedButton( onPressed: () => ref.refresh(feeInvoiceDetailProvider(invoiceId)), child: const Text('Retry'), ), ], ), ), data: (invoice) { final sym = invoice.currencySymbol; return ListView( controller: scrollController, padding: const EdgeInsets.all(20), children: [ Center( child: Container( width: 40, height: 4, decoration: BoxDecoration( color: AppTheme.borderLight, borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( invoice.invoiceNumber, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 4), Text( invoice.title, style: const TextStyle(color: AppTheme.textSecondary, fontSize: 14), ), ], ), ), _buildStatusBadge(invoice.paymentStatus), ], ), const Divider(height: 24), // Metadata Grid Row( children: [ if (invoice.academicYear != null) Expanded( child: _buildMetaItem('Academic Year', invoice.academicYear!.name), ), if (invoice.semester != null) Expanded( child: _buildMetaItem('Semester', invoice.semester!.name), ), ], ), const SizedBox(height: 12), Row( children: [ if (invoice.assignmentDate != null) Expanded( child: _buildMetaItem('Issue Date', invoice.assignmentDate!), ), if (invoice.dueDate != null) Expanded( child: _buildMetaItem('Due Date', invoice.dueDate!), ), ], ), const Divider(height: 28), // Itemized Breakdown const Text( 'Fee Breakdown', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), if (invoice.lines.isEmpty) const Text('No line items recorded', style: TextStyle(color: AppTheme.textMuted)) else ...invoice.lines.map((line) => Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( line.name, style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14), ), if (line.category != null) Text( line.category!, style: const TextStyle(fontSize: 11, color: AppTheme.textMuted), ), ], ), ), Text( formatCurrency(line.amount, sym), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14), ), ], ), )), const Divider(height: 24), // Totals Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Total Amount', style: TextStyle(color: AppTheme.textSecondary)), Text(formatCurrency(invoice.amount, sym), style: const TextStyle(fontWeight: FontWeight.bold)), ], ), const SizedBox(height: 6), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Amount Paid', style: TextStyle(color: AppTheme.textSecondary)), Text( formatCurrency(invoice.amountPaid, sym), style: const TextStyle(color: AppTheme.successColor, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 6), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Balance Due', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15)), Text( formatCurrency(invoice.amountDue, sym), style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: invoice.isOverdue ? AppTheme.errorColor : AppTheme.primaryColor, ), ), ], ), // Payment History on this invoice if (invoice.payments.isNotEmpty) ...[ const Divider(height: 28), const Text( 'Payments Received', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), ...invoice.payments.map((p) => Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.surfaceColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: AppTheme.borderLight), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( p.receiptNumber, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13), ), if (p.paymentDate != null) Text( p.paymentDate!, style: const TextStyle(fontSize: 11, color: AppTheme.textMuted), ), ], ), Text( formatCurrency(p.amount, sym), style: const TextStyle( fontWeight: FontWeight.bold, color: AppTheme.successColor, ), ), ], ), )), ], const SizedBox(height: 24), // Safe Payment Notice (Gateway not enabled) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.blue.shade200), ), child: const Row( children: [ Icon(Icons.info_outline, color: AppTheme.primaryLight, size: 20), SizedBox(width: 10), Expanded( child: Text( 'To make fee payments, please visit the university accounts office or use the authorized institution bank transfer details.', style: TextStyle(fontSize: 12, color: AppTheme.primaryDark), ), ), ], ), ), ], ); }, ); }, ); } Widget _buildMetaItem(String label, String value) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(fontSize: 11, color: AppTheme.textMuted)), const SizedBox(height: 2), Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ], ); } Widget _buildStatusBadge(String status) { Color color; String label; switch (status) { case 'paid': color = AppTheme.successColor; label = 'Paid'; break; case 'overdue': color = AppTheme.errorColor; label = 'Overdue'; break; case 'partial': color = AppTheme.warningColor; label = 'Partial'; break; default: color = AppTheme.textSecondary; label = 'Unpaid'; } return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(6), border: Border.all(color: color), ), child: Text( label, style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 12), ), ); } } class _PaymentHistoryTab extends ConsumerWidget { final String Function(double, String) formatCurrency; const _PaymentHistoryTab({required this.formatCurrency}); @override Widget build(BuildContext context, WidgetRef ref) { final paymentsAsync = ref.watch(feePaymentsProvider); return RefreshIndicator( onRefresh: () async { ref.invalidate(feePaymentsProvider); await ref.read(feePaymentsProvider.future); }, child: paymentsAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48, color: AppTheme.errorColor), const SizedBox(height: 12), Text('Failed to load payment history: $err', textAlign: TextAlign.center), const SizedBox(height: 16), ElevatedButton( onPressed: () => ref.refresh(feePaymentsProvider), child: const Text('Retry'), ), ], ), ), ), data: (pageResult) { if (pageResult.payments.isEmpty) { return const Center( child: Padding( padding: EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.payment_outlined, size: 54, color: AppTheme.textMuted), SizedBox(height: 12), Text( 'No payment records found.', style: TextStyle(color: AppTheme.textSecondary, fontSize: 14), ), ], ), ), ); } return ListView.builder( padding: const EdgeInsets.all(16), itemCount: pageResult.payments.length, itemBuilder: (context, index) { final payment = pageResult.payments[index]; final sym = payment.currencySymbol; return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: const BorderSide(color: AppTheme.borderLight), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppTheme.successColor.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: const Icon( Icons.check_circle_outline, color: AppTheme.successColor, size: 20, ), ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( payment.receiptNumber, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), if (payment.paymentDate != null) Text( payment.paymentDate!, style: const TextStyle(fontSize: 11, color: AppTheme.textMuted), ), ], ), ], ), Text( formatCurrency(payment.amount, sym), style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppTheme.successColor, ), ), ], ), const SizedBox(height: 12), const Divider(height: 1), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Method: ${payment.paymentMethodLabel}', style: const TextStyle(fontSize: 12, color: AppTheme.textSecondary), ), if (payment.invoiceNumber != null) Text( 'Invoice: ${payment.invoiceNumber}', style: const TextStyle(fontSize: 12, color: AppTheme.primaryLight), ), ], ), if (payment.referenceNumber != null && payment.referenceNumber!.isNotEmpty) ...[ const SizedBox(height: 4), Text( 'Ref: ${payment.referenceNumber}', style: const TextStyle(fontSize: 11, color: AppTheme.textMuted), ), ], if (payment.remarks != null && payment.remarks!.isNotEmpty) ...[ const SizedBox(height: 4), Text( 'Note: ${payment.remarks}', style: const TextStyle(fontSize: 11, color: AppTheme.textMuted, fontStyle: FontStyle.italic), ), ], ], ), ), ); }, ); }, ), ); } }