2489 lines
		
	
	
		
			76 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			2489 lines
		
	
	
		
			76 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:convert';
 | |
| import 'package:get/get.dart';
 | |
| import 'package:http/http.dart' as http;
 | |
| import 'package:image_picker/image_picker.dart';
 | |
| import 'package:intl/intl.dart';
 | |
| 
 | |
| import 'package:marco/helpers/services/auth_service.dart';
 | |
| import 'package:marco/helpers/services/api_endpoints.dart';
 | |
| import 'package:marco/helpers/services/storage/local_storage.dart';
 | |
| import 'package:jwt_decoder/jwt_decoder.dart';
 | |
| import 'package:marco/model/dashboard/project_progress_model.dart';
 | |
| import 'package:marco/model/dashboard/dashboard_tasks_model.dart';
 | |
| import 'package:marco/model/dashboard/dashboard_teams_model.dart';
 | |
| import 'package:marco/helpers/services/app_logger.dart';
 | |
| import 'package:marco/model/document/document_filter_model.dart';
 | |
| import 'package:marco/model/document/documents_list_model.dart';
 | |
| import 'package:marco/model/document/master_document_tags.dart';
 | |
| import 'package:marco/model/document/master_document_type_model.dart';
 | |
| import 'package:marco/model/document/document_details_model.dart';
 | |
| import 'package:marco/model/document/document_version_model.dart';
 | |
| import 'package:marco/model/attendance/organization_per_project_list_model.dart';
 | |
| import 'package:marco/model/tenant/tenant_services_model.dart';
 | |
| import 'package:marco/model/dailyTaskPlanning/daily_task_model.dart';
 | |
| import 'package:marco/model/dailyTaskPlanning/daily_progress_report_filter_response_model.dart';
 | |
| import 'package:marco/model/all_organization_model.dart';
 | |
| 
 | |
