419 lines
15 KiB
Dart
419 lines
15 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 SelectMemberDialog extends StatefulWidget {
|
|
final ChitGroup group;
|
|
|
|
const SelectMemberDialog({
|
|
super.key,
|
|
required this.group,
|
|
});
|
|
|
|
@override
|
|
State<SelectMemberDialog> createState() => _SelectMemberDialogState();
|
|
}
|
|
|
|
class _SelectMemberDialogState extends State<SelectMemberDialog> {
|
|
final ChitGroupService _service = ChitGroupService.to;
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
List<User> _users = [];
|
|
List<User> _filteredUsers = [];
|
|
bool _isLoading = false;
|
|
bool _isAdding = false;
|
|
String _searchQuery = '';
|
|
User? _selectedUser;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadUsers();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadUsers() async {
|
|
setState(() => _isLoading = true);
|
|
|
|
try {
|
|
final users = await _service.getAllUsers(search: _searchQuery);
|
|
setState(() {
|
|
_users = users;
|
|
_filteredUsers = users;
|
|
});
|
|
} catch (e) {
|
|
Get.snackbar('Error', 'Failed to load users');
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
void _filterUsers(String query) {
|
|
setState(() {
|
|
_searchQuery = query;
|
|
if (query.isEmpty) {
|
|
_filteredUsers = _users;
|
|
} else {
|
|
_filteredUsers = _users.where((user) {
|
|
return user.fullName.toLowerCase().contains(query.toLowerCase()) ||
|
|
user.mobileNumber.contains(query);
|
|
}).toList();
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _addSelectedUserToGroup() async {
|
|
if (_selectedUser == null) {
|
|
Get.snackbar(
|
|
'Error',
|
|
'Please select a user first',
|
|
backgroundColor: Colors.orange.shade100,
|
|
colorText: Colors.orange.shade800,
|
|
snackPosition: SnackPosition.TOP,
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isAdding = true);
|
|
|
|
try {
|
|
final success = await _service.addMemberToGroup(widget.group.id, {
|
|
'mobile_number': _selectedUser!.mobileNumber,
|
|
});
|
|
|
|
if (success) {
|
|
Get.back(); // Close dialog
|
|
Get.snackbar(
|
|
'Success',
|
|
'${_selectedUser!.fullName} added to chitfund 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(() => _isAdding = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.r)),
|
|
child: Container(
|
|
width: 600.w,
|
|
height: 0.8.sh,
|
|
child: Column(
|
|
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.people,
|
|
color: Colors.white,
|
|
size: 24.w,
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Add Member to Chitfund',
|
|
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(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// User Selection
|
|
Expanded(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(16.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Search Bar
|
|
TextField(
|
|
controller: _searchController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Search by name or mobile number...',
|
|
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: _loadUsers,
|
|
),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
onChanged: _filterUsers,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// User Dropdown
|
|
Text(
|
|
'Select User',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
|
|
DropdownButtonFormField<User>(
|
|
value: _selectedUser,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
|
|
),
|
|
hint: Text(
|
|
_isLoading ? 'Loading users...' : 'Choose a user to add',
|
|
style: TextStyle(fontSize: 14.sp),
|
|
),
|
|
items: _filteredUsers.map((user) {
|
|
return DropdownMenuItem<User>(
|
|
value: user,
|
|
child: Row(
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 12.r,
|
|
backgroundColor: Colors.blue.shade100,
|
|
child: Text(
|
|
user.fullName.isNotEmpty ? user.fullName[0].toUpperCase() : '?',
|
|
style: TextStyle(
|
|
color: Colors.blue.shade700,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12.sp,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
user.fullName,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
Text(
|
|
user.mobileNumber,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
onChanged: (User? newValue) {
|
|
setState(() {
|
|
_selectedUser = newValue;
|
|
});
|
|
},
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Selected User Info
|
|
if (_selectedUser != null)
|
|
Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(color: Colors.blue.shade200),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.person,
|
|
color: Colors.blue.shade600,
|
|
size: 20.w,
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Selected: ${_selectedUser!.fullName}',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade800,
|
|
),
|
|
),
|
|
Text(
|
|
'Mobile: ${_selectedUser!.mobileNumber}',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.blue.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Empty State
|
|
if (_filteredUsers.isEmpty && !_isLoading)
|
|
Expanded(
|
|
child: 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'),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Footer
|
|
Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(16.r),
|
|
bottomRight: Radius.circular(16.r),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
'${_filteredUsers.length} users found',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
OutlinedButton(
|
|
onPressed: () => Get.back(),
|
|
child: const Text('Cancel'),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
ElevatedButton(
|
|
onPressed: _isAdding ? null : _addSelectedUserToGroup,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: _isAdding
|
|
? SizedBox(
|
|
width: 16.w,
|
|
height: 16.w,
|
|
child: const CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
|
),
|
|
)
|
|
: const Text('Add Member'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
}
|