import 'package:get/get.dart'; import 'package:logger/logger.dart'; import 'package:marco/controller/attendance/attendance_screen_controller.dart'; import 'package:marco/controller/task_planning/daily_task_controller.dart'; import 'package:marco/controller/task_Planning/daily_task_Planning_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 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 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 data) { switch (keyword) { /// 🔹 Attendance case 'Attendance': if (_isAttendanceAction(action)) { _handleAttendanceUpdated(data); _handleDashboardUpdate(data); } break; /// 🔹 Tasks case 'Report_Task': _handleTaskUpdated(data, isComment: false); _handleDashboardUpdate(data); break; case 'Task_Comment': _handleTaskUpdated(data, isComment: true); _handleDashboardUpdate(data); break; case 'Task_Modified': case 'WorkArea_Modified': case 'Floor_Modified': case 'Building_Modified': _handleTaskPlanningUpdated(data); _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 void _handleTaskPlanningUpdated(Map data) { final projectId = data['ProjectId']; if (projectId == null) { _logger.w("âš ī¸ TaskPlanning update received without ProjectId: $data"); return; } _safeControllerUpdate( onFound: (controller) { controller.fetchTaskData(projectId); }, notFoundMessage: 'âš ī¸ DailyTaskPlanningController not found, cannot refresh.', successMessage: '✅ DailyTaskPlanningController refreshed from notification.', ); } 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 data) { final expenseId = data['ExpenseId']; if (expenseId == null) { _logger.w("âš ī¸ Expense update received without ExpenseId: $data"); return; } // Update Expense List _safeControllerUpdate( 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( 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 data) { _safeControllerUpdate( onFound: (controller) => controller.refreshDataFromNotification( projectId: data['ProjectId'], ), notFoundMessage: 'âš ī¸ AttendanceController not found, cannot update.', successMessage: '✅ AttendanceController refreshed from notification.', ); } static void _handleTaskUpdated(Map data, {required bool isComment}) { _safeControllerUpdate( onFound: (controller) => controller.refreshTasksFromNotification( projectId: data['ProjectId'], taskAllocationId: data['TaskAllocationId'], ), notFoundMessage: 'âš ī¸ DailyTaskController not found, cannot update.', successMessage: '✅ DailyTaskController refreshed from notification.', ); } /// ---------------------- DOCUMENT HANDLER ---------------------- static void _handleDocumentModified(Map 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()) { _safeControllerUpdate( 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()) { _safeControllerUpdate( 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 ---------------------- /// ---------------------- DIRECTORY HANDLERS ---------------------- static void _handleContactModified(Map data) { final contactId = data['ContactId']; // Always refresh the contact list _safeControllerUpdate( 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( onFound: (controller) => controller.fetchNotes(), notFoundMessage: 'âš ī¸ NotesController not found, cannot refresh notes.', successMessage: '✅ Notes refreshed from notification.', ); } static void _handleContactNoteModified(Map data) { final contactId = data['ContactId']; // Refresh both contacts and notes when a note is modified _handleContactModified(data); } static void _handleBucketModified(Map data) { _safeControllerUpdate( onFound: (controller) => controller.fetchBuckets(), notFoundMessage: 'âš ī¸ DirectoryController not found, cannot refresh.', successMessage: '✅ Buckets refreshed from notification.', ); } static void _handleBucketAssigned(Map data) { _safeControllerUpdate( onFound: (controller) => controller.fetchBuckets(), notFoundMessage: 'âš ī¸ DirectoryController not found, cannot refresh.', successMessage: '✅ Bucket assignments refreshed from notification.', ); } /// ---------------------- DASHBOARD HANDLER ---------------------- static void _handleDashboardUpdate(Map data) { _safeControllerUpdate( 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 'full_dashboard_refresh': default: await controller.refreshDashboard(); } }, notFoundMessage: 'âš ī¸ DashboardController not found, cannot refresh.', successMessage: '✅ DashboardController refreshed from notification.', ); } /// ---------------------- UTILITY ---------------------- static void _safeControllerUpdate({ required void Function(T controller) onFound, required String notFoundMessage, required String successMessage, }) { try { final controller = Get.find(); onFound(controller); _logger.i(successMessage); } catch (e) { _logger.w(notFoundMessage); } } }