411 lines
14 KiB
Dart
411 lines
14 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/models/chit_group.dart';
|
|
import '../../core/design_system/app_components/app_scaffold.dart';
|
|
import '../../core/design_system/app_components/state_view.dart';
|
|
import '../../shared/widgets/empty_state_widget.dart';
|
|
import 'create_group_dialog.dart';
|
|
import 'import_existing_group_dialog.dart';
|
|
import 'member_selection_dialog.dart';
|
|
import 'group_details_page.dart';
|
|
import 'combined_draw_dialog.dart';
|
|
|
|
class ChitGroupsPage extends StatelessWidget {
|
|
const ChitGroupsPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final scheme = Theme.of(context).colorScheme;
|
|
|
|
return AppScaffold(
|
|
title: 'My Chitfunds',
|
|
actions: [
|
|
PopupMenuButton<String>(
|
|
icon: Icon(Icons.add, size: 24.w),
|
|
onSelected: (value) {
|
|
if (value == 'create') {
|
|
_showCreateGroupDialog(context);
|
|
} else if (value == 'import') {
|
|
_showImportGroupDialog(context);
|
|
}
|
|
},
|
|
itemBuilder: (context) => [
|
|
PopupMenuItem(
|
|
value: 'create',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.add_circle, color: scheme.primary, size: 20.w),
|
|
SizedBox(width: 12.w),
|
|
const Text('Create New Group'),
|
|
],
|
|
),
|
|
),
|
|
PopupMenuItem(
|
|
value: 'import',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.upload, color: scheme.secondary, size: 20.w),
|
|
SizedBox(width: 12.w),
|
|
const Text('Import Existing Group'),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
body: Obx(() {
|
|
final service = ChitGroupService.to;
|
|
|
|
return StateView(
|
|
isLoading: service.isLoading.value,
|
|
isEmpty: service.chitGroups.isEmpty,
|
|
emptyType: EmptyStateType.noGroups,
|
|
emptyTitle: 'No chit groups yet',
|
|
emptyMessage: 'Create a new chit group or import an existing one.',
|
|
onRetry: () => service.loadManagerChitGroups(),
|
|
child: RefreshIndicator(
|
|
onRefresh: () => service.loadManagerChitGroups(),
|
|
child: ListView.builder(
|
|
padding: EdgeInsets.all(12.w),
|
|
itemCount: service.chitGroups.length,
|
|
itemBuilder: (context, index) {
|
|
final group = service.chitGroups[index];
|
|
return _buildGroupCard(context, group);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
|
|
Widget _buildGroupCard(BuildContext context, ChitGroup group) {
|
|
return Card(
|
|
margin: EdgeInsets.only(bottom: 12.h),
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: InkWell(
|
|
onTap: () => _showGroupDetails(context, group),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
child: Padding(
|
|
padding: EdgeInsets.all(16.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
group.name,
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
),
|
|
_buildStatusChip(group.status),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Group details - Mobile optimized layout
|
|
Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildDetailItem(
|
|
'Total Value',
|
|
'₹${group.totalValue.toStringAsFixed(0)}',
|
|
Icons.currency_rupee,
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: _buildDetailItem(
|
|
'Monthly',
|
|
'₹${group.monthlyInstallment.toStringAsFixed(0)}',
|
|
Icons.payment,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildDetailItem(
|
|
'Members',
|
|
'${group.currentMemberCount}/${group.maxMembers}',
|
|
Icons.people,
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: _buildDetailItem(
|
|
'Duration',
|
|
'${group.durationMonths} months',
|
|
Icons.calendar_today,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Action buttons - Mobile optimized
|
|
if (group.isForming) ...[
|
|
Column(
|
|
children: [
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _showAddMemberDialog(context, group),
|
|
icon: Icon(Icons.person_add, size: 18.w),
|
|
label: Text(
|
|
'Add Member',
|
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: group.canStart ? () => _startGroup(group) : null,
|
|
icon: Icon(Icons.play_arrow, size: 18.w),
|
|
label: Text(
|
|
'Start Group',
|
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.green.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
] else if (group.isActive) ...[
|
|
Column(
|
|
children: [
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _showGroupDetails(context, group),
|
|
icon: Icon(Icons.visibility, size: 18.w),
|
|
label: Text(
|
|
'View Details',
|
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.orange.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _conductDraw(context, group),
|
|
icon: Icon(Icons.casino, size: 18.w),
|
|
label: Text(
|
|
'Conduct Draw',
|
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.purple.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
] else ...[
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _showGroupDetails(context, group),
|
|
icon: Icon(Icons.visibility, size: 18.w),
|
|
label: Text(
|
|
'View Details',
|
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.grey.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
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.1),
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
border: Border.all(color: color, width: 1.5),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDetailItem(String label, String value, IconData icon) {
|
|
return Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(icon, size: 18.w, color: Colors.grey.shade600),
|
|
SizedBox(width: 6.w),
|
|
Expanded(
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showCreateGroupDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => const CreateGroupDialog(),
|
|
);
|
|
}
|
|
|
|
void _showImportGroupDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => const ImportExistingGroupDialog(),
|
|
).then((result) {
|
|
if (result == true) {
|
|
// Reload groups after successful import
|
|
ChitGroupService.to.loadManagerChitGroups();
|
|
}
|
|
});
|
|
}
|
|
|
|
void _showAddMemberDialog(BuildContext context, ChitGroup group) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => MemberSelectionDialog(group: group),
|
|
);
|
|
}
|
|
|
|
void _showGroupDetails(BuildContext context, ChitGroup group) {
|
|
Get.to(() => GroupDetailsPage(group: group));
|
|
}
|
|
|
|
void _startGroup(ChitGroup group) async {
|
|
final success = await ChitGroupService.to.startChitGroup(group.id);
|
|
if (success) {
|
|
Get.snackbar('Success', 'Chit group started successfully');
|
|
}
|
|
}
|
|
|
|
void _conductDraw(BuildContext context, ChitGroup group) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => CombinedDrawDialog(group: group),
|
|
);
|
|
}
|
|
}
|