341 lines
12 KiB
Dart
341 lines
12 KiB
Dart
import 'package:get/get.dart';
|
||
import 'package:logger/logger.dart';
|
||
import 'package:marco/controller/attendance/attendance_screen_controller.dart';
|
||
import 'package:marco/controller/expense/expense_screen_controller.dart';
|
||
import 'package:marco/controller/expense/expense_detail_controller.dart';
|
||
import 'package:marco/controller/directory/directory_controller.dart';
|
||
import 'package:marco/controller/directory/notes_controller.dart';
|
||
import 'package:marco/controller/document/user_document_controller.dart';
|
||
import 'package:marco/controller/document/document_details_controller.dart';
|
||
import 'package:marco/controller/dashboard/dashboard_controller.dart';
|
||
import 'package:marco/helpers/utils/permission_constants.dart';
|
||
|
||
/// Handles incoming FCM notification actions and updates UI/controllers.
|
||
class NotificationActionHandler {
|
||
static final Logger _logger = Logger();
|
||
|
||
/// Main entry point — call this for any notification `data` map.
|
||
static void handle(Map<String, dynamic> data) {
|
||
_logger.i('📲 Handling notification action: $data');
|
||
|
||
if (data.isEmpty) {
|
||
_logger.w('⚠️ Empty notification data received.');
|
||
return;
|
||
}
|
||
|
||
final type = data['type'];
|
||
final action = data['Action'];
|
||
final keyword = data['Keyword'];
|
||
|
||
if (type != null) {
|
||
_handleByType(type, data);
|
||
} else if (keyword != null) {
|
||
_handleByKeyword(keyword, action, data);
|
||
} else {
|
||
_logger.w('⚠️ Unhandled notification: $data');
|
||
}
|
||
}
|
||
|
||
/// Handle notification if identified by `type`
|
||
static void _handleByType(String type, Map<String, dynamic> data) {
|
||
switch (type) {
|
||
case 'expense_updated':
|
||
_handleExpenseUpdated(data);
|
||
break;
|
||
case 'attendance_updated':
|
||
_handleAttendanceUpdated(data);
|
||
_handleDashboardUpdate(data); // refresh dashboard attendance
|
||
break;
|
||
case 'dashboard_update':
|
||
_handleDashboardUpdate(data); // full dashboard refresh
|
||
break;
|
||
default:
|
||
_logger.w('⚠️ Unknown notification type: $type');
|
||
}
|
||
}
|
||
|
||
/// Handle notification if identified by `keyword`
|
||
static void _handleByKeyword(
|
||
String keyword, String? action, Map<String, dynamic> data) {
|
||
switch (keyword) {
|
||
/// 🔹 Attendance
|
||
case 'Attendance':
|
||
if (_isAttendanceAction(action)) {
|
||
_handleAttendanceUpdated(data);
|
||
_handleDashboardUpdate(data);
|
||
}
|
||
break;
|
||
case 'Team_Modified':
|
||
// Call method to handle team modifications and dashboard update
|
||
_handleDashboardUpdate(data);
|
||
break;
|
||
/// 🔹 Expenses
|
||
case 'Expenses_Modified':
|
||
_handleExpenseUpdated(data);
|
||
_handleDashboardUpdate(data);
|
||
break;
|
||
|
||
/// 🔹 Documents
|
||
case 'Employee_Document_Modified':
|
||
case 'Project_Document_Modified':
|
||
_handleDocumentModified(data);
|
||
break;
|
||
|
||
/// 🔹 Directory / Contacts
|
||
case 'Contact_Modified':
|
||
_handleContactModified(data);
|
||
break;
|
||
|
||
case 'Contact_Note_Modified':
|
||
_handleContactNoteModified(data);
|
||
break;
|
||
|
||
case 'Bucket_Modified':
|
||
_handleBucketModified(data);
|
||
break;
|
||
|
||
case 'Bucket_Assigned':
|
||
_handleBucketAssigned(data);
|
||
break;
|
||
|
||
default:
|
||
_logger.w('⚠️ Unhandled notification keyword: $keyword');
|
||
}
|
||
}
|
||
|
||
/// ---------------------- HANDLERS ----------------------
|
||
|
||
|
||
|
||
static bool _isAttendanceAction(String? action) {
|
||
const validActions = {
|
||
'CHECK_IN',
|
||
'CHECK_OUT',
|
||
'REQUEST_REGULARIZE',
|
||
'REQUEST_DELETE',
|
||
'REGULARIZE',
|
||
'REGULARIZE_REJECT'
|
||
};
|
||
return validActions.contains(action);
|
||
}
|
||
|
||
static void _handleExpenseUpdated(Map<String, dynamic> data) {
|
||
final expenseId = data['ExpenseId'];
|
||
if (expenseId == null) {
|
||
_logger.w("⚠️ Expense update received without ExpenseId: $data");
|
||
return;
|
||
}
|
||
|
||
// Update Expense List
|
||
_safeControllerUpdate<ExpenseController>(
|
||
onFound: (controller) async {
|
||
await controller.fetchExpenses();
|
||
},
|
||
notFoundMessage: '⚠️ ExpenseController not found, cannot refresh list.',
|
||
successMessage:
|
||
'✅ ExpenseController refreshed from expense notification.',
|
||
);
|
||
|
||
// Update Expense Detail (if open and matches this expenseId)
|
||
_safeControllerUpdate<ExpenseDetailController>(
|
||
onFound: (controller) async {
|
||
if (controller.expense.value?.id == expenseId) {
|
||
await controller.fetchExpenseDetails();
|
||
_logger
|
||
.i("✅ ExpenseDetailController refreshed for Expense $expenseId");
|
||
}
|
||
},
|
||
notFoundMessage: 'ℹ️ ExpenseDetailController not active, skipping.',
|
||
successMessage: '✅ ExpenseDetailController checked for refresh.',
|
||
);
|
||
}
|
||
|
||
static void _handleAttendanceUpdated(Map<String, dynamic> data) {
|
||
_safeControllerUpdate<AttendanceController>(
|
||
onFound: (controller) => controller.refreshDataFromNotification(
|
||
projectId: data['ProjectId'],
|
||
),
|
||
notFoundMessage: '⚠️ AttendanceController not found, cannot update.',
|
||
successMessage: '✅ AttendanceController refreshed from notification.',
|
||
);
|
||
}
|
||
|
||
/// ---------------------- DOCUMENT HANDLER ----------------------
|
||
static void _handleDocumentModified(Map<String, dynamic> data) {
|
||
String entityTypeId;
|
||
String entityId;
|
||
String? documentId = data['DocumentId'];
|
||
|
||
// Determine entity type and ID
|
||
if (data['Keyword'] == 'Employee_Document_Modified') {
|
||
entityTypeId = Permissions.employeeEntity;
|
||
entityId = data['EmployeeId'] ?? '';
|
||
} else if (data['Keyword'] == 'Project_Document_Modified') {
|
||
entityTypeId = Permissions.projectEntity;
|
||
entityId = data['ProjectId'] ?? '';
|
||
} else {
|
||
_logger.w("⚠️ Document update received with unknown keyword: $data");
|
||
return;
|
||
}
|
||
|
||
if (entityId.isEmpty) {
|
||
_logger.w("⚠️ Document update missing entityId: $data");
|
||
return;
|
||
}
|
||
|
||
_logger.i(
|
||
"🔔 Document notification received: keyword=${data['Keyword']}, entityTypeId=$entityTypeId, entityId=$entityId, documentId=$documentId");
|
||
|
||
// Refresh Document List
|
||
if (Get.isRegistered<DocumentController>()) {
|
||
_safeControllerUpdate<DocumentController>(
|
||
onFound: (controller) async {
|
||
await controller.fetchDocuments(
|
||
entityTypeId: entityTypeId,
|
||
entityId: entityId,
|
||
reset: true,
|
||
);
|
||
},
|
||
notFoundMessage:
|
||
'⚠️ DocumentController not found, cannot refresh list.',
|
||
successMessage: '✅ DocumentController refreshed from notification.',
|
||
);
|
||
} else {
|
||
_logger.w('⚠️ DocumentController not registered, skipping list refresh.');
|
||
}
|
||
|
||
// Refresh Document Details (if open)
|
||
if (documentId != null && Get.isRegistered<DocumentDetailsController>()) {
|
||
_safeControllerUpdate<DocumentDetailsController>(
|
||
onFound: (controller) async {
|
||
// Refresh details regardless of current document
|
||
await controller.fetchDocumentDetails(documentId);
|
||
_logger.i(
|
||
"✅ DocumentDetailsController refreshed for Document $documentId");
|
||
},
|
||
notFoundMessage:
|
||
'ℹ️ DocumentDetailsController not active, skipping details refresh.',
|
||
successMessage: '✅ DocumentDetailsController checked for refresh.',
|
||
);
|
||
} else if (documentId != null) {
|
||
_logger.w(
|
||
'⚠️ DocumentDetailsController not registered, cannot refresh document details.');
|
||
}
|
||
}
|
||
|
||
/// ---------------------- DIRECTORY HANDLERS ----------------------
|
||
static void _handleContactModified(Map<String, dynamic> data) {
|
||
final contactId = data['ContactId'];
|
||
|
||
// Always refresh the contact list
|
||
_safeControllerUpdate<DirectoryController>(
|
||
onFound: (controller) {
|
||
controller.fetchContacts();
|
||
// If a specific contact is provided, refresh its notes as well
|
||
if (contactId != null) {
|
||
controller.fetchCommentsForContact(contactId);
|
||
}
|
||
},
|
||
notFoundMessage:
|
||
'⚠️ DirectoryController not found, cannot refresh contacts.',
|
||
successMessage:
|
||
'✅ Directory contacts (and notes if applicable) refreshed from notification.',
|
||
);
|
||
|
||
// Refresh notes globally as well
|
||
_safeControllerUpdate<NotesController>(
|
||
onFound: (controller) => controller.fetchNotes(),
|
||
notFoundMessage: '⚠️ NotesController not found, cannot refresh notes.',
|
||
successMessage: '✅ Notes refreshed from notification.',
|
||
);
|
||
}
|
||
|
||
static void _handleContactNoteModified(Map<String, dynamic> data) {
|
||
// Refresh both contacts and notes when a note is modified
|
||
_handleContactModified(data);
|
||
}
|
||
|
||
static void _handleBucketModified(Map<String, dynamic> data) {
|
||
_safeControllerUpdate<DirectoryController>(
|
||
onFound: (controller) => controller.fetchBuckets(),
|
||
notFoundMessage: '⚠️ DirectoryController not found, cannot refresh.',
|
||
successMessage: '✅ Buckets refreshed from notification.',
|
||
);
|
||
}
|
||
|
||
static void _handleBucketAssigned(Map<String, dynamic> data) {
|
||
_safeControllerUpdate<DirectoryController>(
|
||
onFound: (controller) => controller.fetchBuckets(),
|
||
notFoundMessage: '⚠️ DirectoryController not found, cannot refresh.',
|
||
successMessage: '✅ Bucket assignments refreshed from notification.',
|
||
);
|
||
}
|
||
|
||
/// ---------------------- DASHBOARD HANDLER ----------------------
|
||
static void _handleDashboardUpdate(Map<String, dynamic> data) {
|
||
_safeControllerUpdate<DashboardController>(
|
||
onFound: (controller) async {
|
||
final type = data['type'] ?? '';
|
||
switch (type) {
|
||
case 'attendance_updated':
|
||
await controller.fetchRoleWiseAttendance();
|
||
break;
|
||
|
||
case 'task_updated':
|
||
await controller.fetchDashboardTasks(
|
||
projectId: controller.projectController.selectedProjectId.value,
|
||
);
|
||
break;
|
||
|
||
case 'project_progress_update':
|
||
await controller.fetchProjectProgress();
|
||
break;
|
||
|
||
case 'Employee_Suspend':
|
||
final currentProjectId =
|
||
controller.projectController.selectedProjectId.value;
|
||
final projectIdsString = data['ProjectIds'] ?? '';
|
||
|
||
// Convert comma-separated string to List<String>
|
||
final notificationProjectIds =
|
||
projectIdsString.split(',').map((e) => e.trim()).toList();
|
||
|
||
// Refresh only if current project ID is in the list
|
||
if (notificationProjectIds.contains(currentProjectId)) {
|
||
await controller.fetchDashboardTeams(projectId: currentProjectId);
|
||
}
|
||
break;
|
||
|
||
case 'Team_Modified':
|
||
final projectId = data['ProjectId'] ??
|
||
controller.projectController.selectedProjectId.value;
|
||
await controller.fetchDashboardTeams(projectId: projectId);
|
||
break;
|
||
|
||
case 'full_dashboard_refresh':
|
||
default:
|
||
await controller.refreshDashboard();
|
||
}
|
||
},
|
||
notFoundMessage: '⚠️ DashboardController not found, cannot refresh.',
|
||
successMessage: '✅ DashboardController refreshed from notification.',
|
||
);
|
||
}
|
||
|
||
/// ---------------------- UTILITY ----------------------
|
||
|
||
static void _safeControllerUpdate<T>({
|
||
required void Function(T controller) onFound,
|
||
required String notFoundMessage,
|
||
required String successMessage,
|
||
}) {
|
||
try {
|
||
final controller = Get.find<T>();
|
||
onFound(controller);
|
||
_logger.i(successMessage);
|
||
} catch (e) {
|
||
_logger.w(notFoundMessage);
|
||
}
|
||
}
|
||
}
|