2025-10-08 11:06:39 +05:30

2347 lines
72 KiB
Dart

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