Vaibhav Surve 1e48c686b2 refactor: update application ID and improve attendance upload functionality
- Changed application ID from "com.marco.aiotstage" to "com.marco.aiot".
- Made markTime and date parameters required in uploadAttendanceImage method.
- Added logic to handle date selection and ensure attendance logs are uploaded with the correct date.
- Enhanced UI components for better user experience in attendance and directory views.
2025-08-18 15:32:17 +05:30

1421 lines
43 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/helpers/services/app_logger.dart';
class ApiService {
static const Duration timeout = Duration(seconds: 30);
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(timeout);
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 = timeout,
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 = timeout,
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;
}
}
// === 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(timeout);
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 ===
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);
}
/// 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(timeout);
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<List<dynamic>?> getDirectoryComments(String contactId) async {
final url = "${ApiEndpoints.getDirectoryNotes}/$contactId";
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>?> getEmployeesByProject(String projectId) async =>
_getRequest(ApiEndpoints.getEmployeesByProject,
queryParams: {"projectId": projectId})
.then((res) =>
res != null ? _parseResponse(res, label: 'Employees') : null);
static Future<List<dynamic>?> getAttendanceLogs(
String projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) 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),
};
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<List<dynamic>?> getRegularizationLogs(String projectId) async =>
_getRequest(ApiEndpoints.getRegularizationLogs,
queryParams: {"projectId": projectId})
.then((res) => res != null
? _parseResponse(res, label: 'Regularization Logs')
: 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) async {
if (projectId.isEmpty) throw ArgumentError('projectId must not be empty');
final endpoint = "${ApiEndpoints.getAllEmployeesByProject}/$projectId";
return _getRequest(endpoint).then(
(res) => res != null
? _parseResponse(res, label: 'Employees by Project')
: null,
);
}
static Future<List<dynamic>?> getAllEmployees() async =>
_getRequest(ApiEndpoints.getAllEmployees).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({
required String firstName,
required String lastName,
required String phoneNumber,
required String gender,
required String jobRoleId,
}) async {
final body = {
"firstName": firstName,
"lastName": lastName,
"phoneNumber": phoneNumber,
"gender": gender,
"jobRoleId": jobRoleId,
};
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<dynamic>?> getDailyTasks(
String projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) 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),
};
return _getRequest(ApiEndpoints.getDailyTask, queryParams: query).then(
(res) =>
res != null ? _parseResponse(res, label: 'Daily Tasks') : 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;
}
static Future<Map<String, dynamic>?> getDailyTasksDetails(
String projectId) async {
final url = "${ApiEndpoints.dailyTaskDetails}/$projectId";
final response = await _getRequest(url);
return response != null
? _parseResponseForAllData(response, label: 'Daily Task Details')
as Map<String, dynamic>?
: null;
}
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;
}
}