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

563 lines
22 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter/services.dart';
import '../../core/services/api_service.dart';
import '../../core/models/group_member.dart';
import '../../core/utils/snackbar_util.dart';
class EditMemberDialog extends StatefulWidget {
final GroupMember member;
const EditMemberDialog({
super.key,
required this.member,
});
@override
State<EditMemberDialog> createState() => _EditMemberDialogState();
}
class _EditMemberDialogState extends State<EditMemberDialog> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _phoneController = TextEditingController();
final _emailController = TextEditingController();
final _addressController = TextEditingController();
final _emergencyContactController = TextEditingController();
final _apiService = ApiService();
bool _isLoading = false;
bool _showMemberId = false;
@override
void initState() {
super.initState();
_nameController.text = widget.member.user?.fullName ?? '';
_phoneController.text = widget.member.user?.mobileNumber ?? '';
_emailController.text = widget.member.user?.email ?? '';
_addressController.text = widget.member.user?.address ?? '';
_emergencyContactController.text = widget.member.user?.emergencyContact ?? '';
}
@override
void dispose() {
_nameController.dispose();
_phoneController.dispose();
_emailController.dispose();
_addressController.dispose();
_emergencyContactController.dispose();
super.dispose();
}
Future<void> _handleSubmit() async {
if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true);
try {
final updates = <String, dynamic>{};
// Only include changed fields
if (_nameController.text != (widget.member.user?.fullName ?? '')) {
updates['full_name'] = _nameController.text;
}
if (_phoneController.text != (widget.member.user?.mobileNumber ?? '')) {
updates['mobile_number'] = _phoneController.text;
}
if (_emailController.text != (widget.member.user?.email ?? '')) {
updates['email'] = _emailController.text.isEmpty ? null : _emailController.text;
}
if (_addressController.text != (widget.member.user?.address ?? '')) {
updates['address'] = _addressController.text.isEmpty ? null : _addressController.text;
}
if (_emergencyContactController.text != (widget.member.user?.emergencyContact ?? '')) {
updates['emergency_contact'] = _emergencyContactController.text.isEmpty
? null
: _emergencyContactController.text;
}
if (updates.isEmpty) {
SnackbarUtil.showWarning('No changes made');
Get.back();
return;
}
final response = await _apiService.updateMemberDetails(
widget.member.userId,
updates,
);
if (response['success']) {
SnackbarUtil.showSuccess(
'Member details updated successfully',
title: 'Success',
);
Get.back(result: true);
} else {
SnackbarUtil.showError(
response['message'] ?? 'Failed to update member',
title: 'Error',
);
}
} catch (e) {
SnackbarUtil.showError('Error: ${e.toString()}');
} finally {
setState(() => _isLoading = false);
}
}
}
void _copyMemberId() {
Clipboard.setData(ClipboardData(text: widget.member.userId));
SnackbarUtil.showSuccess(
'Member ID copied to clipboard',
duration: const Duration(seconds: 2),
);
}
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.r)),
child: Container(
width: 500.w,
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.85,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Fixed Header
Container(
padding: EdgeInsets.all(24.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.r),
topRight: Radius.circular(20.r),
),
border: Border(
bottom: BorderSide(color: Colors.grey.shade200),
),
),
child: Row(
children: [
// Member Number Badge
Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: Colors.purple.shade600,
shape: BoxShape.circle,
),
child: Center(
child: Text(
'#${widget.member.memberNumber}',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16.sp,
),
),
),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Edit Member #${widget.member.memberNumber}',
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
),
),
Text(
widget.member.user?.fullName ?? 'Unknown',
style: TextStyle(
fontSize: 13.sp,
color: Colors.grey.shade600,
),
),
],
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Get.back(),
),
],
),
),
// Scrollable Content
Flexible(
child: SingleChildScrollView(
padding: EdgeInsets.all(24.w),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Member Number & ID Section
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.purple.shade50,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: Colors.purple.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.badge,
color: Colors.purple.shade700, size: 22.w),
SizedBox(width: 8.w),
Text(
'Member Number (Readable)',
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w600,
color: Colors.purple.shade900,
),
),
],
),
SizedBox(height: 12.h),
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Colors.purple.shade300, width: 2),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Member #${widget.member.memberNumber}',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Colors.purple.shade700,
),
),
],
),
),
SizedBox(height: 8.h),
Text(
'Use this number to easily reference this member',
style: TextStyle(
fontSize: 12.sp,
color: Colors.purple.shade700,
fontStyle: FontStyle.italic,
),
),
],
),
),
SizedBox(height: 16.h),
// Unique ID Section (Technical)
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.fingerprint,
color: Colors.grey.shade700, size: 20.w),
SizedBox(width: 8.w),
Text(
'Unique ID (Technical)',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade700,
),
),
const Spacer(),
IconButton(
icon: Icon(_showMemberId ? Icons.visibility_off : Icons.visibility,
size: 18.w),
onPressed: () {
setState(() => _showMemberId = !_showMemberId);
},
tooltip: _showMemberId ? 'Hide ID' : 'Show ID',
),
],
),
if (_showMemberId) ...[
SizedBox(height: 8.h),
Row(
children: [
Expanded(
child: Text(
widget.member.userId,
style: TextStyle(
fontSize: 11.sp,
fontFamily: 'monospace',
color: Colors.grey.shade800,
),
),
),
IconButton(
icon: const Icon(Icons.copy, size: 18),
onPressed: _copyMemberId,
tooltip: 'Copy UUID',
),
],
),
],
SizedBox(height: 4.h),
Text(
'UUID for database operations',
style: TextStyle(
fontSize: 11.sp,
color: Colors.grey.shade600,
fontStyle: FontStyle.italic,
),
),
],
),
),
SizedBox(height: 24.h),
// Full Name
Text(
'Full Name *',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8.h),
TextFormField(
controller: _nameController,
decoration: InputDecoration(
hintText: 'Full name',
prefixIcon: const Icon(Icons.person),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
filled: true,
fillColor: Colors.grey.shade50,
),
validator: (value) {
if (value?.isEmpty ?? true) return 'Name is required';
if (value!.length < 2) return 'Name too short';
return null;
},
),
SizedBox(height: 20.h),
// Mobile Number
Text(
'Mobile Number *',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8.h),
TextFormField(
controller: _phoneController,
keyboardType: TextInputType.phone,
maxLength: 10,
decoration: InputDecoration(
hintText: '10-digit mobile number',
prefixIcon: const Icon(Icons.phone),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
filled: true,
fillColor: Colors.grey.shade50,
counterText: '',
),
validator: (value) {
if (value?.isEmpty ?? true) return 'Mobile number is required';
if (!RegExp(r'^[0-9]{10}$').hasMatch(value!)) {
return 'Must be exactly 10 digits';
}
return null;
},
),
SizedBox(height: 20.h),
// Email (Optional)
Text(
'Email (Optional)',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8.h),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'email@example.com',
prefixIcon: const Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
filled: true,
fillColor: Colors.grey.shade50,
),
validator: (value) {
if (value != null && value.isNotEmpty) {
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Invalid email format';
}
}
return null;
},
),
SizedBox(height: 20.h),
// Address (Optional)
Text(
'Address (Optional)',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8.h),
TextFormField(
controller: _addressController,
maxLines: 3,
decoration: InputDecoration(
hintText: 'Full address',
prefixIcon: const Icon(Icons.location_on),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
filled: true,
fillColor: Colors.grey.shade50,
),
),
SizedBox(height: 20.h),
// Emergency Contact (Optional)
Text(
'Emergency Contact (Optional)',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8.h),
TextFormField(
controller: _emergencyContactController,
keyboardType: TextInputType.phone,
maxLength: 10,
decoration: InputDecoration(
hintText: '10-digit emergency contact',
prefixIcon: const Icon(Icons.contacts),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
filled: true,
fillColor: Colors.grey.shade50,
counterText: '',
),
validator: (value) {
if (value != null && value.isNotEmpty) {
if (!RegExp(r'^[0-9]{10}$').hasMatch(value)) {
return 'Must be exactly 10 digits';
}
}
return null;
},
),
],
),
),
),
),
// Fixed Footer with Actions
Container(
padding: EdgeInsets.all(24.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20.r),
bottomRight: Radius.circular(20.r),
),
border: Border(
top: BorderSide(color: Colors.grey.shade200),
),
),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: _isLoading ? null : () => Get.back(),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
),
child: Text(
'Cancel',
style: TextStyle(fontSize: 16.sp),
),
),
),
SizedBox(width: 16.w),
Expanded(
child: ElevatedButton(
onPressed: _isLoading ? null : _handleSubmit,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade600,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 14.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(
'Update Member',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
),
),
),
],
),
),
],
),
),
);
}
}