/// Page: Payments Page /// /// Displays list of payment transactions following html/payments.html. library; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/utils/extensions.dart'; import 'package:worker/features/orders/domain/entities/payment.dart'; import 'package:worker/features/orders/presentation/providers/payments_provider.dart'; /// Payments Page /// /// Features: /// - List of transaction cards /// - Pull-to-refresh /// - Empty state /// - Transaction detail modal class PaymentsPage extends ConsumerWidget { const PaymentsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final paymentsAsync = ref.watch(paymentsProvider); final colorScheme = context.colorScheme; return Scaffold( backgroundColor: colorScheme.surfaceContainerLowest, appBar: AppBar( leading: IconButton( icon: Icon(Icons.arrow_back, color: colorScheme.onSurface), onPressed: () => context.pop(), ), title: Text( 'Lịch sử Thanh toán', style: TextStyle( color: colorScheme.onSurface, fontSize: 20, fontWeight: FontWeight.w600, ), ), elevation: AppBarSpecs.elevation, backgroundColor: colorScheme.surface, centerTitle: false, actions: const [SizedBox(width: AppSpacing.sm)], ), body: paymentsAsync.when( data: (payments) { if (payments.isEmpty) { return _buildEmptyState(context, ref); } return RefreshIndicator( onRefresh: () async { await ref.read(paymentsProvider.notifier).refresh(); }, child: ListView.builder( padding: const EdgeInsets.all(20), itemCount: payments.length, itemBuilder: (context, index) { final payment = payments[index]; return _TransactionCard( payment: payment, onTap: () => _showTransactionDetail(context, payment), ); }, ), ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => _buildErrorState(context, ref, error), ), ); } /// Build error state Widget _buildErrorState(BuildContext context, WidgetRef ref, Object error) { final colorScheme = context.colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ FaIcon( FontAwesomeIcons.circleExclamation, size: 64, color: colorScheme.error.withValues(alpha: 0.7), ), const SizedBox(height: 16), Text( 'Có lỗi xảy ra', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), const SizedBox(height: 8), Text( error.toString(), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant), textAlign: TextAlign.center, ), const SizedBox(height: 16), ElevatedButton( onPressed: () => ref.invalidate(paymentsProvider), child: const Text('Thử lại'), ), ], ), ); } /// Build empty state Widget _buildEmptyState(BuildContext context, WidgetRef ref) { final colorScheme = context.colorScheme; return RefreshIndicator( onRefresh: () async { await ref.read(paymentsProvider.notifier).refresh(); }, child: ListView( padding: const EdgeInsets.symmetric(horizontal: 20), children: [ SizedBox( height: 500, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ FaIcon( FontAwesomeIcons.receipt, size: 64, color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), ), const SizedBox(height: 20), Text( 'Không có giao dịch nào', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: colorScheme.onSurface.withValues(alpha: 0.8), ), ), const SizedBox(height: 8), Text( 'Hiện tại không có giao dịch nào trong danh mục này', style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ), ), ], ), ); } /// Show transaction detail modal void _showTransactionDetail(BuildContext context, Payment payment) { final colorScheme = context.colorScheme; final currencyFormatter = NumberFormat.currency( locale: 'vi_VN', symbol: 'đ', decimalDigits: 0, ); final dateTimeFormatter = DateFormat('dd/MM/yyyy'); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => Container( decoration: BoxDecoration( color: colorScheme.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( border: Border( bottom: BorderSide(color: colorScheme.outlineVariant), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Chi tiết giao dịch', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), GestureDetector( onTap: () => Navigator.pop(context), child: Container( width: 32, height: 32, decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest, shape: BoxShape.circle, ), child: Icon(Icons.close, size: 18, color: colorScheme.onSurfaceVariant), ), ), ], ), ), // Body Padding( padding: const EdgeInsets.all(20), child: Column( children: [ _DetailRow( label: 'Mã giao dịch:', value: '#${payment.name}', ), _DetailRow( label: 'Loại giao dịch:', value: 'Tiền ra (Thanh toán)', ), _DetailRow( label: 'Ngày thanh toán:', value: dateTimeFormatter.format(payment.postingDate), ), _DetailRow( label: 'Phương thức:', value: payment.displayPaymentMethod, ), _DetailRow( label: 'Mô tả:', value: payment.description, ), if (payment.invoiceId != null) _DetailRow( label: 'Mã hóa đơn:', value: payment.invoiceId!, ), if (payment.orderId != null) _DetailRow( label: 'Mã đơn hàng:', value: payment.orderId!, ), Container( padding: const EdgeInsets.symmetric(vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Số tiền:', style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant, ), ), Text( currencyFormatter.format(payment.paidAmount), style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: colorScheme.error, ), ), ], ), ), ], ), ), // Safe area padding SizedBox(height: MediaQuery.of(context).padding.bottom + 10), ], ), ), ); } } /// Transaction Card Widget class _TransactionCard extends StatelessWidget { const _TransactionCard({ required this.payment, this.onTap, }); final Payment payment; final VoidCallback? onTap; @override Widget build(BuildContext context) { final dateFormatter = DateFormat('dd/MM/yyyy'); final colorScheme = context.colorScheme; return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 2, shadowColor: colorScheme.shadow, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), color: colorScheme.surface, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: ID and datetime Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '#${payment.name}', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), Text( dateFormatter.format(payment.postingDate), style: TextStyle( fontSize: 12, color: colorScheme.onSurfaceVariant.withValues(alpha: 0.8), ), ), ], ), const SizedBox(height: 8), // Description Text( payment.description, style: TextStyle( fontSize: 13, color: colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 8), // Footer: method and amount Container( padding: const EdgeInsets.only(top: 8), decoration: BoxDecoration( border: Border( top: BorderSide(color: colorScheme.outlineVariant), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Payment method Row( children: [ FaIcon( FontAwesomeIcons.buildingColumns, size: 14, color: colorScheme.onSurfaceVariant, ), const SizedBox(width: 4), Text( payment.displayPaymentMethod, style: TextStyle( fontSize: 13, color: colorScheme.onSurfaceVariant, ), ), ], ), // Amount Text( payment.paidAmount.toVNCurrency, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: colorScheme.error, ), ), ], ), ), ], ), ), ), ); } } /// Detail Row Widget for modal class _DetailRow extends StatelessWidget { const _DetailRow({ required this.label, required this.value, }); final String label; final String value; @override Widget build(BuildContext context) { final colorScheme = context.colorScheme; return Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( border: Border( bottom: BorderSide(color: colorScheme.outlineVariant), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant, ), ), const SizedBox(width: 16), Flexible( child: Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), textAlign: TextAlign.right, ), ), ], ), ); } }