257 lines
8.0 KiB
Dart
257 lines
8.0 KiB
Dart
import 'package:get/get.dart';
|
|
import 'package:marco/helpers/services/api_service.dart';
|
|
import 'package:marco/helpers/services/app_logger.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 {
|
|
RxList<ContactModel> allContacts = <ContactModel>[].obs;
|
|
RxList<ContactModel> filteredContacts = <ContactModel>[].obs;
|
|
RxList<ContactCategory> contactCategories = <ContactCategory>[].obs;
|
|
RxList<String> selectedCategories = <String>[].obs;
|
|
RxList<String> selectedBuckets = <String>[].obs;
|
|
RxBool isActive = true.obs;
|
|
RxBool isLoading = false.obs;
|
|
RxList<ContactBucket> contactBuckets = <ContactBucket>[].obs;
|
|
RxString searchQuery = ''.obs;
|
|
RxBool showFabMenu = false.obs;
|
|
final RxBool showFullEditorToolbar = false.obs;
|
|
final RxBool isEditorFocused = false.obs;
|
|
RxBool isNotesView = false.obs;
|
|
|
|
final Map<String, RxList<DirectoryComment>> contactCommentsMap = {};
|
|
RxList<DirectoryComment> getCommentsForContact(String contactId) {
|
|
return contactCommentsMap[contactId] ?? <DirectoryComment>[].obs;
|
|
}
|
|
|
|
final editingCommentId = Rxn<String>();
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
fetchContacts();
|
|
fetchBuckets();
|
|
}
|
|
// inside DirectoryController
|
|
|
|
Future<void> updateComment(DirectoryComment comment) async {
|
|
try {
|
|
logSafe(
|
|
"Attempting to update comment. id: ${comment.id}, contactId: ${comment.contactId}");
|
|
|
|
final commentList = contactCommentsMap[comment.contactId];
|
|
final oldComment =
|
|
commentList?.firstWhereOrNull((c) => c.id == comment.id);
|
|
|
|
if (oldComment == null) {
|
|
logSafe("Old comment not found. id: ${comment.id}");
|
|
} else {
|
|
logSafe("Old comment note: ${oldComment.note}");
|
|
logSafe("New comment note: ${comment.note}");
|
|
}
|
|
|
|
if (oldComment != null && oldComment.note.trim() == comment.note.trim()) {
|
|
logSafe("No changes detected in comment. id: ${comment.id}");
|
|
showAppSnackbar(
|
|
title: "No Changes",
|
|
message: "No changes were made to the comment.",
|
|
type: SnackbarType.info,
|
|
);
|
|
return;
|
|
}
|
|
|
|
final success = await ApiService.updateContactComment(
|
|
comment.id,
|
|
comment.note,
|
|
comment.contactId,
|
|
);
|
|
|
|
if (success) {
|
|
logSafe("Comment updated successfully. id: ${comment.id}");
|
|
await fetchCommentsForContact(comment.contactId);
|
|
|
|
// ✅ Show success message
|
|
showAppSnackbar(
|
|
title: "Success",
|
|
message: "Comment updated successfully.",
|
|
type: SnackbarType.success,
|
|
);
|
|
} else {
|
|
logSafe("Failed to update comment via API. id: ${comment.id}");
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Failed to update comment.",
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
} catch (e, stackTrace) {
|
|
logSafe("Update comment failed: ${e.toString()}");
|
|
logSafe("StackTrace: ${stackTrace.toString()}");
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Failed to update comment.",
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> fetchCommentsForContact(String contactId) async {
|
|
try {
|
|
final data = await ApiService.getDirectoryComments(contactId);
|
|
logSafe("Fetched comments for contact $contactId: $data");
|
|
|
|
final comments =
|
|
data?.map((e) => DirectoryComment.fromJson(e)).toList() ?? [];
|
|
|
|
if (!contactCommentsMap.containsKey(contactId)) {
|
|
contactCommentsMap[contactId] = <DirectoryComment>[].obs;
|
|
}
|
|
|
|
contactCommentsMap[contactId]!.assignAll(comments);
|
|
contactCommentsMap[contactId]?.refresh();
|
|
} catch (e) {
|
|
logSafe("Error fetching comments for contact $contactId: $e",
|
|
level: LogLevel.error);
|
|
|
|
contactCommentsMap[contactId] ??= <DirectoryComment>[].obs;
|
|
contactCommentsMap[contactId]!.clear();
|
|
}
|
|
}
|
|
|
|
Future<void> fetchBuckets() async {
|
|
try {
|
|
final response = await ApiService.getContactBucketList();
|
|
if (response != null && response['data'] is List) {
|
|
final buckets = (response['data'] as List)
|
|
.map((e) => ContactBucket.fromJson(e))
|
|
.toList();
|
|
contactBuckets.assignAll(buckets);
|
|
} else {
|
|
contactBuckets.clear();
|
|
}
|
|
} catch (e) {
|
|
logSafe("Bucket fetch error: $e", level: LogLevel.error);
|
|
}
|
|
}
|
|
|
|
Future<void> 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);
|
|
extractCategoriesFromContacts();
|
|
applyFilters();
|
|
} else {
|
|
allContacts.clear();
|
|
filteredContacts.clear();
|
|
}
|
|
} catch (e) {
|
|
logSafe("Directory fetch error: $e", level: LogLevel.error);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
void extractCategoriesFromContacts() {
|
|
final uniqueCategories = <String, ContactCategory>{};
|
|
|
|
for (final contact in allContacts) {
|
|
final category = contact.contactCategory;
|
|
if (category != null && !uniqueCategories.containsKey(category.id)) {
|
|
uniqueCategories[category.id] = category;
|
|
}
|
|
}
|
|
|
|
contactCategories.value = uniqueCategories.values.toList();
|
|
}
|
|
|
|
void applyFilters() {
|
|
final query = searchQuery.value.toLowerCase();
|
|
|
|
filteredContacts.value = allContacts.where((contact) {
|
|
final categoryMatch = selectedCategories.isEmpty ||
|
|
(contact.contactCategory != null &&
|
|
selectedCategories.contains(contact.contactCategory!.id));
|
|
|
|
final bucketMatch = selectedBuckets.isEmpty ||
|
|
contact.bucketIds.any((id) => selectedBuckets.contains(id));
|
|
|
|
// Name, org, email, phone, tags
|
|
final nameMatch = contact.name.toLowerCase().contains(query);
|
|
final orgMatch = contact.organization.toLowerCase().contains(query);
|
|
|
|
final emailMatch = contact.contactEmails
|
|
.any((e) => e.emailAddress.toLowerCase().contains(query));
|
|
|
|
final phoneMatch = contact.contactPhones
|
|
.any((p) => p.phoneNumber.toLowerCase().contains(query));
|
|
|
|
final tagMatch =
|
|
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() ??
|
|
'';
|
|
return bucketName.contains(query);
|
|
});
|
|
|
|
final searchMatch = query.isEmpty ||
|
|
nameMatch ||
|
|
orgMatch ||
|
|
emailMatch ||
|
|
phoneMatch ||
|
|
tagMatch ||
|
|
categoryNameMatch ||
|
|
bucketNameMatch;
|
|
|
|
return categoryMatch && bucketMatch && searchMatch;
|
|
}).toList();
|
|
}
|
|
|
|
void toggleCategory(String categoryId) {
|
|
if (selectedCategories.contains(categoryId)) {
|
|
selectedCategories.remove(categoryId);
|
|
} else {
|
|
selectedCategories.add(categoryId);
|
|
}
|
|
}
|
|
|
|
void toggleBucket(String bucketId) {
|
|
if (selectedBuckets.contains(bucketId)) {
|
|
selectedBuckets.remove(bucketId);
|
|
} else {
|
|
selectedBuckets.add(bucketId);
|
|
}
|
|
}
|
|
|
|
void updateSearchQuery(String value) {
|
|
searchQuery.value = value;
|
|
applyFilters();
|
|
}
|
|
|
|
String getBucketNames(ContactModel contact, List<ContactBucket> allBuckets) {
|
|
return contact.bucketIds
|
|
.map((id) => allBuckets.firstWhereOrNull((b) => b.id == id)?.name ?? '')
|
|
.where((name) => name.isNotEmpty)
|
|
.join(', ');
|
|
}
|
|
|
|
bool hasActiveFilters() {
|
|
return selectedCategories.isNotEmpty ||
|
|
selectedBuckets.isNotEmpty ||
|
|
searchQuery.value.trim().isNotEmpty;
|
|
}
|
|
}
|