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/expense/expense_detail_model.dart'; import 'package:marco/model/employees/employee_model.dart'; import 'package:flutter/material.dart'; class ExpenseDetailController extends GetxController { final Rx expense = Rx(null); final RxBool isLoading = false.obs; final RxString errorMessage = ''.obs; final Rx selectedReimbursedBy = Rx(null); final RxList allEmployees = [].obs; final RxList employeeSearchResults = [].obs; late String _expenseId; bool _isInitialized = false; final employeeSearchController = TextEditingController(); final isSearchingEmployees = false.obs; /// Call this once from the screen (NOT inside build) to initialize void init(String expenseId) { if (_isInitialized) return; _isInitialized = true; _expenseId = expenseId; // Use Future.wait to fetch details and employees concurrently Future.wait([ fetchExpenseDetails(), fetchAllEmployees(), ]); } /// Generic method to handle API calls with loading and error states Future _apiCallWrapper( Future Function() apiCall, String operationName) async { isLoading.value = true; errorMessage.value = ''; // Clear previous errors try { logSafe("Initiating $operationName..."); final result = await apiCall(); logSafe("$operationName completed successfully."); return result; } catch (e, stack) { errorMessage.value = 'An unexpected error occurred during $operationName.'; logSafe("Exception in $operationName: $e", level: LogLevel.error); logSafe("StackTrace: $stack", level: LogLevel.debug); return null; } finally { isLoading.value = false; } } /// Fetch expense details by stored ID Future fetchExpenseDetails() async { final result = await _apiCallWrapper( () => ApiService.getExpenseDetailsApi(expenseId: _expenseId), "fetch expense details"); if (result != null) { try { expense.value = ExpenseDetailModel.fromJson(result); logSafe("Expense details loaded successfully: ${expense.value?.id}"); } catch (e) { errorMessage.value = 'Failed to parse expense details: $e'; logSafe("Parse error in fetchExpenseDetails: $e", level: LogLevel.error); } } else { errorMessage.value = 'Failed to fetch expense details from server.'; logSafe("fetchExpenseDetails failed: null response", level: LogLevel.error); } } // This method seems like a utility and might be better placed in a helper or utility class // if it's used across multiple controllers. Keeping it here for now as per original code. List parsePermissionIds(dynamic permissionData) { if (permissionData == null) return []; if (permissionData is List) { return permissionData .map((e) => e.toString().trim()) .where((e) => e.isNotEmpty) .toList(); } if (permissionData is String) { final clean = permissionData.replaceAll(RegExp(r'[\[\]]'), ''); return clean .split(',') .map((e) => e.trim()) .where((e) => e.isNotEmpty) .toList(); } return []; } Future searchEmployees(String query) async { if (query.trim().isEmpty) return employeeSearchResults.clear(); isSearchingEmployees.value = true; try { final data = await ApiService.searchEmployeesBasic(searchString: query.trim()); employeeSearchResults.assignAll( (data ?? []).map((e) => EmployeeModel.fromJson(e)), ); } catch (e) { logSafe("Error searching employees: $e", level: LogLevel.error); employeeSearchResults.clear(); } finally { isSearchingEmployees.value = false; } } /// Fetch all employees Future fetchAllEmployees() async { final response = await _apiCallWrapper( () => ApiService.getAllEmployees(), "fetch all employees"); if (response != null && response.isNotEmpty) { try { allEmployees.assignAll(response.map((e) => EmployeeModel.fromJson(e))); logSafe("All Employees fetched: ${allEmployees.length}", level: LogLevel.info); } catch (e) { errorMessage.value = 'Failed to parse employee data: $e'; logSafe("Parse error in fetchAllEmployees: $e", level: LogLevel.error); } } else { allEmployees.clear(); logSafe("No employees found.", level: LogLevel.warning); } // `update()` is typically not needed for RxList directly unless you have specific GetBuilder/Obx usage that requires it // If you are using Obx widgets, `allEmployees.assignAll` will automatically trigger a rebuild. } /// Update expense with reimbursement info and status Future updateExpenseStatusWithReimbursement({ required String comment, required String reimburseTransactionId, required String reimburseDate, required String reimburseById, required String statusId, }) async { final success = await _apiCallWrapper( () => ApiService.updateExpenseStatusApi( expenseId: _expenseId, statusId: statusId, comment: comment, reimburseTransactionId: reimburseTransactionId, reimburseDate: reimburseDate, reimbursedById: reimburseById, ), "submit reimbursement", ); if (success == true) { // Explicitly check for true as _apiCallWrapper returns T? await fetchExpenseDetails(); // Refresh details after successful update return true; } else { errorMessage.value = "Failed to submit reimbursement."; return false; } } /// Update status for this specific expense Future updateExpenseStatus(String statusId, {String? comment}) async { final success = await _apiCallWrapper( () => ApiService.updateExpenseStatusApi( expenseId: _expenseId, statusId: statusId, comment: comment, ), "update expense status", ); if (success == true) { await fetchExpenseDetails(); return true; } else { errorMessage.value = "Failed to update expense status."; return false; } } }