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(); // === Required validations only for name, organization, and bucket === 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 (selectedBucket.value.trim().isEmpty || bucketId == null) { showAppSnackbar( title: "Missing Bucket", message: "Please select a bucket.", type: SnackbarType.warning, ); return; } // === Build body (include optional fields if available) === 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(), if (selectedCategory.value.isNotEmpty && categoryId != null) "contactCategoryId": categoryId, if (projectIds.isNotEmpty) "projectIds": projectIds, "bucketIds": [bucketId], if (enteredTags.isNotEmpty) "tags": tagObjects, if (emails.isNotEmpty) "contactEmails": emails, if (phones.isNotEmpty) "contactPhones": phones, if (address.trim().isNotEmpty) "address": address.trim(), if (description.trim().isNotEmpty) "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); } }