feat(contact): support multiple project selection in AddContact functionality
This commit is contained in:
parent
a8c890a60d
commit
2fef2e508e
@ -25,6 +25,7 @@ class AddContactController extends GetxController {
|
||||
final RxMap<String, String> projectsMap = <String, String>{}.obs;
|
||||
final RxMap<String, String> tagsMap = <String, String>{}.obs;
|
||||
final RxBool isInitialized = false.obs;
|
||||
final RxList<String> selectedProjects = <String>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -54,6 +55,7 @@ class AddContactController extends GetxController {
|
||||
enteredTags.clear();
|
||||
filteredSuggestions.clear();
|
||||
filteredOrgSuggestions.clear();
|
||||
selectedProjects.clear();
|
||||
}
|
||||
|
||||
Future<void> fetchBuckets() async {
|
||||
@ -96,7 +98,10 @@ class AddContactController extends GetxController {
|
||||
}) async {
|
||||
final categoryId = categoriesMap[selectedCategory.value];
|
||||
final bucketId = bucketsMap[selectedBucket.value];
|
||||
final projectId = projectsMap[selectedProject.value];
|
||||
final projectIds = selectedProjects
|
||||
.map((name) => projectsMap[name])
|
||||
.whereType<String>()
|
||||
.toList();
|
||||
|
||||
// === Per-field Validation with Specific Messages ===
|
||||
if (name.trim().isEmpty) {
|
||||
@ -171,10 +176,10 @@ class AddContactController extends GetxController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedProject.value.trim().isEmpty || projectId == null) {
|
||||
if (selectedProjects.isEmpty || projectIds.isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Project",
|
||||
message: "Please select a project.",
|
||||
title: "Missing Projects",
|
||||
message: "Please select at least one project.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
@ -203,7 +208,7 @@ class AddContactController extends GetxController {
|
||||
"name": name.trim(),
|
||||
"organization": organization.trim(),
|
||||
"contactCategoryId": categoryId,
|
||||
"projectIds": [projectId],
|
||||
"projectIds": projectIds,
|
||||
"bucketIds": [bucketId],
|
||||
"tags": tagObjects,
|
||||
"contactEmails": emails,
|
||||
|
@ -74,7 +74,7 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
|
||||
ever(controller.isInitialized, (bool ready) {
|
||||
if (ready) {
|
||||
final projectId = widget.existingContact!.projectIds?.firstOrNull;
|
||||
final projectIds = widget.existingContact!.projectIds;
|
||||
final bucketId = widget.existingContact!.bucketIds.firstOrNull;
|
||||
final categoryName = widget.existingContact!.contactCategory?.name;
|
||||
|
||||
@ -82,15 +82,17 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
controller.selectedCategory.value = categoryName;
|
||||
}
|
||||
|
||||
if (projectId != null) {
|
||||
final name = controller.projectsMap.entries
|
||||
.firstWhereOrNull((e) => e.value == projectId)
|
||||
if (projectIds != null) {
|
||||
final names = projectIds
|
||||
.map((id) {
|
||||
return controller.projectsMap.entries
|
||||
.firstWhereOrNull((e) => e.value == id)
|
||||
?.key;
|
||||
if (name != null) {
|
||||
controller.selectedProject.value = name;
|
||||
})
|
||||
.whereType<String>()
|
||||
.toList();
|
||||
controller.selectedProjects.assignAll(names);
|
||||
}
|
||||
}
|
||||
|
||||
if (bucketId != null) {
|
||||
final name = controller.bucketsMap.entries
|
||||
.firstWhereOrNull((e) => e.value == bucketId)
|
||||
@ -270,12 +272,18 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
onTap: () async {
|
||||
final selected = await showMenu<String>(
|
||||
context: context,
|
||||
position: const RelativeRect.fromLTRB(100, 300, 100, 0),
|
||||
items: options
|
||||
.map((e) => PopupMenuItem(value: e, child: Text(e)))
|
||||
.toList(),
|
||||
position: RelativeRect.fromLTRB(100, 300, 100, 0),
|
||||
items: options.map((option) {
|
||||
return PopupMenuItem<String>(
|
||||
value: option,
|
||||
child: Text(option),
|
||||
);
|
||||
if (selected != null) selectedValue.value = selected;
|
||||
}).toList(),
|
||||
);
|
||||
|
||||
if (selected != null) {
|
||||
selectedValue.value = selected;
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 48,
|
||||
@ -560,10 +568,105 @@ class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Select Projects"),
|
||||
MySpacing.height(8),
|
||||
_popupSelector(
|
||||
hint: "Select Project",
|
||||
selectedValue: controller.selectedProject,
|
||||
options: controller.globalProjects,
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Select Projects'),
|
||||
content: Obx(() {
|
||||
return SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children:
|
||||
controller.globalProjects.map((project) {
|
||||
final isSelected = controller
|
||||
.selectedProjects
|
||||
.contains(project);
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
unselectedWidgetColor: Colors
|
||||
.black, // checkbox border when not selected
|
||||
checkboxTheme: CheckboxThemeData(
|
||||
fillColor: MaterialStateProperty
|
||||
.resolveWith<Color>((states) {
|
||||
if (states.contains(
|
||||
MaterialState.selected)) {
|
||||
return Colors
|
||||
.white; // fill when selected
|
||||
}
|
||||
return Colors.transparent;
|
||||
}),
|
||||
checkColor: MaterialStateProperty.all(
|
||||
Colors.black), // check mark color
|
||||
side: const BorderSide(
|
||||
color: Colors.black,
|
||||
width: 2), // border color
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: CheckboxListTile(
|
||||
dense: true,
|
||||
title: Text(project),
|
||||
value: isSelected,
|
||||
onChanged: (bool? selected) {
|
||||
if (selected == true) {
|
||||
controller.selectedProjects
|
||||
.add(project);
|
||||
} else {
|
||||
controller.selectedProjects
|
||||
.remove(project);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Done'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Obx(() {
|
||||
final selected = controller.selectedProjects;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
selected.isEmpty
|
||||
? "Select Projects"
|
||||
: selected.join(', '),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
const Icon(Icons.expand_more, size: 20),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Select Bucket"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user