import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../../core/models/chit_group.dart'; import '../../core/models/payment.dart'; import '../../core/services/payment_service.dart'; import 'record_payment_dialog.dart'; class PaymentHistoryPage extends StatefulWidget { final ChitGroup group; const PaymentHistoryPage({ super.key, required this.group, }); @override State createState() => _PaymentHistoryPageState(); } class _PaymentHistoryPageState extends State with SingleTickerProviderStateMixin { late final PaymentService _paymentService; late TabController _tabController; String? _selectedStatus; int? _selectedMonth; int? _selectedYear; // Track which months are expanded final Set _expandedMonths = {}; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); // Get payment service (already initialized in app.dart) _paymentService = Get.find(); // Load data after widget is fully built WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(const Duration(milliseconds: 300), () { if (mounted) { _loadData(); } }); }); } @override void dispose() { _tabController.dispose(); super.dispose(); } Future _loadData() async { await Future.wait([ _paymentService.loadGroupPayments(widget.group.id), _paymentService.loadPaymentSummary(widget.group.id), _paymentService.loadPendingPayments(widget.group.id), ]); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey.shade50, appBar: AppBar( title: Row( children: [ Flexible( child: Text( 'Payment History', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), if (_expandedMonths.isNotEmpty) ...[ SizedBox(width: 6.w), Container( padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8.r), ), child: Text( '${_expandedMonths.length}', style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ], ], ), backgroundColor: Colors.green.shade600, foregroundColor: Colors.white, elevation: 2, actions: [ IconButton( icon: Icon(Icons.expand_more, size: 24.w), onPressed: _expandAllMonths, tooltip: 'Expand All', ), IconButton( icon: Icon(Icons.expand_less, size: 24.w), onPressed: _collapseAllMonths, tooltip: 'Collapse All', ), IconButton( icon: Icon(Icons.add, size: 24.w), onPressed: () => _showRecordPaymentDialog(), tooltip: 'Record Payment', ), IconButton( icon: Icon(Icons.refresh, size: 24.w), onPressed: _loadData, tooltip: 'Refresh', ), ], bottom: TabBar( controller: _tabController, indicatorColor: Colors.white, labelColor: Colors.white, unselectedLabelColor: Colors.white70, labelStyle: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600), unselectedLabelStyle: TextStyle(fontSize: 14.sp), tabs: const [ Tab(icon: Icon(Icons.history), text: 'History'), Tab(icon: Icon(Icons.pending), text: 'Pending'), Tab(icon: Icon(Icons.analytics), text: 'Summary'), ], ), ), body: TabBarView( controller: _tabController, children: [ _buildHistoryTab(), _buildPendingTab(), _buildSummaryTab(), ], ), ); } Widget _buildHistoryTab() { return Column( children: [ // Filters Container( padding: EdgeInsets.all(6.w), color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Filters', style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), SizedBox(height: 4.h), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ SizedBox( width: 80.w, child: _buildStatusFilter(), ), SizedBox(width: 4.w), SizedBox( width: 80.w, child: _buildMonthFilter(), ), SizedBox(width: 4.w), SizedBox( width: 80.w, child: _buildYearFilter(), ), ], ), ), ], ), ), // Payment List Expanded( child: Obx(() { if (_paymentService.isLoading) { return const Center(child: CircularProgressIndicator()); } if (_paymentService.error.isNotEmpty) { return _buildErrorWidget(_paymentService.error); } final payments = _paymentService.payments; if (payments.isEmpty) { return _buildEmptyWidget('No payments found'); } return RefreshIndicator( onRefresh: _loadData, child: _buildGroupedPaymentsList(payments), ); }), ), ], ); } Widget _buildGroupedPaymentsList(List payments) { // Get all months from chitfund start date to current date final allMonths = _getAllMonthsFromStartDate(); // Group payments by month and year final Map> groupedPayments = {}; for (final payment in payments) { final key = '${payment.year}-${payment.month.toString().padLeft(2, '0')}'; if (!groupedPayments.containsKey(key)) { groupedPayments[key] = []; } groupedPayments[key]!.add(payment); } // Sort months in descending order (newest first) final sortedKeys = allMonths.toList() ..sort((a, b) => b.compareTo(a)); return ListView.builder( padding: EdgeInsets.all(16.w), itemCount: sortedKeys.length, itemBuilder: (context, index) { final monthKey = sortedKeys[index]; final monthPayments = groupedPayments[monthKey] ?? []; final monthYear = monthKey.split('-'); final year = int.parse(monthYear[0]); final month = int.parse(monthYear[1]); return _buildMonthGroup(monthKey, month, year, monthPayments); }, ); } Set _getAllMonthsFromStartDate() { final Set months = {}; if (widget.group.startDate == null) { return months; } final startDate = widget.group.startDate!; final currentDate = DateTime.now(); // Start from the chitfund start date DateTime currentMonth = DateTime(startDate.year, startDate.month); final endMonth = DateTime(currentDate.year, currentDate.month); // Add all months from start to current (inclusive) while (currentMonth.isBefore(endMonth) || currentMonth.isAtSameMomentAs(endMonth)) { final key = '${currentMonth.year}-${currentMonth.month.toString().padLeft(2, '0')}'; months.add(key); // Move to next month currentMonth = DateTime(currentMonth.year, currentMonth.month + 1); } return months; } Widget _buildMonthGroup(String monthKey, int month, int year, List payments) { final monthName = _getMonthName(month); final totalAmount = payments.fold(0.0, (sum, payment) => sum + payment.amount); final successfulCount = payments.where((p) => p.status == 'success').length; final totalCount = payments.length; final allMembers = widget.group.members ?? []; final expectedCount = allMembers.length; final isExpanded = _expandedMonths.contains(monthKey); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Month Header (Clickable to expand/collapse) GestureDetector( onTap: () => _toggleMonthExpansion(monthKey), child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), decoration: BoxDecoration( color: isExpanded ? (totalCount == 0 ? Colors.grey.shade100 : Colors.green.shade100) : (totalCount == 0 ? Colors.grey.shade50 : Colors.green.shade50), borderRadius: BorderRadius.circular(8.r), border: Border.all( color: isExpanded ? (totalCount == 0 ? Colors.grey.shade300 : Colors.green.shade300) : (totalCount == 0 ? Colors.grey.shade200 : Colors.green.shade200), width: isExpanded ? 1.5 : 1.0, ), ), child: Row( children: [ Icon( Icons.calendar_month, color: totalCount == 0 ? Colors.grey.shade700 : Colors.green.shade700, size: 20.w, ), SizedBox(width: 8.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '$monthName $year', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: totalCount == 0 ? Colors.grey.shade800 : Colors.green.shade800, ), ), Text( '$successfulCount/$expectedCount payments • ${_formatIndianCurrency(totalAmount)}', style: TextStyle( fontSize: 11.sp, color: totalCount == 0 ? Colors.grey.shade600 : Colors.green.shade600, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ], ), ), Flexible( child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 3.h), decoration: BoxDecoration( color: totalCount == 0 ? Colors.grey.shade100 : Colors.green.shade100, borderRadius: BorderRadius.circular(10.r), ), child: Text( '$totalCount/$expectedCount', style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: totalCount == 0 ? Colors.grey.shade700 : Colors.green.shade700, ), ), ), SizedBox(width: 4.w), GestureDetector( onTap: () => _updateMonthPayments(month, year), child: Container( padding: EdgeInsets.all(4.w), decoration: BoxDecoration( color: Colors.blue.shade100, borderRadius: BorderRadius.circular(4.r), ), child: Icon( Icons.edit, size: 12.w, color: Colors.blue.shade700, ), ), ), SizedBox(width: 4.w), // Expand/Collapse Icon AnimatedRotation( turns: isExpanded ? 0.5 : 0.0, duration: const Duration(milliseconds: 200), child: Icon( Icons.keyboard_arrow_down, color: totalCount == 0 ? Colors.grey.shade700 : Colors.green.shade700, size: 16.w, ), ), ], ), ), ], ), ), ), // Collapsible Content AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, height: isExpanded ? null : 0, child: isExpanded ? Column( children: [ SizedBox(height: 8.h), // Member Payment Status Grid _buildMemberPaymentGrid(payments, monthName, year, month), ], ) : const SizedBox.shrink(), ), SizedBox(height: 16.h), ], ); } void _toggleMonthExpansion(String monthKey) { setState(() { if (_expandedMonths.contains(monthKey)) { _expandedMonths.remove(monthKey); } else { _expandedMonths.add(monthKey); } }); } void _expandAllMonths() { setState(() { final allMonths = _getAllMonthsFromStartDate(); _expandedMonths.addAll(allMonths); }); } void _collapseAllMonths() { setState(() { _expandedMonths.clear(); }); } Widget _buildMemberPaymentGrid(List payments, String monthName, int year, int month) { // Get all members from the group final allMembers = widget.group.members ?? []; // Create a map of user ID to payment for quick lookup final paymentMap = {}; for (final payment in payments) { paymentMap[payment.userId] = payment; } return Container( padding: EdgeInsets.all(8.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8.r), border: Border.all(color: Colors.grey.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Member Payment Status', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), SizedBox(height: 8.h), // Show message if no payments for this month if (payments.isEmpty) Container( width: double.infinity, padding: EdgeInsets.all(20.w), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(8.r), border: Border.all(color: Colors.grey.shade200), ), child: Column( children: [ Icon( Icons.payment_outlined, size: 32.w, color: Colors.grey.shade400, ), SizedBox(height: 8.h), Text( 'No payments recorded for this month', style: TextStyle( fontSize: 14.sp, color: Colors.grey.shade600, fontWeight: FontWeight.w500, ), ), SizedBox(height: 4.h), Text( 'Tap the edit button to add payments', style: TextStyle( fontSize: 12.sp, color: Colors.grey.shade500, ), ), ], ), ) else // Grid of member payment statuses GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 4.0, // Increased aspect ratio to make cards shorter crossAxisSpacing: 6.w, mainAxisSpacing: 6.h, ), itemCount: allMembers.length, itemBuilder: (context, index) { final member = allMembers[index]; final payment = paymentMap[member.userId]; return _buildMemberPaymentStatusCard(member, payment, monthName, year); }, ), ], ), ); } Widget _buildMemberPaymentStatusCard(dynamic member, Payment? payment, String monthName, int year) { final hasPayment = payment != null; final isSuccess = payment?.status == 'success'; final isPending = payment?.status == 'pending'; final isFailed = payment?.status == 'failed'; Color statusColor; IconData statusIcon; String statusText; if (hasPayment) { if (isSuccess) { statusColor = Colors.green; statusIcon = Icons.check_circle; statusText = 'Paid'; } else if (isPending) { statusColor = Colors.orange; statusIcon = Icons.schedule; statusText = 'Pending'; } else if (isFailed) { statusColor = Colors.red; statusIcon = Icons.error; statusText = 'Failed'; } else { statusColor = Colors.grey; statusIcon = Icons.help; statusText = 'Unknown'; } } else { statusColor = Colors.grey.shade400; statusIcon = Icons.cancel; statusText = 'Not Paid'; } return Container( padding: EdgeInsets.all(6.w), decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(6.r), border: Border.all(color: statusColor.withOpacity(0.3)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( children: [ Icon( statusIcon, color: statusColor, size: 14.w, ), SizedBox(width: 4.w), Expanded( child: Text( member.user?.fullName ?? 'Unknown', style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ], ), SizedBox(height: 2.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: Text( hasPayment ? _formatIndianCurrency(payment.amount) : '₹0', style: TextStyle( fontSize: 9.sp, color: statusColor, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), Container( padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 1.h), decoration: BoxDecoration( color: statusColor.withOpacity(0.2), borderRadius: BorderRadius.circular(3.r), ), child: Text( statusText, style: TextStyle( fontSize: 7.sp, fontWeight: FontWeight.w600, color: statusColor, ), ), ), ], ), ], ), ); } void _updateMonthPayments(int month, int year) { showDialog( context: context, builder: (context) => _buildUpdateMonthPaymentsDialog(month, year), ).then((result) { if (result == true) { _loadData(); // Refresh the payment data } }); } Widget _buildUpdateMonthPaymentsDialog(int month, int year) { final monthName = _getMonthName(month); final allMembers = widget.group.members ?? []; return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), ), child: Container( width: double.infinity, constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.8, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header Container( padding: EdgeInsets.all(20.w), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.only( topLeft: Radius.circular(16.r), topRight: Radius.circular(16.r), ), ), child: Row( children: [ Icon( Icons.edit_calendar, color: Colors.blue.shade700, size: 24.w, ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Update Payments', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, color: Colors.blue.shade800, ), ), Text( '$monthName $year', style: TextStyle( fontSize: 14.sp, color: Colors.blue.shade600, ), ), ], ), ), IconButton( onPressed: () => Navigator.of(context).pop(), icon: Icon( Icons.close, color: Colors.blue.shade700, ), ), ], ), ), // Content Flexible( child: SingleChildScrollView( padding: EdgeInsets.all(20.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Member Payment Status', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), SizedBox(height: 16.h), // Member payment status list ...allMembers.map((member) => _buildMemberPaymentUpdateCard(member, month, year)), ], ), ), ), // Footer Container( padding: EdgeInsets.all(20.w), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(16.r), bottomRight: Radius.circular(16.r), ), ), child: Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.of(context).pop(), style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.r), ), ), child: Text('Cancel'), ), ), SizedBox(width: 12.w), Expanded( child: ElevatedButton( onPressed: () { // TODO: Implement bulk payment update Navigator.of(context).pop(true); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade600, padding: EdgeInsets.symmetric(vertical: 12.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.r), ), ), child: Text('Update Payments'), ), ), ], ), ), ], ), ), ); } Widget _buildMemberPaymentUpdateCard(dynamic member, int month, int year) { // Find existing payment for this member Payment? existingPayment; try { existingPayment = _paymentService.payments.firstWhere( (p) => p.userId == member.userId && p.month == month && p.year == year, ); } catch (e) { existingPayment = null; } final hasPayment = existingPayment != null; final isSuccess = existingPayment?.status == 'success'; return Container( margin: EdgeInsets.only(bottom: 12.h), padding: EdgeInsets.all(12.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8.r), border: Border.all( color: isSuccess ? Colors.green.shade200 : Colors.grey.shade200, ), ), child: Row( children: [ // Member info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( member.user?.fullName ?? 'Unknown Member', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), Text( member.user?.mobileNumber ?? '', style: TextStyle( fontSize: 12.sp, color: Colors.grey.shade600, ), ), ], ), ), // Payment status Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( color: isSuccess ? Colors.green.shade100 : Colors.grey.shade100, borderRadius: BorderRadius.circular(4.r), ), child: Text( hasPayment ? (isSuccess ? 'Paid' : 'Pending') : 'Not Paid', style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: isSuccess ? Colors.green.shade700 : Colors.grey.shade700, ), ), ), SizedBox(height: 4.h), Text( hasPayment ? _formatIndianCurrency(existingPayment.amount) : '₹0', style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w500, color: Colors.grey.shade800, ), ), ], ), SizedBox(width: 12.w), // Action button GestureDetector( onTap: () => _recordPaymentForMember(member.userId), child: Container( padding: EdgeInsets.all(8.w), decoration: BoxDecoration( color: Colors.blue.shade100, borderRadius: BorderRadius.circular(6.r), ), child: Icon( Icons.add, size: 16.w, color: Colors.blue.shade700, ), ), ), ], ), ); } Widget _buildPendingTab() { return Obx(() { if (_paymentService.isLoading) { return const Center(child: CircularProgressIndicator()); } if (_paymentService.error.isNotEmpty) { return _buildErrorWidget(_paymentService.error); } final pendingPayments = _paymentService.pendingPayments; if (pendingPayments.isEmpty) { return _buildEmptyWidget('No pending payments'); } return RefreshIndicator( onRefresh: _loadData, child: ListView.separated( padding: EdgeInsets.all(16.w), itemCount: pendingPayments.length, separatorBuilder: (context, index) => SizedBox(height: 12.h), itemBuilder: (context, index) { return _buildPendingPaymentCard(pendingPayments[index]); }, ), ); }); } Widget _buildSummaryTab() { return Obx(() { if (_paymentService.isLoading) { return const Center(child: CircularProgressIndicator()); } if (_paymentService.error.isNotEmpty) { return _buildErrorWidget(_paymentService.error); } final summary = _paymentService.paymentSummary; if (summary.isEmpty) { return _buildEmptyWidget('No summary data available'); } return RefreshIndicator( onRefresh: _loadData, child: SingleChildScrollView( padding: EdgeInsets.all(16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSummaryCards(summary), SizedBox(height: 24.h), _buildCollectionProgress(summary), ], ), ), ); }); } Widget _buildPaymentCard(Payment payment) { return Card( elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.r), ), child: Padding( padding: EdgeInsets.all(12.w), child: Row( children: [ Container( padding: EdgeInsets.all(6.w), decoration: BoxDecoration( color: _getStatusColor(payment.status).withOpacity(0.1), borderRadius: BorderRadius.circular(6.r), ), child: Icon( _getStatusIcon(payment.status), color: _getStatusColor(payment.status), size: 16.w, ), ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( payment.user?.fullName ?? 'Unknown Member', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), SizedBox(height: 2.h), Row( children: [ _buildInfoChip( 'Method', _paymentService.getPaymentMethodLabel(payment.paymentMethod), Icons.payment, ), if (payment.transactionId != null) ...[ SizedBox(width: 6.w), _buildInfoChip( 'TXN', payment.transactionId!.substring(0, 8) + '...', Icons.receipt, ), ], ], ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( _formatIndianCurrency(payment.amount), style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), SizedBox(height: 4.h), _buildStatusChip(payment.status), ], ), ], ), ), ); } Widget _buildPendingPaymentCard(Map pendingPayment) { final member = pendingPayment['member']; final amountDue = pendingPayment['amount_due']; return Card( elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.r), ), child: Padding( padding: EdgeInsets.all(16.w), child: Row( children: [ Container( padding: EdgeInsets.all(8.w), decoration: BoxDecoration( color: Colors.orange.shade100, borderRadius: BorderRadius.circular(8.r), ), child: Icon( Icons.schedule, color: Colors.orange.shade600, size: 20.w, ), ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( member['full_name'] ?? 'Unknown Member', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), Text( member['mobile_number'] ?? '', style: TextStyle( fontSize: 14.sp, color: Colors.grey.shade600, ), ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '₹${(amountDue is num ? amountDue.toStringAsFixed(0) : '0')}', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), SizedBox(height: 2.h), ElevatedButton( onPressed: () => _recordPaymentForMember(member['id']), style: ElevatedButton.styleFrom( backgroundColor: Colors.green.shade600, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), minimumSize: Size(0, 0), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6.r), ), ), child: Text( 'Record', style: TextStyle(fontSize: 12.sp), ), ), ], ), ], ), ), ); } Widget _buildSummaryCards(Map summary) { final stats = summary['payment_stats']; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Payment Statistics', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), SizedBox(height: 16.h), Column( children: [ IntrinsicHeight( child: Row( children: [ Expanded( child: _buildSummaryCard( 'Total Payments', stats['total_payments'].toString(), Icons.payments, Colors.blue, ), ), SizedBox(width: 8.w), Expanded( child: _buildSummaryCard( 'Successful', stats['successful_payments'].toString(), Icons.check_circle, Colors.green, ), ), ], ), ), SizedBox(height: 8.h), IntrinsicHeight( child: Row( children: [ Expanded( child: _buildSummaryCard( 'Pending', stats['pending_payments'].toString(), Icons.schedule, Colors.orange, ), ), SizedBox(width: 8.w), Expanded( child: _buildSummaryCard( 'Failed', stats['failed_payments'].toString(), Icons.error, Colors.red, ), ), ], ), ), ], ), ], ); } Widget _buildCollectionProgress(Map summary) { final stats = summary['payment_stats']; final totalCollection = stats['total_collection'] ?? 0.0; final expectedCollection = stats['expected_collection'] ?? 1.0; final percentage = (stats['collection_percentage'] ?? 0.0).clamp(0.0, 100.0); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Collection Progress', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), SizedBox(height: 16.h), Card( elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.r), ), child: Padding( padding: EdgeInsets.all(20.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Collected: ₹${(totalCollection is num ? totalCollection.toStringAsFixed(0) : '0')}', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.green.shade700, ), ), Text( '${(percentage is num ? percentage.toStringAsFixed(1) : '0.0')}%', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.green.shade700, ), ), ], ), SizedBox(height: 8.h), LinearProgressIndicator( value: (percentage is num ? percentage / 100 : 0.0), backgroundColor: Colors.grey.shade200, valueColor: AlwaysStoppedAnimation(Colors.green.shade600), minHeight: 8.h, ), SizedBox(height: 8.h), Text( 'Expected: ₹${(expectedCollection is num ? expectedCollection.toStringAsFixed(0) : '0')}', style: TextStyle( fontSize: 14.sp, color: Colors.grey.shade600, ), ), ], ), ), ), ], ); } Widget _buildSummaryCard(String title, String value, IconData icon, Color color) { return Card( elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.r), ), child: Padding( padding: EdgeInsets.all(4.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Icon(icon, color: color, size: 12.w), SizedBox(width: 2.w), Expanded( child: Text( title, style: TextStyle( fontSize: 8.sp, color: Colors.grey.shade600, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ], ), Text( value, style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: color, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ], ), ), ); } Widget _buildStatusFilter() { return DropdownButtonFormField( value: _selectedStatus, decoration: InputDecoration( labelText: 'Status', labelStyle: TextStyle(fontSize: 9.sp), border: OutlineInputBorder(borderRadius: BorderRadius.circular(3.r)), contentPadding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.h), isDense: true, ), style: TextStyle(fontSize: 9.sp), items: [ const DropdownMenuItem(value: null, child: Text('All')), const DropdownMenuItem(value: 'success', child: Text('Success')), const DropdownMenuItem(value: 'pending', child: Text('Pending')), const DropdownMenuItem(value: 'failed', child: Text('Failed')), const DropdownMenuItem(value: 'cancelled', child: Text('Cancelled')), ], onChanged: (value) { setState(() { _selectedStatus = value; }); _applyFilters(); }, ); } Widget _buildMonthFilter() { final months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; return DropdownButtonFormField( value: _selectedMonth, decoration: InputDecoration( labelText: 'Month', labelStyle: TextStyle(fontSize: 9.sp), border: OutlineInputBorder(borderRadius: BorderRadius.circular(3.r)), contentPadding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.h), isDense: true, ), style: TextStyle(fontSize: 9.sp), items: [ const DropdownMenuItem(value: null, child: Text('All')), ...List.generate(12, (index) { return DropdownMenuItem( value: index + 1, child: Text(months[index]), ); }), ], onChanged: (value) { setState(() { _selectedMonth = value; }); _applyFilters(); }, ); } Widget _buildYearFilter() { final currentYear = DateTime.now().year; final years = List.generate(5, (index) => currentYear - 2 + index); return DropdownButtonFormField( value: _selectedYear, decoration: InputDecoration( labelText: 'Year', labelStyle: TextStyle(fontSize: 9.sp), border: OutlineInputBorder(borderRadius: BorderRadius.circular(3.r)), contentPadding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.h), isDense: true, ), style: TextStyle(fontSize: 9.sp), items: [ const DropdownMenuItem(value: null, child: Text('All')), ...years.map((year) { return DropdownMenuItem( value: year, child: Text(year.toString()), ); }), ], onChanged: (value) { setState(() { _selectedYear = value; }); _applyFilters(); }, ); } Widget _buildStatusChip(String status) { return Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( color: _getStatusColor(status).withOpacity(0.1), borderRadius: BorderRadius.circular(12.r), border: Border.all(color: _getStatusColor(status).withOpacity(0.3)), ), child: Text( status.toUpperCase(), style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: _getStatusColor(status), ), ), ); } Widget _buildInfoChip(String label, String value, IconData icon) { return Container( padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(4.r), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 10.w, color: Colors.grey.shade600), SizedBox(width: 2.w), Text( '$label: $value', style: TextStyle( fontSize: 9.sp, color: Colors.grey.shade700, ), ), ], ), ); } Widget _buildErrorWidget(String error) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 64.w, color: Colors.red.shade300, ), SizedBox(height: 16.h), Text( 'Error', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, color: Colors.red.shade700, ), ), SizedBox(height: 8.h), Text( error, style: TextStyle( fontSize: 14.sp, color: Colors.red.shade600, ), textAlign: TextAlign.center, ), SizedBox(height: 16.h), ElevatedButton( onPressed: _loadData, child: const Text('Retry'), ), ], ), ); } Widget _buildEmptyWidget(String message) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.payment, size: 64.w, color: Colors.grey.shade300, ), SizedBox(height: 16.h), Text( message, style: TextStyle( fontSize: 16.sp, color: Colors.grey.shade600, ), ), ], ), ); } Color _getStatusColor(String status) { switch (status) { case 'success': return Colors.green; case 'pending': return Colors.orange; case 'failed': return Colors.red; case 'cancelled': return Colors.grey; default: return Colors.grey; } } IconData _getStatusIcon(String status) { switch (status) { case 'success': return Icons.check_circle; case 'pending': return Icons.schedule; case 'failed': return Icons.error; case 'cancelled': return Icons.cancel; default: return Icons.help; } } String _getMonthName(int month) { const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; return months[month - 1]; } String _formatIndianCurrency(double amount) { // Convert to integer to avoid decimal places int intAmount = amount.round(); // Format with Indian numbering system (commas every 2 digits after the first 3) String amountStr = intAmount.toString(); String formatted = ''; if (amountStr.length <= 3) { formatted = amountStr; } else { // For amounts > 999, use Indian comma system int remaining = amountStr.length; int start = 0; // First group (rightmost 3 digits) if (remaining > 3) { formatted = amountStr.substring(amountStr.length - 3); remaining -= 3; start = amountStr.length - 3; } else { formatted = amountStr; remaining = 0; } // Subsequent groups (2 digits each) while (remaining > 0) { int groupSize = remaining >= 2 ? 2 : remaining; int groupStart = start - groupSize; String group = amountStr.substring(groupStart, start); formatted = group + ',' + formatted; start = groupStart; remaining -= groupSize; } } return '₹$formatted'; } void _applyFilters() { _paymentService.loadGroupPayments( widget.group.id, status: _selectedStatus, month: _selectedMonth, year: _selectedYear, ); } void _showRecordPaymentDialog() { showDialog( context: context, builder: (context) => RecordPaymentDialog(group: widget.group), ).then((result) { if (result == true) { _loadData(); } }); } void _recordPaymentForMember(String memberId) { final member = widget.group.members?.firstWhere( (m) => m.userId == memberId, orElse: () => widget.group.members!.first, ); showDialog( context: context, builder: (context) => RecordPaymentDialog( group: widget.group, selectedMember: member, ), ).then((result) { if (result == true) { _loadData(); } }); } }