From 03082aeea924943b0bdcbc246ded1d120679a51a Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Mon, 15 Dec 2025 10:53:16 +0530 Subject: [PATCH] added team member list --- ...fra_project_screen_details_controller.dart | 53 +- lib/helpers/services/api_endpoints.dart | 2 + lib/helpers/services/api_service copy.dart | 4051 ----------------- lib/helpers/services/api_service.dart | 38 + .../infra_project/infra_team_list_model.dart | 119 + .../infra_project_details_screen.dart | 268 +- 6 files changed, 418 insertions(+), 4113 deletions(-) delete mode 100644 lib/helpers/services/api_service copy.dart create mode 100644 lib/model/infra_project/infra_team_list_model.dart diff --git a/lib/controller/infra_project/infra_project_screen_details_controller.dart b/lib/controller/infra_project/infra_project_screen_details_controller.dart index a877236..527dc47 100644 --- a/lib/controller/infra_project/infra_project_screen_details_controller.dart +++ b/lib/controller/infra_project/infra_project_screen_details_controller.dart @@ -1,6 +1,7 @@ import 'package:get/get.dart'; -import 'package:on_field_work/helpers/services/api_service.dart'; +import 'package:on_field_work/helpers/services/api_service.dart'; import 'package:on_field_work/model/infra_project/infra_project_details.dart'; +import 'package:on_field_work/model/infra_project/infra_team_list_model.dart'; class InfraProjectDetailsController extends GetxController { final String projectId; @@ -9,25 +10,39 @@ class InfraProjectDetailsController extends GetxController { var isLoading = true.obs; var projectDetails = Rxn(); + var teamList = [].obs; + var teamLoading = true.obs; var errorMessage = ''.obs; - + var teamErrorMessage = ''.obs; @override void onInit() { super.onInit(); fetchProjectDetails(); + fetchProjectTeamList(); + } + + Map> get groupedTeamByRole { + final Map> map = {}; + for (final member in teamList) { + map.putIfAbsent(member.jobRoleId, () => []).add(member); + } + return map; } Future fetchProjectDetails() async { try { isLoading.value = true; - final response = await ApiService.getInfraProjectDetails(projectId: projectId); + final response = + await ApiService.getInfraProjectDetails(projectId: projectId); - if (response != null && response.success == true && response.data != null) { + if (response != null && + response.success == true && + response.data != null) { projectDetails.value = response.data; - isLoading.value = false; - + errorMessage.value = ''; } else { - errorMessage.value = response?.message ?? "Failed to load project details"; + errorMessage.value = + response?.message ?? "Failed to load project details"; } } catch (e) { errorMessage.value = "Error fetching project details: $e"; @@ -35,4 +50,28 @@ class InfraProjectDetailsController extends GetxController { isLoading.value = false; } } + + Future fetchProjectTeamList() async { + try { + teamLoading.value = true; + teamErrorMessage.value = ''; + + final response = await ApiService.getInfraProjectTeamListApi( + projectId: projectId, + includeInactive: false, + ); + + if (response?.success == true && response!.data.isNotEmpty) { + teamList.assignAll(response.data); + } else { + teamList.clear(); + teamErrorMessage.value = response?.message ?? "No team members found."; + } + } catch (e) { + teamList.clear(); + teamErrorMessage.value = "Failed to load team members"; + } finally { + teamLoading.value = false; + } + } } diff --git a/lib/helpers/services/api_endpoints.dart b/lib/helpers/services/api_endpoints.dart index fa12d19..b39cb40 100644 --- a/lib/helpers/services/api_endpoints.dart +++ b/lib/helpers/services/api_endpoints.dart @@ -167,4 +167,6 @@ class ApiEndpoints { // Infra Project Module API Endpoints static const String getInfraProjectsList = "/project/list"; static const String getInfraProjectDetail = "/project/details"; + static const String getInfraProjectTeamList = "/project/allocation"; + } diff --git a/lib/helpers/services/api_service copy.dart b/lib/helpers/services/api_service copy.dart deleted file mode 100644 index dbf41d2..0000000 --- a/lib/helpers/services/api_service copy.dart +++ /dev/null @@ -1,4051 +0,0 @@ -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:on_field_work/helpers/services/auth_service.dart'; -import 'package:on_field_work/helpers/services/api_endpoints.dart'; -import 'package:on_field_work/helpers/services/storage/local_storage.dart'; -import 'package:jwt_decoder/jwt_decoder.dart'; -import 'package:on_field_work/model/dashboard/project_progress_model.dart'; -import 'package:on_field_work/model/dashboard/dashboard_tasks_model.dart'; -import 'package:on_field_work/model/dashboard/dashboard_teams_model.dart'; -import 'package:on_field_work/helpers/services/app_logger.dart'; -import 'package:on_field_work/model/document/document_filter_model.dart'; -import 'package:on_field_work/model/document/documents_list_model.dart'; -import 'package:on_field_work/model/document/master_document_tags.dart'; -import 'package:on_field_work/model/document/master_document_type_model.dart'; -import 'package:on_field_work/model/document/document_details_model.dart'; -import 'package:on_field_work/model/document/document_version_model.dart'; -import 'package:on_field_work/model/attendance/organization_per_project_list_model.dart'; -import 'package:on_field_work/model/tenant/tenant_services_model.dart'; -import 'package:on_field_work/model/dailyTaskPlanning/daily_task_model.dart'; -import 'package:on_field_work/model/dailyTaskPlanning/daily_progress_report_filter_response_model.dart'; -import 'package:on_field_work/model/all_organization_model.dart'; -import 'package:on_field_work/model/dashboard/pending_expenses_model.dart'; -import 'package:on_field_work/model/dashboard/expense_type_report_model.dart'; -import 'package:on_field_work/model/dashboard/monthly_expence_model.dart'; -import 'package:on_field_work/model/finance/expense_category_model.dart'; -import 'package:on_field_work/model/finance/currency_list_model.dart'; -import 'package:on_field_work/model/finance/payment_payee_request_model.dart'; -import 'package:on_field_work/model/finance/payment_request_list_model.dart'; -import 'package:on_field_work/model/finance/payment_request_filter.dart'; -import 'package:on_field_work/model/finance/payment_request_details_model.dart'; -import 'package:on_field_work/model/finance/advance_payment_model.dart'; -import 'package:on_field_work/model/service_project/service_projects_list_model.dart'; -import 'package:on_field_work/model/service_project/service_projects_details_model.dart'; -import 'package:on_field_work/model/service_project/job_list_model.dart'; -import 'package:on_field_work/model/service_project/service_project_job_detail_model.dart'; -import 'package:on_field_work/model/service_project/job_attendance_logs_model.dart'; -import 'package:on_field_work/model/service_project/job_allocation_model.dart'; -import 'package:on_field_work/model/service_project/service_project_branches_model.dart'; -import 'package:on_field_work/model/service_project/job_status_response.dart'; -import 'package:on_field_work/model/service_project/job_comments.dart'; -import 'package:on_field_work/model/employees/employee_model.dart'; -import 'package:on_field_work/model/infra_project/infra_project_list.dart'; -import 'package:on_field_work/model/infra_project/infra_project_details.dart'; -import 'package:on_field_work/model/dashboard/collection_overview_model.dart'; -import 'package:on_field_work/model/dashboard/purchase_invoice_model.dart'; -import 'package:on_field_work/helpers/utils/encryption_helper.dart'; - -class ApiService { - static const bool enableLogs = true; - static const Duration extendedTimeout = Duration(seconds: 60); - - static Future _getToken() async { - final token = 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 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 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 _headers(String token) => { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $token', - }; - - static void _log(String message, {LogLevel level = LogLevel.info}) { - if (enableLogs) { - logSafe(message, level: level); - } - } - - static dynamic _parseResponse(http.Response response, {String label = ''}) { - _log("$label Encrypted Response: ${response.body}"); // Log encrypted body - - // --- ⚠️ START of Decryption Change ⚠️ --- - final decryptedData = - decryptResponse(response.body); // Decrypt the Base64 string - - if (decryptedData == null) { - _log("Decryption failed for [$label]. Cannot parse response."); - return null; - } - // If decryptedData is a Map/List (JSON), use it directly. - // If it's a plain String, you'll need to decode it to JSON. - - // Assuming the decrypted result is a Map/List (JSON), as per your API structure: - final json = decryptedData; - - // Now proceed with your existing logic using the decrypted JSON object - if (response.statusCode == 200 && json is Map && json['success'] == true) { - _log("$label Decrypted Data: ${json['data']}"); - return json['data']; - } - - // Handle error cases using the decrypted data - if (json is Map) { - _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}"); - } else { - _log("API Error [$label]: Decrypted response not a map: $json"); - } - - // --- ⚠️ END of Decryption Change ⚠️ --- - return null; - } - - static dynamic _parseResponseForAllData(http.Response response, - {String label = ''}) { - _log("$label Encrypted Response: ${response.body}"); - - // --- ⚠️ START of Decryption Change ⚠️ --- - final body = response.body.trim(); - if (body.isEmpty) { - _log("Empty response body for [$label]"); - return null; - } - - final json = decryptResponse(body); // Decrypt and auto-decode JSON - - if (json == null) { - _log("Decryption failed for [$label]. Cannot parse response."); - return null; - } - - if (json is Map && response.statusCode == 200 && json['success'] == true) { - _log("$label Decrypted JSON: $json"); - return json; // Return the full JSON map - } - - // Handle error cases - if (json is Map) { - _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}"); - } else { - _log("API Error [$label]: Decrypted response not a map: $json"); - } - - // --- ⚠️ END of Decryption Change ⚠️ --- - return null; - } - - static Future _getRequest( - String endpoint, { - Map? 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 _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 _putRequest( - String endpoint, - dynamic body, { - Map? 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 _deleteRequest( - String endpoint, { - Map? 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 PURCHASE INVOICE OVERVIEW (Dashboard) - /// ============================================ - static Future getPurchaseInvoiceOverview({ - String? projectId, - }) async { - try { - final queryParams = {}; - if (projectId != null && projectId.isNotEmpty) { - queryParams['projectId'] = projectId; - } - - final response = await _getRequest( - ApiEndpoints.getPurchaseInvoiceOverview, - queryParams: queryParams, - ); - - if (response == null) { - _log("getPurchaseInvoiceOverview: No response from server", - level: LogLevel.error); - return null; - } - - final parsedJson = _parseResponseForAllData( - response, - label: "PurchaseInvoiceOverview", - ); - - if (parsedJson == null) return null; - - return PurchaseInvoiceOverviewResponse.fromJson(parsedJson); - } catch (e, stack) { - _log("Exception in getPurchaseInvoiceOverview: $e\n$stack", - level: LogLevel.error); - return null; - } - } - - /// ============================================ - /// GET COLLECTION OVERVIEW (Dashboard) - /// ============================================ - static Future getCollectionOverview({ - String? projectId, - }) async { - try { - // Build query params (only add projectId if not null) - final queryParams = {}; - if (projectId != null && projectId.isNotEmpty) { - queryParams['projectId'] = projectId; - } - - final response = await _getRequest( - ApiEndpoints.getCollectionOverview, - queryParams: queryParams, - ); - - if (response == null) { - _log("getCollectionOverview: No response from server", - level: LogLevel.error); - return null; - } - - // Parse full JSON (success, message, data, etc.) - final parsedJson = - _parseResponseForAllData(response, label: "CollectionOverview"); - - if (parsedJson == null) return null; - - return CollectionOverviewResponse.fromJson(parsedJson); - } catch (e, stack) { - _log("Exception in getCollectionOverview: $e\n$stack", - level: LogLevel.error); - return null; - } - } - -// Infra Project Module APIs - - /// ================================ - /// GET INFRA PROJECT DETAILS - /// ================================ - static Future getInfraProjectDetails({ - required String projectId, - }) async { - final endpoint = "${ApiEndpoints.getInfraProjectDetail}/$projectId"; - - try { - final response = await _getRequest(endpoint); - - if (response == null) { - _log("getInfraProjectDetails: No response from server", - level: LogLevel.error); - return null; - } - - final parsedJson = - _parseResponseForAllData(response, label: "InfraProjectDetails"); - - if (parsedJson == null) return null; - - return ProjectDetailsResponse.fromJson(parsedJson); - } catch (e, stack) { - _log("Exception in getInfraProjectDetails: $e\n$stack", - level: LogLevel.error); - return null; - } - } - - /// ================================ - /// GET INFRA PROJECTS LIST - /// ================================ - static Future getInfraProjectsList({ - int pageSize = 20, - int pageNumber = 1, - String searchString = "", - }) async { - final queryParams = { - "pageSize": pageSize.toString(), - "pageNumber": pageNumber.toString(), - "searchString": searchString, - }; - - try { - final response = await _getRequest( - ApiEndpoints.getInfraProjectsList, - queryParams: queryParams, - ); - - if (response == null) { - _log("getInfraProjectsList: No response from server", - level: LogLevel.error); - return null; - } - - final parsedJson = - _parseResponseForAllData(response, label: "InfraProjectsList"); - - if (parsedJson == null) return null; - - return ProjectsResponse.fromJson(parsedJson); - } catch (e, stack) { - _log("Exception in getInfraProjectsList: $e\n$stack", - level: LogLevel.error); - return null; - } - } - - static Future getJobCommentList({ - required String jobTicketId, - int pageNumber = 1, - int pageSize = 20, - }) async { - final queryParams = { - 'jobTicketId': jobTicketId, - 'pageNumber': pageNumber.toString(), - 'pageSize': pageSize.toString(), - }; - - try { - final response = await _getRequest( - ApiEndpoints.getJobCommentList, - queryParams: queryParams, - ); - - if (response == null) { - _log("getJobCommentList: No response from server", - level: LogLevel.error); - return null; - } - - final parsedJson = - _parseResponseForAllData(response, label: "JobCommentList"); - if (parsedJson == null) return null; - - return JobCommentResponse.fromJson(parsedJson); - } catch (e, stack) { - _log("Exception in getJobCommentList: $e\n$stack", level: LogLevel.error); - return null; - } - } - - static Future addJobComment({ - required String jobTicketId, - required String comment, - List> attachments = const [], - }) async { - final body = { - "jobTicketId": jobTicketId, - "comment": comment, - "attachments": attachments, - }; - - try { - final response = await _postRequest( - ApiEndpoints.addJobComment, - body, - ); - - if (response == null) { - _log("addJobComment: No response from server", level: LogLevel.error); - return false; - } - - // Handle 201 Created as success manually - if (response.statusCode == 201) { - _log("AddJobComment: Comment added successfully (201).", - level: LogLevel.info); - return true; - } - - // Otherwise fallback to existing _parseResponse - final parsed = _parseResponse(response, label: "AddJobComment"); - - if (parsed != null && parsed['success'] == true) { - _log("AddJobComment: Comment added successfully.", - level: LogLevel.info); - return true; - } else { - _log( - "AddJobComment failed: ${parsed?['message'] ?? 'Unknown error'}", - level: LogLevel.error, - ); - return false; - } - } catch (e, stack) { - _log("Exception in addJobComment: $e\n$stack", level: LogLevel.error); - return false; - } - } - - static Future?> getMasterJobStatus({ - required String statusId, - required String projectId, - }) async { - final queryParams = { - 'statusId': statusId, - 'projectId': projectId, - }; - - try { - final response = await _getRequest( - ApiEndpoints.getMasterJobStatus, - queryParams: queryParams, - ); - - if (response == null) { - _log("getMasterJobStatus: No response received."); - return null; - } - - final parsedJson = - _parseResponseForAllData(response, label: "MasterJobStatus"); - - if (parsedJson == null) return null; - - // Directly parse JobStatus list - final dataList = (parsedJson['data'] as List?) - ?.map((e) => JobStatus.fromJson(e)) - .toList(); - - return dataList; - } catch (e, stack) { - _log("Exception in getMasterJobStatus: $e\n$stack", - level: LogLevel.error); - return null; - } - } - - /// Fetch Service Project Branches with full response - static Future getServiceProjectBranchesFull({ - required String projectId, - int pageNumber = 1, - int pageSize = 20, - String searchString = '', - bool isActive = true, - }) async { - final queryParams = { - 'pageNumber': pageNumber.toString(), - 'pageSize': pageSize.toString(), - 'searchString': searchString, - 'isActive': isActive.toString(), - }; - - final endpoint = "${ApiEndpoints.getServiceProjectBranches}/$projectId"; - - try { - final response = await _getRequest( - endpoint, - queryParams: queryParams, - ); - - if (response == null) { - _log("getServiceProjectBranchesFull: No response received."); - return null; - } - - final parsedJson = _parseResponseForAllData( - response, - label: "ServiceProjectBranchesFull", - ); - - if (parsedJson == null) return null; - - return ServiceProjectBranchesResponse.fromJson(parsedJson); - } catch (e, stack) { - _log( - "Exception in getServiceProjectBranchesFull: $e\n$stack", - level: LogLevel.error, - ); - return null; - } - } - - // Service Project Module APIs - static Future?> getTeamRoles() async { - try { - final response = await _getRequest(ApiEndpoints.getTeamRoles); - - if (response == null) { - _log("getTeamRoles: No response received."); - return null; - } - - final parsedJson = _parseResponseForAllData(response, label: "TeamRoles"); - if (parsedJson == null) return null; - - // Map the 'data' array to List - final List dataList = parsedJson['data'] as List; - return dataList - .map((e) => TeamRole.fromJson(e as Map)) - .toList(); - } catch (e, stack) { - _log("Exception in getTeamRoles: $e\n$stack", level: LogLevel.error); - return null; - } - } - - /// Fetch Service Project Allocation List - - static Future?> - getServiceProjectAllocationList({ - required String projectId, - bool isActive = true, - }) async { - final queryParams = { - 'projectId': projectId, - 'isActive': isActive.toString(), - }; - - try { - final response = await _getRequest( - ApiEndpoints.getServiceProjectUpateJobAllocationList, - queryParams: queryParams, - ); - - if (response == null) { - _log("getServiceProjectAllocationList: No response received."); - return null; - } - - final parsedJson = _parseResponseForAllData(response, - label: "ServiceProjectAllocationList"); - if (parsedJson == null) return null; - - final dataList = (parsedJson['data'] as List) - .map((e) => ServiceProjectAllocation.fromJson(e)) - .toList(); - - return dataList; - } catch (e, stack) { - _log("Exception in getServiceProjectAllocationList: $e\n$stack"); - return null; - } - } - - /// Manage Service Project Allocation - static Future manageServiceProjectAllocation({ - required List> payload, - }) async { - try { - final response = await _postRequest( - ApiEndpoints.manageServiceProjectUpateJobAllocation, - payload, - ); - - if (response == null) { - _log("manageServiceProjectAllocation: No response received.", - level: LogLevel.error); - return false; - } - - final json = jsonDecode(response.body); - if (json['success'] == true) { - _log( - "Service Project Allocation updated successfully: ${json['data']}"); - return true; - } else { - _log( - "Failed to update Service Project Allocation: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - return false; - } - } catch (e, stack) { - _log("Exception during manageServiceProjectAllocation: $e", - level: LogLevel.error); - _log("StackTrace: $stack", level: LogLevel.debug); - return false; - } - } - - static Future getJobAttendanceLog({ - required String attendanceId, - }) async { - final endpoint = - "${ApiEndpoints.serviceProjectUpateJobAttendanceLog}/$attendanceId"; - - try { - final response = await _getRequest(endpoint); - - if (response == null) { - _log("getJobAttendanceLog: No response received."); - return null; - } - - final parsedJson = - _parseResponseForAllData(response, label: "JobAttendanceLog"); - if (parsedJson == null) return null; - - return JobAttendanceResponse.fromJson(parsedJson); - } catch (e, stack) { - _log("Exception in getJobAttendanceLog: $e\n$stack"); - return null; - } - } - - /// Update Service Project Job Attendance - static Future updateServiceProjectJobAttendance({ - required Map payload, - }) async { - const endpoint = ApiEndpoints.serviceProjectUpateJobAttendance; - - logSafe("Updating Service Project Job Attendance with payload: $payload"); - - try { - final response = await _postRequest(endpoint, payload); - - if (response == null) { - logSafe("Update Job Attendance failed: null response", - level: LogLevel.error); - return false; - } - - logSafe("Update Job Attendance response status: ${response.statusCode}"); - logSafe("Update Job Attendance response body: ${response.body}"); - - final json = jsonDecode(response.body); - if (json['success'] == true) { - logSafe("Job Attendance updated successfully: ${json['data']}"); - return true; - } else { - logSafe( - "Failed to update Job Attendance: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - return false; - } - } catch (e, stack) { - logSafe("Exception during updateServiceProjectJobAttendance: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - return false; - } - } - - /// Edit a Service Project Job - static Future editServiceProjectJobApi({ - required String jobId, - required List> operations, - }) async { - final endpoint = "${ApiEndpoints.editServiceProjectJob}/$jobId"; - - logSafe("Editing Service Project Job: $jobId with operations: $operations"); - - try { - // PATCH request is usually similar to PUT, but with http.patch - String? token = await _getToken(); - if (token == null) return false; - - final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint"); - - final headers = _headers(token); - - final response = await http - .patch(uri, headers: headers, body: jsonEncode(operations)) - .timeout(extendedTimeout); - - logSafe( - "Edit Service Project Job response status: ${response.statusCode}"); - logSafe("Edit Service Project Job response body: ${response.body}"); - - final json = jsonDecode(response.body); - - if (response.statusCode == 200 && json['success'] == true) { - logSafe("Service Project Job edited successfully: ${json['data']}"); - return true; - } else { - logSafe( - "Failed to edit Service Project Job: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - return false; - } - } catch (e, stack) { - logSafe("Exception during editServiceProjectJobApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - return false; - } - } - - /// Get details for a single Service Project Job - static Future getServiceProjectJobDetailApi( - String jobId) async { - final endpoint = "${ApiEndpoints.getServiceProjectJobDetail}/$jobId"; - logSafe("Fetching Job Detail for Job ID: $jobId"); - - try { - final response = await _getRequest(endpoint); - if (response == null) { - logSafe("Service Project Job Detail request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = _parseResponseForAllData( - response, - label: "Service Project Job Detail", - ); - - if (jsonResponse != null) { - return JobDetailsResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getServiceProjectJobDetailApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - static Future createServiceProjectJobApi({ - required String title, - required String description, - required String projectId, - required List> assignees, - required DateTime startDate, - required DateTime dueDate, - required List> tags, - required String? branchId, - }) async { - const endpoint = ApiEndpoints.createServiceProjectJob; - logSafe("Creating Service Project Job for projectId: $projectId"); - - final body = { - "title": title, - "description": description, - "projectId": projectId, - "assignees": assignees, - "startDate": startDate.toIso8601String(), - "dueDate": dueDate.toIso8601String(), - "tags": tags, - "projectBranchId": branchId, - }; - - try { - final response = await _postRequest(endpoint, body); - - if (response == null) return null; - - final json = jsonDecode(response.body); - - if (json['success'] == true) { - final jobId = json['data']?['id']; - logSafe("Service Project Job created successfully: $jobId"); - return jobId; - } - - return null; - } catch (e, stack) { - logSafe("Exception during createServiceProjectJobApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - return null; - } - } - - /// Get Service Project Job List (Active or Archived) - static Future getServiceProjectJobListApi({ - required String projectId, - int pageNumber = 1, - int pageSize = 20, - bool isActive = true, - bool isArchive = false, // new parameter to fetch archived jobs - }) async { - const endpoint = ApiEndpoints.getServiceProjectJobList; - logSafe( - "Fetching Job List for Service Project: $projectId | isActive: $isActive | isArchive: $isArchive", - ); - - try { - final queryParams = { - 'projectId': projectId, - 'pageNumber': pageNumber.toString(), - 'pageSize': pageSize.toString(), - 'isActive': isActive.toString(), - if (isArchive) 'isArchive': 'true', - }; - - final response = await _getRequest(endpoint, queryParams: queryParams); - - if (response == null) { - logSafe( - "Service Project Job List request failed: null response", - level: LogLevel.error, - ); - return null; - } - - final jsonResponse = _parseResponseForAllData( - response, - label: isArchive - ? "Archived Service Project Job List" - : "Active Service Project Job List", - ); - - if (jsonResponse != null) { - return JobResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe( - "Exception during getServiceProjectJobListApi: $e", - level: LogLevel.error, - ); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - -// API to get all employees from basic - static Future?> allEmployeesBasic({ - bool allEmployee = true, - }) async { - final queryParams = {}; - - // Always include allEmployee parameter - queryParams['allEmployee'] = allEmployee.toString(); - - final response = await _getRequest( - ApiEndpoints.getEmployeesWithoutPermission, - queryParams: queryParams, - ); - - if (response != null) { - return _parseResponse(response, label: ' All Employees Basic'); - } - - return null; - } - - /// Get details of a single service project - static Future getServiceProjectDetailApi( - String projectId) async { - final endpoint = "${ApiEndpoints.getServiceProjectDetail}/$projectId"; - logSafe("Fetching details for Service Project ID: $projectId"); - - try { - final response = await _getRequest(endpoint); - - if (response == null) { - logSafe( - "Service Project Detail request failed: null response", - level: LogLevel.error, - ); - return null; - } - - final jsonResponse = _parseResponseForAllData( - response, - label: "Service Project Detail", - ); - - if (jsonResponse != null) { - return ServiceProjectDetailModel.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe( - "Exception during getServiceProjectDetailApi: $e", - level: LogLevel.error, - ); - logSafe( - "StackTrace: $stack", - level: LogLevel.debug, - ); - } - - return null; - } - - /// Get Service Project List - static Future getServiceProjectsListApi({ - int pageNumber = 1, - int pageSize = 20, - }) async { - const endpoint = ApiEndpoints.getServiceProjectsList; - logSafe("Fetching Service Project List"); - - try { - final queryParams = { - 'pageNumber': pageNumber.toString(), - 'pageSize': pageSize.toString(), - }; - - final response = await _getRequest(endpoint, queryParams: queryParams); - - if (response == null) { - logSafe("Service Project List request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = _parseResponseForAllData( - response, - label: "Service Project List", - ); - - if (jsonResponse != null) { - return ServiceProjectListModel.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getServiceProjectsListApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Edit Expense Payment Request - static Future editExpensePaymentRequestApi({ - required String id, - required String title, - required String description, - required String payee, - required String currencyId, - required double amount, - required String dueDate, - required String projectId, - required String expenseCategoryId, - required bool isAdvancePayment, - List> billAttachments = const [], - }) async { - final endpoint = "${ApiEndpoints.getExpensePaymentRequestEdit}/$id"; - - final body = { - "id": id, - "title": title, - "description": description, - "payee": payee, - "currencyId": currencyId, - "amount": amount, - "dueDate": dueDate, - "projectId": projectId, - "expenseCategoryId": expenseCategoryId, - "isAdvancePayment": isAdvancePayment, - "billAttachments": billAttachments, - }; - - try { - final response = await _putRequest(endpoint, body); - - if (response == null) { - logSafe("Edit Expense Payment Request failed: null response", - level: LogLevel.error); - return false; - } - - logSafe( - "Edit Expense Payment Request response status: ${response.statusCode}"); - logSafe("Edit Expense Payment Request response body: ${response.body}"); - - final json = jsonDecode(response.body); - if (json['success'] == true) { - logSafe( - "Expense Payment Request edited successfully: ${json['data'] ?? 'No data'}"); - return true; - } else { - logSafe( - "Failed to edit Expense Payment Request: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - return false; - } - } catch (e, stack) { - logSafe("Exception during editExpensePaymentRequestApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - return false; - } - } - - /// Create Expense for Payment Request - static Future createExpenseForPRApi({ - required String paymentModeId, - required String location, - required String gstNumber, - required String statusId, - required String paymentRequestId, - required String comment, - List> billAttachments = const [], - }) async { - const endpoint = ApiEndpoints.createExpenseforPR; - - final body = { - "paymentModeId": paymentModeId, - "location": location, - "gstNumber": gstNumber, - "statusId": statusId, - "comment": comment, - "paymentRequestId": paymentRequestId, - "billAttachments": billAttachments, - }; - - try { - final response = await _postRequest(endpoint, body); - - if (response == null) { - logSafe("Create Expense for PR failed: null response", - level: LogLevel.error); - return false; - } - - logSafe("Create Expense for PR response status: ${response.statusCode}"); - logSafe("Create Expense for PR response body: ${response.body}"); - - final json = jsonDecode(response.body); - if (json['success'] == true) { - logSafe( - "Expense for Payment Request created successfully: ${json['data']}"); - return true; - } else { - logSafe( - "Failed to create Expense for Payment Request: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - return false; - } - } catch (e, stack) { - logSafe("Exception during createExpenseForPRApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - return false; - } - } - - /// Update Expense Payment Request Status - static Future updateExpensePaymentRequestStatusApi({ - required String paymentRequestId, - required String statusId, - required String comment, - String? paidTransactionId, - String? paidById, - DateTime? paidAt, - double? baseAmount, - double? taxAmount, - String? tdsPercentage, - }) async { - const endpoint = ApiEndpoints.updateExpensePaymentRequestStatus; - logSafe("Updating Payment Request Status for ID: $paymentRequestId"); - - final body = { - "paymentRequestId": paymentRequestId, - "statusId": statusId, - "comment": comment, - "paidTransactionId": paidTransactionId, - "paidById": paidById, - "paidAt": paidAt?.toIso8601String(), - "baseAmount": baseAmount, - "taxAmount": taxAmount, - "tdsPercentage": tdsPercentage ?? "0", - }; - - try { - final response = await _postRequest(endpoint, body); - - if (response == null) { - logSafe("Update Payment Request Status failed: null response", - level: LogLevel.error); - return false; - } - - logSafe( - "Update Payment Request Status response: ${response.statusCode} -> ${response.body}"); - - final json = jsonDecode(response.body); - if (json['success'] == true) { - logSafe("Payment Request status updated successfully!"); - return true; - } else { - logSafe( - "Failed to update Payment Request Status: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - return false; - } - } catch (e, stack) { - logSafe("Exception during updateExpensePaymentRequestStatusApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - return false; - } - } - - /// Get Expense Payment Request Detail by ID - static Future getExpensePaymentRequestDetailApi( - String paymentRequestId) async { - final endpoint = - "${ApiEndpoints.getExpensePaymentRequestDetails}/$paymentRequestId"; - logSafe( - "Fetching Expense Payment Request Detail for ID: $paymentRequestId"); - - try { - final response = await _getRequest(endpoint); - - if (response == null) { - logSafe("Expense Payment Request Detail request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = _parseResponseForAllData( - response, - label: "Expense Payment Request Detail", - ); - - if (jsonResponse != null) { - return PaymentRequestDetail.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getExpensePaymentRequestDetailApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - static Future - getExpensePaymentRequestFilterApi() async { - const endpoint = ApiEndpoints.getExpensePaymentRequestFilter; - logSafe("Fetching Expense Payment Request Filter"); - - try { - final response = await _getRequest(endpoint); - - if (response == null) { - logSafe("Expense Payment Request Filter request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = _parseResponseForAllData( - response, - label: "Expense Payment Request Filter", - ); - - if (jsonResponse != null) { - return PaymentRequestFilter.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getExpensePaymentRequestFilterApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Get Expense Payment Request List - static Future getExpensePaymentRequestListApi({ - bool isActive = true, - int pageSize = 20, - int pageNumber = 1, - Map? filter, - String searchString = '', - }) async { - const endpoint = ApiEndpoints.getExpensePaymentRequestList; - logSafe("Fetching Expense Payment Request List"); - - try { - final queryParams = { - 'isActive': isActive.toString(), - 'pageSize': pageSize.toString(), - 'pageNumber': pageNumber.toString(), - 'filter': jsonEncode(filter ?? - { - "projectIds": [], - "statusIds": [], - "createdByIds": [], - "currencyIds": [], - "expenseCategoryIds": [], - "payees": [], - "startDate": null, - "endDate": null - }), - 'searchString': searchString, - }; - - final response = await _getRequest(endpoint, queryParams: queryParams); - - if (response == null) { - logSafe("Expense Payment Request List request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = _parseResponseForAllData( - response, - label: "Expense Payment Request List", - ); - - if (jsonResponse != null) { - return PaymentRequestResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getExpensePaymentRequestListApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Create Expense Payment Request (Project API style) - static Future createExpensePaymentRequestApi({ - required String title, - required String projectId, - required String expenseCategoryId, - required String currencyId, - required String payee, - required double amount, - DateTime? dueDate, - required String description, - required bool isAdvancePayment, - List> billAttachments = const [], - }) async { - const endpoint = ApiEndpoints.createExpensePaymentRequest; - - final body = { - "title": title, - "projectId": projectId, - "expenseCategoryId": expenseCategoryId, - "currencyId": currencyId, - "payee": payee, - "amount": amount, - "dueDate": dueDate?.toIso8601String(), - "description": description, - "isAdvancePayment": isAdvancePayment, - "billAttachments": billAttachments, - }; - - try { - final response = await _postRequest(endpoint, body); - if (response == null) { - logSafe("Create Payment Request failed: null response", - level: LogLevel.error); - return false; - } - - logSafe("Create Payment Request response status: ${response.statusCode}"); - logSafe("Create Payment Request response body: ${response.body}"); - - final json = jsonDecode(response.body); - if (json['success'] == true) { - logSafe("Payment Request created successfully: ${json['data']}"); - return true; - } else { - logSafe( - "Failed to create Payment Request: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - return false; - } - } catch (e, stack) { - logSafe("Exception during createExpensePaymentRequestApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - return false; - } - } - - /// Fetch hierarchy list for an employee - static Future?> getOrganizationHierarchyList( - String employeeId) async { - if (employeeId.isEmpty) throw ArgumentError('employeeId must not be empty'); - final endpoint = "${ApiEndpoints.getOrganizationHierarchyList}/$employeeId"; - - return _getRequest(endpoint).then( - (res) => res != null - ? _parseResponse(res, label: 'Organization Hierarchy List') - : null, - ); - } - - /// Manage (create/update) organization hierarchy (assign reporters) for an employee - /// payload is a List> with objects like: - /// { "reportToId": "", "isPrimary": true, "isActive": true } - static Future manageOrganizationHierarchy({ - required String employeeId, - required List> payload, - }) async { - if (employeeId.isEmpty) throw ArgumentError('employeeId must not be empty'); - - final endpoint = "${ApiEndpoints.manageOrganizationHierarchy}/$employeeId"; - - logSafe("manageOrganizationHierarchy for $employeeId payload: $payload"); - - try { - final response = await _postRequest(endpoint, payload); - if (response == null) { - logSafe("Manage hierarchy failed: null response", - level: LogLevel.error); - return false; - } - - logSafe("Manage hierarchy response status: ${response.statusCode}"); - logSafe("Manage hierarchy response body: ${response.body}"); - - final json = jsonDecode(response.body); - if (json['success'] == true) { - logSafe("Manage hierarchy succeeded"); - return true; - } - - logSafe("Manage hierarchy failed: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.error); - return false; - } catch (e, stack) { - logSafe("Exception while manageOrganizationHierarchy: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - return false; - } - } - - /// Get Master Currencies - static Future getMasterCurrenciesApi() async { - const endpoint = ApiEndpoints.getMasterCurrencies; - logSafe("Fetching Master Currencies"); - - try { - final response = await _getRequest(endpoint); - - if (response == null) { - logSafe("Master Currencies request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = - _parseResponseForAllData(response, label: "Master Currencies"); - if (jsonResponse != null) { - return CurrencyListResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getMasterCurrenciesApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Get Master Expense Categories - static Future - getMasterExpenseCategoriesApi() async { - const endpoint = ApiEndpoints.getMasterExpensesCategories; - logSafe("Fetching Master Expense Categories"); - - try { - final response = await _getRequest(endpoint); - - if (response == null) { - logSafe("Master Expense Categories request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = _parseResponseForAllData(response, - label: "Master Expense Categories"); - if (jsonResponse != null) { - return ExpenseCategoryResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getMasterExpenseCategoriesApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Get Expense Payment Request Payee - static Future - getExpensePaymentRequestPayeeApi() async { - const endpoint = ApiEndpoints.getExpensePaymentRequestPayee; - logSafe("Fetching Expense Payment Request Payees"); - - try { - final response = await _getRequest(endpoint); - - if (response == null) { - logSafe("Expense Payment Request Payee request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = _parseResponseForAllData(response, - label: "Expense Payment Request Payee"); - if (jsonResponse != null) { - return PaymentRequestPayeeResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getExpensePaymentRequestPayeeApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Get Monthly Expense Report (categoryId is optional) - static Future - getDashboardMonthlyExpensesApi({ - String? categoryId, - int months = 12, - }) async { - const endpoint = ApiEndpoints.getDashboardMonthlyExpenses; - logSafe("Fetching Dashboard Monthly Expenses for last $months months"); - - try { - final queryParams = { - 'months': months.toString(), - if (categoryId != null && categoryId.isNotEmpty) - 'categoryId': categoryId, - }; - - final response = await _getRequest( - endpoint, - queryParams: queryParams, - ); - - if (response == null) { - logSafe("Monthly Expense request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = _parseResponseForAllData(response, - label: "Dashboard Monthly Expenses"); - - if (jsonResponse != null) { - return DashboardMonthlyExpenseResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getDashboardMonthlyExpensesApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Get Expense Category Report - static Future getExpenseTypeReportApi({ - required String projectId, - required DateTime startDate, - required DateTime endDate, - }) async { - const endpoint = ApiEndpoints.getExpenseTypeReport; - logSafe("Fetching Expense Category Report for projectId: $projectId"); - - try { - final response = await _getRequest( - endpoint, - queryParams: { - 'projectId': projectId, - 'startDate': startDate.toIso8601String(), - 'endDate': endDate.toIso8601String(), - }, - ); - - if (response == null) { - logSafe("Expense Category Report request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = - _parseResponseForAllData(response, label: "Expense Category Report"); - - if (jsonResponse != null) { - return ExpenseTypeReportResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getExpenseTypeReportApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Get Pending Expenses - static Future getPendingExpensesApi({ - required String projectId, - }) async { - const endpoint = ApiEndpoints.getPendingExpenses; - logSafe("Fetching Pending Expenses for projectId: $projectId"); - - try { - final response = await _getRequest( - endpoint, - queryParams: {'projectId': projectId}, - ); - - if (response == null) { - logSafe("Pending Expenses request failed: null response", - level: LogLevel.error); - return null; - } - - final jsonResponse = - _parseResponseForAllData(response, label: "Pending Expenses"); - - if (jsonResponse != null) { - return PendingExpensesResponse.fromJson(jsonResponse); - } - } catch (e, stack) { - logSafe("Exception during getPendingExpensesApi: $e", - level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return null; - } - - /// Create Project API - static Future createProjectApi({ - required String name, - required String projectAddress, - required String shortName, - required String contactPerson, - required DateTime startDate, - required DateTime endDate, - required String projectStatusId, - }) async { - const endpoint = ApiEndpoints.createProject; - logSafe("Creating project: $name"); - - final Map payload = { - "name": name, - "projectAddress": projectAddress, - "shortName": shortName, - "contactPerson": contactPerson, - "startDate": startDate.toIso8601String(), - "endDate": endDate.toIso8601String(), - "projectStatusId": projectStatusId, - }; - - try { - final response = - await _postRequest(endpoint, payload, customTimeout: extendedTimeout); - - if (response == null) { - logSafe("Create project failed: null response", level: LogLevel.error); - return false; - } - - logSafe("Create project response status: ${response.statusCode}"); - logSafe("Create project response body: ${response.body}"); - - final json = jsonDecode(response.body); - if (json['success'] == true) { - logSafe("Project created successfully: ${json['data']}"); - return true; - } else { - logSafe( - "Failed to create project: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - } - } catch (e, stack) { - logSafe("Exception during createProjectApi: $e", level: LogLevel.error); - logSafe("StackTrace: $stack", level: LogLevel.debug); - } - - return false; - } - - /// Get Organizations assigned to a Project - static Future 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 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 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 postLogsApi(List> 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', - }; - - // Send logs as JSON - 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}"); - - // --- Decrypt response before parsing --- - final decryptedData = - decryptResponse(response.body); // returns Map/List or null - - if (decryptedData == null) { - logSafe("Decryption failed. Cannot parse logs response.", - level: LogLevel.error); - return false; - } - - // Expecting decrypted data to be a Map with 'success' field - if (response.statusCode == 200 && - decryptedData is Map && - decryptedData['success'] == true) { - logSafe("Logs posted successfully."); - return true; - } else { - final errorMsg = decryptedData is Map - ? decryptedData['message'] ?? 'Unknown error' - : 'Decrypted response not a Map: $decryptedData'; - logSafe("Failed to post logs: $errorMsg", 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 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 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 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 editDocumentApi({ - required String id, - required String name, - required String documentId, - String? description, - List> tags = const [], - Map? attachment, // 👈 can be null - }) async { - final endpoint = "${ApiEndpoints.editDocument}/$id"; - logSafe("Editing document with id: $id"); - - final Map 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 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 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 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 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 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> tags = const [], - }) async { - const endpoint = ApiEndpoints.uploadDocument; - logSafe("Uploading document: $name for entity: $entityId"); - - final Map 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 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 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 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?> 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) { - 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 editExpenseApi({ - required String expenseId, - required Map 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 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?> 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) { - 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 updateExpenseStatusApi({ - required String expenseId, - required String statusId, - String? comment, - String? reimburseTransactionId, - String? reimburseDate, - String? reimbursedById, - double? baseAmount, - double? taxAmount, - double? tdsPercent, - double? netPayable, - }) async { - final Map 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; - if (baseAmount != null) payload["baseAmount"] = baseAmount; - if (taxAmount != null) payload["taxAmount"] = taxAmount; - if (tdsPercent != null) payload["tdsPercent"] = tdsPercent; - if (netPayable != null) payload["netPayable"] = netPayable; - - 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?> 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) { - 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; - } - } - - static Future> getAdvancePayments( - String employeeId) async { - try { - final endpoint = "${ApiEndpoints.getAdvancePayments}/$employeeId"; - - final response = await _getRequest(endpoint); - - if (response == null) { - logSafe("❌ getAdvancePayments: Null response"); - return []; - } - - if (response.statusCode == 200) { - // 🟢 Added log to inspect raw JSON - logSafe("🔍 AdvancePayment raw response: ${response.body}"); - - final Map body = jsonDecode(response.body); - final data = body['data'] ?? body; - return AdvancePayment.listFromJson(data); - } else { - logSafe("⚠ getAdvancePayments failed → ${response.statusCode}"); - return []; - } - } catch (e, s) { - logSafe("❌ ApiService.getAdvancePayments error: $e\n$s", - level: LogLevel.error); - return []; - } - } - - /// Fetch employees with optional query. Returns raw list (List) - static Future> getEmployees( - {Map? queryParams}) async { - try { - final endpoint = ApiEndpoints.getEmployeesWithoutPermission; - - final resp = await _getRequest(endpoint, queryParams: queryParams); - if (resp == null) return []; - - final body = jsonDecode(resp.body); - if (body is Map && body.containsKey('data')) { - final data = body['data']; - if (data is List) return data; - return []; - } else if (body is List) { - return body; - } else { - return []; - } - } catch (e, s) { - logSafe("❌ ApiService.getEmployees error: $e\n$s", level: LogLevel.error); - return []; - } - } - - /// Fetch Master Payment Modes - static Future?> 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?> getMasterExpenseStatus() async { - const endpoint = ApiEndpoints.getMasterExpenseStatus; - return _getRequest(endpoint).then((res) => res != null - ? _parseResponse(res, label: 'Master Expense Status') - : null); - } - - /// Fetch Master Expense Categorys - static Future?> getMasterExpenseTypes() async { - const endpoint = ApiEndpoints.getMasterExpenseCategory; - return _getRequest(endpoint).then((res) => res != null - ? _parseResponse(res, label: 'Master Expense Categorys') - : null); - } - - /// Create Expense API - static Future 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> billAttachments, - }) async { - final payload = { - "projectId": projectId, - "expenseCategoryId": 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 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 && - 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 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 && - 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?> 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 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 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 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 assignEmployeesToBucket({ - required String bucketId, - required List> 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 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?> 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 ? data : null; - } - - static Future 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?> 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 assignProjects({ - required String employeeId, - required List> 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 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 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?> 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 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 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 updateContact( - String contactId, Map 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 createContact(Map 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> 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.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?> getContactCategoryList() async => - _getRequest(ApiEndpoints.getDirectoryContactCategory).then((res) => - res != null - ? _parseResponseForAllData(res, label: 'Contact Category List') - : null); - - static Future?> getContactTagList() async => - _getRequest(ApiEndpoints.getDirectoryContactTags).then((res) => - res != null - ? _parseResponseForAllData(res, label: 'Contact Tag List') - : null); - - static Future?> 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?> getContactBucketList() async => - _getRequest(ApiEndpoints.getDirectoryBucketList).then((res) => res != null - ? _parseResponseForAllData(res, label: 'Contact Bucket List') - : null); - - // === Attendance APIs === - - static Future?> getProjects() async => - _getRequest(ApiEndpoints.getProjects).then( - (res) => res != null ? _parseResponse(res, label: 'Projects') : null); - - static Future?> getGlobalProjects() async => - _getRequest(ApiEndpoints.getGlobalProjects).then((res) => - res != null ? _parseResponse(res, label: 'Global Projects') : null); - - static Future?> 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?> getAttendanceForDashboard( - String projectId) async { - String endpoint = ApiEndpoints.getAttendanceForDashboard.replaceFirst( - ':projectId', - projectId, - ); - - final res = await _getRequest(endpoint); - - if (res == null) return null; - - final data = _parseResponse(res, label: 'Dashboard Attendance'); - if (data == null) return null; - - // Wrap single object in a list if needed - if (data is Map) { - return [EmployeeModel.fromJson(data)]; - } else if (data is List) { - return data.map((e) => EmployeeModel.fromJson(e)).toList(); - } - - return null; - } - - static Future?> 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?> 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?> getAttendanceLogView(String id) async => - _getRequest("${ApiEndpoints.getAttendanceLogView}/$id").then((res) => - res != null ? _parseResponse(res, label: 'Log Details') : null); - - static Future 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?> searchEmployeesBasic({ - String? searchString, - }) async { - // Remove ArgumentError check because searchString is optional now - - final queryParams = {}; - - // 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?> 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?> 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 = {}; - 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?> 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?> getRoles() async => - _getRequest(ApiEndpoints.getRoles).then( - (res) => res != null ? _parseResponse(res, label: 'Roles') : null); - static Future?> 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?> 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 ? data : null; - } - - // === Daily Task APIs === - /// Get Daily Task Project Report Filter - static Future 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?> getDailyTasks( - String projectId, { - Map? 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 reportTask({ - required String id, - required int completedTask, - required String comment, - required List> checkList, - List>? 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 commentTask({ - required String id, - required String comment, - List>? 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?> 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?; - } - - /// Fetch work items for a given work area, optionally filtered by service - static Future?> 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?; - } - - static Future assignDailyTask({ - required String workItemId, - required int plannedTask, - required String description, - required List 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?> 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?; - } - - static Future?> getMasterWorkCategories() async => - _getRequest(ApiEndpoints.getmasterWorkCategories).then((res) => - res != null - ? _parseResponseForAllData(res, label: 'Master Work Categories') - : null); - - static Future approveTask({ - required String id, - required String comment, - required String workStatus, - required int approvedTask, - List>? 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 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; - } -} diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index c21ea8e..f56341e 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -51,6 +51,7 @@ import 'package:on_field_work/model/infra_project/infra_project_list.dart'; import 'package:on_field_work/model/infra_project/infra_project_details.dart'; import 'package:on_field_work/model/dashboard/collection_overview_model.dart'; import 'package:on_field_work/model/dashboard/purchase_invoice_model.dart'; +import 'package:on_field_work/model/infra_project/infra_team_list_model.dart'; class ApiService { static const bool enableLogs = true; @@ -2007,6 +2008,43 @@ class ApiService { label: "Comment Task", returnFullResponse: true); return parsed != null && parsed['success'] == true; } + static Future getInfraProjectTeamListApi({ + required String projectId, + String? serviceId, + bool includeInactive = false, + }) async { + if (projectId.isEmpty) { + _log("projectId must not be empty for getInfraProjectTeamListApi", + level: LogLevel.error); + return null; + } + final endpoint = "${ApiEndpoints.getInfraProjectTeamList}/$projectId"; + + final queryParams = { + 'includeInactive': includeInactive.toString(), + }; + if (serviceId != null && serviceId.isNotEmpty) { + queryParams['serviceId'] = serviceId; + } + + final response = await _safeApiCall( + endpoint, + method: 'GET', + queryParams: queryParams, + ); + + if (response == null) return null; + + final parsedJson = _parseAndDecryptResponse( + response, + label: "InfraProjectTeamList", + returnFullResponse: true, + ); + if (parsedJson == null || parsedJson is! Map) { + return null; + } + return ProjectAllocationResponse.fromJson(parsedJson); + } static Future?> getInfraDetails(String projectId, {String? serviceId}) async { diff --git a/lib/model/infra_project/infra_team_list_model.dart b/lib/model/infra_project/infra_team_list_model.dart new file mode 100644 index 0000000..48b16ec --- /dev/null +++ b/lib/model/infra_project/infra_team_list_model.dart @@ -0,0 +1,119 @@ + +class ProjectAllocationResponse { + final bool success; + final String message; + final List data; + final int statusCode; + final String timestamp; + + ProjectAllocationResponse({ + required this.success, + required this.message, + required this.data, + required this.statusCode, + required this.timestamp, + }); + + factory ProjectAllocationResponse.fromJson(Map json) { + final List? rawData = json['data'] as List?; + + final List allocations = rawData + ?.map((item) => ProjectAllocation.fromJson(item as Map)) + .toList() + ?? []; + + return ProjectAllocationResponse( + success: json['success'] as bool? ?? false, + message: json['message'] as String? ?? 'An unknown API error occurred.', + data: allocations, + statusCode: json['statusCode'] as int? ?? 0, + timestamp: json['timestamp'] as String? ?? '', + ); + } + + /// Converts the [ProjectAllocationResponse] object back to a JSON map. + Map toJson() { + return { + 'success': success, + 'message': message, + 'data': data.map((e) => e.toJson()).toList(), + 'statusCode': statusCode, + 'timestamp': timestamp, + }; + } +} + +// --- Allocation Detail Class --- + +class ProjectAllocation { + final String id; + final String employeeId; + final String projectId; + final String allocationDate; + final bool isActive; + final String firstName; + final String lastName; + final String middleName; + final String organizationId; + final String organizationName; + final String serviceId; + final String serviceName; + final String jobRoleId; + final String jobRoleName; + + ProjectAllocation({ + required this.id, + required this.employeeId, + required this.projectId, + required this.allocationDate, + required this.isActive, + required this.firstName, + required this.lastName, + required this.middleName, + required this.organizationId, + required this.organizationName, + required this.serviceId, + required this.serviceName, + required this.jobRoleId, + required this.jobRoleName + }); + + + factory ProjectAllocation.fromJson(Map json) { + return ProjectAllocation( + id: json['id'] as String? ?? '', + employeeId: json['employeeId'] as String? ?? '', + projectId: json['projectId'] as String? ?? '', + allocationDate: json['allocationDate'] as String? ?? '', + isActive: json['isActive'] as bool? ?? false, + firstName: json['firstName'] as String? ?? '', + lastName: json['lastName'] as String? ?? '', + middleName: json['middleName'] as String? ?? '', + organizationId: json['organizationId'] as String? ?? '', + organizationName: json['organizationName'] as String? ?? '', + serviceId: json['serviceId'] as String? ?? '', + serviceName: json['serviceName'] as String? ?? '', + jobRoleId: json['jobRoleId'] as String? ?? '', + jobRoleName: json['jobRoleName'] as String? ?? '', + ); + } + + Map toJson() { + return { + 'id': id, + 'employeeId': employeeId, + 'projectId': projectId, + 'allocationDate': allocationDate, + 'isActive': isActive, + 'firstName': firstName, + 'lastName': lastName, + 'middleName': middleName, + 'organizationId': organizationId, + 'organizationName': organizationName, + 'serviceId': serviceId, + 'serviceName': serviceName, + 'jobRoleId': jobRoleId, + 'jobRoleName': jobRoleName + }; + } +} \ No newline at end of file diff --git a/lib/view/infraProject/infra_project_details_screen.dart b/lib/view/infraProject/infra_project_details_screen.dart index ef7b4b2..c4af071 100644 --- a/lib/view/infraProject/infra_project_details_screen.dart +++ b/lib/view/infraProject/infra_project_details_screen.dart @@ -10,11 +10,14 @@ import 'package:on_field_work/helpers/utils/launcher_utils.dart'; import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart'; +import 'package:on_field_work/helpers/widgets/avatar.dart'; +import 'package:on_field_work/view/employees/employee_profile_screen.dart'; import 'package:on_field_work/controller/dynamicMenu/dynamic_menu_controller.dart'; import 'package:on_field_work/controller/infra_project/infra_project_screen_details_controller.dart'; import 'package:on_field_work/view/taskPlanning/daily_progress_report.dart'; import 'package:on_field_work/view/taskPlanning/daily_task_planning.dart'; +import 'package:on_field_work/model/infra_project/infra_team_list_model.dart'; class InfraProjectDetailsScreen extends StatefulWidget { final String projectId; @@ -36,17 +39,20 @@ class _InfraProjectDetailsScreenState extends State late final TabController _tabController; final DynamicMenuController menuController = Get.find(); + late final InfraProjectDetailsController controller; final List<_InfraTab> _tabs = []; @override void initState() { super.initState(); + controller = + Get.put(InfraProjectDetailsController(projectId: widget.projectId)); _prepareTabs(); } void _prepareTabs() { - // Profile tab is always added _tabs.add(_InfraTab(name: "Profile", view: _buildProfileTab())); + _tabs.add(_InfraTab(name: "Team", view: _buildTeamTab())); final allowedMenu = menuController.menuItems.where((m) => m.available); @@ -77,10 +83,148 @@ class _InfraProjectDetailsScreenState extends State super.dispose(); } - Widget _buildProfileTab() { - final controller = - Get.put(InfraProjectDetailsController(projectId: widget.projectId)); + Widget _buildTeamTab() { + return Obx(() { + if (controller.teamLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + if (controller.teamErrorMessage.isNotEmpty) { + return Center( + child: MyText.bodyMedium(controller.teamErrorMessage.value), + ); + } + + if (controller.teamList.isEmpty) { + return const Center( + child: Text("No team members allocated to this project."), + ); + } + + final roleGroups = controller.groupedTeamByRole; + + final sortedRoleEntries = roleGroups.entries.toList() + ..sort((a, b) { + final aName = (a.value.isNotEmpty ? a.value.first.jobRoleName : '') + .toLowerCase(); + final bName = (b.value.isNotEmpty ? b.value.first.jobRoleName : '') + .toLowerCase(); + return aName.compareTo(bName); + }); + + return MyRefreshIndicator( + onRefresh: controller.fetchProjectTeamList, + backgroundColor: Colors.indigo, + color: Colors.white, + child: ListView.separated( + padding: const EdgeInsets.all(12), + itemCount: sortedRoleEntries.length, + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (context, index) { + final teamMembers = sortedRoleEntries[index].value; + return _buildRoleCard(teamMembers); + }, + ), + ); + }); + } + + Widget _buildRoleCard(List teamMembers) { + teamMembers.sort((a, b) { + final aName = ("${a.firstName} ${a.lastName}").trim().toLowerCase(); + final bName = ("${b.firstName} ${b.lastName}").trim().toLowerCase(); + return aName.compareTo(bName); + }); + + final String roleName = + (teamMembers.isNotEmpty ? (teamMembers.first.jobRoleName) : '').trim(); + + return Card( + elevation: 3, + shadowColor: Colors.black26, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // TOP: Job Role name + if (roleName.isNotEmpty) ...[ + MyText.bodyLarge( + roleName, + fontWeight: 700, + ), + const Divider(height: 20), + ] else + const Divider(height: 20), + + // Team members list + ...teamMembers.map((allocation) { + return InkWell( + onTap: () { + Get.to( + () => EmployeeProfilePage( + employeeId: allocation.employeeId, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Avatar( + firstName: allocation.firstName, + lastName: allocation.lastName, + size: 32, + ), + MySpacing.width(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.titleMedium( + "${allocation.firstName} ${allocation.lastName}", + fontWeight: 600, + ), + MyText.bodySmall( + allocation.serviceName.isNotEmpty + ? "Service: ${allocation.serviceName}" + : "No Service Assigned", + color: Colors.grey[700], + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + MyText.bodySmall( + "Allocated", + color: Colors.grey.shade500, + ), + MyText.bodySmall( + DateFormat('d MMM yyyy').format( + DateTime.parse(allocation.allocationDate), + ), + fontWeight: 600, + ), + ], + ), + ], + ), + ), + ); + }).toList(), + ], + ), + ), + ); + } + + Widget _buildProfileTab() { return Obx(() { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); @@ -153,35 +297,40 @@ class _InfraProjectDetailsScreenState extends State titleIcon: Icons.info_outline, children: [ _buildDetailRow( - icon: Icons.location_on_outlined, - label: 'Address', - value: data.projectAddress ?? "-"), + icon: Icons.location_on_outlined, + label: 'Address', + value: data.projectAddress ?? "-", + ), _buildDetailRow( - icon: Icons.calendar_today_outlined, - label: 'Start Date', - value: data.startDate != null - ? DateFormat('d/M/yyyy').format(data.startDate!) - : "-"), + icon: Icons.calendar_today_outlined, + label: 'Start Date', + value: data.startDate != null + ? DateFormat('d/M/yyyy').format(data.startDate!) + : "-", + ), _buildDetailRow( - icon: Icons.calendar_today_outlined, - label: 'End Date', - value: data.endDate != null - ? DateFormat('d/M/yyyy').format(data.endDate!) - : "-"), + icon: Icons.calendar_today_outlined, + label: 'End Date', + value: data.endDate != null + ? DateFormat('d/M/yyyy').format(data.endDate!) + : "-", + ), _buildDetailRow( - icon: Icons.flag_outlined, - label: 'Status', - value: data.projectStatus?.status ?? "-"), + icon: Icons.flag_outlined, + label: 'Status', + value: data.projectStatus?.status ?? "-", + ), _buildDetailRow( - icon: Icons.person_outline, - label: 'Contact Person', - value: data.contactPerson ?? "-", - isActionable: true, - onTap: () { - if (data.contactPerson != null) { - LauncherUtils.launchPhone(data.contactPerson!); - } - }), + icon: Icons.person_outline, + label: 'Contact Person', + value: data.contactPerson ?? "-", + isActionable: true, + onTap: () { + if (data.contactPerson != null) { + LauncherUtils.launchPhone(data.contactPerson!); + } + }, + ), ], ); } @@ -192,22 +341,24 @@ class _InfraProjectDetailsScreenState extends State titleIcon: Icons.business_outlined, children: [ _buildDetailRow( - icon: Icons.person_outline, - label: 'Name', - value: promoter.name ?? "-"), + icon: Icons.person_outline, + label: 'Name', + value: promoter.name ?? "-", + ), _buildDetailRow( - icon: Icons.phone_outlined, - label: 'Contact', - value: promoter.contactNumber ?? "-", - isActionable: true, - onTap: () => - LauncherUtils.launchPhone(promoter.contactNumber ?? "")), + icon: Icons.phone_outlined, + label: 'Contact', + value: promoter.contactNumber ?? "-", + isActionable: true, + onTap: () => LauncherUtils.launchPhone(promoter.contactNumber ?? ""), + ), _buildDetailRow( - icon: Icons.email_outlined, - label: 'Email', - value: promoter.email ?? "-", - isActionable: true, - onTap: () => LauncherUtils.launchEmail(promoter.email ?? "")), + icon: Icons.email_outlined, + label: 'Email', + value: promoter.email ?? "-", + isActionable: true, + onTap: () => LauncherUtils.launchEmail(promoter.email ?? ""), + ), ], ); } @@ -218,19 +369,24 @@ class _InfraProjectDetailsScreenState extends State titleIcon: Icons.engineering_outlined, children: [ _buildDetailRow( - icon: Icons.person_outline, label: 'Name', value: pmc.name ?? "-"), + icon: Icons.person_outline, + label: 'Name', + value: pmc.name ?? "-", + ), _buildDetailRow( - icon: Icons.phone_outlined, - label: 'Contact', - value: pmc.contactNumber ?? "-", - isActionable: true, - onTap: () => LauncherUtils.launchPhone(pmc.contactNumber ?? "")), + icon: Icons.phone_outlined, + label: 'Contact', + value: pmc.contactNumber ?? "-", + isActionable: true, + onTap: () => LauncherUtils.launchPhone(pmc.contactNumber ?? ""), + ), _buildDetailRow( - icon: Icons.email_outlined, - label: 'Email', - value: pmc.email ?? "-", - isActionable: true, - onTap: () => LauncherUtils.launchEmail(pmc.email ?? "")), + icon: Icons.email_outlined, + label: 'Email', + value: pmc.email ?? "-", + isActionable: true, + onTap: () => LauncherUtils.launchEmail(pmc.email ?? ""), + ), ], ); } @@ -251,7 +407,9 @@ class _InfraProjectDetailsScreenState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.all(8), child: Icon(icon, size: 20)), + padding: const EdgeInsets.all(8), + child: Icon(icon, size: 20), + ), MySpacing.width(16), Expanded( child: Column(