509 lines
17 KiB
Dart
509 lines
17 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 MemberSelectionDialog extends StatefulWidget {
|
|
final ChitGroup group;
|
|
|
|
const MemberSelectionDialog({
|
|
super.key,
|
|
required this.group,
|
|
});
|
|
|
|
@override
|
|
State<MemberSelectionDialog> createState() => _MemberSelectionDialogState();
|
|
}
|
|
|
|
class _MemberSelectionDialogState extends State<MemberSelectionDialog> {
|
|
final ChitGroupService _service = ChitGroupService.to;
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
List<User> _allUsers = [];
|
|
List<User> _filteredUsers = [];
|
|
List<User> _selectedUsers = [];
|
|
bool _isLoading = false;
|
|
bool _isAdding = false;
|
|
String _searchQuery = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Load available users directly (no need to load existing members separately)
|
|
_loadAllUsers();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
|
|
Future<void> _loadAllUsers() async {
|
|
try {
|
|
// Use the enhanced API that filters by group ID
|
|
final users = await _service.getAvailableUsersForGroup(widget.group.id);
|
|
|
|
print('Total available users for group ${widget.group.id}: ${users.length}');
|
|
print('User IDs: ${users.map((u) => u.id).toList()}');
|
|
|
|
setState(() {
|
|
_allUsers = users;
|
|
_filteredUsers = users;
|
|
});
|
|
|
|
} catch (e) {
|
|
print('Error loading available users: $e');
|
|
setState(() {
|
|
_allUsers = [];
|
|
_filteredUsers = [];
|
|
});
|
|
|
|
// Show error message instead of sample data
|
|
Get.snackbar(
|
|
'Error',
|
|
'Failed to load available users. Please try again.',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
snackPosition: SnackPosition.TOP,
|
|
);
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
|
|
void _filterUsers(String query) {
|
|
setState(() {
|
|
_searchQuery = query;
|
|
if (query.isEmpty) {
|
|
_filteredUsers = _allUsers;
|
|
} else {
|
|
_filteredUsers = _allUsers.where((user) {
|
|
return user.fullName.toLowerCase().contains(query.toLowerCase()) ||
|
|
user.mobileNumber.contains(query);
|
|
}).toList();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
void _toggleUserSelection(User user) {
|
|
setState(() {
|
|
if (_selectedUsers.contains(user)) {
|
|
_selectedUsers.remove(user);
|
|
} else {
|
|
_selectedUsers.add(user);
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _addSelectedUsersToGroup() async {
|
|
if (_selectedUsers.isEmpty) {
|
|
Get.snackbar(
|
|
'Error',
|
|
'Please select at least one user',
|
|
backgroundColor: Colors.orange.shade100,
|
|
colorText: Colors.orange.shade800,
|
|
snackPosition: SnackPosition.TOP,
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isAdding = true);
|
|
|
|
try {
|
|
int successCount = 0;
|
|
int errorCount = 0;
|
|
|
|
for (final user in _selectedUsers) {
|
|
final success = await _service.addMemberToGroup(widget.group.id, {
|
|
'mobile_number': user.mobileNumber,
|
|
});
|
|
|
|
if (success) {
|
|
successCount++;
|
|
} else {
|
|
errorCount++;
|
|
}
|
|
}
|
|
|
|
Navigator.of(context).pop(); // Close dialog
|
|
|
|
if (successCount > 0) {
|
|
Get.snackbar(
|
|
'Success',
|
|
'$successCount user(s) added to chitfund successfully!',
|
|
backgroundColor: Colors.green.shade100,
|
|
colorText: Colors.green.shade800,
|
|
snackPosition: SnackPosition.TOP,
|
|
);
|
|
}
|
|
|
|
if (errorCount > 0) {
|
|
Get.snackbar(
|
|
'Warning',
|
|
'$errorCount user(s) could not be added (may already be members)',
|
|
backgroundColor: Colors.orange.shade100,
|
|
colorText: Colors.orange.shade800,
|
|
snackPosition: SnackPosition.TOP,
|
|
);
|
|
}
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
'Error',
|
|
'Failed to add members. Please try again.',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
snackPosition: SnackPosition.TOP,
|
|
);
|
|
} finally {
|
|
setState(() => _isAdding = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.r)),
|
|
child: Container(
|
|
width: 0.85.sw,
|
|
height: 0.8.sh,
|
|
child: Column(
|
|
children: [
|
|
// Header
|
|
Container(
|
|
padding: EdgeInsets.all(12.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.people_alt,
|
|
color: Colors.white,
|
|
size: 24.w,
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Select Members',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
Text(
|
|
widget.group.name,
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.8),
|
|
fontSize: 12.sp,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
IconButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
icon: const Icon(Icons.close, color: Colors.white),
|
|
padding: EdgeInsets.zero,
|
|
constraints: const BoxConstraints(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Chitfund Info
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
|
|
color: Colors.blue.shade50,
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInfoItem('Monthly', '₹${widget.group.monthlyInstallment.toStringAsFixed(0)}'),
|
|
),
|
|
Expanded(
|
|
child: _buildInfoItem('Members', '${widget.group.currentMemberCount}/${widget.group.maxMembers}'),
|
|
),
|
|
Expanded(
|
|
child: _buildInfoItem('Available', '${widget.group.maxMembers - widget.group.currentMemberCount}'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Search and Selection Controls
|
|
Container(
|
|
padding: EdgeInsets.all(8.w),
|
|
child: Column(
|
|
children: [
|
|
// Search Bar
|
|
TextField(
|
|
controller: _searchController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Search users...',
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: _isLoading
|
|
? SizedBox(
|
|
width: 20.w,
|
|
height: 20.w,
|
|
child: const CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
onPressed: () {
|
|
print('Manual refresh triggered');
|
|
_loadAllUsers();
|
|
},
|
|
tooltip: 'Refresh members',
|
|
),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
|
|
),
|
|
onChanged: _filterUsers,
|
|
),
|
|
SizedBox(height: 6.h),
|
|
|
|
|
|
// Selection Summary
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
'${_filteredUsers.length} users available',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
),
|
|
if (_selectedUsers.isNotEmpty)
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade100,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Text(
|
|
'${_selectedUsers.length} selected',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade700,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Users List
|
|
Flexible(
|
|
child: _isLoading && _allUsers.isEmpty
|
|
? const Center(child: CircularProgressIndicator())
|
|
: _filteredUsers.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.people_outline,
|
|
size: 64.w,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
_searchQuery.isEmpty
|
|
? 'No users found'
|
|
: 'No users match your search',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
if (_searchQuery.isNotEmpty) ...[
|
|
SizedBox(height: 8.h),
|
|
TextButton(
|
|
onPressed: () {
|
|
_searchController.clear();
|
|
_filterUsers('');
|
|
},
|
|
child: const Text('Clear search'),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
|
itemCount: _filteredUsers.length,
|
|
itemBuilder: (context, index) {
|
|
final user = _filteredUsers[index];
|
|
final isSelected = _selectedUsers.contains(user);
|
|
return _buildUserCard(user, isSelected);
|
|
},
|
|
),
|
|
),
|
|
|
|
// Footer
|
|
Container(
|
|
padding: EdgeInsets.all(8.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(16.r),
|
|
bottomRight: Radius.circular(16.r),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Status text
|
|
Text(
|
|
_selectedUsers.isEmpty
|
|
? 'Select users to add'
|
|
: 'Ready to add ${_selectedUsers.length} user(s)',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: 8.h),
|
|
// Action buttons
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
style: OutlinedButton.styleFrom(
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
),
|
|
child: const Text('Cancel'),
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: _isAdding || _selectedUsers.isEmpty ? null : _addSelectedUsersToGroup,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
),
|
|
child: _isAdding
|
|
? SizedBox(
|
|
width: 16.w,
|
|
height: 16.w,
|
|
child: const CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
|
),
|
|
)
|
|
: Text('Add ${_selectedUsers.length} Member(s)'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoItem(String label, String value) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
color: Colors.blue.shade600,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: 2.h),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade800,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildUserCard(User user, bool isSelected) {
|
|
return Card(
|
|
margin: EdgeInsets.only(bottom: 4.h),
|
|
elevation: isSelected ? 4 : 1,
|
|
color: isSelected ? Colors.blue.shade50 : Colors.white,
|
|
child: ListTile(
|
|
dense: true,
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
|
|
leading: CircleAvatar(
|
|
radius: 16.r,
|
|
backgroundColor: isSelected ? Colors.blue.shade600 : Colors.blue.shade100,
|
|
child: Text(
|
|
user.fullName.isNotEmpty ? user.fullName[0].toUpperCase() : '?',
|
|
style: TextStyle(
|
|
color: isSelected ? Colors.white : Colors.blue.shade700,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12.sp,
|
|
),
|
|
),
|
|
),
|
|
title: Text(
|
|
user.fullName,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: isSelected ? Colors.blue.shade800 : Colors.grey.shade800,
|
|
),
|
|
),
|
|
subtitle: Text(
|
|
user.mobileNumber,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: isSelected ? Colors.blue.shade600 : Colors.grey.shade600,
|
|
),
|
|
),
|
|
trailing: Checkbox(
|
|
value: isSelected,
|
|
onChanged: (bool? value) => _toggleUserSelection(user),
|
|
activeColor: Colors.blue.shade600,
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
),
|
|
onTap: () => _toggleUserSelection(user),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _formatDate(DateTime date) {
|
|
return '${date.day}/${date.month}/${date.year}';
|
|
}
|
|
}
|