// Updated AddContactController to support multiple emails and phones import 'package:get/get.dart'; import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; class AddContactController extends GetxController { final RxList categories = [].obs; final RxList buckets = [].obs; final RxList globalProjects = [].obs; final RxList tags = [].obs; final RxString selectedCategory = ''.obs; final RxString selectedBucket = ''.obs; final RxString selectedProject = ''.obs; final RxList enteredTags = [].obs; final RxList filteredSuggestions = [].obs; final RxList organizationNames = [].obs; final RxList filteredOrgSuggestions = [].obs; final RxMap categoriesMap = {}.obs; final RxMap bucketsMap = {}.obs; final RxMap projectsMap = {}.obs; final RxMap tagsMap = {}.obs; final RxBool isInitialized = false.obs; final RxList selectedProjects = [].obs; @override void onInit() { super.onInit(); logSafe("AddContactController initialized", level: LogLevel.debug); fetchInitialData(); } Future fetchInitialData() async { logSafe("Fetching initial dropdown data", level: LogLevel.debug); await Future.wait([ fetchBuckets(), fetchGlobalProjects(), fetchTags(), fetchCategories(), fetchOrganizationNames(), ]); // ✅ Mark initialization as done isInitialized.value = true; } void resetForm() { selectedCategory.value = ''; selectedProject.value = ''; selectedBucket.value = ''; enteredTags.clear(); filteredSuggestions.clear(); filteredOrgSuggestions.clear(); selectedProjects.clear(); } Future fetchBuckets() async { try { final response = await ApiService.getContactBucketList(); if (response != null && response['data'] is List) { final names = []; for (var item in response['data']) { if (item['name'] != null && item['id'] != null) { bucketsMap[item['name']] = item['id'].toString(); names.add(item['name']); } } buckets.assignAll(names); logSafe("Fetched \${names.length} buckets"); } } catch (e) { logSafe("Failed to fetch buckets: \$e", level: LogLevel.error); } } Future fetchOrganizationNames() async { try { final orgs = await ApiService.getOrganizationList(); organizationNames.assignAll(orgs); logSafe("Fetched \${orgs.length} organization names"); } catch (e) { logSafe("Failed to load organization names: \$e", level: LogLevel.error); } } Future submitContact({ String? id, required String name, required String organization, required List> emails, required List> phones, required String address, required String description, }) async { final categoryId = categoriesMap[selectedCategory.value]; final bucketId = bucketsMap[selectedBucket.value]; final projectIds = selectedProjects .map((name) => projectsMap[name]) .whereType() .toList(); // === Per-field Validation with Specific Messages === if (name.trim().isEmpty) { showAppSnackbar( title: "Missing Name", message: "Please enter the contact name.", type: SnackbarType.warning, ); return; } if (organization.trim().isEmpty) { showAppSnackbar( title: "Missing Organization", message: "Please enter the organization name.", type: SnackbarType.warning, ); return; } if (emails.isEmpty) { showAppSnackbar( title: "Missing Email", message: "Please add at least one email.", type: SnackbarType.warning, ); return; } if (phones.isEmpty) { showAppSnackbar( title: "Missing Phone Number", message: "Please add at least one phone number.", type: SnackbarType.warning, ); return; } if (address.trim().isEmpty) { showAppSnackbar( title: "Missing Address", message: "Please enter the address.", type: SnackbarType.warning, ); return; } if (description.trim().isEmpty) { showAppSnackbar( title: "Missing Description", message: "Please enter a description.", type: SnackbarType.warning, ); return; } if (selectedCategory.value.trim().isEmpty || categoryId == null) { showAppSnackbar( title: "Missing Category", message: "Please select a contact category.", type: SnackbarType.warning, ); return; } if (selectedBucket.value.trim().isEmpty || bucketId == null) { showAppSnackbar( title: "Missing Bucket", message: "Please select a bucket.", type: SnackbarType.warning, ); return; } if (selectedProjects.isEmpty || projectIds.isEmpty) { showAppSnackbar( title: "Missing Projects", message: "Please select at least one project.", type: SnackbarType.warning, ); return; } if (enteredTags.isEmpty) { showAppSnackbar( title: "Missing Tags", message: "Please enter at least one tag.", type: SnackbarType.warning, ); return; } // === Submit if all validations passed === try { final tagObjects = enteredTags.map((tagName) { final tagId = tagsMap[tagName]; return tagId != null ? {"id": tagId, "name": tagName} : {"name": tagName}; }).toList(); final body = { if (id != null) "id": id, "name": name.trim(), "organization": organization.trim(), "contactCategoryId": categoryId, "projectIds": projectIds, "bucketIds": [bucketId], "tags": tagObjects, "contactEmails": emails, "contactPhones": phones, "address": address.trim(), "description": description.trim(), }; logSafe("${id != null ? 'Updating' : 'Creating'} contact"); final response = id != null ? await ApiService.updateContact(id, body) : await ApiService.createContact(body); if (response == true) { Get.back(result: true); showAppSnackbar( title: "Success", message: id != null ? "Contact updated successfully" : "Contact created successfully", type: SnackbarType.success, ); } else { showAppSnackbar( title: "Error", message: "Failed to ${id != null ? 'update' : 'create'} contact", type: SnackbarType.error, ); } } catch (e) { logSafe("Submit contact error: $e", level: LogLevel.error); showAppSnackbar( title: "Error", message: "Something went wrong", type: SnackbarType.error, ); } } void filterOrganizationSuggestions(String query) { if (query.trim().isEmpty) { filteredOrgSuggestions.clear(); return; } final lower = query.toLowerCase(); filteredOrgSuggestions.assignAll( organizationNames .where((name) => name.toLowerCase().contains(lower)) .toList(), ); logSafe("Filtered organization suggestions for: \$query", level: LogLevel.debug); } Future fetchGlobalProjects() async { try { final response = await ApiService.getGlobalProjects(); if (response != null) { final names = []; for (var item in response) { final name = item['name']?.toString().trim(); final id = item['id']?.toString().trim(); if (name != null && id != null && name.isNotEmpty) { projectsMap[name] = id; names.add(name); } } globalProjects.assignAll(names); logSafe("Fetched \${names.length} global projects"); } } catch (e) { logSafe("Failed to fetch global projects: \$e", level: LogLevel.error); } } Future fetchTags() async { try { final response = await ApiService.getContactTagList(); if (response != null && response['data'] is List) { tags.assignAll(List.from( response['data'].map((e) => e['name'] ?? '').where((e) => e != ''), )); logSafe("Fetched \${tags.length} tags"); } } catch (e) { logSafe("Failed to fetch tags: \$e", level: LogLevel.error); } } void filterSuggestions(String query) { if (query.trim().isEmpty) { filteredSuggestions.clear(); return; } final lower = query.toLowerCase(); filteredSuggestions.assignAll( tags .where((tag) => tag.toLowerCase().contains(lower) && !enteredTags.contains(tag)) .toList(), ); logSafe("Filtered tag suggestions for: \$query", level: LogLevel.debug); } void clearSuggestions() { filteredSuggestions.clear(); logSafe("Cleared tag suggestions", level: LogLevel.debug); } Future fetchCategories() async { try { final response = await ApiService.getContactCategoryList(); if (response != null && response['data'] is List) { final names = []; for (var item in response['data']) { final name = item['name']?.toString().trim(); final id = item['id']?.toString().trim(); if (name != null && id != null && name.isNotEmpty) { categoriesMap[name] = id; names.add(name); } } categories.assignAll(names); logSafe("Fetched \${names.length} contact categories"); } } catch (e) { logSafe("Failed to fetch categories: \$e", level: LogLevel.error); } } void addEnteredTag(String tag) { if (tag.trim().isNotEmpty && !enteredTags.contains(tag.trim())) { enteredTags.add(tag.trim()); logSafe("Added tag: \$tag", level: LogLevel.debug); } } void removeEnteredTag(String tag) { enteredTags.remove(tag); logSafe("Removed tag: \$tag", level: LogLevel.debug); } }