diff --git a/lib/controller/directory/directory_controller.dart b/lib/controller/directory/directory_controller.dart index 596e63e..1cdcbc4 100644 --- a/lib/controller/directory/directory_controller.dart +++ b/lib/controller/directory/directory_controller.dart @@ -1,13 +1,13 @@ 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'; import 'package:marco/model/directory/contact_model.dart'; import 'package:marco/model/directory/contact_bucket_list_model.dart'; import 'package:marco/model/directory/directory_comment_model.dart'; -import 'package:marco/helpers/widgets/my_snackbar.dart'; class DirectoryController extends GetxController { - // Contacts + // -------------------- CONTACTS -------------------- RxList allContacts = [].obs; RxList filteredContacts = [].obs; RxList contactCategories = [].obs; @@ -18,7 +18,7 @@ class DirectoryController extends GetxController { RxList contactBuckets = [].obs; RxString searchQuery = ''.obs; - // Notes / Comments + // -------------------- COMMENTS -------------------- final Map> activeCommentsMap = {}; final Map> inactiveCommentsMap = {}; final editingCommentId = Rxn(); @@ -30,7 +30,7 @@ class DirectoryController extends GetxController { fetchBuckets(); } - // -------------------- COMMENTS -------------------- + // -------------------- COMMENTS HANDLING -------------------- RxList getCommentsForContact(String contactId, {bool active = true}) { return active @@ -41,7 +41,14 @@ class DirectoryController extends GetxController { Future fetchCommentsForContact(String contactId, {bool active = true}) async { try { final data = await ApiService.getDirectoryComments(contactId, active: active); - final comments = data?.map((e) => DirectoryComment.fromJson(e)).toList() ?? []; + var comments = data?.map((e) => DirectoryComment.fromJson(e)).toList() ?? []; + + // ✅ Deduplicate by ID before storing + final Map uniqueMap = { + for (var c in comments) c.id: c, + }; + comments = uniqueMap.values.toList() + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); if (active) { activeCommentsMap[contactId] = [].obs..assignAll(comments); @@ -60,22 +67,30 @@ class DirectoryController extends GetxController { } } - RxList combinedComments(String contactId) { - final active = getCommentsForContact(contactId, active: true).toList(); - final inactive = getCommentsForContact(contactId, active: false).toList(); + List combinedComments(String contactId) { + final activeList = getCommentsForContact(contactId, active: true); + final inactiveList = getCommentsForContact(contactId, active: false); - final combined = [...active, ...inactive] + // ✅ Deduplicate by ID (active wins) + final Map byId = {}; + for (final c in inactiveList) { + byId[c.id] = c; + } + for (final c in activeList) { + byId[c.id] = c; + } + + final combined = byId.values.toList() ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - - return combined.obs; + return combined; } Future updateComment(DirectoryComment comment) async { try { - final oldComment = - getCommentsForContact(comment.contactId).firstWhereOrNull((c) => c.id == comment.id); + final existing = getCommentsForContact(comment.contactId) + .firstWhereOrNull((c) => c.id == comment.id); - if (oldComment != null && oldComment.note.trim() == comment.note.trim()) { + if (existing != null && existing.note.trim() == comment.note.trim()) { showAppSnackbar( title: "No Changes", message: "No changes were made to the comment.", @@ -84,12 +99,12 @@ class DirectoryController extends GetxController { return; } - final success = await ApiService.updateContactComment(comment.id, comment.note, comment.contactId); + final success = + await ApiService.updateContactComment(comment.id, comment.note, comment.contactId); if (success) { await fetchCommentsForContact(comment.contactId, active: true); await fetchCommentsForContact(comment.contactId, active: false); - showAppSnackbar( title: "Success", message: "Comment updated successfully.", @@ -103,9 +118,8 @@ class DirectoryController extends GetxController { ); } } catch (e, stack) { - logSafe("Update comment failed: ${e.toString()}", level: LogLevel.error); + logSafe("Update comment failed: $e", level: LogLevel.error); logSafe(stack.toString(), level: LogLevel.debug); - showAppSnackbar( title: "Error", message: "Failed to update comment.", @@ -120,10 +134,8 @@ class DirectoryController extends GetxController { if (success) { if (editingCommentId.value == commentId) editingCommentId.value = null; - await fetchCommentsForContact(contactId, active: true); await fetchCommentsForContact(contactId, active: false); - showAppSnackbar( title: "Deleted", message: "Comment deleted successfully.", @@ -139,7 +151,6 @@ class DirectoryController extends GetxController { } catch (e, stack) { logSafe("Delete comment failed: $e", level: LogLevel.error); logSafe(stack.toString(), level: LogLevel.debug); - showAppSnackbar( title: "Error", message: "Something went wrong while deleting comment.", @@ -155,7 +166,6 @@ class DirectoryController extends GetxController { if (success) { await fetchCommentsForContact(contactId, active: true); await fetchCommentsForContact(contactId, active: false); - showAppSnackbar( title: "Restored", message: "Comment restored successfully.", @@ -171,7 +181,6 @@ class DirectoryController extends GetxController { } catch (e, stack) { logSafe("Restore comment failed: $e", level: LogLevel.error); logSafe(stack.toString(), level: LogLevel.debug); - showAppSnackbar( title: "Error", message: "Something went wrong while restoring comment.", @@ -180,7 +189,7 @@ class DirectoryController extends GetxController { } } - // -------------------- CONTACTS -------------------- + // -------------------- CONTACTS HANDLING -------------------- Future fetchBuckets() async { try { @@ -201,8 +210,8 @@ class DirectoryController extends GetxController { Future fetchContacts({bool active = true}) async { try { isLoading.value = true; - final response = await ApiService.getDirectoryData(isActive: active); + if (response != null) { final contacts = response.map((e) => ContactModel.fromJson(e)).toList(); allContacts.assignAll(contacts); @@ -223,8 +232,8 @@ class DirectoryController extends GetxController { final uniqueCategories = {}; for (final contact in allContacts) { final category = contact.contactCategory; - if (category != null && !uniqueCategories.containsKey(category.id)) { - uniqueCategories[category.id] = category; + if (category != null) { + uniqueCategories.putIfAbsent(category.id, () => category); } } contactCategories.value = uniqueCategories.values.toList(); @@ -251,9 +260,13 @@ class DirectoryController extends GetxController { contact.tags.any((tag) => tag.name.toLowerCase().contains(query)); final categoryNameMatch = contact.contactCategory?.name.toLowerCase().contains(query) ?? false; + final bucketNameMatch = contact.bucketIds.any((id) { - final bucketName = - contactBuckets.firstWhereOrNull((b) => b.id == id)?.name.toLowerCase() ?? ''; + final bucketName = contactBuckets + .firstWhereOrNull((b) => b.id == id) + ?.name + .toLowerCase() ?? + ''; return bucketName.contains(query); }); @@ -269,8 +282,7 @@ class DirectoryController extends GetxController { return categoryMatch && bucketMatch && searchMatch; }).toList(); - filteredContacts - .sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); + filteredContacts.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); } void toggleCategory(String categoryId) { diff --git a/lib/model/directory/directory_comment_model.dart b/lib/model/directory/directory_comment_model.dart index 562e6dc..f416eab 100644 --- a/lib/model/directory/directory_comment_model.dart +++ b/lib/model/directory/directory_comment_model.dart @@ -69,6 +69,15 @@ class DirectoryComment { isActive: json['isActive'] ?? true, ); } + @override + bool operator ==(Object other) => + identical(this, other) || + other is DirectoryComment && + runtimeType == other.runtimeType && + id == other.id; + + @override + int get hashCode => id.hashCode; DirectoryComment copyWith({ String? id,