added multiple bucket assignment
This commit is contained in:
parent
5dc2db0a8b
commit
cd92d4d309
@ -10,7 +10,7 @@ class AddContactController extends GetxController {
|
|||||||
final RxList<String> tags = <String>[].obs;
|
final RxList<String> tags = <String>[].obs;
|
||||||
|
|
||||||
final RxString selectedCategory = ''.obs;
|
final RxString selectedCategory = ''.obs;
|
||||||
final RxString selectedBucket = ''.obs;
|
final RxList<String> selectedBuckets = <String>[].obs;
|
||||||
final RxString selectedProject = ''.obs;
|
final RxString selectedProject = ''.obs;
|
||||||
|
|
||||||
final RxList<String> enteredTags = <String>[].obs;
|
final RxList<String> enteredTags = <String>[].obs;
|
||||||
@ -50,7 +50,7 @@ class AddContactController extends GetxController {
|
|||||||
void resetForm() {
|
void resetForm() {
|
||||||
selectedCategory.value = '';
|
selectedCategory.value = '';
|
||||||
selectedProject.value = '';
|
selectedProject.value = '';
|
||||||
selectedBucket.value = '';
|
selectedBuckets.clear();
|
||||||
enteredTags.clear();
|
enteredTags.clear();
|
||||||
filteredSuggestions.clear();
|
filteredSuggestions.clear();
|
||||||
filteredOrgSuggestions.clear();
|
filteredOrgSuggestions.clear();
|
||||||
@ -100,7 +100,21 @@ class AddContactController extends GetxController {
|
|||||||
isSubmitting.value = true;
|
isSubmitting.value = true;
|
||||||
|
|
||||||
final categoryId = categoriesMap[selectedCategory.value];
|
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
|
final projectIds = selectedProjects
|
||||||
.map((name) => projectsMap[name])
|
.map((name) => projectsMap[name])
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
@ -126,10 +140,10 @@ class AddContactController extends GetxController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedBucket.value.trim().isEmpty || bucketId == null) {
|
if (selectedBuckets.isEmpty) {
|
||||||
showAppSnackbar(
|
showAppSnackbar(
|
||||||
title: "Missing Bucket",
|
title: "Missing Bucket",
|
||||||
message: "Please select a bucket.",
|
message: "Please select at least one bucket.",
|
||||||
type: SnackbarType.warning,
|
type: SnackbarType.warning,
|
||||||
);
|
);
|
||||||
isSubmitting.value = false;
|
isSubmitting.value = false;
|
||||||
@ -151,7 +165,7 @@ class AddContactController extends GetxController {
|
|||||||
if (selectedCategory.value.isNotEmpty && categoryId != null)
|
if (selectedCategory.value.isNotEmpty && categoryId != null)
|
||||||
"contactCategoryId": categoryId,
|
"contactCategoryId": categoryId,
|
||||||
if (projectIds.isNotEmpty) "projectIds": projectIds,
|
if (projectIds.isNotEmpty) "projectIds": projectIds,
|
||||||
"bucketIds": [bucketId],
|
"bucketIds": bucketIds,
|
||||||
if (enteredTags.isNotEmpty) "tags": tagObjects,
|
if (enteredTags.isNotEmpty) "tags": tagObjects,
|
||||||
if (emails.isNotEmpty) "contactEmails": emails,
|
if (emails.isNotEmpty) "contactEmails": emails,
|
||||||
if (phones.isNotEmpty) "contactPhones": phones,
|
if (phones.isNotEmpty) "contactPhones": phones,
|
||||||
|
|||||||
@ -74,12 +74,20 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
|||||||
|
|
||||||
ever(controller.isInitialized, (bool ready) {
|
ever(controller.isInitialized, (bool ready) {
|
||||||
if (ready) {
|
if (ready) {
|
||||||
|
// Buckets - map all
|
||||||
|
if (c.bucketIds.isNotEmpty) {
|
||||||
|
final names = c.bucketIds
|
||||||
|
.map((id) {
|
||||||
|
return controller.bucketsMap.entries
|
||||||
|
.firstWhereOrNull((e) => e.value == id)
|
||||||
|
?.key;
|
||||||
|
})
|
||||||
|
.whereType<String>()
|
||||||
|
.toList();
|
||||||
|
controller.selectedBuckets.assignAll(names);
|
||||||
|
}
|
||||||
|
// Projects and Category mapping - as before
|
||||||
final projectIds = c.projectIds;
|
final projectIds = c.projectIds;
|
||||||
final bucketId = c.bucketIds.firstOrNull;
|
|
||||||
final category = c.contactCategory?.name;
|
|
||||||
|
|
||||||
if (category != null) controller.selectedCategory.value = category;
|
|
||||||
|
|
||||||
if (projectIds != null) {
|
if (projectIds != null) {
|
||||||
controller.selectedProjects.assignAll(
|
controller.selectedProjects.assignAll(
|
||||||
projectIds
|
projectIds
|
||||||
@ -90,16 +98,12 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
|||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
final category = c.contactCategory?.name;
|
||||||
if (bucketId != null) {
|
if (category != null) controller.selectedCategory.value = category;
|
||||||
final name = controller.bucketsMap.entries
|
|
||||||
.firstWhereOrNull((e) => e.value == bucketId)
|
|
||||||
?.key;
|
|
||||||
if (name != null) controller.selectedBucket.value = name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
showAdvanced.value = false; // Optional
|
||||||
emailCtrls.add(TextEditingController());
|
emailCtrls.add(TextEditingController());
|
||||||
emailLabels.add('Office'.obs);
|
emailLabels.add('Office'.obs);
|
||||||
phoneCtrls.add(TextEditingController());
|
phoneCtrls.add(TextEditingController());
|
||||||
@ -363,10 +367,129 @@ 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 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: [
|
||||||
|
PopupMenuItem(
|
||||||
|
enabled: false,
|
||||||
|
child: StatefulBuilder(
|
||||||
|
builder: (context, setState) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 250,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: items.map((f) {
|
||||||
|
final isChecked = selectedValues.contains(f.id);
|
||||||
|
return CheckboxListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(f.name),
|
||||||
|
value: isChecked,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
controlAffinity:
|
||||||
|
ListTileControlAffinity.leading,
|
||||||
|
side: const BorderSide(
|
||||||
|
color: Colors.black, width: 1.5),
|
||||||
|
fillColor:
|
||||||
|
MaterialStateProperty.resolveWith<Color>(
|
||||||
|
(states) {
|
||||||
|
if (states
|
||||||
|
.contains(MaterialState.selected)) {
|
||||||
|
return Colors.indigo; // selected color
|
||||||
|
}
|
||||||
|
return Colors
|
||||||
|
.white; // unselected background
|
||||||
|
}),
|
||||||
|
checkColor: Colors.white, // tick color
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _handleSubmit() {
|
void _handleSubmit() {
|
||||||
bool valid = formKey.currentState?.validate() ?? false;
|
bool valid = formKey.currentState?.validate() ?? false;
|
||||||
|
|
||||||
if (controller.selectedBucket.value.isEmpty) {
|
if (controller.selectedBuckets.isEmpty) {
|
||||||
bucketError.value = "Bucket is required";
|
bucketError.value = "Bucket is required";
|
||||||
valid = false;
|
valid = false;
|
||||||
} else {
|
} else {
|
||||||
@ -430,29 +553,14 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
|||||||
MySpacing.height(16),
|
MySpacing.height(16),
|
||||||
_textField("Organization", orgCtrl, required: true),
|
_textField("Organization", orgCtrl, required: true),
|
||||||
MySpacing.height(16),
|
MySpacing.height(16),
|
||||||
_labelWithStar("Bucket", required: true),
|
_labelWithStar("Buckets", required: true),
|
||||||
MySpacing.height(8),
|
MySpacing.height(8),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
_popupSelector(controller.selectedBucket, controller.buckets,
|
_bucketMultiSelectField(),
|
||||||
"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)),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
MySpacing.height(24),
|
MySpacing.height(12),
|
||||||
Obx(() => GestureDetector(
|
Obx(() => GestureDetector(
|
||||||
onTap: () => showAdvanced.toggle(),
|
onTap: () => showAdvanced.toggle(),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -562,3 +670,9 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FilterItem {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
FilterItem({required this.id, required this.name});
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user