/// Page: Invoices Page /// /// Displays list of invoices following html/invoice-list.html design. 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:worker/core/constants/ui_constants.dart'; import 'package:worker/core/router/app_router.dart'; import 'package:worker/core/utils/extensions.dart'; import 'package:worker/features/invoices/domain/entities/invoice.dart'; import 'package:worker/features/invoices/presentation/providers/invoices_provider.dart'; /// Invoices Page /// /// Features: /// - List of invoice cards /// - Status badges (Đã thanh toán, Chưa thanh toán, Thanh toán 1 phần) /// - Pull-to-refresh /// - Empty state /// - Navigation to invoice detail class InvoicesPage extends ConsumerWidget { const InvoicesPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final invoicesAsync = ref.watch(invoicesProvider); final colorScheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: colorScheme.surfaceContainerLowest, appBar: AppBar( leading: IconButton( icon: Icon(Icons.arrow_back, color: colorScheme.onSurface), onPressed: () => context.pop(), ), title: Text( 'Hóa đơn đã mua', 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: invoicesAsync.when( data: (invoices) { if (invoices.isEmpty) { return _buildEmptyState(context, ref); } return RefreshIndicator( onRefresh: () async { await ref.read(invoicesProvider.notifier).refresh(); }, child: ListView.builder( padding: const EdgeInsets.all(20), itemCount: invoices.length, itemBuilder: (context, index) { final invoice = invoices[index]; return _InvoiceCard( invoice: invoice, onTap: () => context.push( RouteNames.invoiceDetail.replaceFirst(':id', invoice.name), ), ); }, ), ); }, 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 = Theme.of(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(invoicesProvider), child: const Text('Thử lại'), ), ], ), ); } /// Build empty state Widget _buildEmptyState(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: () async { await ref.read(invoicesProvider.notifier).refresh(); }, child: ListView( padding: const EdgeInsets.symmetric(horizontal: 20), children: [ SizedBox( height: 500, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ FaIcon( FontAwesomeIcons.fileInvoiceDollar, size: 64, color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), ), const SizedBox(height: 20), Text( 'Không có hóa đơn nào', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: colorScheme.onSurface.withValues(alpha: 0.8), ), ), const SizedBox(height: 8), Text( 'Khi bạn mua hàng, hóa đơn sẽ xuất hiện ở đây', style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ), ), ], ), ); } } /// Invoice Card Widget class _InvoiceCard extends StatelessWidget { const _InvoiceCard({ required this.invoice, this.onTap, }); final Invoice invoice; final VoidCallback? onTap; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Card( margin: const EdgeInsets.only(bottom: 16), elevation: 2, shadowColor: colorScheme.shadow.withValues(alpha: 0.08), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: colorScheme.outline.withValues(alpha: 0.1), width: 1), ), 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: Invoice ID and Status Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Invoice ID and Date Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '#${invoice.name}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), const SizedBox(height: 4), Text( 'Ngày xuất: ${invoice.formattedDate}', style: TextStyle( fontSize: 13, color: colorScheme.onSurfaceVariant, ), ), ], ), ), // Status Badge _StatusBadge( status: invoice.status, statusColor: invoice.statusColor, ), ], ), const SizedBox(height: 12), // Details Section Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( border: Border( top: BorderSide(color: colorScheme.outlineVariant), bottom: BorderSide(color: colorScheme.outlineVariant), ), ), child: Column( children: [ // Order ID (if available) if (invoice.orderId != null) _DetailRow( label: 'Đơn hàng:', value: '#${invoice.orderId}', ), _DetailRow( label: 'Tổng tiền:', value: invoice.grandTotal.toVNCurrency, isTotal: true, ), ], ), ), ], ), ), ), ); } } /// Status Badge Widget class _StatusBadge extends StatelessWidget { const _StatusBadge({ required this.status, required this.statusColor, }); final String status; final String statusColor; @override Widget build(BuildContext context) { Color backgroundColor; Color textColor; switch (statusColor.toLowerCase()) { case 'success': backgroundColor = const Color(0xFFD1FAE5); textColor = const Color(0xFF065F46); case 'danger': backgroundColor = const Color(0xFFFEF3C7); textColor = const Color(0xFFD97706); case 'warning': backgroundColor = const Color(0xFFFEF3C7); textColor = const Color(0xFFD97706); case 'info': backgroundColor = const Color(0xFFE0E7FF); textColor = const Color(0xFF3730A3); default: backgroundColor = const Color(0xFFF3F4F6); textColor = const Color(0xFF6B7280); } return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(20), ), child: Text( status, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: textColor, ), ), ); } } /// Detail Row Widget class _DetailRow extends StatelessWidget { const _DetailRow({ required this.label, required this.value, this.isTotal = false, }); final String label; final String value; final bool isTotal; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant, ), ), Text( value, style: TextStyle( fontSize: isTotal ? 16 : 14, fontWeight: isTotal ? FontWeight.w700 : FontWeight.w600, color: isTotal ? colorScheme.error : colorScheme.onSurface, ), ), ], ), ); } }