606 lines
25 KiB
Dart
606 lines
25 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/utils/snackbar_util.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
class ImportExistingGroupDialog extends StatefulWidget {
|
|
const ImportExistingGroupDialog({super.key});
|
|
|
|
@override
|
|
State<ImportExistingGroupDialog> createState() => _ImportExistingGroupDialogState();
|
|
}
|
|
|
|
class _ImportExistingGroupDialogState extends State<ImportExistingGroupDialog> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _nameController = TextEditingController();
|
|
final _totalValueController = TextEditingController();
|
|
final _monthlyInstallmentController = TextEditingController();
|
|
final _durationMonthsController = TextEditingController();
|
|
final _maxMembersController = TextEditingController();
|
|
final _foremanCommissionController = TextEditingController();
|
|
final _drawDateController = TextEditingController();
|
|
final _currentMonthController = TextEditingController();
|
|
|
|
DateTime? _selectedStartDate;
|
|
bool _isLoading = false;
|
|
int _step = 1; // Multi-step wizard
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_drawDateController.text = '15';
|
|
_foremanCommissionController.text = '250';
|
|
_currentMonthController.text = '1';
|
|
// Default to 6 months ago
|
|
_selectedStartDate = DateTime.now().subtract(const Duration(days: 180));
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameController.dispose();
|
|
_totalValueController.dispose();
|
|
_monthlyInstallmentController.dispose();
|
|
_durationMonthsController.dispose();
|
|
_maxMembersController.dispose();
|
|
_foremanCommissionController.dispose();
|
|
_drawDateController.dispose();
|
|
_currentMonthController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _selectStartDate() async {
|
|
final DateTime? picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: _selectedStartDate ?? DateTime.now().subtract(const Duration(days: 180)),
|
|
firstDate: DateTime(2020),
|
|
lastDate: DateTime.now(),
|
|
helpText: 'Select when the group started',
|
|
);
|
|
if (picked != null) {
|
|
setState(() {
|
|
_selectedStartDate = picked;
|
|
_calculateCurrentMonth();
|
|
});
|
|
}
|
|
}
|
|
|
|
void _calculateCurrentMonth() {
|
|
if (_selectedStartDate != null) {
|
|
final now = DateTime.now();
|
|
final diff = now.difference(_selectedStartDate!);
|
|
final monthsPassed = (diff.inDays / 30).floor() + 1;
|
|
_currentMonthController.text = monthsPassed.toString();
|
|
}
|
|
}
|
|
|
|
void _calculateMonthlyInstallment() {
|
|
final totalValue = double.tryParse(_totalValueController.text);
|
|
final durationMonths = int.tryParse(_durationMonthsController.text);
|
|
final commission = double.tryParse(_foremanCommissionController.text) ?? 250.0;
|
|
|
|
if (totalValue != null && durationMonths != null) {
|
|
final subscriptionPerMonth = totalValue / durationMonths;
|
|
final monthlyInstallment = subscriptionPerMonth + commission;
|
|
_monthlyInstallmentController.text = monthlyInstallment.toStringAsFixed(0);
|
|
}
|
|
}
|
|
|
|
Future<void> _handleImport() async {
|
|
if (_formKey.currentState!.validate()) {
|
|
if (_selectedStartDate == null) {
|
|
SnackbarUtil.showError('Please select a start date');
|
|
return;
|
|
}
|
|
|
|
setState(() => _isLoading = true);
|
|
|
|
try {
|
|
final data = {
|
|
'name': _nameController.text.trim(),
|
|
'total_value': double.parse(_totalValueController.text),
|
|
'monthly_installment': double.parse(_monthlyInstallmentController.text),
|
|
'duration_months': int.parse(_durationMonthsController.text),
|
|
'max_members': int.parse(_maxMembersController.text),
|
|
'foreman_commission_amount': double.parse(_foremanCommissionController.text),
|
|
'foreman_commission_type': 'fixed',
|
|
'draw_date': int.parse(_drawDateController.text),
|
|
'start_date': _selectedStartDate!.toIso8601String(),
|
|
'current_month': int.parse(_currentMonthController.text),
|
|
'is_import': true, // Flag to indicate this is an existing group
|
|
'status': 'active', // Start as active directly
|
|
};
|
|
|
|
final success = await ChitGroupService.to.createChitGroup(data);
|
|
|
|
if (success) {
|
|
SnackbarUtil.showSuccess(
|
|
'Existing group imported successfully! You can now add members and past draw results.',
|
|
title: 'Import Successful',
|
|
);
|
|
Get.back(result: true);
|
|
} else {
|
|
SnackbarUtil.showError('Failed to import group. Please try again.');
|
|
}
|
|
} catch (e) {
|
|
SnackbarUtil.showError('Error: ${e.toString()}');
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.r)),
|
|
child: Container(
|
|
width: 600.w,
|
|
constraints: BoxConstraints(maxHeight: 700.h),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Header
|
|
Container(
|
|
padding: EdgeInsets.all(20.w),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Colors.blue.shade600, Colors.blue.shade700],
|
|
),
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(20.r),
|
|
topRight: Radius.circular(20.r),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.upload, color: Colors.white, size: 28.w),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Text(
|
|
'Import Existing Chit Group',
|
|
style: TextStyle(
|
|
fontSize: 20.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.close, color: Colors.white, size: 24.w),
|
|
onPressed: () => Get.back(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Form
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
padding: EdgeInsets.all(24.w),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Info Box
|
|
Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.blue.shade200),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.info_outline, color: Colors.blue.shade700, size: 20.w),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Text(
|
|
'Import a chit group that has already started. You can add past draw results and payments later.',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.blue.shade900,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 24.h),
|
|
|
|
// Group Name
|
|
Text(
|
|
'Group Name',
|
|
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _nameController,
|
|
decoration: InputDecoration(
|
|
hintText: 'e.g., January 2024 Batch',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.r)),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) => value?.isEmpty ?? true ? 'Required' : null,
|
|
),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Start Date
|
|
Text(
|
|
'When Did This Group Start?',
|
|
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
InkWell(
|
|
onTap: _selectStartDate,
|
|
child: Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
color: Colors.grey.shade50,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.calendar_today, color: Colors.blue.shade600),
|
|
SizedBox(width: 12.w),
|
|
Text(
|
|
_selectedStartDate != null
|
|
? DateFormat('dd/MM/yyyy').format(_selectedStartDate!)
|
|
: 'Select start date',
|
|
style: TextStyle(fontSize: 16.sp),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Current Month
|
|
Text(
|
|
'Current Month Number',
|
|
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _currentMonthController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'e.g., 6 (if 6 months have passed)',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.r)),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
suffixText: 'months elapsed',
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
final month = int.tryParse(value!);
|
|
if (month == null || month < 1) return 'Must be at least 1';
|
|
return null;
|
|
},
|
|
),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Financial Details Header
|
|
Divider(),
|
|
SizedBox(height: 12.h),
|
|
Text(
|
|
'Chitfund Details',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Total Value
|
|
Text('Total Value', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _totalValueController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'e.g., 100000',
|
|
prefixText: '₹ ',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.r)),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
onChanged: (_) => _calculateMonthlyInstallment(),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
if (double.tryParse(value!) == null) return 'Invalid number';
|
|
return null;
|
|
},
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Duration
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Duration (Months)', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _durationMonthsController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'e.g., 20',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.r)),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
onChanged: (_) => _calculateMonthlyInstallment(),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
if (int.tryParse(value!) == null) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Max Members', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _maxMembersController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'e.g., 20',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.r)),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
if (int.tryParse(value!) == null) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Monthly Installment & Commission
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Monthly Installment', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _monthlyInstallmentController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'e.g., 5250',
|
|
prefixText: '₹ ',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.r)),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
if (double.tryParse(value!) == null) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Commission', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _foremanCommissionController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'e.g., 250',
|
|
prefixText: '₹ ',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.r)),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
onChanged: (_) => _calculateMonthlyInstallment(),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
if (double.tryParse(value!) == null) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Draw Date
|
|
Text('Draw Date (Day of Month)', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _drawDateController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'e.g., 15 (15th of every month)',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.r)),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
final date = int.tryParse(value!);
|
|
if (date == null || date < 1 || date > 31) return '1-31 only';
|
|
return null;
|
|
},
|
|
),
|
|
SizedBox(height: 24.h),
|
|
|
|
// Summary Box
|
|
Container(
|
|
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.info_outline, color: Colors.green.shade700, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Text(
|
|
'Next Steps After Import',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
_buildNextStep('1', 'Add all members to the group'),
|
|
_buildNextStep('2', 'Record past draw results (if any)'),
|
|
_buildNextStep('3', 'Record past payments for each member'),
|
|
_buildNextStep('4', 'Continue with regular monthly operations'),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Actions
|
|
Container(
|
|
padding: EdgeInsets.all(20.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade100,
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(20.r),
|
|
bottomRight: Radius.circular(20.r),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
onPressed: _isLoading ? null : () => Get.back(),
|
|
style: OutlinedButton.styleFrom(
|
|
padding: EdgeInsets.symmetric(vertical: 16.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Cancel',
|
|
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
flex: 2,
|
|
child: ElevatedButton(
|
|
onPressed: _isLoading ? null : _handleImport,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 16.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
elevation: 2,
|
|
),
|
|
child: _isLoading
|
|
? SizedBox(
|
|
height: 20.h,
|
|
width: 20.w,
|
|
child: const CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
|
),
|
|
)
|
|
: Text(
|
|
'Import Group',
|
|
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildNextStep(String number, String text) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(bottom: 8.h),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
width: 24.w,
|
|
height: 24.w,
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade600,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
number,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: EdgeInsets.only(top: 2.h),
|
|
child: Text(
|
|
text,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.green.shade900,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|