540 lines
22 KiB
Dart
540 lines
22 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/utils/snackbar_util.dart';
|
|
|
|
class EditGroupDialog extends StatefulWidget {
|
|
final ChitGroup group;
|
|
|
|
const EditGroupDialog({
|
|
super.key,
|
|
required this.group,
|
|
});
|
|
|
|
@override
|
|
State<EditGroupDialog> createState() => _EditGroupDialogState();
|
|
}
|
|
|
|
class _EditGroupDialogState extends State<EditGroupDialog> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _nameController = TextEditingController();
|
|
final _totalValueController = TextEditingController();
|
|
final _monthlyInstallmentController = TextEditingController();
|
|
final _durationController = TextEditingController();
|
|
final _maxMembersController = TextEditingController();
|
|
final _commissionController = TextEditingController();
|
|
final _drawDateController = TextEditingController();
|
|
final _service = ChitGroupService.to;
|
|
|
|
bool _isLoading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_nameController.text = widget.group.name;
|
|
_totalValueController.text = widget.group.totalValue.toStringAsFixed(0);
|
|
_monthlyInstallmentController.text = widget.group.monthlyInstallment.toStringAsFixed(0);
|
|
_durationController.text = widget.group.durationMonths.toString();
|
|
_maxMembersController.text = widget.group.maxMembers.toString();
|
|
_commissionController.text = widget.group.foremanCommissionAmount.toStringAsFixed(0);
|
|
_drawDateController.text = widget.group.drawDate.toString();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameController.dispose();
|
|
_totalValueController.dispose();
|
|
_monthlyInstallmentController.dispose();
|
|
_durationController.dispose();
|
|
_maxMembersController.dispose();
|
|
_commissionController.dispose();
|
|
_drawDateController.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.group.name) {
|
|
updates['name'] = _nameController.text;
|
|
}
|
|
|
|
final newTotalValue = double.parse(_totalValueController.text);
|
|
if (newTotalValue != widget.group.totalValue) {
|
|
updates['total_value'] = newTotalValue;
|
|
}
|
|
|
|
final newInstallment = double.parse(_monthlyInstallmentController.text);
|
|
if (newInstallment != widget.group.monthlyInstallment) {
|
|
updates['monthly_installment'] = newInstallment;
|
|
}
|
|
|
|
final newDuration = int.parse(_durationController.text);
|
|
if (newDuration != widget.group.durationMonths) {
|
|
updates['duration_months'] = newDuration;
|
|
}
|
|
|
|
final newMaxMembers = int.parse(_maxMembersController.text);
|
|
if (newMaxMembers != widget.group.maxMembers) {
|
|
updates['max_members'] = newMaxMembers;
|
|
}
|
|
|
|
final newCommission = double.parse(_commissionController.text);
|
|
if (newCommission != widget.group.foremanCommissionAmount) {
|
|
updates['foreman_commission_amount'] = newCommission;
|
|
}
|
|
|
|
final newDrawDate = int.parse(_drawDateController.text);
|
|
if (newDrawDate != widget.group.drawDate) {
|
|
updates['draw_date'] = newDrawDate;
|
|
}
|
|
|
|
if (updates.isEmpty) {
|
|
SnackbarUtil.showWarning('No changes made');
|
|
Get.back();
|
|
return;
|
|
}
|
|
|
|
final response = await _service.updateChitGroup(widget.group.id, updates);
|
|
|
|
if (response) {
|
|
SnackbarUtil.showSuccess(
|
|
'Group details updated successfully',
|
|
title: 'Success',
|
|
);
|
|
Get.back(result: true);
|
|
}
|
|
} catch (e) {
|
|
SnackbarUtil.showError('Error: ${e.toString()}');
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
@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: [
|
|
Icon(Icons.edit, color: Colors.green.shade600, size: 28.w),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Edit Group Details',
|
|
style: TextStyle(
|
|
fontSize: 20.sp,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
Text(
|
|
'Only works for forming groups',
|
|
style: TextStyle(
|
|
fontSize: 12.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: [
|
|
// Group Name
|
|
Text(
|
|
'Group Name *',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _nameController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Group name',
|
|
prefixIcon: const Icon(Icons.group),
|
|
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 < 3) return 'Name too short';
|
|
return null;
|
|
},
|
|
),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Financial Details Row 1
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Total Value *',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _totalValueController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'Total value',
|
|
prefixText: '₹ ',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
if (double.tryParse(value!) == null) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Monthly Installment *',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _monthlyInstallmentController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'Installment',
|
|
prefixText: '₹ ',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
if (double.tryParse(value!) == null) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Financial Details Row 2
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Duration (Months) *',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _durationController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'Months',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
final num = int.tryParse(value!);
|
|
if (num == null || num < 1) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Max Members *',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _maxMembersController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'Members',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
final num = int.tryParse(value!);
|
|
if (num == null || num < 1) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Commission and Draw Date Row
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Commission Amount *',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _commissionController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'Commission',
|
|
prefixText: '₹ ',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
if (double.tryParse(value!) == null) return 'Invalid';
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Draw Date *',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
TextFormField(
|
|
controller: _drawDateController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
hintText: 'Day (1-31)',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
validator: (value) {
|
|
if (value?.isEmpty ?? true) return 'Required';
|
|
final num = int.tryParse(value!);
|
|
if (num == null || num < 1 || num > 31) {
|
|
return '1-31 only';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Info Note
|
|
Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.blue.shade200),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.info_outline,
|
|
color: Colors.blue.shade700, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: Text(
|
|
'Group can only be edited while in "Forming" status. Once started, it cannot be changed.',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.blue.shade900,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// 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.green.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 Group',
|
|
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|