chitfund/luckychit/lib/interfaces/manager/member_selection_dialog.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}';
}
}