| class ApiService {
 | |
|   static const bool enableLogs = true;
 | |
|   static const Duration extendedTimeout = Duration(seconds: 60);
 | |
| 
 | |
|   static Future<String?> _getToken() async {
 | |
|     final token = await LocalStorage.getJwtToken();
 | |
| 
 | |
|     if (token == null) {
 | |
|       logSafe("No JWT token found. Logging out...");
 | |
|       await LocalStorage.logout();
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       if (JwtDecoder.isExpired(token)) {
 | |
|         logSafe("Access token is expired. Attempting refresh...");
 | |
|         final refreshed = await AuthService.refreshToken();
 | |
|         if (refreshed) {
 | |
|           return await LocalStorage.getJwtToken();
 | |
|         } else {
 | |
|           logSafe("Token refresh failed. Logging out immediately...");
 | |
|           await LocalStorage.logout();
 | |
|           return null;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       final expirationDate = JwtDecoder.getExpirationDate(token);
 | |
|       final now = DateTime.now();
 | |
|       final difference = expirationDate.difference(now);
 | |
| 
 | |
|       if (difference.inMinutes < 2) {
 | |
|         logSafe(
 | |
|             "Access token is about to expire in ${difference.inSeconds}s. Refreshing...");
 | |
|         final refreshed = await AuthService.refreshToken();
 | |
|         if (refreshed) {
 | |
|           return await LocalStorage.getJwtToken();
 | |
|         } else {
 | |
|           logSafe("Token refresh failed (near expiry). Logging out...");
 | |
|           await LocalStorage.logout();
 | |
|           return null;
 | |
|         }
 | |
|       }
 | |
|     } catch (e) {
 | |
|       logSafe("Token decoding error: $e", level: LogLevel.error);
 | |
|       await LocalStorage.logout();
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     return token;
 | |
|   }
 | |
| 
 | |
|   static Map<String, String> _headers(String token) => {
 | |
|         'Content-Type': 'application/json',
 | |
|         'Authorization': 'Bearer $token',
 | |
|       };
 | |
| 
 | |
|   static void _log(String message) {
 | |
|     if (enableLogs) logSafe(message);
 | |
|   }
 | |
| 
 | |
|   static dynamic _parseResponse(http.Response response, {String label = ''}) {
 | |
|     _log("$label Response: ${response.body}");
 | |
|     try {
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (response.statusCode == 200 && json['success'] == true) {
 | |
|         return json['data'];
 | |
|       }
 | |
|       _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}");
 | |
|     } catch (e) {
 | |
|       _log("Response parsing error [$label]: $e");
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   static dynamic _parseResponseForAllData(http.Response response,
 | |
|       {String label = ''}) {
 | |
|     _log("$label Response: ${response.body}");
 | |
| 
 | |
|     try {
 | |
|       final body = response.body.trim();
 | |
|       if (body.isEmpty) throw FormatException("Empty response body");
 | |
| 
 | |
|       final json = jsonDecode(body);
 | |
|       if (response.statusCode == 200 && json['success'] == true) {
 | |
|         return json;
 | |
|       }
 | |
| 
 | |
|       _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}");
 | |
|     } catch (e) {
 | |
|       _log("Response parsing error [$label]: $e");
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   static Future<http.Response?> _getRequest(
 | |
|     String endpoint, {
 | |
|     Map<String, String>? queryParams,
 | |
|     bool hasRetried = false,
 | |
|   }) async {
 | |
|     String? token = await _getToken();
 | |
|     if (token == null) {
 | |
|       logSafe("Token is null. Forcing logout from GET request.",
 | |
|           level: LogLevel.error);
 | |
|       await LocalStorage.logout();
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint")
 | |
|         .replace(queryParameters: queryParams);
 | |
| 
 | |
|     logSafe("Initiating GET request", level: LogLevel.debug);
 | |
|     logSafe("URL: $uri", level: LogLevel.debug);
 | |
|     logSafe("Query Parameters: ${queryParams ?? {}}", level: LogLevel.debug);
 | |
|     logSafe("Headers: ${_headers(token)}", level: LogLevel.debug);
 | |
| 
 | |
|     try {
 | |
|       final response = await http
 | |
|           .get(uri, headers: _headers(token))
 | |
|           .timeout(extendedTimeout);
 | |
| 
 | |
|       logSafe("Response Status: ${response.statusCode}", level: LogLevel.debug);
 | |
|       logSafe("Response Body: ${response.body}", level: LogLevel.debug);
 | |
| 
 | |
|       if (response.statusCode == 401 && !hasRetried) {
 | |
|         logSafe("Unauthorized (401). Attempting token refresh...",
 | |
|             level: LogLevel.warning);
 | |
| 
 | |
|         if (await AuthService.refreshToken()) {
 | |
|           logSafe("Token refresh succeeded. Retrying request...",
 | |
|               level: LogLevel.info);
 | |
|           return await _getRequest(
 | |
|             endpoint,
 | |
|             queryParams: queryParams,
 | |
|             hasRetried: true,
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         logSafe("Token refresh failed. Logging out user.",
 | |
|             level: LogLevel.error);
 | |
|         await LocalStorage.logout();
 | |
|       }
 | |
| 
 | |
|       return response;
 | |
|     } catch (e) {
 | |
|       logSafe("HTTP GET Exception: $e", level: LogLevel.error);
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static Future<http.Response?> _postRequest(
 | |
|     String endpoint,
 | |
|     dynamic body, {
 | |
|     Duration customTimeout = extendedTimeout,
 | |
|     bool hasRetried = false,
 | |
|   }) async {
 | |
|     String? token = await _getToken();
 | |
|     if (token == null) return null;
 | |
| 
 | |
|     final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
 | |
|     logSafe(
 | |
|       "POST $uri\nHeaders: ${_headers(token)}\nBody: $body",
 | |
|     );
 | |
| 
 | |
|     try {
 | |
|       final response = await http
 | |
|           .post(uri, headers: _headers(token), body: jsonEncode(body))
 | |
|           .timeout(customTimeout);
 | |
| 
 | |
|       if (response.statusCode == 401 && !hasRetried) {
 | |
|         logSafe("Unauthorized POST. Attempting token refresh...");
 | |
|         if (await AuthService.refreshToken()) {
 | |
|           return await _postRequest(endpoint, body,
 | |
|               customTimeout: customTimeout, hasRetried: true);
 | |
|         }
 | |
|       }
 | |
|       return response;
 | |
|     } catch (e) {
 | |
|       logSafe("HTTP POST Exception: $e", level: LogLevel.error);
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static Future<http.Response?> _putRequest(
 | |
|     String endpoint,
 | |
|     dynamic body, {
 | |
|     Map<String, String>? additionalHeaders,
 | |
|     Duration customTimeout = extendedTimeout,
 | |
|     bool hasRetried = false,
 | |
|   }) async {
 | |
|     String? token = await _getToken();
 | |
|     if (token == null) return null;
 | |
| 
 | |
|     final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
 | |
|     logSafe(
 | |
|       "PUT $uri\nHeaders: ${_headers(token)}\nBody: $body",
 | |
|     );
 | |
|     final headers = {
 | |
|       ..._headers(token),
 | |
|       if (additionalHeaders != null) ...additionalHeaders,
 | |
|     };
 | |
| 
 | |
|     logSafe(
 | |
|       "PUT $uri\nHeaders: $headers\nBody: $body",
 | |
|     );
 | |
| 
 | |
|     try {
 | |
|       final response = await http
 | |
|           .put(uri, headers: headers, body: jsonEncode(body))
 | |
|           .timeout(customTimeout);
 | |
| 
 | |
|       if (response.statusCode == 401 && !hasRetried) {
 | |
|         logSafe("Unauthorized PUT. Attempting token refresh...");
 | |
|         if (await AuthService.refreshToken()) {
 | |
|           return await _putRequest(endpoint, body,
 | |
|               additionalHeaders: additionalHeaders,
 | |
|               customTimeout: customTimeout,
 | |
|               hasRetried: true);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return response;
 | |
|     } catch (e) {
 | |
|       logSafe("HTTP PUT Exception: $e", level: LogLevel.error);
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static Future<http.Response?> _deleteRequest(
 | |
|     String endpoint, {
 | |
|     Map<String, String>? additionalHeaders,
 | |
|     Duration customTimeout = extendedTimeout,
 | |
|     bool hasRetried = false,
 | |
|   }) async {
 | |
|     String? token = await _getToken();
 | |
|     if (token == null) return null;
 | |
| 
 | |
|     final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
 | |
|     final headers = {
 | |
|       ..._headers(token),
 | |
|       if (additionalHeaders != null) ...additionalHeaders,
 | |
|     };
 | |
| 
 | |
|     logSafe("DELETE $uri\nHeaders: $headers");
 | |
| 
 | |
|     try {
 | |
|       final response =
 | |
|           await http.delete(uri, headers: headers).timeout(customTimeout);
 | |
| 
 | |
|       if (response.statusCode == 401 && !hasRetried) {
 | |
|         logSafe("Unauthorized DELETE. Attempting token refresh...");
 | |
|         if (await AuthService.refreshToken()) {
 | |
|           return await _deleteRequest(
 | |
|             endpoint,
 | |
|             additionalHeaders: additionalHeaders,
 | |
|             customTimeout: customTimeout,
 | |
|             hasRetried: true,
 | |
|           );
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return response;
 | |
|     } catch (e) {
 | |
|       logSafe("HTTP DELETE Exception: $e", level: LogLevel.error);
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Get Organizations assigned to a Project
 | |
|   static Future<OrganizationListResponse?> getAssignedOrganizations(
 | |
|       String projectId) async {
 | |
|     final endpoint = "${ApiEndpoints.getAssignedOrganizations}/$projectId";
 | |
|     logSafe("Fetching organizations assigned to projectId: $projectId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Assigned Organizations request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Assigned Organizations");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return OrganizationListResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getAssignedOrganizations: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   static Future<AllOrganizationListResponse?> getAllOrganizations() async {
 | |
|     final endpoint = "${ApiEndpoints.getAllOrganizations}";
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("All Organizations request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "All Organizations");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return AllOrganizationListResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getAllOrganizations: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   //// Get Services assigned to a Project
 | |
|   static Future<ServiceListResponse?> getAssignedServices(
 | |
|       String projectId) async {
 | |
|     final endpoint = "${ApiEndpoints.getAssignedServices}/$projectId";
 | |
|     logSafe("Fetching services assigned to projectId: $projectId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Assigned Services request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Assigned Services");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return ServiceListResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getAssignedServices: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> postLogsApi(List<Map<String, dynamic>> logs) async {
 | |
|     const endpoint = "${ApiEndpoints.uploadLogs}";
 | |
|     logSafe("Posting logs... count=${logs.length}");
 | |
| 
 | |
|     try {
 | |
|       // Get token directly without triggering logout or refresh
 | |
|       final token = await LocalStorage.getJwtToken();
 | |
|       if (token == null) {
 | |
|         logSafe("No token available. Skipping logs post.",
 | |
|             level: LogLevel.warning);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
 | |
|       final headers = {
 | |
|         'Content-Type': 'application/json',
 | |
|         'Authorization': 'Bearer $token',
 | |
|       };
 | |
| 
 | |
|       final response = await http
 | |
|           .post(uri, headers: headers, body: jsonEncode(logs))
 | |
|           .timeout(ApiService.extendedTimeout);
 | |
| 
 | |
|       logSafe("Post logs response status: ${response.statusCode}");
 | |
|       logSafe("Post logs response body: ${response.body}");
 | |
| 
 | |
|       if (response.statusCode == 200 && response.body.isNotEmpty) {
 | |
|         final json = jsonDecode(response.body);
 | |
|         if (json['success'] == true) {
 | |
|           logSafe("Logs posted successfully.");
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       logSafe("Failed to post logs: ${response.body}", level: LogLevel.warning);
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during postLogsApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Verify Document API
 | |
|   static Future<bool> verifyDocumentApi({
 | |
|     required String id,
 | |
|     bool isVerify = true,
 | |
|   }) async {
 | |
|     final endpoint = "${ApiEndpoints.verifyDocument}/$id";
 | |
|     final queryParams = {"isVerify": isVerify.toString()};
 | |
|     logSafe("Verifying document with id: $id | isVerify: $isVerify");
 | |
| 
 | |
|     try {
 | |
|       final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint")
 | |
|           .replace(queryParameters: queryParams);
 | |
| 
 | |
|       String? token = await _getToken();
 | |
|       if (token == null) return false;
 | |
| 
 | |
|       final headers = _headers(token);
 | |
|       logSafe("POST (verify) $uri\nHeaders: $headers");
 | |
| 
 | |
|       final response =
 | |
|           await http.post(uri, headers: headers).timeout(extendedTimeout);
 | |
| 
 | |
|       if (response.statusCode == 401) {
 | |
|         logSafe("Unauthorized VERIFY. Attempting token refresh...");
 | |
|         if (await AuthService.refreshToken()) {
 | |
|           return await verifyDocumentApi(id: id, isVerify: isVerify);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       logSafe("Verify document response status: ${response.statusCode}");
 | |
|       logSafe("Verify document response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Document verify success: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|           "Failed to verify document: ${json['message'] ?? 'Unknown error'}",
 | |
|           level: LogLevel.warning,
 | |
|         );
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during verifyDocumentApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Get Pre-Signed URL for Old Version
 | |
|   static Future<String?> getPresignedUrlApi(String versionId) async {
 | |
|     final endpoint = "${ApiEndpoints.getDocumentVersion}/$versionId";
 | |
|     logSafe("Fetching Pre-Signed URL for versionId: $versionId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Pre-Signed URL request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Pre-Signed URL");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return jsonResponse['data'] as String?;
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getPresignedUrlApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Delete (Soft Delete / Deactivate) Document API
 | |
|   static Future<bool> deleteDocumentApi({
 | |
|     required String id,
 | |
|     bool isActive = false, // default false = delete
 | |
|   }) async {
 | |
|     final endpoint = "${ApiEndpoints.deleteDocument}/$id";
 | |
|     final queryParams = {"isActive": isActive.toString()};
 | |
|     logSafe("Deleting document with id: $id | isActive: $isActive");
 | |
| 
 | |
|     try {
 | |
|       final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint")
 | |
|           .replace(queryParameters: queryParams);
 | |
| 
 | |
|       String? token = await _getToken();
 | |
|       if (token == null) return false;
 | |
| 
 | |
|       final headers = _headers(token);
 | |
|       logSafe("DELETE (PUT/POST style) $uri\nHeaders: $headers");
 | |
| 
 | |
|       // some backends use PUT instead of DELETE for soft deletes
 | |
|       final response =
 | |
|           await http.delete(uri, headers: headers).timeout(extendedTimeout);
 | |
| 
 | |
|       if (response.statusCode == 401) {
 | |
|         logSafe("Unauthorized DELETE. Attempting token refresh...");
 | |
|         if (await AuthService.refreshToken()) {
 | |
|           return await deleteDocumentApi(id: id, isActive: isActive);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       logSafe("Delete document response status: ${response.statusCode}");
 | |
|       logSafe("Delete document response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Document delete/update success: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|           "Failed to delete document: ${json['message'] ?? 'Unknown error'}",
 | |
|           level: LogLevel.warning,
 | |
|         );
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during deleteDocumentApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Edit Document API
 | |
|   static Future<bool> editDocumentApi({
 | |
|     required String id,
 | |
|     required String name,
 | |
|     required String documentId,
 | |
|     String? description,
 | |
|     List<Map<String, dynamic>> tags = const [],
 | |
|     Map<String, dynamic>? attachment, // 👈 can be null
 | |
|   }) async {
 | |
|     final endpoint = "${ApiEndpoints.editDocument}/$id";
 | |
|     logSafe("Editing document with id: $id");
 | |
| 
 | |
|     final Map<String, dynamic> payload = {
 | |
|       "id": id,
 | |
|       "name": name,
 | |
|       "documentId": documentId,
 | |
|       "description": description ?? "",
 | |
|       "tags": tags.isNotEmpty
 | |
|           ? tags
 | |
|           : [
 | |
|               {"name": "default", "isActive": true}
 | |
|             ],
 | |
|       "attachment": attachment, // 👈 null or object
 | |
|     };
 | |
| 
 | |
|     try {
 | |
|       final response =
 | |
|           await _putRequest(endpoint, payload, customTimeout: extendedTimeout);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Edit document failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Edit document response status: ${response.statusCode}");
 | |
|       logSafe("Edit document response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Document edited successfully: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|           "Failed to edit document: ${json['message'] ?? 'Unknown error'}",
 | |
|           level: LogLevel.warning,
 | |
|         );
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during editDocumentApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Get List of Versions by ParentAttachmentId
 | |
|   static Future<DocumentVersionsResponse?> getDocumentVersionsApi({
 | |
|     required String parentAttachmentId,
 | |
|     int pageNumber = 1,
 | |
|     int pageSize = 20,
 | |
|   }) async {
 | |
|     final endpoint = "${ApiEndpoints.getDocumentVersions}/$parentAttachmentId";
 | |
|     final queryParams = {
 | |
|       "pageNumber": pageNumber.toString(),
 | |
|       "pageSize": pageSize.toString(),
 | |
|     };
 | |
| 
 | |
|     logSafe(
 | |
|         "Fetching document versions for parentAttachmentId: $parentAttachmentId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint, queryParams: queryParams);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Document versions request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Document Versions");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return DocumentVersionsResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getDocumentVersionsApi: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Get Document Details by ID
 | |
|   static Future<DocumentDetailsResponse?> getDocumentDetailsApi(
 | |
|       String documentId) async {
 | |
|     final endpoint = "${ApiEndpoints.getDocumentDetails}/$documentId";
 | |
|     logSafe("Fetching document details for id: $documentId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Document details request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Document Details");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return DocumentDetailsResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getDocumentDetailsApi: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Get Document Types by CategoryId
 | |
|   static Future<DocumentTypesResponse?> getDocumentTypesByCategoryApi(
 | |
|       String documentCategoryId) async {
 | |
|     const endpoint = ApiEndpoints.getDocumentTypesByCategory;
 | |
| 
 | |
|     logSafe("Fetching document types for category: $documentCategoryId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(
 | |
|         endpoint,
 | |
|         queryParams: {"documentCategoryId": documentCategoryId},
 | |
|       );
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Document types by category request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse = _parseResponseForAllData(response,
 | |
|           label: "Document Types by Category");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return DocumentTypesResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getDocumentTypesByCategoryApi: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Get Master Document Types (Category Types)
 | |
|   static Future<DocumentTypesResponse?> getMasterDocumentTypesApi() async {
 | |
|     const endpoint = ApiEndpoints.getMasterDocumentCategories;
 | |
|     logSafe("Fetching master document types...");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Document types request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Master Document Types");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return DocumentTypesResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getMasterDocumentTypesApi: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Upload Document API
 | |
|   static Future<bool> uploadDocumentApi({
 | |
|     required String name,
 | |
|     String? documentId,
 | |
|     String? description,
 | |
|     required String entityId,
 | |
|     required String documentTypeId,
 | |
|     required String fileName,
 | |
|     required String base64Data,
 | |
|     required String contentType,
 | |
|     required int fileSize,
 | |
|     String? fileDescription,
 | |
|     bool isActive = true,
 | |
|     List<Map<String, dynamic>> tags = const [],
 | |
|   }) async {
 | |
|     const endpoint = ApiEndpoints.uploadDocument;
 | |
|     logSafe("Uploading document: $name for entity: $entityId");
 | |
| 
 | |
|     final Map<String, dynamic> payload = {
 | |
|       "name": name,
 | |
|       "documentId": documentId ?? "",
 | |
|       "description": description ?? "",
 | |
|       "entityId": entityId,
 | |
|       "documentTypeId": documentTypeId,
 | |
|       "attachment": {
 | |
|         "fileName": fileName,
 | |
|         "base64Data": base64Data,
 | |
|         "contentType": contentType,
 | |
|         "fileSize": fileSize,
 | |
|         "description": fileDescription ?? "",
 | |
|         "isActive": isActive,
 | |
|       },
 | |
|       "tags": tags.isNotEmpty
 | |
|           ? tags
 | |
|           : [
 | |
|               {"name": "default", "isActive": true}
 | |
|             ],
 | |
|     };
 | |
| 
 | |
|     try {
 | |
|       final response =
 | |
|           await _postRequest(endpoint, payload, customTimeout: extendedTimeout);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Upload document failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Upload document response status: ${response.statusCode}");
 | |
|       logSafe("Upload document response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Document uploaded successfully: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|           "Failed to upload document: ${json['message'] ?? 'Unknown error'}",
 | |
|           level: LogLevel.warning,
 | |
|         );
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during uploadDocumentApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Get Master Document Tags
 | |
|   static Future<TagResponse?> getMasterDocumentTagsApi() async {
 | |
|     const endpoint = ApiEndpoints.getMasterDocumentTags;
 | |
|     logSafe("Fetching master document tags...");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Tags request failed: null response", level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Master Document Tags");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return TagResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getMasterDocumentTagsApi: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Get Document List by EntityTypeId and EntityId
 | |
|   static Future<DocumentsResponse?> getDocumentListApi({
 | |
|     required String entityTypeId,
 | |
|     required String entityId,
 | |
|     String filter = "",
 | |
|     String searchString = "",
 | |
|     bool isActive = true,
 | |
|     int pageNumber = 1,
 | |
|     int pageSize = 20,
 | |
|   }) async {
 | |
|     final endpoint =
 | |
|         "${ApiEndpoints.getDocumentList}/$entityTypeId/entity/$entityId";
 | |
|     final queryParams = {
 | |
|       "filter": filter,
 | |
|       "searchString": searchString,
 | |
|       "isActive": isActive.toString(),
 | |
|       "pageNumber": pageNumber.toString(),
 | |
|       "pageSize": pageSize.toString(),
 | |
|     };
 | |
| 
 | |
|     logSafe(
 | |
|         "Fetching document list for entityTypeId: $entityTypeId, entityId: $entityId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint, queryParams: queryParams);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Document list request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Document List");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return DocumentsResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getDocumentListApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Get Document Filters by EntityTypeId
 | |
|   static Future<DocumentFiltersResponse?> getDocumentFilters(
 | |
|       String entityTypeId) async {
 | |
|     final endpoint = "${ApiEndpoints.getDocumentFilter}/$entityTypeId";
 | |
|     logSafe("Fetching document filters for entityTypeId: $entityTypeId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint, queryParams: null);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Document filter request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse =
 | |
|           _parseResponseForAllData(response, label: "Document Filters");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return DocumentFiltersResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getDocumentFilters: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
| // === Menu APIs === //
 | |
| 
 | |
|   /// Get Sidebar Menu API
 | |
|   static Future<Map<String, dynamic>?> getMenuApi() async {
 | |
|     logSafe("Fetching sidebar menu...");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(ApiEndpoints.getDynamicMenu);
 | |
|       if (response == null) {
 | |
|         logSafe("Menu request failed: null response", level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final body = response.body.trim();
 | |
|       if (body.isEmpty) {
 | |
|         logSafe("Menu response body is empty", level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse = jsonDecode(body);
 | |
|       if (jsonResponse is Map<String, dynamic>) {
 | |
|         if (jsonResponse['success'] == true) {
 | |
|           logSafe("Sidebar menu fetched successfully");
 | |
|           return jsonResponse; // ✅ return full response
 | |
|         } else {
 | |
|           logSafe(
 | |
|             "Failed to fetch sidebar menu: ${jsonResponse['message'] ?? 'Unknown error'}",
 | |
|             level: LogLevel.warning,
 | |
|           );
 | |
|         }
 | |
|       } else {
 | |
|         logSafe("Unexpected response structure: $jsonResponse",
 | |
|             level: LogLevel.error);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getMenuApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
| // === Expense APIs === //
 | |
| 
 | |
|   /// Edit Expense API
 | |
|   static Future<bool> editExpenseApi({
 | |
|     required String expenseId,
 | |
|     required Map<String, dynamic> payload,
 | |
|   }) async {
 | |
|     final endpoint = "${ApiEndpoints.editExpense}/$expenseId";
 | |
|     logSafe("Editing expense $expenseId with payload: $payload");
 | |
| 
 | |
|     try {
 | |
|       final response = await _putRequest(
 | |
|         endpoint,
 | |
|         payload,
 | |
|         customTimeout: extendedTimeout,
 | |
|       );
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Edit expense failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Edit expense response status: ${response.statusCode}");
 | |
|       logSafe("Edit expense response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Expense updated successfully: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|           "Failed to update expense: ${json['message'] ?? 'Unknown error'}",
 | |
|           level: LogLevel.warning,
 | |
|         );
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during editExpenseApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> deleteExpense(String expenseId) async {
 | |
|     final endpoint = "${ApiEndpoints.deleteExpense}/$expenseId";
 | |
| 
 | |
|     try {
 | |
|       final token = await _getToken();
 | |
|       if (token == null) {
 | |
|         logSafe("Token is null. Cannot proceed with DELETE request.",
 | |
|             level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
 | |
| 
 | |
|       logSafe("Sending DELETE request to $uri", level: LogLevel.debug);
 | |
| 
 | |
|       final response = await http
 | |
|           .delete(uri, headers: _headers(token))
 | |
|           .timeout(extendedTimeout);
 | |
| 
 | |
|       logSafe("DELETE expense response status: ${response.statusCode}");
 | |
|       logSafe("DELETE expense response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (response.statusCode == 200 && json['success'] == true) {
 | |
|         logSafe("Expense deleted successfully.");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|             "Failed to delete expense: ${json['message'] ?? 'Unknown error'}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during deleteExpenseApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Get Expense Details API
 | |
|   static Future<Map<String, dynamic>?> getExpenseDetailsApi({
 | |
|     required String expenseId,
 | |
|   }) async {
 | |
|     final endpoint = "${ApiEndpoints.getExpenseDetails}/$expenseId";
 | |
|     logSafe("Fetching expense details for ID: $expenseId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
|       if (response == null) {
 | |
|         logSafe("Expense details request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final body = response.body.trim();
 | |
|       if (body.isEmpty) {
 | |
|         logSafe("Expense details response body is empty",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse = jsonDecode(body);
 | |
|       if (jsonResponse is Map<String, dynamic>) {
 | |
|         if (jsonResponse['success'] == true) {
 | |
|           logSafe("Expense details fetched successfully");
 | |
|           return jsonResponse['data']; // Return the expense details object
 | |
|         } else {
 | |
|           logSafe(
 | |
|             "Failed to fetch expense details: ${jsonResponse['message'] ?? 'Unknown error'}",
 | |
|             level: LogLevel.warning,
 | |
|           );
 | |
|         }
 | |
|       } else {
 | |
|         logSafe("Unexpected response structure: $jsonResponse",
 | |
|             level: LogLevel.error);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getExpenseDetailsApi: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Update Expense Status API
 | |
|   static Future<bool> updateExpenseStatusApi({
 | |
|     required String expenseId,
 | |
|     required String statusId,
 | |
|     String? comment,
 | |
|     String? reimburseTransactionId,
 | |
|     String? reimburseDate,
 | |
|     String? reimbursedById,
 | |
|   }) async {
 | |
|     final Map<String, dynamic> payload = {
 | |
|       "expenseId": expenseId,
 | |
|       "statusId": statusId,
 | |
|     };
 | |
| 
 | |
|     if (comment != null) {
 | |
|       payload["comment"] = comment;
 | |
|     }
 | |
|     if (reimburseTransactionId != null) {
 | |
|       payload["reimburseTransactionId"] = reimburseTransactionId;
 | |
|     }
 | |
|     if (reimburseDate != null) {
 | |
|       payload["reimburseDate"] = reimburseDate;
 | |
|     }
 | |
|     if (reimbursedById != null) {
 | |
|       payload["reimburseById"] = reimbursedById;
 | |
|     }
 | |
| 
 | |
|     const endpoint = ApiEndpoints.updateExpenseStatus;
 | |
|     logSafe("Updating expense status with payload: $payload");
 | |
| 
 | |
|     try {
 | |
|       final response = await _postRequest(
 | |
|         endpoint,
 | |
|         payload,
 | |
|         customTimeout: extendedTimeout,
 | |
|       );
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Update expense status failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Update expense status response status: ${response.statusCode}");
 | |
|       logSafe("Update expense status response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Expense status updated successfully: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|           "Failed to update expense status: ${json['message'] ?? 'Unknown error'}",
 | |
|           level: LogLevel.warning,
 | |
|         );
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during updateExpenseStatus API: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<Map<String, dynamic>?> getExpenseListApi({
 | |
|     String? filter,
 | |
|     int pageSize = 20,
 | |
|     int pageNumber = 1,
 | |
|   }) async {
 | |
|     String endpoint = ApiEndpoints.getExpenseList;
 | |
|     final queryParams = {
 | |
|       'pageSize': pageSize.toString(),
 | |
|       'pageNumber': pageNumber.toString(),
 | |
|     };
 | |
| 
 | |
|     if (filter?.isNotEmpty ?? false) {
 | |
|       queryParams['filter'] = filter!;
 | |
|     }
 | |
| 
 | |
|     final uri = Uri.parse(endpoint).replace(queryParameters: queryParams);
 | |
|     logSafe("Fetching expense list with URI: $uri");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(uri.toString());
 | |
|       if (response == null) {
 | |
|         logSafe("Expense list request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null; // real failure
 | |
|       }
 | |
| 
 | |
|       final body = response.body.trim();
 | |
|       if (body.isEmpty) {
 | |
|         logSafe("Expense list response body is empty", level: LogLevel.warning);
 | |
|         return {
 | |
|           "status": true,
 | |
|           "data": {"data": [], "totalPages": 0, "currentPage": pageNumber}
 | |
|         }; // treat as empty list
 | |
|       }
 | |
| 
 | |
|       final jsonResponse = jsonDecode(body);
 | |
|       if (jsonResponse is Map<String, dynamic>) {
 | |
|         logSafe("Expense list response parsed successfully");
 | |
|         return jsonResponse; // always return valid JSON, even if data list is empty
 | |
|       } else {
 | |
|         logSafe("Unexpected response structure: $jsonResponse",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getExpenseListApi: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Fetch Master Payment Modes
 | |
|   static Future<List<dynamic>?> getMasterPaymentModes() async {
 | |
|     const endpoint = ApiEndpoints.getMasterPaymentModes;
 | |
|     return _getRequest(endpoint).then((res) => res != null
 | |
|         ? _parseResponse(res, label: 'Master Payment Modes')
 | |
|         : null);
 | |
|   }
 | |
| 
 | |
|   /// Fetch Master Expense Status
 | |
|   static Future<List<dynamic>?> getMasterExpenseStatus() async {
 | |
|     const endpoint = ApiEndpoints.getMasterExpenseStatus;
 | |
|     return _getRequest(endpoint).then((res) => res != null
 | |
|         ? _parseResponse(res, label: 'Master Expense Status')
 | |
|         : null);
 | |
|   }
 | |
| 
 | |
|   /// Fetch Master Expense Types
 | |
|   static Future<List<dynamic>?> getMasterExpenseTypes() async {
 | |
|     const endpoint = ApiEndpoints.getMasterExpenseTypes;
 | |
|     return _getRequest(endpoint).then((res) => res != null
 | |
|         ? _parseResponse(res, label: 'Master Expense Types')
 | |
|         : null);
 | |
|   }
 | |
| 
 | |
|   /// Create Expense API
 | |
|   static Future<bool> createExpenseApi({
 | |
|     required String projectId,
 | |
|     required String expensesTypeId,
 | |
|     required String paymentModeId,
 | |
|     required String paidById,
 | |
|     required DateTime transactionDate,
 | |
|     required String transactionId,
 | |
|     required String description,
 | |
|     required String location,
 | |
|     required String supplerName,
 | |
|     required double amount,
 | |
|     required int noOfPersons,
 | |
|     required List<Map<String, dynamic>> billAttachments,
 | |
|   }) async {
 | |
|     final payload = {
 | |
|       "projectId": projectId,
 | |
|       "expensesTypeId": expensesTypeId,
 | |
|       "paymentModeId": paymentModeId,
 | |
|       "paidById": paidById,
 | |
|       "transactionDate": transactionDate.toIso8601String(),
 | |
|       "transactionId": transactionId,
 | |
|       "description": description,
 | |
|       "location": location,
 | |
|       "supplerName": supplerName,
 | |
|       "amount": amount,
 | |
|       "noOfPersons": noOfPersons,
 | |
|       "billAttachments": billAttachments,
 | |
|     };
 | |
| 
 | |
|     const endpoint = ApiEndpoints.createExpense;
 | |
|     logSafe("Creating expense with payload: $payload");
 | |
| 
 | |
|     try {
 | |
|       final response =
 | |
|           await _postRequest(endpoint, payload, customTimeout: extendedTimeout);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Create expense failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Create expense response status: ${response.statusCode}");
 | |
|       logSafe("Create expense response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Expense created successfully: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|             "Failed to create expense: ${json['message'] ?? 'Unknown error'}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during createExpense API: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // === Dashboard Endpoints ===
 | |
|   /// Get Dashboard Tasks
 | |
|   static Future<DashboardTasks?> getDashboardTasks(
 | |
|       {required String projectId}) async {
 | |
|     try {
 | |
|       final queryParams = {'projectId': projectId};
 | |
| 
 | |
|       final response = await _getRequest(ApiEndpoints.getDashboardTasks,
 | |
|           queryParams: queryParams);
 | |
| 
 | |
|       if (response == null || response.body.trim().isEmpty) {
 | |
|         logSafe("Dashboard tasks request failed or response empty",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse = jsonDecode(response.body);
 | |
|       if (jsonResponse is Map<String, dynamic> &&
 | |
|           jsonResponse['success'] == true) {
 | |
|         logSafe(
 | |
|             "Dashboard tasks fetched successfully: ${jsonResponse['data']}");
 | |
|         return DashboardTasks.fromJson(jsonResponse);
 | |
|       } else {
 | |
|         logSafe(
 | |
|             "Failed to fetch dashboard tasks: ${jsonResponse['message'] ?? 'Unknown error'}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getDashboardTasks API: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Get Dashboard Teams
 | |
|   static Future<DashboardTeams?> getDashboardTeams(
 | |
|       {required String projectId}) async {
 | |
|     try {
 | |
|       final queryParams = {'projectId': projectId};
 | |
| 
 | |
|       final response = await _getRequest(ApiEndpoints.getDashboardTeams,
 | |
|           queryParams: queryParams);
 | |
| 
 | |
|       if (response == null || response.body.trim().isEmpty) {
 | |
|         logSafe("Dashboard teams request failed or response empty",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse = jsonDecode(response.body);
 | |
|       if (jsonResponse is Map<String, dynamic> &&
 | |
|           jsonResponse['success'] == true) {
 | |
|         logSafe(
 | |
|             "Dashboard teams fetched successfully: ${jsonResponse['data']}");
 | |
|         return DashboardTeams.fromJson(jsonResponse);
 | |
|       } else {
 | |
|         logSafe(
 | |
|             "Failed to fetch dashboard teams: ${jsonResponse['message'] ?? 'Unknown error'}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getDashboardTeams API: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   static Future<List<dynamic>?> getDashboardAttendanceOverview(
 | |
|       String projectId, int days) async {
 | |
|     if (projectId.isEmpty) throw ArgumentError('projectId must not be empty');
 | |
|     if (days <= 0) throw ArgumentError('days must be greater than 0');
 | |
| 
 | |
|     final endpoint =
 | |
|         "${ApiEndpoints.getDashboardAttendanceOverview}/$projectId?days=$days";
 | |
| 
 | |
|     return _getRequest(endpoint).then((res) => res != null
 | |
|         ? _parseResponse(res, label: 'Dashboard Attendance Overview')
 | |
|         : null);
 | |
|   }
 | |
| 
 | |
|   /// Fetch Project Progress
 | |
|   static Future<ProjectResponse?> getProjectProgress({
 | |
|     required String projectId,
 | |
|     required int days,
 | |
|     DateTime? fromDate, // make optional
 | |
|   }) async {
 | |
|     const endpoint = ApiEndpoints.getDashboardProjectProgress;
 | |
| 
 | |
|     // Use today's date if fromDate is not provided
 | |
|     final actualFromDate = fromDate ?? DateTime.now();
 | |
| 
 | |
|     final queryParams = {
 | |
|       "projectId": projectId,
 | |
|       "days": days.toString(),
 | |
|       "FromDate":
 | |
|           DateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS").format(actualFromDate),
 | |
|     };
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint, queryParams: queryParams);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe(
 | |
|           "Project Progress request failed: null response",
 | |
|           level: LogLevel.error,
 | |
|         );
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final parsed =
 | |
|           _parseResponseForAllData(response, label: "ProjectProgress");
 | |
|       if (parsed != null) {
 | |
|         logSafe("✅ Project progress fetched successfully");
 | |
|         return ProjectResponse.fromJson(parsed);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getProjectProgress: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /// Directory calling the API
 | |
| 
 | |
|   static Future<bool> deleteBucket(String id) async {
 | |
|     final endpoint = "${ApiEndpoints.updateBucket}/$id";
 | |
| 
 | |
|     try {
 | |
|       final token = await _getToken();
 | |
|       if (token == null) {
 | |
|         logSafe("Token is null. Cannot proceed with DELETE request.",
 | |
|             level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
 | |
| 
 | |
|       logSafe("Sending DELETE request to $uri", level: LogLevel.debug);
 | |
| 
 | |
|       final response = await http
 | |
|           .delete(uri, headers: _headers(token))
 | |
|           .timeout(extendedTimeout);
 | |
| 
 | |
|       logSafe("DELETE bucket response status: ${response.statusCode}");
 | |
|       logSafe("DELETE bucket response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (response.statusCode == 200 && json['success'] == true) {
 | |
|         logSafe("Bucket deleted successfully.");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe(
 | |
|             "Failed to delete bucket: ${json['message'] ?? 'Unknown error'}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during deleteBucket API: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> updateBucket({
 | |
|     required String id,
 | |
|     required String name,
 | |
|     required String description,
 | |
|   }) async {
 | |
|     final payload = {
 | |
|       "id": id,
 | |
|       "name": name,
 | |
|       "description": description,
 | |
|     };
 | |
| 
 | |
|     final endpoint = "${ApiEndpoints.updateBucket}/$id";
 | |
| 
 | |
|     logSafe("Updating bucket with payload: $payload");
 | |
| 
 | |
|     try {
 | |
|       final response = await _putRequest(endpoint, payload);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Update bucket failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Update bucket response status: ${response.statusCode}");
 | |
|       logSafe("Update bucket response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Bucket updated successfully: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe("Failed to update bucket: ${json['message']}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during updateBucket API: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Assign employees to a bucket
 | |
|   static Future<bool> assignEmployeesToBucket({
 | |
|     required String bucketId,
 | |
|     required List<Map<String, dynamic>> employees,
 | |
|   }) async {
 | |
|     final endpoint = "${ApiEndpoints.assignBucket}/$bucketId";
 | |
| 
 | |
|     logSafe("Assigning employees to bucket $bucketId: $employees");
 | |
| 
 | |
|     try {
 | |
|       final response = await _postRequest(endpoint, employees);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Assign employees failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Assign employees response status: ${response.statusCode}");
 | |
|       logSafe("Assign employees response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Employees assigned successfully");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe("Failed to assign employees: ${json['message']}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during assignEmployeesToBucket API: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> createBucket({
 | |
|     required String name,
 | |
|     required String description,
 | |
|   }) async {
 | |
|     final payload = {
 | |
|       "name": name,
 | |
|       "description": description,
 | |
|     };
 | |
| 
 | |
|     final endpoint = ApiEndpoints.createBucket;
 | |
| 
 | |
|     logSafe("Creating bucket with payload: $payload");
 | |
| 
 | |
|     try {
 | |
|       final response = await _postRequest(endpoint, payload);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Create bucket failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Create bucket response status: ${response.statusCode}");
 | |
|       logSafe("Create bucket response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
| 
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Bucket created successfully: ${json['data']}");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe("Failed to create bucket: ${json['message']}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during createBucket API: ${e.toString()}",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: ${stack.toString()}", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<Map<String, dynamic>?> getDirectoryNotes({
 | |
|     int pageSize = 1000,
 | |
|     int pageNumber = 1,
 | |
|   }) async {
 | |
|     final queryParams = {
 | |
|       'pageSize': pageSize.toString(),
 | |
|       'pageNumber': pageNumber.toString(),
 | |
|     };
 | |
| 
 | |
|     final response = await _getRequest(
 | |
|       ApiEndpoints.getDirectoryNotes,
 | |
|       queryParams: queryParams,
 | |
|     );
 | |
| 
 | |
|     final data = response != null
 | |
|         ? _parseResponse(response, label: 'Directory Notes')
 | |
|         : null;
 | |
| 
 | |
|     return data is Map<String, dynamic> ? data : null;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> addContactComment(String note, String contactId) async {
 | |
|     final payload = {
 | |
|       "note": note,
 | |
|       "contactId": contactId,
 | |
|     };
 | |
| 
 | |
|     final endpoint = ApiEndpoints.updateDirectoryNotes;
 | |
| 
 | |
|     logSafe("Adding new comment with payload: $payload");
 | |
|     logSafe("Sending add comment request to $endpoint");
 | |
| 
 | |
|     try {
 | |
|       final response = await _postRequest(endpoint, payload);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Add comment failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Add comment response status: ${response.statusCode}");
 | |
|       logSafe("Add comment response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
| 
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Comment added successfully for contactId: $contactId");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe("Failed to add comment: ${json['message']}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during addComment API: ${e.toString()}",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: ${stack.toString()}", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Get list of assigned projects for a specific employee
 | |
|   /// Get list of assigned projects for a specific employee
 | |
|   static Future<List<dynamic>?> getAssignedProjects(String employeeId) async {
 | |
|     if (employeeId.isEmpty) {
 | |
|       throw ArgumentError("employeeId must not be empty");
 | |
|     }
 | |
| 
 | |
|     final endpoint = "${ApiEndpoints.getAssignedProjects}/$employeeId";
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Failed to fetch assigned projects: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final parsed = _parseResponse(response, label: "Assigned Projects");
 | |
|       if (parsed is List) {
 | |
|         return parsed;
 | |
|       } else {
 | |
|         logSafe("Unexpected response format for assigned projects.",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getAssignedProjects API: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Assign projects to a specific employee
 | |
|   static Future<bool> assignProjects({
 | |
|     required String employeeId,
 | |
|     required List<Map<String, dynamic>> projects,
 | |
|   }) async {
 | |
|     if (employeeId.isEmpty) {
 | |
|       throw ArgumentError("employeeId must not be empty");
 | |
|     }
 | |
|     if (projects.isEmpty) {
 | |
|       throw ArgumentError("projects list must not be empty");
 | |
|     }
 | |
| 
 | |
|     final endpoint = "${ApiEndpoints.assignProjects}/$employeeId";
 | |
| 
 | |
|     logSafe("Assigning projects to employee $employeeId: $projects");
 | |
| 
 | |
|     try {
 | |
|       final response = await _postRequest(endpoint, projects);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Assign projects failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Assign projects response status: ${response.statusCode}");
 | |
|       logSafe("Assign projects response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Projects assigned successfully");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe("Failed to assign projects: ${json['message']}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during assignProjects API: $e", level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> updateContactComment(
 | |
|       String commentId, String note, String contactId) async {
 | |
|     final payload = {
 | |
|       "id": commentId,
 | |
|       "contactId": contactId,
 | |
|       "note": note,
 | |
|     };
 | |
| 
 | |
|     final endpoint = "${ApiEndpoints.updateDirectoryNotes}/$commentId";
 | |
| 
 | |
|     final headers = {
 | |
|       "comment-id": commentId,
 | |
|     };
 | |
| 
 | |
|     logSafe("Updating comment with payload: $payload");
 | |
|     logSafe("Headers for update comment: $headers");
 | |
|     logSafe("Sending update comment request to $endpoint");
 | |
| 
 | |
|     try {
 | |
|       final response = await _putRequest(
 | |
|         endpoint,
 | |
|         payload,
 | |
|         additionalHeaders: headers,
 | |
|       );
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Update comment failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Update comment response status: ${response.statusCode}");
 | |
|       logSafe("Update comment response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
| 
 | |
|       if (json['success'] == true) {
 | |
|         logSafe("Comment updated successfully. commentId: $commentId");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe("Failed to update comment: ${json['message']}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during updateComment API: ${e.toString()}",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: ${stack.toString()}", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> restoreContactComment(
 | |
|     String commentId,
 | |
|     bool isActive,
 | |
|   ) async {
 | |
|     final endpoint =
 | |
|         "${ApiEndpoints.updateDirectoryNotes}/$commentId?active=$isActive";
 | |
| 
 | |
|     logSafe(
 | |
|         "Updating comment active status. commentId: $commentId, isActive: $isActive");
 | |
|     logSafe("Sending request to $endpoint ");
 | |
| 
 | |
|     try {
 | |
|       final response = await _deleteRequest(
 | |
|         endpoint,
 | |
|       );
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Update comment failed: null response", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       logSafe("Update comment response status: ${response.statusCode}");
 | |
|       logSafe("Update comment response body: ${response.body}");
 | |
| 
 | |
|       final json = jsonDecode(response.body);
 | |
|       if (json['success'] == true) {
 | |
|         logSafe(
 | |
|             "Comment active status updated successfully. commentId: $commentId");
 | |
|         return true;
 | |
|       } else {
 | |
|         logSafe("Failed to update comment: ${json['message']}",
 | |
|             level: LogLevel.warning);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during updateComment API: ${e.toString()}",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: ${stack.toString()}", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<List<dynamic>?> getDirectoryComments(
 | |
|     String contactId, {
 | |
|     bool active = true,
 | |
|   }) async {
 | |
|     final url = "${ApiEndpoints.getDirectoryNotes}/$contactId?active=$active";
 | |
|     final response = await _getRequest(url);
 | |
|     final data = response != null
 | |
|         ? _parseResponse(response, label: 'Directory Comments')
 | |
|         : null;
 | |
| 
 | |
|     return data is List ? data : null;
 | |
|   }
 | |
| 
 | |
|   /// Deletes a directory contact (sets active=false)
 | |
|   static Future<bool> deleteDirectoryContact(String contactId) async {
 | |
|     final endpoint = "${ApiEndpoints.updateContact}/$contactId/";
 | |
|     final queryParams = {'active': 'false'};
 | |
| 
 | |
|     final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint")
 | |
|         .replace(queryParameters: queryParams);
 | |
| 
 | |
|     _log("Deleting directory contact at $uri");
 | |
| 
 | |
|     final response = await _deleteRequest(
 | |
|       "$endpoint?active=false", 
 | |
|     );
 | |
| 
 | |
|     if (response != null && response.statusCode == 200) {
 | |
|       _log("Contact deleted successfully: ${response.body}");
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     _log("Failed to delete contact: ${response?.body}");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /// Restores a directory contact (sets active=true)
 | |
|   static Future<bool> restoreDirectoryContact(String contactId) async {
 | |
|     final endpoint = "${ApiEndpoints.updateContact}/$contactId/";
 | |
|     final queryParams = {'active': 'true'};
 | |
| 
 | |
|     final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint")
 | |
|         .replace(queryParameters: queryParams);
 | |
| 
 | |
|     _log("Restoring directory contact at $uri");
 | |
| 
 | |
|     final response = await _deleteRequest(
 | |
|       "$endpoint?active=true",
 | |
|     );
 | |
| 
 | |
|     if (response != null && response.statusCode == 200) {
 | |
|       _log("Contact restored successfully: ${response.body}");
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     _log("Failed to restore contact: ${response?.body}");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> updateContact(
 | |
|       String contactId, Map<String, dynamic> payload) async {
 | |
|     try {
 | |
|       final endpoint = "${ApiEndpoints.updateContact}/$contactId";
 | |
| 
 | |
|       logSafe("Updating contact [$contactId] with payload: $payload");
 | |
| 
 | |
|       final response = await _putRequest(endpoint, payload);
 | |
|       if (response != null) {
 | |
|         final json = jsonDecode(response.body);
 | |
|         if (json['success'] == true) {
 | |
|           logSafe("Contact updated successfully.");
 | |
|           return true;
 | |
|         } else {
 | |
|           logSafe("Update contact failed: ${json['message']}",
 | |
|               level: LogLevel.warning);
 | |
|         }
 | |
|       }
 | |
|     } catch (e) {
 | |
|       logSafe("Error updating contact: $e", level: LogLevel.error);
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> createContact(Map<String, dynamic> payload) async {
 | |
|     try {
 | |
|       logSafe("Submitting contact payload: $payload");
 | |
| 
 | |
|       final response = await _postRequest(ApiEndpoints.createContact, payload);
 | |
|       if (response != null) {
 | |
|         final json = jsonDecode(response.body);
 | |
|         if (json['success'] == true) {
 | |
|           logSafe("Contact created successfully.");
 | |
|           return true;
 | |
|         } else {
 | |
|           logSafe("Create contact failed: ${json['message']}",
 | |
|               level: LogLevel.warning);
 | |
|         }
 | |
|       }
 | |
|     } catch (e) {
 | |
|       logSafe("Error creating contact: $e", level: LogLevel.error);
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<List<String>> getOrganizationList() async {
 | |
|     try {
 | |
|       final url = ApiEndpoints.getDirectoryOrganization;
 | |
|       logSafe("Sending GET request to: $url", level: LogLevel.info);
 | |
| 
 | |
|       final response = await _getRequest(url);
 | |
| 
 | |
|       logSafe("Response status: ${response?.statusCode}",
 | |
|           level: LogLevel.debug);
 | |
|       logSafe("Response body: ${response?.body}", level: LogLevel.debug);
 | |
| 
 | |
|       if (response != null && response.statusCode == 200) {
 | |
|         final body = jsonDecode(response.body);
 | |
|         if (body['success'] == true && body['data'] is List) {
 | |
|           return List<String>.from(body['data']);
 | |
|         }
 | |
|       }
 | |
|     } catch (e, stackTrace) {
 | |
|       logSafe("Failed to fetch organization names: $e", level: LogLevel.error);
 | |
|       logSafe("Stack trace: $stackTrace", level: LogLevel.debug);
 | |
|     }
 | |
|     return [];
 | |
|   }
 | |
| 
 | |
|   static Future<Map<String, dynamic>?> getContactCategoryList() async =>
 | |
|       _getRequest(ApiEndpoints.getDirectoryContactCategory).then((res) =>
 | |
|           res != null
 | |
|               ? _parseResponseForAllData(res, label: 'Contact Category List')
 | |
|               : null);
 | |
| 
 | |
|   static Future<Map<String, dynamic>?> getContactTagList() async =>
 | |
|       _getRequest(ApiEndpoints.getDirectoryContactTags).then((res) =>
 | |
|           res != null
 | |
|               ? _parseResponseForAllData(res, label: 'Contact Tag List')
 | |
|               : null);
 | |
| 
 | |
|   static Future<List<dynamic>?> getDirectoryData(
 | |
|       {required bool isActive}) async {
 | |
|     final queryParams = {
 | |
|       "active": isActive.toString(),
 | |
|     };
 | |
| 
 | |
|     return _getRequest(ApiEndpoints.getDirectoryContacts,
 | |
|             queryParams: queryParams)
 | |
|         .then((res) =>
 | |
|             res != null ? _parseResponse(res, label: 'Directory Data') : null);
 | |
|   }
 | |
| 
 | |
|   static Future<Map<String, dynamic>?> getContactBucketList() async =>
 | |
|       _getRequest(ApiEndpoints.getDirectoryBucketList).then((res) => res != null
 | |
|           ? _parseResponseForAllData(res, label: 'Contact Bucket List')
 | |
|           : null);
 | |
| 
 | |
|   // === Attendance APIs ===
 | |
| 
 | |
|   static Future<List<dynamic>?> getProjects() async =>
 | |
|       _getRequest(ApiEndpoints.getProjects).then(
 | |
|           (res) => res != null ? _parseResponse(res, label: 'Projects') : null);
 | |
| 
 | |
|   static Future<List<dynamic>?> getGlobalProjects() async =>
 | |
|       _getRequest(ApiEndpoints.getGlobalProjects).then((res) =>
 | |
|           res != null ? _parseResponse(res, label: 'Global Projects') : null);
 | |
| 
 | |
|   static Future<List<dynamic>?> getTodaysAttendance(
 | |
|     String projectId, {
 | |
|     String? organizationId,
 | |
|   }) async {
 | |
|     final query = {
 | |
|       "projectId": projectId,
 | |
|       if (organizationId != null) "organizationId": organizationId,
 | |
|     };
 | |
| 
 | |
|     return _getRequest(ApiEndpoints.getTodaysAttendance, queryParams: query)
 | |
|         .then((res) =>
 | |
|             res != null ? _parseResponse(res, label: 'Employees') : null);
 | |
|   }
 | |
| 
 | |
|   static Future<List<dynamic>?> getRegularizationLogs(
 | |
|     String projectId, {
 | |
|     String? organizationId,
 | |
|   }) async {
 | |
|     final query = {
 | |
|       "projectId": projectId,
 | |
|       if (organizationId != null) "organizationId": organizationId,
 | |
|     };
 | |
| 
 | |
|     return _getRequest(ApiEndpoints.getRegularizationLogs, queryParams: query)
 | |
|         .then((res) => res != null
 | |
|             ? _parseResponse(res, label: 'Regularization Logs')
 | |
|             : null);
 | |
|   }
 | |
| 
 | |
|   static Future<List<dynamic>?> getAttendanceLogs(
 | |
|     String projectId, {
 | |
|     DateTime? dateFrom,
 | |
|     DateTime? dateTo,
 | |
|     String? organizationId,
 | |
|   }) async {
 | |
|     final query = {
 | |
|       "projectId": projectId,
 | |
|       if (dateFrom != null)
 | |
|         "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
 | |
|       if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
 | |
|       if (organizationId != null) "organizationId": organizationId,
 | |
|     };
 | |
| 
 | |
|     return _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query).then(
 | |
|         (res) =>
 | |
|             res != null ? _parseResponse(res, label: 'Attendance Logs') : null);
 | |
|   }
 | |
| 
 | |
|   static Future<List<dynamic>?> getAttendanceLogView(String id) async =>
 | |
|       _getRequest("${ApiEndpoints.getAttendanceLogView}/$id").then((res) =>
 | |
|           res != null ? _parseResponse(res, label: 'Log Details') : null);
 | |
| 
 | |
|   static Future<bool> uploadAttendanceImage(
 | |
|     String id,
 | |
|     String employeeId,
 | |
|     XFile? imageFile,
 | |
|     double latitude,
 | |
|     double longitude, {
 | |
|     required String imageName,
 | |
|     required String projectId,
 | |
|     String comment = "",
 | |
|     required int action,
 | |
|     bool imageCapture = true,
 | |
|     required String markTime, // 👈 now required
 | |
|     required String date, // 👈 new required param
 | |
|   }) async {
 | |
|     final body = {
 | |
|       "id": id,
 | |
|       "employeeId": employeeId,
 | |
|       "projectId": projectId,
 | |
|       "markTime": markTime, // 👈 directly from UI
 | |
|       "comment": comment,
 | |
|       "action": action,
 | |
|       "date": date, // 👈 directly from UI
 | |
|       if (imageCapture) "latitude": '$latitude',
 | |
|       if (imageCapture) "longitude": '$longitude',
 | |
|     };
 | |
| 
 | |
|     if (imageCapture && imageFile != null) {
 | |
|       try {
 | |
|         final bytes = await imageFile.readAsBytes();
 | |
|         final fileSize = await imageFile.length();
 | |
|         final contentType = "image/${imageFile.path.split('.').last}";
 | |
|         body["image"] = {
 | |
|           "fileName": imageName,
 | |
|           "contentType": contentType,
 | |
|           "fileSize": fileSize,
 | |
|           "description": "Employee attendance photo",
 | |
|           "base64Data": base64Encode(bytes),
 | |
|         };
 | |
|       } catch (e) {
 | |
|         logSafe("Image encoding error: $e", level: LogLevel.error);
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     final response = await _postRequest(
 | |
|       ApiEndpoints.uploadAttendanceImage,
 | |
|       body,
 | |
|       customTimeout: extendedTimeout,
 | |
|     );
 | |
| 
 | |
|     if (response == null) return false;
 | |
| 
 | |
|     final json = jsonDecode(response.body);
 | |
|     if (response.statusCode == 200 && json['success'] == true) return true;
 | |
| 
 | |
|     logSafe("Failed to upload image: ${json['message'] ?? 'Unknown error'}");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static String generateImageName(String employeeId, int count) {
 | |
|     final now = DateTime.now();
 | |
|     final dateStr = DateFormat('yyyyMMdd_HHmmss').format(now);
 | |
|     final imageNumber = count.toString().padLeft(3, '0');
 | |
|     return "${employeeId}_${dateStr}_$imageNumber.jpg";
 | |
|   }
 | |
| 
 | |
|   // === Employee APIs ===
 | |
|   /// Search employees by first name and last name only (not middle name)
 | |
|   /// Returns a list of up to 10 employee records matching the search string.
 | |
|   static Future<List<dynamic>?> searchEmployeesBasic({
 | |
|     String? searchString,
 | |
|   }) async {
 | |
|     // Remove ArgumentError check because searchString is optional now
 | |
| 
 | |
|     final queryParams = <String, String>{};
 | |
| 
 | |
|     // Add searchString to query parameters only if it's not null or empty
 | |
|     if (searchString != null && searchString.isNotEmpty) {
 | |
|       queryParams['searchString'] = searchString;
 | |
|     }
 | |
| 
 | |
|     final response = await _getRequest(
 | |
|       ApiEndpoints.getEmployeesWithoutPermission,
 | |
|       queryParams: queryParams,
 | |
|     );
 | |
| 
 | |
|     if (response != null) {
 | |
|       return _parseResponse(response, label: 'Search Employees Basic');
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   static Future<List<dynamic>?> getAllEmployeesByProject(String projectId,
 | |
|       {String? organizationId}) async {
 | |
|     if (projectId.isEmpty) throw ArgumentError('projectId must not be empty');
 | |
| 
 | |
|     // Build the endpoint with optional organizationId query
 | |
|     var endpoint = "${ApiEndpoints.getAllEmployeesByProject}/$projectId";
 | |
|     if (organizationId != null && organizationId.isNotEmpty) {
 | |
|       endpoint += "?organizationId=$organizationId";
 | |
|     }
 | |
| 
 | |
|     return _getRequest(endpoint).then(
 | |
|       (res) => res != null
 | |
|           ? _parseResponse(res, label: 'Employees by Project')
 | |
|           : null,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   /// Fetches employees by projectId, serviceId, and organizationId
 | |
|   static Future<List<dynamic>?> getEmployeesByProjectService(
 | |
|     String projectId, {
 | |
|     String? serviceId,
 | |
|     String? organizationId,
 | |
|   }) async {
 | |
|     if (projectId.isEmpty) {
 | |
|       throw ArgumentError('projectId must not be empty');
 | |
|     }
 | |
| 
 | |
|     // Construct query parameters only if non-empty
 | |
|     final queryParams = <String, String>{};
 | |
|     if (serviceId != null && serviceId.isNotEmpty) {
 | |
|       queryParams['serviceId'] = serviceId;
 | |
|     }
 | |
|     if (organizationId != null && organizationId.isNotEmpty) {
 | |
|       queryParams['organizationId'] = organizationId;
 | |
|     }
 | |
| 
 | |
|     final endpoint = "${ApiEndpoints.getAllEmployeesByOrganization}/$projectId";
 | |
| 
 | |
|     final response = await _getRequest(endpoint, queryParams: queryParams);
 | |
| 
 | |
|     if (response != null) {
 | |
|       return _parseResponse(response, label: 'Employees by Project Service');
 | |
|     } else {
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static Future<List<dynamic>?> getAllEmployees(
 | |
|       {String? organizationId}) async {
 | |
|     var endpoint = ApiEndpoints.getAllEmployees;
 | |
| 
 | |
|     // Add organization filter if provided
 | |
|     if (organizationId != null && organizationId.isNotEmpty) {
 | |
|       endpoint += "?organizationId=$organizationId";
 | |
|     }
 | |
| 
 | |
|     return _getRequest(endpoint).then(
 | |
|       (res) => res != null ? _parseResponse(res, label: 'All Employees') : null,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   static Future<List<dynamic>?> getRoles() async =>
 | |
|       _getRequest(ApiEndpoints.getRoles).then(
 | |
|           (res) => res != null ? _parseResponse(res, label: 'Roles') : null);
 | |
|   static Future<Map<String, dynamic>?> createEmployee({
 | |
|     String? id,
 | |
|     required String firstName,
 | |
|     required String lastName,
 | |
|     required String phoneNumber,
 | |
|     required String gender,
 | |
|     required String jobRoleId,
 | |
|     required String joiningDate,
 | |
|     String? email,
 | |
|     String? organizationId,
 | |
|     bool? hasApplicationAccess,
 | |
|   }) async {
 | |
|     final body = {
 | |
|       if (id != null) "id": id,
 | |
|       "firstName": firstName,
 | |
|       "lastName": lastName,
 | |
|       "phoneNumber": phoneNumber,
 | |
|       "gender": gender,
 | |
|       "jobRoleId": jobRoleId,
 | |
|       "joiningDate": joiningDate,
 | |
|       if (email != null && email.isNotEmpty) "email": email,
 | |
|       if (organizationId != null && organizationId.isNotEmpty)
 | |
|         "organizationId": organizationId,
 | |
|       if (hasApplicationAccess != null)
 | |
|         "hasApplicationAccess": hasApplicationAccess,
 | |
|     };
 | |
| 
 | |
|     final response = await _postRequest(
 | |
|       ApiEndpoints.createEmployee,
 | |
|       body,
 | |
|       customTimeout: extendedTimeout,
 | |
|     );
 | |
| 
 | |
|     if (response == null) return null;
 | |
| 
 | |
|     final json = jsonDecode(response.body);
 | |
|     return {
 | |
|       "success": response.statusCode == 200 && json['success'] == true,
 | |
|       "data": json,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   static Future<Map<String, dynamic>?> getEmployeeDetails(
 | |
|       String employeeId) async {
 | |
|     final url = "${ApiEndpoints.getEmployeeInfo}/$employeeId";
 | |
|     final response = await _getRequest(url);
 | |
|     final data = response != null
 | |
|         ? _parseResponse(response, label: 'Employee Details')
 | |
|         : null;
 | |
|     return data is Map<String, dynamic> ? data : null;
 | |
|   }
 | |
| 
 | |
|   // === Daily Task APIs ===
 | |
|   /// Get Daily Task Project Report Filter
 | |
|   static Future<DailyProgressReportFilterResponse?> getDailyTaskFilter(
 | |
|       String projectId) async {
 | |
|     final endpoint =
 | |
|         "${ApiEndpoints.getDailyTaskProjectProgressFilter}/$projectId";
 | |
|     logSafe("Fetching daily task Progress filter for projectId: $projectId");
 | |
| 
 | |
|     try {
 | |
|       final response = await _getRequest(endpoint);
 | |
| 
 | |
|       if (response == null) {
 | |
|         logSafe("Daily task filter request failed: null response",
 | |
|             level: LogLevel.error);
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       final jsonResponse = _parseResponseForAllData(response,
 | |
|           label: "Daily Task Progress Filter");
 | |
| 
 | |
|       if (jsonResponse != null) {
 | |
|         return DailyProgressReportFilterResponse.fromJson(jsonResponse);
 | |
|       }
 | |
|     } catch (e, stack) {
 | |
|       logSafe("Exception during getDailyTask Progress Filter: $e",
 | |
|           level: LogLevel.error);
 | |
|       logSafe("StackTrace: $stack", level: LogLevel.debug);
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   static Future<List<TaskModel>?> getDailyTasks(
 | |
|     String projectId, {
 | |
|     Map<String, dynamic>? filter,
 | |
|     int pageNumber = 1,
 | |
|     int pageSize = 20,
 | |
|   }) async {
 | |
|     // Build query parameters
 | |
|     final query = {
 | |
|       "projectId": projectId,
 | |
|       "pageNumber": pageNumber.toString(),
 | |
|       "pageSize": pageSize.toString(),
 | |
|       if (filter != null) "filter": jsonEncode(filter),
 | |
|     };
 | |
| 
 | |
|     final uri =
 | |
|         Uri.parse(ApiEndpoints.getDailyTask).replace(queryParameters: query);
 | |
| 
 | |
|     final response = await _getRequest(uri.toString());
 | |
|     final parsed = response != null
 | |
|         ? _parseResponse(response, label: 'Daily Tasks')
 | |
|         : null;
 | |
| 
 | |
|     if (parsed != null && parsed['data'] != null) {
 | |
|       return (parsed['data'] as List)
 | |
|           .map((e) => TaskModel.fromJson(e))
 | |
|           .toList();
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> reportTask({
 | |
|     required String id,
 | |
|     required int completedTask,
 | |
|     required String comment,
 | |
|     required List<Map<String, dynamic>> checkList,
 | |
|     List<Map<String, dynamic>>? images,
 | |
|   }) async {
 | |
|     final body = {
 | |
|       "id": id,
 | |
|       "completedTask": completedTask,
 | |
|       "comment": comment,
 | |
|       "reportedDate": DateTime.now().toUtc().toIso8601String(),
 | |
|       "checkList": checkList,
 | |
|       if (images != null && images.isNotEmpty) "images": images,
 | |
|     };
 | |
| 
 | |
|     final response = await _postRequest(
 | |
|       ApiEndpoints.reportTask,
 | |
|       body,
 | |
|       customTimeout: extendedTimeout,
 | |
|     );
 | |
| 
 | |
|     if (response == null) return false;
 | |
|     final json = jsonDecode(response.body);
 | |
|     if (response.statusCode == 200 && json['success'] == true) {
 | |
|       Get.back();
 | |
|       return true;
 | |
|     }
 | |
|     logSafe("Failed to report task: ${json['message'] ?? 'Unknown error'}");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> commentTask({
 | |
|     required String id,
 | |
|     required String comment,
 | |
|     List<Map<String, dynamic>>? images,
 | |
|   }) async {
 | |
|     final body = {
 | |
|       "taskAllocationId": id,
 | |
|       "comment": comment,
 | |
|       "commentDate": DateTime.now().toUtc().toIso8601String(),
 | |
|       if (images != null && images.isNotEmpty) "images": images,
 | |
|     };
 | |
| 
 | |
|     final response = await _postRequest(ApiEndpoints.commentTask, body);
 | |
|     if (response == null) return false;
 | |
|     final json = jsonDecode(response.body);
 | |
|     return response.statusCode == 200 && json['success'] == true;
 | |
|   }
 | |
| 
 | |
|   /// Fetch infra details for a project, optionally filtered by service
 | |
|   static Future<Map<String, dynamic>?> getInfraDetails(String projectId,
 | |
|       {String? serviceId}) async {
 | |
|     String endpoint = "/project/infra-details/$projectId";
 | |
| 
 | |
|     if (serviceId != null && serviceId.isNotEmpty) {
 | |
|       endpoint += "?serviceId=$serviceId";
 | |
|     }
 | |
| 
 | |
|     final res = await _getRequest(endpoint);
 | |
|     if (res == null) {
 | |
|       logSafe('Infra Details API returned null');
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     logSafe('Infra Details raw response: ${res.body}');
 | |
|     return _parseResponseForAllData(res, label: 'Infra Details')
 | |
|         as Map<String, dynamic>?;
 | |
|   }
 | |
| 
 | |
|   /// Fetch work items for a given work area, optionally filtered by service
 | |
|   static Future<Map<String, dynamic>?> getWorkItemsByWorkArea(String workAreaId,
 | |
|       {String? serviceId}) async {
 | |
|     String endpoint = "/project/tasks/$workAreaId";
 | |
| 
 | |
|     if (serviceId != null && serviceId.isNotEmpty) {
 | |
|       endpoint += "?serviceId=$serviceId";
 | |
|     }
 | |
| 
 | |
|     final res = await _getRequest(endpoint);
 | |
|     if (res == null) {
 | |
|       logSafe('Work Items API returned null');
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     logSafe('Work Items raw response: ${res.body}');
 | |
|     return _parseResponseForAllData(res, label: 'Work Items')
 | |
|         as Map<String, dynamic>?;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> assignDailyTask({
 | |
|     required String workItemId,
 | |
|     required int plannedTask,
 | |
|     required String description,
 | |
|     required List<String> taskTeam,
 | |
|     DateTime? assignmentDate,
 | |
|     String? organizationId,
 | |
|     String? serviceId,
 | |
|   }) async {
 | |
|     final body = {
 | |
|       "workItemId": workItemId,
 | |
|       "plannedTask": plannedTask,
 | |
|       "description": description,
 | |
|       "taskTeam": taskTeam,
 | |
|       "organizationId": organizationId,
 | |
|       "serviceId": serviceId,
 | |
|       "assignmentDate":
 | |
|           (assignmentDate ?? DateTime.now()).toUtc().toIso8601String(),
 | |
|     };
 | |
|     final response = await _postRequest(ApiEndpoints.assignDailyTask, body);
 | |
|     if (response == null) return false;
 | |
|     final json = jsonDecode(response.body);
 | |
|     if (response.statusCode == 200 && json['success'] == true) {
 | |
|       Get.back();
 | |
|       return true;
 | |
|     }
 | |
|     logSafe(
 | |
|         "Failed to assign daily task: ${json['message'] ?? 'Unknown error'}");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static Future<Map<String, dynamic>?> getWorkStatus() async {
 | |
|     final res = await _getRequest(ApiEndpoints.getWorkStatus);
 | |
|     if (res == null) {
 | |
|       logSafe('Work Status API returned null');
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     logSafe('Work Status raw response: ${res.body}');
 | |
|     return _parseResponseForAllData(res, label: 'Work Status')
 | |
|         as Map<String, dynamic>?;
 | |
|   }
 | |
| 
 | |
|   static Future<Map<String, dynamic>?> getMasterWorkCategories() async =>
 | |
|       _getRequest(ApiEndpoints.getmasterWorkCategories).then((res) =>
 | |
|           res != null
 | |
|               ? _parseResponseForAllData(res, label: 'Master Work Categories')
 | |
|               : null);
 | |
| 
 | |
|   static Future<bool> approveTask({
 | |
|     required String id,
 | |
|     required String comment,
 | |
|     required String workStatus,
 | |
|     required int approvedTask,
 | |
|     List<Map<String, dynamic>>? images,
 | |
|   }) async {
 | |
|     final body = {
 | |
|       "id": id,
 | |
|       "workStatus": workStatus,
 | |
|       "approvedTask": approvedTask,
 | |
|       "comment": comment,
 | |
|       if (images != null && images.isNotEmpty) "images": images,
 | |
|     };
 | |
| 
 | |
|     final response = await _postRequest(ApiEndpoints.approveReportAction, body);
 | |
|     if (response == null) return false;
 | |
| 
 | |
|     final json = jsonDecode(response.body);
 | |
|     return response.statusCode == 200 && json['success'] == true;
 | |
|   }
 | |
| 
 | |
|   static Future<bool> createTask({
 | |
|     required String parentTaskId,
 | |
|     required int plannedTask,
 | |
|     required String comment,
 | |
|     required String workAreaId,
 | |
|     required String activityId,
 | |
|     DateTime? assignmentDate,
 | |
|     required String categoryId,
 | |
|   }) async {
 | |
|     final body = [
 | |
|       {
 | |
|         "parentTaskId": parentTaskId,
 | |
|         "plannedWork": plannedTask,
 | |
|         "comment": comment,
 | |
|         "workAreaID": workAreaId,
 | |
|         "activityID": activityId,
 | |
|         "workCategoryId": categoryId,
 | |
|         'completedWork': 0,
 | |
|       }
 | |
|     ];
 | |
| 
 | |
|     final response = await _postRequest(ApiEndpoints.assignTask, body);
 | |
|     if (response == null) return false;
 | |
| 
 | |
|     final json = jsonDecode(response.body);
 | |
|     if (response.statusCode == 200 && json['success'] == true) {
 | |
|       Get.back();
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     logSafe("Failed to create task: ${json['message'] ?? 'Unknown error'}");
 | |
|     return false;
 | |
|   }
 | |
| }
 |