chitfund/luckychit/lib/interfaces/manager/add_member_dialog.dart

533 lines
21 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/models/user.dart';
class AddMemberDialog extends StatefulWidget {
final ChitGroup group;
const AddMemberDialog({
super.key,
required this.group,
});
@override
State<AddMemberDialog> createState() => _AddMemberDialogState();
}
class _AddMemberDialogState extends State<AddMemberDialog> {
final _formKey = GlobalKey<FormState>();
final _mobileNumberController = TextEditingController();
final _fullNameController = TextEditingController();
final _emailController = TextEditingController();
final _addressController = TextEditingController();
final _emergencyContactController = TextEditingController();
bool _isLoading = false;
bool _isSearching = false;
User? _existingUser;
String _searchStatus = '';
@override
void dispose() {
_mobileNumberController.dispose();
_fullNameController.dispose();
_emailController.dispose();
_addressController.dispose();
_emergencyContactController.dispose();
super.dispose();
}
Future<void> _searchExistingUser() async {
final mobileNumber = _mobileNumberController.text.trim();
if (mobileNumber.isEmpty) return;
setState(() {
_isSearching = true;
_searchStatus = 'Searching...';
_existingUser = null;
});
try {
// This would typically call an API to search for existing users
// For now, we'll simulate the search
await Future.delayed(const Duration(seconds: 1));
// Simulate finding an existing user (you would replace this with actual API call)
if (mobileNumber == '9876543211') {
setState(() {
_existingUser = User(
id: 'user-2',
mobileNumber: mobileNumber,
fullName: 'John Doe',
role: 'member',
isActive: true,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
_searchStatus = 'User found!';
_fullNameController.text = _existingUser!.fullName;
});
} else {
setState(() {
_existingUser = null;
_searchStatus = 'No existing user found. Creating new member.';
_fullNameController.clear();
});
}
} catch (e) {
setState(() {
_searchStatus = 'Search failed. Please try again.';
});
} finally {
setState(() {
_isSearching = false;
});
}
}
Future<void> _addMember() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try {
final memberData = {
'group_id': widget.group.id,
'mobile_number': _mobileNumberController.text.trim(),
'full_name': _fullNameController.text.trim(),
'email': _emailController.text.trim().isEmpty ? null : _emailController.text.trim(),
'address': _addressController.text.trim().isEmpty ? null : _addressController.text.trim(),
'emergency_contact': _emergencyContactController.text.trim().isEmpty ? null : _emergencyContactController.text.trim(),
};
final success = await ChitGroupService.to.addMemberToGroup(widget.group.id, memberData);
if (success) {
Get.back(); // Close dialog
Get.snackbar(
'Success',
'Member added successfully!',
backgroundColor: Colors.green.shade100,
colorText: Colors.green.shade800,
snackPosition: SnackPosition.TOP,
);
}
} catch (e) {
Get.snackbar(
'Error',
'Failed to add member. Please try again.',
backgroundColor: Colors.red.shade100,
colorText: Colors.red.shade800,
snackPosition: SnackPosition.TOP,
);
} finally {
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.r)),
child: Container(
width: 500.w,
constraints: BoxConstraints(maxHeight: 0.8.sh),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: Colors.blue.shade600,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.r),
topRight: Radius.circular(16.r),
),
),
child: Row(
children: [
Icon(
Icons.person_add,
color: Colors.white,
size: 24.w,
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Add Member',
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.w600,
),
),
Text(
'Chitfund: ${widget.group.name}',
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 14.sp,
),
),
],
),
),
IconButton(
onPressed: () => Get.back(),
icon: const Icon(Icons.close, color: Colors.white),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
),
// Form Content
Flexible(
child: SingleChildScrollView(
padding: EdgeInsets.all(20.w),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Chitfund Info
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Colors.blue.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Chitfund Information',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.blue.shade800,
),
),
SizedBox(height: 8.h),
Row(
children: [
Expanded(
child: _buildInfoItem('Monthly Installment', '${widget.group.monthlyInstallment.toStringAsFixed(0)}'),
),
Expanded(
child: _buildInfoItem('Current Members', '${widget.group.currentMemberCount}/${widget.group.maxMembers}'),
),
],
),
],
),
),
SizedBox(height: 24.h),
// Member Information Section
_buildSectionTitle('Member Information'),
SizedBox(height: 16.h),
// Mobile Number with Search
TextFormField(
controller: _mobileNumberController,
decoration: InputDecoration(
labelText: 'Mobile Number *',
hintText: 'Enter 10-digit mobile number',
prefixIcon: const Icon(Icons.phone),
suffixIcon: _isSearching
? SizedBox(
width: 20.w,
height: 20.w,
child: const CircularProgressIndicator(strokeWidth: 2),
)
: IconButton(
icon: const Icon(Icons.search),
onPressed: _searchExistingUser,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
keyboardType: TextInputType.phone,
maxLength: 10,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Mobile number is required';
}
if (value.length != 10) {
return 'Mobile number must be 10 digits';
}
if (!RegExp(r'^[0-9]+$').hasMatch(value)) {
return 'Mobile number must contain only digits';
}
return null;
},
onChanged: (_) {
setState(() {
_existingUser = null;
_searchStatus = '';
});
},
),
SizedBox(height: 8.h),
// Search Status
if (_searchStatus.isNotEmpty)
Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: _existingUser != null ? Colors.green.shade50 : Colors.orange.shade50,
borderRadius: BorderRadius.circular(4.r),
border: Border.all(
color: _existingUser != null ? Colors.green.shade200 : Colors.orange.shade200,
),
),
child: Row(
children: [
Icon(
_existingUser != null ? Icons.check_circle : Icons.info,
size: 16.w,
color: _existingUser != null ? Colors.green.shade600 : Colors.orange.shade600,
),
SizedBox(width: 8.w),
Expanded(
child: Text(
_searchStatus,
style: TextStyle(
fontSize: 12.sp,
color: _existingUser != null ? Colors.green.shade700 : Colors.orange.shade700,
),
),
),
],
),
),
SizedBox(height: 16.h),
// Full Name
TextFormField(
controller: _fullNameController,
decoration: InputDecoration(
labelText: 'Full Name *',
hintText: 'Enter full name',
prefixIcon: const Icon(Icons.person),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Full name is required';
}
if (value.trim().length < 2) {
return 'Name must be at least 2 characters';
}
return null;
},
),
SizedBox(height: 16.h),
// Email (Optional)
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email (Optional)',
hintText: 'Enter email address',
prefixIcon: const Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value != null && value.trim().isNotEmpty) {
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Please enter a valid email address';
}
}
return null;
},
),
SizedBox(height: 16.h),
// Address (Optional)
TextFormField(
controller: _addressController,
decoration: InputDecoration(
labelText: 'Address (Optional)',
hintText: 'Enter address',
prefixIcon: const Icon(Icons.location_on),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
maxLines: 2,
),
SizedBox(height: 16.h),
// Emergency Contact (Optional)
TextFormField(
controller: _emergencyContactController,
decoration: InputDecoration(
labelText: 'Emergency Contact (Optional)',
hintText: 'Enter emergency contact number',
prefixIcon: const Icon(Icons.emergency),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
keyboardType: TextInputType.phone,
maxLength: 10,
validator: (value) {
if (value != null && value.trim().isNotEmpty) {
if (value.length != 10) {
return 'Emergency contact must be 10 digits';
}
if (!RegExp(r'^[0-9]+$').hasMatch(value)) {
return 'Emergency contact must contain only digits';
}
}
return null;
},
),
SizedBox(height: 24.h),
// Terms and Conditions
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Terms & Conditions',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
SizedBox(height: 8.h),
Text(
'• Member agrees to pay monthly installment of ₹${widget.group.monthlyInstallment.toStringAsFixed(0)}\n'
'• Member must attend monthly draws on ${widget.group.drawDate}th of each month\n'
'• Member can win the lottery only once per group\n'
'• Foreman commission of ₹${widget.group.foremanCommissionAmount} applies',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey.shade600,
height: 1.4,
),
),
],
),
),
SizedBox(height: 24.h),
// Action Buttons
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: _isLoading ? null : () => Get.back(),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
child: Text(
'Cancel',
style: TextStyle(fontSize: 16.sp),
),
),
),
SizedBox(width: 16.w),
Expanded(
child: ElevatedButton(
onPressed: _isLoading ? null : _addMember,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade600,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
child: _isLoading
? SizedBox(
height: 20.h,
width: 20.w,
child: const CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'Add Member',
style: TextStyle(fontSize: 16.sp),
),
),
),
],
),
],
),
),
),
),
],
),
),
);
}
Widget _buildSectionTitle(String title) {
return Text(
title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
);
}
Widget _buildInfoItem(String label, String value) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12.sp,
color: Colors.blue.shade600,
),
),
SizedBox(height: 4.h),
Text(
value,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.blue.shade800,
),
),
],
);
}
}