392 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			392 lines
		
	
	
		
			13 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/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<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;
 | ||
| 
 | ||
|       /// 🔹 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<String, dynamic> data) {
 | ||
|     final projectId = data['ProjectId'];
 | ||
|     if (projectId == null) {
 | ||
|       _logger.w("⚠️ TaskPlanning update received without ProjectId: $data");
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
|     _safeControllerUpdate<DailyTaskPlanningController>(
 | ||
|       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<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.',
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   static void _handleTaskUpdated(Map<String, dynamic> data,
 | ||
|       {required bool isComment}) {
 | ||
|     _safeControllerUpdate<DailyTaskController>(
 | ||
|       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<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);
 | ||
|     }
 | ||
|   }
 | ||
| }
 |