made chnages in add contach for assignment bucket
This commit is contained in:
parent
a18c4dad45
commit
2b8196b216
@ -10,7 +10,7 @@ class AddContactController extends GetxController {
|
||||
final RxList<String> tags = <String>[].obs;
|
||||
|
||||
final RxString selectedCategory = ''.obs;
|
||||
final RxString selectedBucket = ''.obs;
|
||||
final RxList<String> selectedBuckets = <String>[].obs;
|
||||
final RxString selectedProject = ''.obs;
|
||||
|
||||
final RxList<String> enteredTags = <String>[].obs;
|
||||
@ -50,7 +50,7 @@ class AddContactController extends GetxController {
|
||||
void resetForm() {
|
||||
selectedCategory.value = '';
|
||||
selectedProject.value = '';
|
||||
selectedBucket.value = '';
|
||||
selectedBuckets.clear();
|
||||
enteredTags.clear();
|
||||
filteredSuggestions.clear();
|
||||
filteredOrgSuggestions.clear();
|
||||
@ -100,7 +100,21 @@ class AddContactController extends GetxController {
|
||||
isSubmitting.value = true;
|
||||
|
||||
final categoryId = categoriesMap[selectedCategory.value];
|
||||
final bucketId = bucketsMap[selectedBucket.value];
|
||||
final bucketIds = selectedBuckets
|
||||
.map((name) => bucketsMap[name])
|
||||
.whereType<String>()
|
||||
.toList();
|
||||
|
||||
if (bucketIds.isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Buckets",
|
||||
message: "Please select at least one bucket.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
isSubmitting.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
final projectIds = selectedProjects
|
||||
.map((name) => projectsMap[name])
|
||||
.whereType<String>()
|
||||
@ -126,10 +140,10 @@ class AddContactController extends GetxController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedBucket.value.trim().isEmpty || bucketId == null) {
|
||||
if (selectedBuckets.isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Bucket",
|
||||
message: "Please select a bucket.",
|
||||
message: "Please select at least one bucket.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
isSubmitting.value = false;
|
||||
@ -151,7 +165,7 @@ class AddContactController extends GetxController {
|
||||
if (selectedCategory.value.isNotEmpty && categoryId != null)
|
||||
"contactCategoryId": categoryId,
|
||||
if (projectIds.isNotEmpty) "projectIds": projectIds,
|
||||
"bucketIds": [bucketId],
|
||||
"bucketIds": bucketIds,
|
||||
if (enteredTags.isNotEmpty) "tags": tagObjects,
|
||||
if (emails.isNotEmpty) "contactEmails": emails,
|
||||
if (phones.isNotEmpty) "contactPhones": phones,
|
||||
|
@ -73,7 +73,8 @@ class AddEmployeeController extends MyController {
|
||||
controller: TextEditingController(),
|
||||
);
|
||||
|
||||
logSafe('Fields initialized for first_name, phone_number, last_name, email.');
|
||||
logSafe(
|
||||
'Fields initialized for first_name, phone_number, last_name, email.');
|
||||
}
|
||||
|
||||
// Prefill fields in edit mode
|
||||
@ -87,7 +88,8 @@ class AddEmployeeController extends MyController {
|
||||
editingEmployeeData?['phone_number'] ?? '';
|
||||
|
||||
selectedGender = editingEmployeeData?['gender'] != null
|
||||
? Gender.values.firstWhereOrNull((g) => g.name == editingEmployeeData!['gender'])
|
||||
? Gender.values
|
||||
.firstWhereOrNull((g) => g.name == editingEmployeeData!['gender'])
|
||||
: null;
|
||||
|
||||
basicValidator.getController('email')?.text =
|
||||
@ -121,12 +123,24 @@ class AddEmployeeController extends MyController {
|
||||
if (result != null) {
|
||||
roles = List<Map<String, dynamic>>.from(result);
|
||||
logSafe('Roles fetched successfully.');
|
||||
|
||||
// ✅ If editing, and role already selected, update the role controller text here
|
||||
if (editingEmployeeData != null && selectedRoleId != null) {
|
||||
final selectedRole = roles.firstWhereOrNull(
|
||||
(r) => r['id'] == selectedRoleId,
|
||||
);
|
||||
if (selectedRole != null) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
} else {
|
||||
logSafe('Failed to fetch roles: null result', level: LogLevel.error);
|
||||
}
|
||||
} catch (e, st) {
|
||||
logSafe('Error fetching roles', level: LogLevel.error, error: e, stackTrace: st);
|
||||
logSafe('Error fetching roles',
|
||||
level: LogLevel.error, error: e, stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,7 +170,8 @@ class AddEmployeeController extends MyController {
|
||||
|
||||
final firstName = basicValidator.getController('first_name')?.text.trim();
|
||||
final lastName = basicValidator.getController('last_name')?.text.trim();
|
||||
final phoneNumber = basicValidator.getController('phone_number')?.text.trim();
|
||||
final phoneNumber =
|
||||
basicValidator.getController('phone_number')?.text.trim();
|
||||
|
||||
try {
|
||||
// sanitize orgId before sending
|
||||
@ -216,7 +231,8 @@ class AddEmployeeController extends MyController {
|
||||
|
||||
showAppSnackbar(
|
||||
title: 'Permission Required',
|
||||
message: 'Please allow Contacts permission from settings to pick a contact.',
|
||||
message:
|
||||
'Please allow Contacts permission from settings to pick a contact.',
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return false;
|
||||
|
@ -95,7 +95,9 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
final name = controller.bucketsMap.entries
|
||||
.firstWhereOrNull((e) => e.value == bucketId)
|
||||
?.key;
|
||||
if (name != null) controller.selectedBucket.value = name;
|
||||
if (name != null && !controller.selectedBuckets.contains(name)) {
|
||||
controller.selectedBuckets.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -363,10 +365,127 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _bucketMultiSelectField() {
|
||||
return _multiSelectField(
|
||||
items: controller.buckets
|
||||
.map((name) => FilterItem(id: name, name: name))
|
||||
.toList(),
|
||||
fallback: "Choose Buckets",
|
||||
selectedValues: controller.selectedBuckets,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _multiSelectField({
|
||||
required List<FilterItem> items,
|
||||
required String fallback,
|
||||
required RxList<String> selectedValues,
|
||||
}) {
|
||||
if (items.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Obx(() {
|
||||
final selectedNames = items
|
||||
.where((f) => selectedValues.contains(f.id))
|
||||
.map((f) => f.name)
|
||||
.join(", ");
|
||||
final displayText =
|
||||
selectedNames.isNotEmpty ? selectedNames : fallback;
|
||||
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final RenderBox button =
|
||||
context.findRenderObject() as RenderBox;
|
||||
final RenderBox overlay = Overlay.of(context)
|
||||
.context
|
||||
.findRenderObject() as RenderBox;
|
||||
|
||||
final position = button.localToGlobal(Offset.zero);
|
||||
|
||||
await showMenu(
|
||||
context: context,
|
||||
position: RelativeRect.fromLTRB(
|
||||
position.dx,
|
||||
position.dy + button.size.height,
|
||||
overlay.size.width - position.dx - button.size.width,
|
||||
0,
|
||||
),
|
||||
items: items.map((f) {
|
||||
return PopupMenuItem<String>(
|
||||
enabled: false,
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
final isChecked = selectedValues.contains(f.id);
|
||||
return CheckboxListTile(
|
||||
dense: true,
|
||||
value: isChecked,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
title: MyText(f.name),
|
||||
checkColor: Colors.white,
|
||||
side: const BorderSide(
|
||||
color: Colors.black, width: 1.5),
|
||||
fillColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return Colors.indigo;
|
||||
}
|
||||
return Colors.white;
|
||||
},
|
||||
),
|
||||
onChanged: (val) {
|
||||
if (val == true) {
|
||||
selectedValues.add(f.id);
|
||||
} else {
|
||||
selectedValues.remove(f.id);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: MySpacing.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyText(
|
||||
displayText,
|
||||
style: const TextStyle(color: Colors.black87),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const Icon(Icons.arrow_drop_down, color: Colors.grey),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
MySpacing.height(16),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _handleSubmit() {
|
||||
bool valid = formKey.currentState?.validate() ?? false;
|
||||
|
||||
if (controller.selectedBucket.value.isEmpty) {
|
||||
if (controller.selectedBuckets.isEmpty) {
|
||||
bucketError.value = "Bucket is required";
|
||||
valid = false;
|
||||
} else {
|
||||
@ -430,29 +549,14 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
MySpacing.height(16),
|
||||
_textField("Organization", orgCtrl, required: true),
|
||||
MySpacing.height(16),
|
||||
_labelWithStar("Bucket", required: true),
|
||||
_labelWithStar("Buckets", required: true),
|
||||
MySpacing.height(8),
|
||||
Stack(
|
||||
children: [
|
||||
_popupSelector(controller.selectedBucket, controller.buckets,
|
||||
"Choose Bucket"),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 56,
|
||||
child: Obx(() => bucketError.value.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(bucketError.value,
|
||||
style: const TextStyle(
|
||||
color: Colors.red, fontSize: 12)),
|
||||
)),
|
||||
),
|
||||
_bucketMultiSelectField(),
|
||||
],
|
||||
),
|
||||
MySpacing.height(24),
|
||||
MySpacing.height(12),
|
||||
Obx(() => GestureDetector(
|
||||
onTap: () => showAdvanced.toggle(),
|
||||
child: Row(
|
||||
@ -562,3 +666,9 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class FilterItem {
|
||||
final String id;
|
||||
final String name;
|
||||
FilterItem({required this.id, required this.name});
|
||||
}
|
||||
|
@ -37,36 +37,39 @@ class _AddEmployeeBottomSheetState extends State<AddEmployeeBottomSheet>
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = Get.put(
|
||||
AddEmployeeController(),
|
||||
// Unique tag to avoid clashes, but stable for this widget instance
|
||||
tag: UniqueKey().toString(),
|
||||
);
|
||||
_joiningDateController = TextEditingController();
|
||||
_genderController = TextEditingController();
|
||||
_roleController = TextEditingController();
|
||||
|
||||
_orgFieldController = TextEditingController(text: '');
|
||||
_joiningDateController = TextEditingController(text: '');
|
||||
_genderController = TextEditingController(text: '');
|
||||
_roleController = TextEditingController(text: '');
|
||||
_controller = Get.put(AddEmployeeController(), tag: UniqueKey().toString());
|
||||
|
||||
// Prefill when editing
|
||||
if (widget.employeeData != null) {
|
||||
_controller.editingEmployeeData = widget.employeeData;
|
||||
_controller.prefillFields();
|
||||
// 👇 joining date & gender already handled in your code
|
||||
if (_controller.joiningDate != null) {
|
||||
_joiningDateController.text =
|
||||
DateFormat('dd MMM yyyy').format(_controller.joiningDate!);
|
||||
}
|
||||
|
||||
if (_controller.selectedGender != null) {
|
||||
_genderController.text =
|
||||
_controller.selectedGender!.name.capitalizeFirst ?? '';
|
||||
}
|
||||
|
||||
final roleName = _controller.roles.firstWhereOrNull(
|
||||
(r) => r['id'] == _controller.selectedRoleId)?['name'] ??
|
||||
'';
|
||||
_roleController.text = roleName;
|
||||
} else {}
|
||||
// ✅ Important part: fetch roles, then set roleController
|
||||
_controller.fetchRoles().then((_) {
|
||||
if (_controller.selectedRoleId != null) {
|
||||
final roleName = _controller.roles.firstWhereOrNull(
|
||||
(r) => r['id'] == _controller.selectedRoleId,
|
||||
)?['name'];
|
||||
if (roleName != null) {
|
||||
_roleController.text = roleName;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_controller.fetchRoles();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -422,7 +425,7 @@ class _AddEmployeeBottomSheetState extends State<AddEmployeeBottomSheet>
|
||||
context: context,
|
||||
initialDate: _controller.joiningDate ?? DateTime.now(),
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2100),
|
||||
lastDate: DateTime.now(),
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
@ -435,6 +438,15 @@ class _AddEmployeeBottomSheetState extends State<AddEmployeeBottomSheet>
|
||||
Future<void> _handleSubmit() async {
|
||||
final isValid =
|
||||
_controller.basicValidator.formKey.currentState?.validate() ?? false;
|
||||
if (_controller.joiningDate != null &&
|
||||
_controller.joiningDate!.isAfter(DateTime.now())) {
|
||||
showAppSnackbar(
|
||||
title: 'Invalid Date',
|
||||
message: 'Joining Date cannot be in the future.',
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValid ||
|
||||
_controller.joiningDate == null ||
|
||||
|
@ -204,14 +204,6 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet> {
|
||||
],
|
||||
_gap(),
|
||||
|
||||
_buildTextFieldSection(
|
||||
icon: Icons.confirmation_number_outlined,
|
||||
title: "GST No.",
|
||||
controller: controller.gstController,
|
||||
hint: "Enter GST No.",
|
||||
),
|
||||
_gap(),
|
||||
|
||||
_buildDropdownField<PaymentModeModel>(
|
||||
icon: Icons.payment,
|
||||
title: "Payment Mode",
|
||||
@ -252,20 +244,11 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet> {
|
||||
),
|
||||
_gap(),
|
||||
|
||||
_buildTextFieldSection(
|
||||
icon: Icons.confirmation_number_outlined,
|
||||
title: "Transaction ID",
|
||||
controller: controller.transactionIdController,
|
||||
hint: "Enter Transaction ID",
|
||||
validator: (v) => (v != null && v.isNotEmpty)
|
||||
? Validators.transactionIdValidator(v)
|
||||
: null,
|
||||
),
|
||||
_gap(),
|
||||
|
||||
_buildTransactionDateField(),
|
||||
_gap(),
|
||||
|
||||
_buildTransactionIdField(),
|
||||
_gap(),
|
||||
_buildLocationField(),
|
||||
_gap(),
|
||||
|
||||
@ -288,6 +271,39 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTransactionIdField() {
|
||||
final paymentMode =
|
||||
controller.selectedPaymentMode.value?.name.toLowerCase() ?? '';
|
||||
final isRequired = paymentMode.isNotEmpty &&
|
||||
paymentMode != 'cash' &&
|
||||
paymentMode != 'cheque';
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionTitle(
|
||||
icon: Icons.confirmation_number_outlined,
|
||||
title: "Transaction ID",
|
||||
requiredField: isRequired,
|
||||
),
|
||||
MySpacing.height(6),
|
||||
CustomTextField(
|
||||
controller: controller.transactionIdController,
|
||||
hint: "Enter Transaction ID",
|
||||
validator: (v) {
|
||||
if (isRequired) {
|
||||
if (v == null || v.isEmpty) {
|
||||
return "Transaction ID is required for this payment mode";
|
||||
}
|
||||
return Validators.transactionIdValidator(v);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _gap([double h = 16]) => MySpacing.height(h);
|
||||
|
||||
Widget _buildDropdownField<T>({
|
||||
@ -326,8 +342,7 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet> {
|
||||
CustomTextField(
|
||||
controller: controller,
|
||||
hint: hint ?? "",
|
||||
keyboardType:
|
||||
keyboardType ?? TextInputType.text,
|
||||
keyboardType: keyboardType ?? TextInputType.text,
|
||||
validator: validator,
|
||||
maxLines: maxLines,
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user