2373 lines
73 KiB
Dart
2373 lines
73 KiB
Dart
import 'dart:convert';
|
|
import 'package:get/get.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
import 'package:marco/helpers/services/auth_service.dart';
|
|
import 'package:marco/helpers/services/api_endpoints.dart';
|
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
|
import 'package:jwt_decoder/jwt_decoder.dart';
|
|
import 'package:marco/model/dashboard/project_progress_model.dart';
|
|
import 'package:marco/model/dashboard/dashboard_tasks_model.dart';
|
|
import 'package:marco/model/dashboard/dashboard_teams_model.dart';
|
|
import 'package:marco/helpers/services/app_logger.dart';
|
|
import 'package:marco/model/document/document_filter_model.dart';
|
|
import 'package:marco/model/document/documents_list_model.dart';
|
|
import 'package:marco/model/document/master_document_tags.dart';
|
|
import 'package:marco/model/document/master_document_type_model.dart';
|
|
import 'package:marco/model/document/document_details_model.dart';
|
|
import 'package:marco/model/document/document_version_model.dart';
|
|
import 'package:marco/model/attendance/organization_per_project_list_model.dart';
|
|
import 'package:marco/model/tenant/tenant_services_model.dart';
|
|
import 'package:marco/model/dailyTaskPlanning/daily_task_model.dart';
|
|
import 'package:marco/model/dailyTaskPlanning/daily_progress_report_filter_response_model.dart';
|
|
|
|
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 ===
|
|
/// Get Daily Task Project Report Filter
|
|
static Future<DailyProgressReportFilterResponse?> getDailyTaskFilter(
|
|
String projectId) async {
|
|
final endpoint =
|
|
"${ApiEndpoints.getDailyTaskProjectProgressFilter}/$projectId";
|
|
logSafe("Fetching daily task Progress filter for projectId: $projectId");
|
|
|
|
try {
|
|
final response = await _getRequest(endpoint);
|
|
|
|
if (response == null) {
|
|
logSafe("Daily task filter request failed: null response",
|
|
level: LogLevel.error);
|
|
return null;
|
|
}
|
|
|
|
final jsonResponse = _parseResponseForAllData(response,
|
|
label: "Daily Task Progress Filter");
|
|
|
|
if (jsonResponse != null) {
|
|
return DailyProgressReportFilterResponse.fromJson(jsonResponse);
|
|
}
|
|
} catch (e, stack) {
|
|
logSafe("Exception during getDailyTask Progress Filter: $e",
|
|
level: LogLevel.error);
|
|
logSafe("StackTrace: $stack", level: LogLevel.debug);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static Future<List<TaskModel>?> getDailyTasks(
|
|
String projectId, {
|
|
Map<String, dynamic>? filter, // <-- New: combined filter
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
}) async {
|
|
// Build query parameters
|
|
final query = {
|
|
"projectId": projectId,
|
|
"pageNumber": pageNumber.toString(),
|
|
"pageSize": pageSize.toString(),
|
|
if (filter != null) "filter": jsonEncode(filter),
|
|
};
|
|
|
|
final uri =
|
|
Uri.parse(ApiEndpoints.getDailyTask).replace(queryParameters: query);
|
|
|
|
final response = await _getRequest(uri.toString());
|
|
final parsed = response != null
|
|
? _parseResponse(response, label: 'Daily Tasks')
|
|
: null;
|
|
|
|
if (parsed != null && parsed['data'] != null) {
|
|
return (parsed['data'] as List)
|
|
.map((e) => TaskModel.fromJson(e))
|
|
.toList();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static Future<bool> reportTask({
|
|
required String id,
|
|
required int completedTask,
|
|
required String comment,
|
|
required List<Map<String, dynamic>> checkList,
|
|
List<Map<String, dynamic>>? images,
|
|
}) async {
|
|
final body = {
|
|
"id": id,
|
|
"completedTask": completedTask,
|
|
"comment": comment,
|
|
"reportedDate": DateTime.now().toUtc().toIso8601String(),
|
|
"checkList": checkList,
|
|
if (images != null && images.isNotEmpty) "images": images,
|
|
};
|
|
|
|
final response = await _postRequest(
|
|
ApiEndpoints.reportTask,
|
|
body,
|
|
customTimeout: extendedTimeout,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
final json = jsonDecode(response.body);
|
|
if (response.statusCode == 200 && json['success'] == true) {
|
|
Get.back();
|
|
return true;
|
|
}
|
|
logSafe("Failed to report task: ${json['message'] ?? 'Unknown error'}");
|
|
return false;
|
|
}
|
|
|
|
static Future<bool> commentTask({
|
|
required String id,
|
|
required String comment,
|
|
List<Map<String, dynamic>>? images,
|
|
}) async {
|
|
final body = {
|
|
"taskAllocationId": id,
|
|
"comment": comment,
|
|
"commentDate": DateTime.now().toUtc().toIso8601String(),
|
|
if (images != null && images.isNotEmpty) "images": images,
|
|
};
|
|
|
|
final response = await _postRequest(ApiEndpoints.commentTask, body);
|
|
if (response == null) return false;
|
|
final json = jsonDecode(response.body);
|
|
return response.statusCode == 200 && json['success'] == true;
|
|
}
|
|
|
|
/// Fetch infra details for a 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;
|
|
}
|
|
}
|