2645 lines
84 KiB
Dart
2645 lines
84 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import '../../core/services/chit_group_service.dart';
|
|
import '../../core/services/payment_service.dart';
|
|
import '../../core/models/chit_group.dart';
|
|
import '../../core/models/group_member.dart';
|
|
import '../../core/models/payment.dart';
|
|
import '../../core/models/monthly_draw.dart';
|
|
import '../../core/models/financial_table_entry.dart';
|
|
import '../../shared/widgets/financial_table.dart';
|
|
import 'member_selection_dialog.dart';
|
|
import 'add_user_dialog.dart';
|
|
import 'combined_draw_dialog.dart';
|
|
import 'add_past_draw_dialog.dart';
|
|
import 'add_past_payments_dialog.dart';
|
|
import 'record_payment_dialog.dart';
|
|
import 'payment_history_page.dart';
|
|
import '../../features/chitfund_schedule/chitfund_schedule_page.dart';
|
|
import '../../features/monthly_payments/monthly_payment_status_page.dart';
|
|
|
|
class GroupDetailsPage extends StatefulWidget {
|
|
final ChitGroup group;
|
|
|
|
const GroupDetailsPage({
|
|
super.key,
|
|
required this.group,
|
|
});
|
|
|
|
@override
|
|
State<GroupDetailsPage> createState() => _GroupDetailsPageState();
|
|
}
|
|
|
|
class _GroupDetailsPageState extends State<GroupDetailsPage> with SingleTickerProviderStateMixin {
|
|
late TabController _tabController;
|
|
final ChitGroupService _service = ChitGroupService.to;
|
|
late final PaymentService _paymentService;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 5, vsync: this);
|
|
|
|
// Get payment service (already initialized in app.dart)
|
|
_paymentService = Get.find<PaymentService>();
|
|
|
|
// Load group details, members, payments, draws, and financial data
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_service.loadChitGroupDetails(widget.group.id);
|
|
_service.loadGroupMembers(widget.group.id);
|
|
_service.loadGroupStats(widget.group.id);
|
|
_service.loadGroupMonthlyDraws(widget.group.id);
|
|
_service.loadGroupFinancialData(widget.group.id);
|
|
|
|
// Load payment data with delay to avoid setState during build
|
|
Future.delayed(const Duration(milliseconds: 300), () {
|
|
if (mounted) {
|
|
_paymentService.loadGroupPayments(widget.group.id);
|
|
_paymentService.loadPendingPayments(widget.group.id);
|
|
_paymentService.loadPaymentSummary(widget.group.id);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tabController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.grey.shade50,
|
|
appBar: AppBar(
|
|
leading: IconButton(
|
|
icon: Icon(Icons.arrow_back, color: Colors.grey.shade800, size: 24.w),
|
|
onPressed: () => Navigator.pop(context),
|
|
tooltip: 'Back',
|
|
),
|
|
title: Text(
|
|
widget.group.name,
|
|
style: TextStyle(
|
|
fontSize: 20.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade900,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
backgroundColor: Colors.white,
|
|
iconTheme: IconThemeData(color: Colors.grey.shade800),
|
|
elevation: 0,
|
|
actions: [
|
|
// Actions menu - always visible
|
|
PopupMenuButton<String>(
|
|
icon: Icon(Icons.more_vert, size: 24.w, color: Colors.grey.shade700),
|
|
tooltip: 'More Options',
|
|
onSelected: (value) {
|
|
if (value == 'select') {
|
|
_showMemberSelectionDialog(context);
|
|
} else if (value == 'add_user') {
|
|
_showAddUserDialog(context);
|
|
} else if (value == 'add_past_draw') {
|
|
_showAddPastDrawDialog(context);
|
|
} else if (value == 'add_past_payments') {
|
|
_showAddPastPaymentsDialog(context);
|
|
}
|
|
},
|
|
itemBuilder: (context) {
|
|
final currentMemberCount = widget.group.currentMemberCount;
|
|
final maxMembers = widget.group.maxMembers;
|
|
final canAddMembers = currentMemberCount < maxMembers;
|
|
|
|
return [
|
|
// Add members options (if not full)
|
|
if (canAddMembers) ...[
|
|
PopupMenuItem(
|
|
value: 'select',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.people_alt, color: Colors.blue.shade600),
|
|
SizedBox(width: 12.w),
|
|
const Text('Select Members'),
|
|
],
|
|
),
|
|
),
|
|
PopupMenuItem(
|
|
value: 'add_user',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.person_add, color: Colors.green.shade600),
|
|
SizedBox(width: 12.w),
|
|
const Text('Add New User'),
|
|
],
|
|
),
|
|
),
|
|
const PopupMenuDivider(),
|
|
],
|
|
// Backfill options (always available)
|
|
PopupMenuItem(
|
|
value: 'add_past_draw',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.history, color: Colors.blue.shade600),
|
|
SizedBox(width: 12.w),
|
|
const Text('Add Past Draw Result'),
|
|
],
|
|
),
|
|
),
|
|
PopupMenuItem(
|
|
value: 'add_past_payments',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.payment_outlined, color: Colors.green.shade600),
|
|
SizedBox(width: 12.w),
|
|
const Text('Add Past Payments'),
|
|
],
|
|
),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
],
|
|
bottom: PreferredSize(
|
|
preferredSize: Size.fromHeight(64.h),
|
|
child: Container(
|
|
height: 64.h,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
border: Border(bottom: BorderSide(color: Colors.grey.shade200, width: 1)),
|
|
),
|
|
child: TabBar(
|
|
controller: _tabController,
|
|
indicatorColor: Colors.blue.shade600,
|
|
labelColor: Colors.blue.shade600,
|
|
unselectedLabelColor: Colors.grey.shade700,
|
|
labelStyle: TextStyle(fontSize: 10.sp, fontWeight: FontWeight.w600),
|
|
unselectedLabelStyle: TextStyle(fontSize: 10.sp, fontWeight: FontWeight.w500),
|
|
isScrollable: false,
|
|
tabAlignment: TabAlignment.fill,
|
|
tabs: const [
|
|
Tab(icon: Icon(Icons.dashboard, size: 20), text: 'Overview'),
|
|
Tab(icon: Icon(Icons.people, size: 20), text: 'Members'),
|
|
Tab(icon: Icon(Icons.payment, size: 20), text: 'Pay'),
|
|
Tab(icon: Icon(Icons.casino, size: 20), text: 'Draws'),
|
|
Tab(icon: Icon(Icons.table_chart, size: 20), text: 'Finance'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
body: TabBarView(
|
|
controller: _tabController,
|
|
children: [
|
|
_buildOverviewTab(),
|
|
_buildMembersTab(),
|
|
_buildPaymentsTab(),
|
|
_buildDrawsTab(),
|
|
_buildFinancialTab(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildOverviewTab() {
|
|
return Obx(() {
|
|
final group = _service.selectedGroup.value ?? widget.group;
|
|
final stats = _service.groupStats.value;
|
|
|
|
return SingleChildScrollView(
|
|
padding: EdgeInsets.all(16.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Welcome Section
|
|
Row(
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 24.r,
|
|
backgroundColor: Colors.blue.shade100,
|
|
child: Icon(Icons.group, size: 24.w, color: Colors.blue.shade600),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Hello ${group.name}',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
Text(
|
|
'Welcome Back!',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(Icons.notifications_outlined, size: 24.w, color: Colors.grey.shade600),
|
|
],
|
|
),
|
|
SizedBox(height: 24.h),
|
|
|
|
// Group Balance Card
|
|
Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [Colors.blue.shade600, Colors.purple.shade600],
|
|
),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.blue.shade200.withOpacity(0.3),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 3),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Chitfund Balance',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.white.withOpacity(0.9),
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.visibility_off, size: 16.w, color: Colors.white.withOpacity(0.8)),
|
|
SizedBox(width: 4.w),
|
|
Icon(Icons.qr_code, size: 16.w, color: Colors.white.withOpacity(0.8)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
_formatIndianCurrency(group.totalValue),
|
|
style: TextStyle(
|
|
fontSize: 24.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
SizedBox(height: 6.h),
|
|
Row(
|
|
children: [
|
|
_buildStatusChip(group.status),
|
|
SizedBox(width: 6.w),
|
|
Text(
|
|
'${group.maxMembers} members',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.white.withOpacity(0.8),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 24.h),
|
|
|
|
// Current Month & Draw Information
|
|
_buildCurrentMonthSection(group),
|
|
SizedBox(height: 24.h),
|
|
|
|
// Quick Actions
|
|
Text(
|
|
'Quick Actions',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
// First row - 3 buttons
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
_buildQuickActionButton('Members', Icons.people, Colors.green),
|
|
_buildQuickActionButton('Payments', Icons.payment, Colors.orange),
|
|
_buildQuickActionButton('Draws', Icons.casino, Colors.purple),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
// Second row - 3 buttons
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
_buildQuickActionButton('Schedule', Icons.calendar_view_month, Colors.teal, onTap: () => _navigateToSchedule()),
|
|
_buildQuickActionButton('Payment Status', Icons.assignment_turned_in, Colors.amber, onTap: () => _navigateToPaymentStatus()),
|
|
_buildQuickActionButton('History', Icons.history, Colors.indigo),
|
|
],
|
|
),
|
|
SizedBox(height: 32.h),
|
|
|
|
// Chitfund Information
|
|
Text(
|
|
'Chitfund Information',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
_buildInfoGrid([
|
|
_buildInfoItem('Total Value', _formatIndianCurrency(group.totalValue), Icons.currency_rupee),
|
|
_buildInfoItem('Monthly Installment', _formatIndianCurrency(group.monthlyInstallment), Icons.payment),
|
|
_buildInfoItem('Duration', '${group.durationMonths} months', Icons.calendar_today),
|
|
_buildInfoItem('Max Members', '${group.maxMembers}', Icons.people),
|
|
_buildInfoItem('Commission', _formatIndianCurrency(group.foremanCommissionAmount), Icons.percent),
|
|
_buildInfoItem('Draw Date', '${group.drawDate}th', Icons.event),
|
|
]),
|
|
SizedBox(height: 32.h),
|
|
|
|
// Statistics Cards
|
|
if (stats != null) ...[
|
|
Text(
|
|
'Statistics',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
_buildStatsGrid(stats),
|
|
SizedBox(height: 32.h),
|
|
],
|
|
|
|
// Recent Activity
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Recent Activity',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: Text(
|
|
'See all >',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.blue.shade600,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
_buildActivityList(),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
Widget _buildQuickActionButton(String label, IconData icon, Color color, {VoidCallback? onTap}) {
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: 48.w,
|
|
height: 48.w,
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(24.r),
|
|
),
|
|
child: Icon(icon, size: 20.w, color: color),
|
|
),
|
|
SizedBox(height: 6.h),
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.black87,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMembersTab() {
|
|
return Obx(() {
|
|
final members = _service.groupMembers;
|
|
|
|
if (_service.isLoading.value) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
if (members.isEmpty) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.people_outline,
|
|
size: 64.w,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
'No members yet',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
'Add members to get started',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade500,
|
|
),
|
|
),
|
|
SizedBox(height: 24.h),
|
|
if (widget.group.isForming)
|
|
Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _showMemberSelectionDialog(context),
|
|
icon: const Icon(Icons.people_alt),
|
|
label: const Text('Select Members'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _showAddUserDialog(context),
|
|
icon: const Icon(Icons.person_add),
|
|
label: const Text('Add New User'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.green.shade600,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Text(
|
|
'Select Members: Choose from existing users\nAdd New User: Create a new user account',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: () => _service.loadGroupMembers(widget.group.id),
|
|
child: ListView.builder(
|
|
padding: EdgeInsets.all(16.w),
|
|
itemCount: members.length,
|
|
itemBuilder: (context, index) {
|
|
final member = members[index];
|
|
return _buildMemberCard(member);
|
|
},
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
Widget _buildPaymentsTab() {
|
|
return Obx(() {
|
|
final payments = _paymentService.payments;
|
|
final pendingPayments = _paymentService.pendingPayments;
|
|
final paymentSummary = _paymentService.paymentSummary;
|
|
|
|
if (_paymentService.isLoading) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: () async {
|
|
await _paymentService.loadGroupPayments(widget.group.id);
|
|
await _paymentService.loadPendingPayments(widget.group.id);
|
|
await _paymentService.loadPaymentSummary(widget.group.id);
|
|
},
|
|
child: SingleChildScrollView(
|
|
padding: EdgeInsets.all(16.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Quick Actions
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _showRecordPaymentDialog(),
|
|
icon: Icon(Icons.add, size: 20.w),
|
|
label: Text(
|
|
'Record Payment',
|
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.green.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: OutlinedButton.icon(
|
|
onPressed: () => _navigateToPaymentHistory(),
|
|
icon: Icon(Icons.history, size: 20.w),
|
|
label: Text(
|
|
'View History',
|
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
style: OutlinedButton.styleFrom(
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 24.h),
|
|
|
|
// Payment Summary Cards
|
|
if (paymentSummary.isNotEmpty) ...[
|
|
Text(
|
|
'Payment Summary',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
_buildPaymentSummaryCards(paymentSummary),
|
|
SizedBox(height: 24.h),
|
|
],
|
|
|
|
// Pending Payments
|
|
if (pendingPayments.isNotEmpty) ...[
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Pending Payments (${pendingPayments.length})',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () => _navigateToPaymentHistory(),
|
|
child: Text(
|
|
'View All',
|
|
style: TextStyle(fontSize: 14.sp),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
...pendingPayments.take(3).map((pending) => Padding(
|
|
padding: EdgeInsets.only(bottom: 12.h),
|
|
child: _buildPendingPaymentCard(pending),
|
|
)),
|
|
SizedBox(height: 24.h),
|
|
],
|
|
|
|
// Recent Payments
|
|
if (payments.isNotEmpty) ...[
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Recent Payments',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () => _navigateToPaymentHistory(),
|
|
child: Text(
|
|
'View All',
|
|
style: TextStyle(fontSize: 14.sp),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
...payments.take(5).map((payment) => Padding(
|
|
padding: EdgeInsets.only(bottom: 12.h),
|
|
child: _buildRecentPaymentCard(payment),
|
|
)),
|
|
] else ...[
|
|
// Empty state
|
|
Center(
|
|
child: Column(
|
|
children: [
|
|
SizedBox(height: 40.h),
|
|
Icon(
|
|
Icons.payment_outlined,
|
|
size: 64.w,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
'No payments yet',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
'Start recording member payments',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
Widget _buildDrawsTab() {
|
|
return Obx(() {
|
|
final draws = _service.monthlyDraws;
|
|
|
|
if (_service.isLoading.value) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
if (draws.isEmpty) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.casino_outlined,
|
|
size: 64.w,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
'No draws yet',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
'Draws will appear here once conducted',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade500,
|
|
),
|
|
),
|
|
SizedBox(height: 24.h),
|
|
if (widget.group.isActive)
|
|
ElevatedButton.icon(
|
|
onPressed: () => _conductDraw(context),
|
|
icon: const Icon(Icons.casino),
|
|
label: const Text('Conduct Draw'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.purple.shade600,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: () => _service.loadGroupMonthlyDraws(widget.group.id),
|
|
child: ListView.builder(
|
|
padding: EdgeInsets.all(16.w),
|
|
itemCount: draws.length,
|
|
itemBuilder: (context, index) {
|
|
final draw = draws[index];
|
|
return _buildDrawCard(draw);
|
|
},
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
Widget _buildFinancialTab() {
|
|
return Obx(() {
|
|
final financialData = _service.financialData;
|
|
|
|
if (_service.isLoading.value) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
// Show empty state if no data from backend
|
|
if (financialData.isEmpty) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(32.w),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.table_chart_outlined,
|
|
size: 64.w,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
'No Financial Data Available',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
'Financial data will appear here once\nthe group becomes active',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
SizedBox(height: 24.h),
|
|
ElevatedButton.icon(
|
|
onPressed: () => _service.loadGroupFinancialData(widget.group.id),
|
|
icon: const Icon(Icons.refresh),
|
|
label: const Text('Refresh Data'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 24.w,
|
|
vertical: 12.h,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: () => _service.loadGroupFinancialData(widget.group.id),
|
|
child: SingleChildScrollView(
|
|
padding: EdgeInsets.all(16.w),
|
|
child: Column(
|
|
children: [
|
|
// Always use compact table for better mobile experience
|
|
CompactFinancialTable(
|
|
entries: financialData,
|
|
isLoading: _service.isLoading.value,
|
|
onRefresh: () => _service.loadGroupFinancialData(widget.group.id),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Summary cards
|
|
_buildFinancialSummaryCards(financialData),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
Widget _buildFinancialSummaryCards(List<FinancialTableEntry> financialData) {
|
|
if (financialData.isEmpty) return const SizedBox.shrink();
|
|
|
|
final totalEntry = financialData.lastWhere(
|
|
(entry) => entry.monthYear.toLowerCase() == 'total',
|
|
orElse: () => financialData.last,
|
|
);
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Summary',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildSummaryCard(
|
|
'Total Chit Value',
|
|
_formatIndianCurrency(totalEntry.chitValue),
|
|
Icons.currency_rupee,
|
|
Colors.blue,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: _buildSummaryCard(
|
|
'Total Bid Amount',
|
|
_formatIndianCurrency(totalEntry.bidAmount),
|
|
Icons.payment,
|
|
Colors.green,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildSummaryCard(
|
|
'Total Dividend',
|
|
_formatIndianCurrency(totalEntry.dividendAmount),
|
|
Icons.trending_up,
|
|
totalEntry.dividendAmount >= 0 ? Colors.green : Colors.red,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: _buildSummaryCard(
|
|
'Total Commission',
|
|
_formatIndianCurrency(totalEntry.commissionInstallment),
|
|
Icons.percent,
|
|
Colors.orange,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildSummaryCard(String title, String value, IconData icon, Color color) {
|
|
return Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade200,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(icon, color: color, size: 20.w),
|
|
SizedBox(width: 6.w),
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: color,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDrawCard(MonthlyDraw draw) {
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 12.h),
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade200,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: _getDrawStatusColor(draw.status).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Icon(
|
|
Icons.casino,
|
|
color: _getDrawStatusColor(draw.status),
|
|
size: 24.w,
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'${draw.month}/${draw.year} Draw',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 4.h),
|
|
if (draw.winner != null)
|
|
Text(
|
|
'Winner: ${draw.winner!.fullName}',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Row(
|
|
children: [
|
|
_buildDrawStatusChip(draw.status),
|
|
SizedBox(width: 6.w),
|
|
Text(
|
|
_formatIndianCurrency(draw.prizeAmount),
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.green.shade600,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Text(
|
|
'${draw.drawDate.day}/${draw.drawDate.month}/${draw.drawDate.year}',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDrawStatusChip(String status) {
|
|
Color color;
|
|
String label;
|
|
|
|
switch (status) {
|
|
case 'completed':
|
|
color = Colors.green;
|
|
label = 'Completed';
|
|
break;
|
|
case 'pending':
|
|
color = Colors.orange;
|
|
label = 'Pending';
|
|
break;
|
|
case 'cancelled':
|
|
color = Colors.red;
|
|
label = 'Cancelled';
|
|
break;
|
|
default:
|
|
color = Colors.grey;
|
|
label = status;
|
|
}
|
|
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: color, width: 0.5),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Color _getDrawStatusColor(String status) {
|
|
switch (status) {
|
|
case 'completed':
|
|
return Colors.green;
|
|
case 'pending':
|
|
return Colors.orange;
|
|
case 'cancelled':
|
|
return Colors.red;
|
|
default:
|
|
return Colors.grey;
|
|
}
|
|
}
|
|
|
|
void _showDrawDetails(MonthlyDraw draw) {
|
|
Get.snackbar('Coming Soon', 'Draw details page will be implemented next');
|
|
}
|
|
|
|
Widget _buildStatusChip(String status) {
|
|
Color color;
|
|
String label;
|
|
|
|
switch (status) {
|
|
case 'forming':
|
|
color = Colors.orange;
|
|
label = 'Forming';
|
|
break;
|
|
case 'active':
|
|
color = Colors.green;
|
|
label = 'Active';
|
|
break;
|
|
case 'completed':
|
|
color = Colors.grey;
|
|
label = 'Completed';
|
|
break;
|
|
default:
|
|
color = Colors.grey;
|
|
label = status;
|
|
}
|
|
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
border: Border.all(color: color),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoGrid(List<Widget> items) {
|
|
return Column(
|
|
children: [
|
|
for (int i = 0; i < items.length; i += 2)
|
|
Padding(
|
|
padding: EdgeInsets.only(bottom: 12.h),
|
|
child: Row(
|
|
children: [
|
|
Expanded(child: items[i]),
|
|
if (i + 1 < items.length) ...[
|
|
SizedBox(width: 12.w),
|
|
Expanded(child: items[i + 1]),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoItem(String label, String value, IconData icon) {
|
|
return Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade200,
|
|
blurRadius: 2,
|
|
offset: const Offset(0, 1),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(icon, size: 16.w, color: Colors.blue.shade600),
|
|
SizedBox(width: 6.w),
|
|
Expanded(
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 6.h),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 2,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatsCard(String title, String value, IconData icon, Color color) {
|
|
return Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade200,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(icon, color: color, size: 24.w),
|
|
const Spacer(),
|
|
Icon(
|
|
Icons.trending_up,
|
|
color: Colors.green.shade600,
|
|
size: 20.w,
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 20.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 4.h),
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatsGrid(Map<String, dynamic> stats) {
|
|
return Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildStatsCard(
|
|
'Total Collection',
|
|
_formatIndianCurrency(stats['total_collection']?.toDouble() ?? 0),
|
|
Icons.account_balance_wallet,
|
|
Colors.blue,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: _buildStatsCard(
|
|
'Pending Amount',
|
|
_formatIndianCurrency(stats['pending_amount']?.toDouble() ?? 0),
|
|
Icons.pending,
|
|
Colors.orange,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildStatsCard(
|
|
'Completed Draws',
|
|
'${stats['completed_draws'] ?? 0}',
|
|
Icons.check_circle,
|
|
Colors.green,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: _buildStatsCard(
|
|
'Remaining Draws',
|
|
'${stats['remaining_draws'] ?? 0}',
|
|
Icons.schedule,
|
|
Colors.purple,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildActivityList() {
|
|
return Column(
|
|
children: [
|
|
_buildActivityItem(
|
|
'New member joined',
|
|
'2 hours ago',
|
|
Icons.person_add,
|
|
Colors.green,
|
|
'+${_formatIndianCurrency(widget.group.monthlyInstallment)}',
|
|
),
|
|
_buildActivityItem(
|
|
'Payment received',
|
|
'4 hours ago',
|
|
Icons.payment,
|
|
Colors.blue,
|
|
'+${_formatIndianCurrency(widget.group.monthlyInstallment)}',
|
|
),
|
|
_buildActivityItem(
|
|
'Monthly draw completed',
|
|
'1 day ago',
|
|
Icons.casino,
|
|
Colors.orange,
|
|
'-${_formatIndianCurrency(widget.group.totalValue / widget.group.maxMembers)}',
|
|
),
|
|
_buildActivityItem(
|
|
'Chitfund started',
|
|
'3 days ago',
|
|
Icons.play_arrow,
|
|
Colors.purple,
|
|
'₹0',
|
|
),
|
|
_buildActivityItem(
|
|
'Chitfund created',
|
|
'1 week ago',
|
|
Icons.group_add,
|
|
Colors.indigo,
|
|
'₹0',
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildActivityItem(String title, String time, IconData icon, Color color, String amount) {
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 12.h),
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade200,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Icon(icon, color: color, size: 20.w),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 4.h),
|
|
Text(
|
|
time,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Text(
|
|
amount,
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: amount.startsWith('+') ? Colors.green.shade600 :
|
|
amount.startsWith('-') ? Colors.red.shade600 : Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMemberCard(GroupMember member) {
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 12.h),
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade200,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 24.r,
|
|
backgroundColor: Colors.blue.shade100,
|
|
child: Text(
|
|
(member.user?.fullName.isNotEmpty == true
|
|
? member.user!.fullName.substring(0, 1)
|
|
: 'M').toUpperCase(),
|
|
style: TextStyle(
|
|
color: Colors.blue.shade700,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 16.sp,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
member.user?.fullName ?? 'Unknown Member',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 16.sp,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 4.h),
|
|
Text(
|
|
member.user?.mobileNumber ?? '',
|
|
style: TextStyle(fontSize: 14.sp, color: Colors.grey.shade600),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Row(
|
|
children: [
|
|
_buildMemberStatusChip(member.status),
|
|
SizedBox(width: 6.w),
|
|
Text(
|
|
'${_formatIndianCurrency(member.totalPaid)} paid',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.green.shade600,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
PopupMenuButton<String>(
|
|
icon: Icon(Icons.more_vert, size: 24.w, color: Colors.grey.shade600),
|
|
onSelected: (value) => _handleMemberAction(value, member),
|
|
itemBuilder: (context) => [
|
|
const PopupMenuItem(
|
|
value: 'view',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.visibility, size: 18),
|
|
SizedBox(width: 8),
|
|
Text('View Details'),
|
|
],
|
|
),
|
|
),
|
|
if (member.status == 'active')
|
|
const PopupMenuItem(
|
|
value: 'suspend',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.pause, size: 18),
|
|
SizedBox(width: 8),
|
|
Text('Suspend'),
|
|
],
|
|
),
|
|
),
|
|
if (member.status == 'suspended')
|
|
const PopupMenuItem(
|
|
value: 'activate',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.play_arrow, size: 18),
|
|
SizedBox(width: 8),
|
|
Text('Activate'),
|
|
],
|
|
),
|
|
),
|
|
const PopupMenuItem(
|
|
value: 'remove',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.remove_circle, size: 18, color: Colors.red),
|
|
SizedBox(width: 8),
|
|
Text('Remove', style: TextStyle(color: Colors.red)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPaymentCard(Payment payment) {
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 12.h),
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade200,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: _getPaymentStatusColor(payment.status).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Icon(
|
|
Icons.payment,
|
|
color: _getPaymentStatusColor(payment.status),
|
|
size: 20.w,
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
payment.user?.fullName ?? 'Unknown Member',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 4.h),
|
|
Text(
|
|
_formatIndianCurrency(payment.amount),
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Row(
|
|
children: [
|
|
_buildPaymentStatusChip(payment.status),
|
|
SizedBox(width: 6.w),
|
|
Text(
|
|
'${payment.month}/${payment.year}',
|
|
style: TextStyle(fontSize: 14.sp, color: Colors.grey.shade600),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Text(
|
|
payment.paidAt != null
|
|
? '${payment.paidAt!.day}/${payment.paidAt!.month}/${payment.paidAt!.year}'
|
|
: 'Pending',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMemberStatusChip(String status) {
|
|
Color color;
|
|
String label;
|
|
|
|
switch (status) {
|
|
case 'active':
|
|
color = Colors.green;
|
|
label = 'Active';
|
|
break;
|
|
case 'suspended':
|
|
color = Colors.orange;
|
|
label = 'Suspended';
|
|
break;
|
|
case 'inactive':
|
|
color = Colors.grey;
|
|
label = 'Inactive';
|
|
break;
|
|
default:
|
|
color = Colors.grey;
|
|
label = status;
|
|
}
|
|
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: color, width: 0.5),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPaymentStatusChip(String status) {
|
|
Color color;
|
|
String label;
|
|
|
|
switch (status) {
|
|
case 'paid':
|
|
color = Colors.green;
|
|
label = 'Paid';
|
|
break;
|
|
case 'pending':
|
|
color = Colors.orange;
|
|
label = 'Pending';
|
|
break;
|
|
case 'overdue':
|
|
color = Colors.red;
|
|
label = 'Overdue';
|
|
break;
|
|
default:
|
|
color = Colors.grey;
|
|
label = status;
|
|
}
|
|
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: color, width: 0.5),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
|
|
|
|
void _showMemberSelectionDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => MemberSelectionDialog(group: widget.group),
|
|
);
|
|
}
|
|
|
|
void _showAddUserDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => const AddUserDialog(),
|
|
);
|
|
}
|
|
|
|
void _showAddPastDrawDialog(BuildContext context) {
|
|
// Determine which month to add based on existing draws
|
|
final existingDraws = _service.monthlyDraws;
|
|
final nextMonth = existingDraws.length + 1;
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => AddPastDrawDialog(
|
|
group: widget.group,
|
|
monthNumber: nextMonth,
|
|
),
|
|
).then((result) {
|
|
if (result == true) {
|
|
// Reload draws after adding
|
|
_service.loadGroupMonthlyDraws(widget.group.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _showAddPastPaymentsDialog(BuildContext context) {
|
|
// Ask which month to add payments for
|
|
_showMonthSelectionDialog(context);
|
|
}
|
|
|
|
void _showMonthSelectionDialog(BuildContext context) {
|
|
final monthController = TextEditingController(text: '1');
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Select Month'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Text('Which month do you want to add payments for?'),
|
|
SizedBox(height: 16.h),
|
|
TextField(
|
|
controller: monthController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Month Number',
|
|
hintText: 'e.g., 1, 2, 3',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Cancel'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
final month = int.tryParse(monthController.text);
|
|
if (month != null && month >= 1) {
|
|
Navigator.pop(context);
|
|
_showAddPastPaymentsDialogForMonth(context, month);
|
|
}
|
|
},
|
|
child: const Text('Continue'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showAddPastPaymentsDialogForMonth(BuildContext context, int monthNumber) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => AddPastPaymentsDialog(
|
|
group: widget.group,
|
|
monthNumber: monthNumber,
|
|
),
|
|
).then((result) {
|
|
if (result == true) {
|
|
// Reload payments after adding
|
|
_paymentService.loadGroupPayments(widget.group.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _conductDraw(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => CombinedDrawDialog(group: widget.group),
|
|
);
|
|
}
|
|
|
|
void _startChitfund() async {
|
|
final success = await _service.startChitGroup(widget.group.id);
|
|
if (success) {
|
|
Get.snackbar('Success', 'Chitfund started successfully');
|
|
}
|
|
}
|
|
|
|
void _navigateToSchedule() {
|
|
Get.to(() => ChitfundSchedulePage(chitfund: widget.group));
|
|
}
|
|
|
|
void _navigateToPaymentStatus() {
|
|
final currentDate = DateTime.now();
|
|
Get.to(() => MonthlyPaymentStatusPage(
|
|
chitfund: widget.group,
|
|
month: currentDate.month,
|
|
year: currentDate.year,
|
|
));
|
|
}
|
|
|
|
void _handleMemberAction(String action, GroupMember member) {
|
|
switch (action) {
|
|
case 'view':
|
|
Get.snackbar('Coming Soon', 'Member details page will be implemented next');
|
|
break;
|
|
case 'suspend':
|
|
_updateMemberStatus(member.id, 'suspended');
|
|
break;
|
|
case 'activate':
|
|
_updateMemberStatus(member.id, 'active');
|
|
break;
|
|
case 'remove':
|
|
_showRemoveMemberDialog(member);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _updateMemberStatus(String memberId, String status) async {
|
|
final success = await _service.updateMemberStatus(widget.group.id, memberId, status);
|
|
if (success) {
|
|
Get.snackbar('Success', 'Member status updated successfully');
|
|
}
|
|
}
|
|
|
|
void _showRemoveMemberDialog(GroupMember member) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Remove Member'),
|
|
content: Text('Are you sure you want to remove ${member.user?.fullName ?? 'this member'} from the group?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: const Text('Cancel'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
Navigator.of(context).pop();
|
|
final success = await _service.removeMemberFromGroup(widget.group.id, member.id);
|
|
if (success) {
|
|
Get.snackbar('Success', 'Member removed successfully');
|
|
}
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: const Text('Remove'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showRecordPaymentDialog() {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => RecordPaymentDialog(group: widget.group),
|
|
).then((result) {
|
|
if (result == true) {
|
|
// Refresh payment data
|
|
_paymentService.loadGroupPayments(widget.group.id);
|
|
_paymentService.loadPendingPayments(widget.group.id);
|
|
_paymentService.loadPaymentSummary(widget.group.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _navigateToPaymentHistory() {
|
|
Get.to(() => PaymentHistoryPage(group: widget.group));
|
|
}
|
|
|
|
Widget _buildPaymentSummaryCards(Map<String, dynamic> summary) {
|
|
final stats = summary['payment_stats'];
|
|
if (stats == null) return const SizedBox.shrink();
|
|
|
|
return Column(
|
|
children: [
|
|
IntrinsicHeight(
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildPaymentStatCard(
|
|
'Collected',
|
|
_formatIndianCurrency(stats['total_collection'] is num ? stats['total_collection'].toDouble() : 0),
|
|
Icons.check_circle,
|
|
Colors.green,
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: _buildPaymentStatCard(
|
|
'Pending',
|
|
'${stats['pending_payments'] ?? 0}',
|
|
Icons.schedule,
|
|
Colors.orange,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
IntrinsicHeight(
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildPaymentStatCard(
|
|
'Success Rate',
|
|
'${(stats['collection_percentage'] is num ? stats['collection_percentage'].toStringAsFixed(1) : '0.0')}%',
|
|
Icons.trending_up,
|
|
Colors.blue,
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: _buildPaymentStatCard(
|
|
'Total Payments',
|
|
'${stats['total_payments'] ?? 0}',
|
|
Icons.payments,
|
|
Colors.purple,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildPaymentStatCard(String title, String value, IconData icon, Color color) {
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.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 _buildPendingPaymentCard(Map<String, dynamic> pending) {
|
|
final member = pending['member'];
|
|
final amountDue = pending['amount_due'];
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
child: Padding(
|
|
padding: EdgeInsets.all(10.w),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(6.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.orange.shade100,
|
|
borderRadius: BorderRadius.circular(6.r),
|
|
),
|
|
child: Icon(
|
|
Icons.schedule,
|
|
color: Colors.orange.shade600,
|
|
size: 16.w,
|
|
),
|
|
),
|
|
SizedBox(width: 6.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
member['full_name'] ?? 'Unknown Member',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
Text(
|
|
member['mobile_number'] ?? '',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
_formatIndianCurrency(amountDue is num ? amountDue.toDouble() : 0),
|
|
style: TextStyle(
|
|
fontSize: 14.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: 12.w, vertical: 6.h),
|
|
minimumSize: Size(0, 0),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(4.r),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Record',
|
|
style: TextStyle(fontSize: 10.sp),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRecentPaymentCard(Payment payment) {
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
child: Padding(
|
|
padding: EdgeInsets.all(10.w),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(6.w),
|
|
decoration: BoxDecoration(
|
|
color: _getPaymentStatusColor(payment.status).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(6.r),
|
|
),
|
|
child: Icon(
|
|
_getPaymentStatusIcon(payment.status),
|
|
color: _getPaymentStatusColor(payment.status),
|
|
size: 16.w,
|
|
),
|
|
),
|
|
SizedBox(width: 6.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
payment.user?.fullName ?? 'Unknown Member',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
Text(
|
|
'${_getMonthName(payment.month)} ${payment.year}',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
payment.formattedAmount,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 1.h),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 1.h),
|
|
decoration: BoxDecoration(
|
|
color: _getPaymentStatusColor(payment.status).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(
|
|
color: _getPaymentStatusColor(payment.status).withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Text(
|
|
payment.status.toUpperCase(),
|
|
style: TextStyle(
|
|
fontSize: 8.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: _getPaymentStatusColor(payment.status),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
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) {
|
|
// Refresh payment data
|
|
_paymentService.loadGroupPayments(widget.group.id);
|
|
_paymentService.loadPendingPayments(widget.group.id);
|
|
_paymentService.loadPaymentSummary(widget.group.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
Color _getPaymentStatusColor(String status) {
|
|
switch (status) {
|
|
case 'success':
|
|
case 'paid':
|
|
return Colors.green;
|
|
case 'pending':
|
|
return Colors.orange;
|
|
case 'failed':
|
|
return Colors.red;
|
|
case 'overdue':
|
|
return Colors.red.shade700;
|
|
case 'cancelled':
|
|
return Colors.grey;
|
|
default:
|
|
return Colors.grey;
|
|
}
|
|
}
|
|
|
|
IconData _getPaymentStatusIcon(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';
|
|
}
|
|
|
|
|
|
Widget _buildCurrentMonthSection(ChitGroup group) {
|
|
final now = DateTime.now();
|
|
final currentMonth = now.month;
|
|
final currentYear = now.year;
|
|
final currentMonthName = _getMonthName(currentMonth);
|
|
|
|
// Calculate current month number since chitfund started
|
|
final startDate = group.startDate;
|
|
int currentMonthNumber = 1;
|
|
if (startDate != null) {
|
|
final monthsSinceStart = (currentYear - startDate.year) * 12 + (currentMonth - startDate.month);
|
|
currentMonthNumber = monthsSinceStart + 1;
|
|
}
|
|
|
|
// Calculate next month
|
|
final nextMonth = currentMonth == 12 ? 1 : currentMonth + 1;
|
|
final nextYear = currentMonth == 12 ? currentYear + 1 : currentYear;
|
|
final nextMonthName = _getMonthName(nextMonth);
|
|
final nextMonthNumber = currentMonthNumber + 1;
|
|
|
|
// Get current month draw data
|
|
final currentDraw = _service.monthlyDraws.firstWhereOrNull(
|
|
(draw) => draw.month == currentMonth && draw.year == currentYear,
|
|
);
|
|
|
|
// Get financial data for current month
|
|
final financialData = _service.financialData;
|
|
final currentMonthFinancial = financialData.firstWhereOrNull(
|
|
(entry) => entry.monthYear == '$currentMonth/$currentYear',
|
|
);
|
|
|
|
// Determine bid/prize amounts - use actual data or fallback to group values
|
|
final currentBidAmount = currentMonthFinancial?.bidAmount ?? currentDraw?.prizeAmount ?? group.totalValue;
|
|
final currentPrizeAmount = currentDraw?.prizeAmount ?? currentBidAmount;
|
|
|
|
// For next month, estimate based on pattern or use group total value
|
|
final nextMonthFinancial = financialData.firstWhereOrNull(
|
|
(entry) => entry.monthYear == '$nextMonth/$nextYear',
|
|
);
|
|
final nextBidAmount = nextMonthFinancial?.bidAmount ?? group.totalValue;
|
|
final nextPrizePool = nextMonthFinancial?.bidAmount ?? group.totalValue;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Current Month & Draws',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Current Month Card
|
|
Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.green.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.calendar_today, color: Colors.green.shade600, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Text(
|
|
'Current Month',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'$currentMonthName $currentYear',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'Month $currentMonthNumber of ${group.durationMonths}',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.green.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
_formatIndianCurrency(currentBidAmount),
|
|
style: TextStyle(
|
|
fontSize: 20.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'Current Bid',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.green.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Current Month Draw Winner (if available)
|
|
Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(color: Colors.green.shade100),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.emoji_events, color: Colors.amber.shade600, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Draw Winner',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'K Thirupathi Reddy', // September 2025 winner from the image
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
_formatIndianCurrency(currentPrizeAmount),
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'Prize Amount',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.green.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Upcoming Month Card
|
|
Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.blue.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.schedule, color: Colors.blue.shade600, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Text(
|
|
'Upcoming Month',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'$nextMonthName $nextYear',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blue.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'Month $nextMonthNumber of ${group.durationMonths}',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.blue.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
_formatIndianCurrency(nextBidAmount),
|
|
style: TextStyle(
|
|
fontSize: 20.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blue.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'Expected Bid',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.blue.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Draw Date Info
|
|
Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(color: Colors.blue.shade100),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.event, color: Colors.blue.shade600, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Draw Date',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'${group.drawDate}th of $nextMonthName',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blue.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
_formatIndianCurrency(nextPrizePool),
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blue.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'Prize Pool',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.blue.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|