added loggers
This commit is contained in:
parent
f5afed0d8b
commit
2a91ccc323
@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:marco/helpers/services/api_service.dart';
|
||||
import 'package:marco/model/attendance_model.dart';
|
||||
import 'package:marco/model/project_model.dart';
|
||||
@ -11,6 +12,10 @@ import 'package:marco/model/attendance_log_model.dart';
|
||||
import 'package:marco/model/regularization_log_model.dart';
|
||||
import 'package:marco/model/attendance_log_view_model.dart';
|
||||
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
final Logger log = Logger();
|
||||
|
||||
class AttendanceController extends GetxController {
|
||||
List<AttendanceModel> attendances = [];
|
||||
List<ProjectModel> projects = [];
|
||||
@ -24,7 +29,7 @@ class AttendanceController extends GetxController {
|
||||
List<RegularizationLogModel> regularizationLogs = [];
|
||||
List<AttendanceLogViewModel> attendenceLogsView = [];
|
||||
|
||||
RxBool isLoading = false.obs; // Added loading flag
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -41,59 +46,63 @@ class AttendanceController extends GetxController {
|
||||
final today = DateTime.now();
|
||||
startDateAttendance = today.subtract(const Duration(days: 7));
|
||||
endDateAttendance = today;
|
||||
log.i("Default date range set: $startDateAttendance to $endDateAttendance");
|
||||
}
|
||||
|
||||
Future<void> fetchProjects() async {
|
||||
isLoading.value = true; // Set loading to true before API call
|
||||
isLoading.value = true;
|
||||
final response = await ApiService.getProjects();
|
||||
isLoading.value = false; // Set loading to false after API call completes
|
||||
isLoading.value = false;
|
||||
|
||||
if (response != null && response.isNotEmpty) {
|
||||
projects = response.map((json) => ProjectModel.fromJson(json)).toList();
|
||||
selectedProjectId = projects.first.id.toString();
|
||||
log.i("Projects fetched: ${projects.length} projects loaded.");
|
||||
await fetchProjectData(selectedProjectId);
|
||||
update(['attendance_dashboard_controller']);
|
||||
} else {
|
||||
print("No projects data found or failed to fetch data.");
|
||||
log.w("No project data found or API call failed.");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchProjectData(String? projectId) async {
|
||||
if (projectId == null) return;
|
||||
|
||||
isLoading.value = true; // Set loading to true before API call
|
||||
isLoading.value = true;
|
||||
await Future.wait([
|
||||
fetchEmployeesByProject(projectId),
|
||||
fetchAttendanceLogs(projectId,
|
||||
dateFrom: startDateAttendance, dateTo: endDateAttendance),
|
||||
fetchRegularizationLogs(projectId),
|
||||
]);
|
||||
isLoading.value = false; // Set loading to false after data is fetched
|
||||
isLoading.value = false;
|
||||
log.i("Project data fetched for project ID: $projectId");
|
||||
}
|
||||
|
||||
Future<void> fetchEmployeesByProject(String? projectId) async {
|
||||
if (projectId == null) return;
|
||||
|
||||
isLoading.value = true; // Set loading to true before API call
|
||||
final response =
|
||||
await ApiService.getEmployeesByProject(projectId);
|
||||
isLoading.value = false; // Set loading to false after API call completes
|
||||
isLoading.value = true;
|
||||
final response = await ApiService.getEmployeesByProject(projectId);
|
||||
isLoading.value = false;
|
||||
|
||||
if (response != null) {
|
||||
employees = response.map((json) => EmployeeModel.fromJson(json)).toList();
|
||||
log.i(
|
||||
"Employees fetched: ${employees.length} employees for project $projectId");
|
||||
update();
|
||||
} else {
|
||||
print("Failed to fetch employees for project $projectId.");
|
||||
log.e("Failed to fetch employees for project $projectId");
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> captureAndUploadAttendance(
|
||||
String id, // Change from int to String
|
||||
String employeeId, // Change from int to String
|
||||
String projectId, {
|
||||
String id,
|
||||
String employeeId,
|
||||
String projectId, {
|
||||
String comment = "Marked via mobile app",
|
||||
required int action,
|
||||
bool imageCapture = true, // <- add this flag
|
||||
bool imageCapture = true,
|
||||
}) async {
|
||||
try {
|
||||
XFile? image;
|
||||
@ -102,7 +111,10 @@ class AttendanceController extends GetxController {
|
||||
source: ImageSource.camera,
|
||||
imageQuality: 80,
|
||||
);
|
||||
if (image == null) return false;
|
||||
if (image == null) {
|
||||
log.w("Image capture cancelled.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
@ -111,9 +123,9 @@ class AttendanceController extends GetxController {
|
||||
|
||||
final imageName = imageCapture
|
||||
? ApiService.generateImageName(employeeId, employees.length + 1)
|
||||
: ""; // Empty or null if not capturing image
|
||||
: "";
|
||||
|
||||
return await ApiService.uploadAttendanceImage(
|
||||
final result = await ApiService.uploadAttendanceImage(
|
||||
id,
|
||||
employeeId,
|
||||
image,
|
||||
@ -123,10 +135,13 @@ class AttendanceController extends GetxController {
|
||||
projectId: projectId,
|
||||
comment: comment,
|
||||
action: action,
|
||||
imageCapture: imageCapture, // <- pass flag down
|
||||
imageCapture: imageCapture,
|
||||
);
|
||||
} catch (e) {
|
||||
print("Error capturing or uploading attendance: $e");
|
||||
|
||||
log.i("Attendance uploaded for $employeeId, action: $action");
|
||||
return result;
|
||||
} catch (e, stacktrace) {
|
||||
log.e("Error uploading attendance", error: e, stackTrace: stacktrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -150,6 +165,8 @@ class AttendanceController extends GetxController {
|
||||
startDateAttendance = picked.start;
|
||||
endDateAttendance = picked.end;
|
||||
|
||||
log.i("Date range selected: $startDateAttendance to $endDateAttendance");
|
||||
|
||||
await controller.fetchAttendanceLogs(
|
||||
controller.selectedProjectId,
|
||||
dateFrom: picked.start,
|
||||
@ -165,39 +182,39 @@ class AttendanceController extends GetxController {
|
||||
}) async {
|
||||
if (projectId == null) return;
|
||||
|
||||
isLoading.value = true; // Set loading to true before API call
|
||||
isLoading.value = true;
|
||||
final response = await ApiService.getAttendanceLogs(
|
||||
projectId,
|
||||
projectId,
|
||||
dateFrom: dateFrom,
|
||||
dateTo: dateTo,
|
||||
);
|
||||
isLoading.value = false; // Set loading to false after API call completes
|
||||
isLoading.value = false;
|
||||
|
||||
if (response != null) {
|
||||
attendanceLogs =
|
||||
response.map((json) => AttendanceLogModel.fromJson(json)).toList();
|
||||
print("Attendance logs fetched: ${response}");
|
||||
log.i("Attendance logs fetched: ${attendanceLogs.length}");
|
||||
update();
|
||||
} else {
|
||||
print("Failed to fetch attendance logs for project $projectId.");
|
||||
log.e("Failed to fetch attendance logs for project $projectId");
|
||||
}
|
||||
}
|
||||
Map<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
|
||||
final groupedLogs = <String, List<AttendanceLogModel>>{};
|
||||
|
||||
for (var log in attendanceLogs) {
|
||||
final checkInDate = log.checkIn != null
|
||||
? DateFormat('dd MMM yyyy').format(log.checkIn!)
|
||||
: 'Unknown';
|
||||
Map<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
|
||||
final groupedLogs = <String, List<AttendanceLogModel>>{};
|
||||
|
||||
if (!groupedLogs.containsKey(checkInDate)) {
|
||||
groupedLogs[checkInDate] = [];
|
||||
for (var logItem in attendanceLogs) {
|
||||
final checkInDate = logItem.checkIn != null
|
||||
? DateFormat('dd MMM yyyy').format(logItem.checkIn!)
|
||||
: 'Unknown';
|
||||
|
||||
groupedLogs.putIfAbsent(checkInDate, () => []);
|
||||
groupedLogs[checkInDate]!.add(logItem);
|
||||
}
|
||||
groupedLogs[checkInDate]!.add(log);
|
||||
}
|
||||
|
||||
return groupedLogs;
|
||||
}
|
||||
log.i("Logs grouped by check-in date.");
|
||||
return groupedLogs;
|
||||
}
|
||||
|
||||
Future<void> fetchRegularizationLogs(
|
||||
String? projectId, {
|
||||
@ -207,34 +224,35 @@ Map<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
|
||||
if (projectId == null) return;
|
||||
|
||||
isLoading.value = true;
|
||||
final response =
|
||||
await ApiService.getRegularizationLogs(projectId);
|
||||
final response = await ApiService.getRegularizationLogs(projectId);
|
||||
isLoading.value = false;
|
||||
|
||||
if (response != null) {
|
||||
regularizationLogs = response
|
||||
.map((json) => RegularizationLogModel.fromJson(json))
|
||||
.toList();
|
||||
log.i("Regularization logs fetched: ${regularizationLogs.length}");
|
||||
update();
|
||||
} else {
|
||||
print("Failed to fetch regularization logs for project $projectId.");
|
||||
log.e("Failed to fetch regularization logs for project $projectId");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchLogsView(String? id) async {
|
||||
if (id == null) return;
|
||||
|
||||
isLoading.value = true; // Set loading to true before API call
|
||||
isLoading.value = true;
|
||||
final response = await ApiService.getAttendanceLogView(id);
|
||||
isLoading.value = false; // Set loading to false after API call completes
|
||||
isLoading.value = false;
|
||||
|
||||
if (response != null) {
|
||||
attendenceLogsView = response
|
||||
.map((json) => AttendanceLogViewModel.fromJson(json))
|
||||
.toList();
|
||||
log.i("Attendance log view fetched for ID: $id");
|
||||
update();
|
||||
} else {
|
||||
print("Failed to fetch regularization logs for project $id.");
|
||||
log.e("Failed to fetch attendance log view for ID $id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,15 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import 'package:marco/helpers/services/permission_service.dart';
|
||||
import 'package:marco/model/user_permission.dart';
|
||||
import 'package:marco/model/employee_info.dart';
|
||||
import 'package:marco/model/projects_model.dart';
|
||||
|
||||
final log = Logger();
|
||||
|
||||
class PermissionController extends GetxController {
|
||||
var permissions = <UserPermission>[].obs;
|
||||
var employeeInfo = Rxn<EmployeeInfo>();
|
||||
@ -20,84 +24,99 @@ class PermissionController extends GetxController {
|
||||
_startAutoRefresh();
|
||||
}
|
||||
|
||||
// Store all data at once to reduce redundant operations
|
||||
Future<void> _storeData() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// Store permissions
|
||||
await prefs.setString(
|
||||
'user_permissions', jsonEncode(permissions.map((e) => e.toJson()).toList()));
|
||||
|
||||
// Store employee info if available
|
||||
if (employeeInfo.value != null) {
|
||||
await prefs.setString(
|
||||
'employee_info', jsonEncode(employeeInfo.value!.toJson()));
|
||||
}
|
||||
'user_permissions',
|
||||
jsonEncode(permissions.map((e) => e.toJson()).toList()),
|
||||
);
|
||||
|
||||
// Store projects info if available
|
||||
if (projectsInfo.isNotEmpty) {
|
||||
await prefs.setString('projects_info',
|
||||
jsonEncode(projectsInfo.map((e) => e.toJson()).toList()));
|
||||
if (employeeInfo.value != null) {
|
||||
await prefs.setString(
|
||||
'employee_info',
|
||||
jsonEncode(employeeInfo.value!.toJson()),
|
||||
);
|
||||
}
|
||||
|
||||
if (projectsInfo.isNotEmpty) {
|
||||
await prefs.setString(
|
||||
'projects_info',
|
||||
jsonEncode(projectsInfo.map((e) => e.toJson()).toList()),
|
||||
);
|
||||
}
|
||||
|
||||
log.i("User data successfully stored in SharedPreferences.");
|
||||
} catch (e, stacktrace) {
|
||||
log.e("Error storing data", error: e, stackTrace: stacktrace);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch and load all required data (permissions, employee info, and projects)
|
||||
Future<void> _loadDataFromAPI() async {
|
||||
final token = await _getAuthToken();
|
||||
if (token?.isNotEmpty ?? false) {
|
||||
await loadData(token!);
|
||||
} else {
|
||||
print("No token available for fetching data.");
|
||||
log.w("No token found for loading API data.");
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch data and update the state (permissions, employee info, and projects)
|
||||
Future<void> loadData(String token) async {
|
||||
try {
|
||||
final userData = await PermissionService.fetchAllUserData(token);
|
||||
|
||||
// Update state variables
|
||||
_updateState(userData);
|
||||
|
||||
// Store all data after fetching
|
||||
await _storeData();
|
||||
} catch (e) {
|
||||
print('Error loading data from API: $e');
|
||||
log.i("Data loaded and state updated successfully.");
|
||||
} catch (e, stacktrace) {
|
||||
log.e("Error loading data from API", error: e, stackTrace: stacktrace);
|
||||
}
|
||||
}
|
||||
|
||||
// Update state variables (permissions, employeeInfo, and projects)
|
||||
void _updateState(Map<String, dynamic> userData) {
|
||||
permissions.assignAll(userData['permissions']);
|
||||
employeeInfo.value = userData['employeeInfo'];
|
||||
projectsInfo.assignAll(userData['projects']);
|
||||
try {
|
||||
permissions.assignAll(userData['permissions']);
|
||||
employeeInfo.value = userData['employeeInfo'];
|
||||
projectsInfo.assignAll(userData['projects']);
|
||||
log.i("State updated with new user data.");
|
||||
} catch (e, stacktrace) {
|
||||
log.e("Error updating state", error: e, stackTrace: stacktrace);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the auth token from SharedPreferences
|
||||
Future<String?> _getAuthToken() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString('jwt_token');
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString('jwt_token');
|
||||
} catch (e, stacktrace) {
|
||||
log.e("Error retrieving auth token", error: e, stackTrace: stacktrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up automatic data refresh every 30 minutes
|
||||
void _startAutoRefresh() {
|
||||
_refreshTimer = Timer.periodic(Duration(minutes: 30), (timer) async {
|
||||
log.i("Auto-refresh triggered.");
|
||||
await _loadDataFromAPI();
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the user has the given permission
|
||||
bool hasPermission(String permissionId) {
|
||||
return permissions.any((p) => p.id == permissionId);
|
||||
final hasPerm = permissions.any((p) => p.id == permissionId);
|
||||
log.d("Checking permission $permissionId: $hasPerm");
|
||||
return hasPerm;
|
||||
}
|
||||
|
||||
bool isUserAssignedToProject(String projectId) {
|
||||
return projectsInfo.any((project) => project.id == projectId);
|
||||
final assigned = projectsInfo.any((project) => project.id == projectId);
|
||||
log.d("Checking project assignment for $projectId: $assigned");
|
||||
return assigned;
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_refreshTimer?.cancel();
|
||||
log.i("PermissionController disposed and timer cancelled.");
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,24 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||
import 'package:marco/helpers/services/auth_service.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
final Logger logger = Logger();
|
||||
|
||||
class ApiService {
|
||||
static const String baseUrl = "https://stageapi.marcoaiot.com/api";
|
||||
static const Duration timeout = Duration(seconds: 10);
|
||||
static const bool enableLogs = true;
|
||||
|
||||
// ===== Common Helpers =====
|
||||
// ===== Helpers =====
|
||||
|
||||
static Future<String?> _getToken() async {
|
||||
final token = LocalStorage.getJwtToken();
|
||||
if (token == null) {
|
||||
print("No JWT token found. Please log in.");
|
||||
final token = await LocalStorage.getJwtToken();
|
||||
if (token == null && enableLogs) {
|
||||
logger.w("No JWT token found. Please log in."); // <-- Use logger
|
||||
}
|
||||
return token;
|
||||
}
|
||||
@ -23,122 +28,121 @@ class ApiService {
|
||||
'Authorization': 'Bearer $token',
|
||||
};
|
||||
|
||||
static void _log(String message) {
|
||||
if (enableLogs) logger.i(message); // <-- Use logger
|
||||
}
|
||||
|
||||
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 Future<http.Response?> _getRequest(String endpoint,
|
||||
{Map<String, String>? queryParams}) async {
|
||||
final token = await _getToken();
|
||||
{Map<String, String>? queryParams, bool hasRetried = false}) async {
|
||||
String? token = await _getToken();
|
||||
if (token == null) return null;
|
||||
|
||||
final uri =
|
||||
Uri.parse("$baseUrl$endpoint").replace(queryParameters: queryParams);
|
||||
print('GET request: $uri');
|
||||
Uri uri = Uri.parse("$baseUrl$endpoint").replace(queryParameters: queryParams);
|
||||
_log('GET request: $uri');
|
||||
|
||||
try {
|
||||
return await http.get(uri, headers: _headers(token));
|
||||
http.Response response = await http.get(uri, headers: _headers(token)).timeout(timeout);
|
||||
if (response.statusCode == 401 && !hasRetried) {
|
||||
_log("Unauthorized. Attempting token refresh...");
|
||||
bool refreshed = await AuthService.refreshToken();
|
||||
if (refreshed) {
|
||||
token = await _getToken();
|
||||
if (token != null) {
|
||||
return await _getRequest(endpoint, queryParams: queryParams, hasRetried: true);
|
||||
}
|
||||
}
|
||||
_log("Refresh failed.");
|
||||
}
|
||||
return response;
|
||||
} catch (e) {
|
||||
print("HTTP GET Exception: $e");
|
||||
_log("HTTP GET Exception: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static dynamic _parseResponse(http.Response response, {String label = ''}) {
|
||||
print("$label Response: ${response.body}");
|
||||
if (response.statusCode == 200) {
|
||||
final json = jsonDecode(response.body);
|
||||
if (json['success'] == true) return json['data'];
|
||||
print("API Error: ${json['message']}");
|
||||
} else {
|
||||
print("HTTP Error [$label]: ${response.statusCode}");
|
||||
static Future<http.Response?> _postRequest(String endpoint, dynamic body) async {
|
||||
String? token = await _getToken();
|
||||
if (token == null) return null;
|
||||
|
||||
final uri = Uri.parse("$baseUrl$endpoint");
|
||||
_log("POST request to $uri with body: $body");
|
||||
|
||||
try {
|
||||
final response = await http
|
||||
.post(uri, headers: _headers(token), body: jsonEncode(body))
|
||||
.timeout(timeout);
|
||||
return response;
|
||||
} catch (e) {
|
||||
_log("HTTP POST Exception: $e");
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ===== API Calls =====
|
||||
|
||||
static Future<List<dynamic>?> getProjects() async {
|
||||
final response = await _getRequest("/project/list");
|
||||
return response != null
|
||||
? _parseResponse(response, label: 'Projects')
|
||||
: null;
|
||||
return response != null ? _parseResponse(response, label: 'Projects') : null;
|
||||
}
|
||||
|
||||
static Future<List<dynamic>?> getEmployeesByProject(String projectId) async {
|
||||
final response = await _getRequest("/attendance/project/team",
|
||||
queryParams: {"projectId": "$projectId"});
|
||||
return response != null
|
||||
? _parseResponse(response, label: 'Employees')
|
||||
: null;
|
||||
queryParams: {"projectId": projectId});
|
||||
return response != null ? _parseResponse(response, label: 'Employees') : null;
|
||||
}
|
||||
|
||||
static Future<List<dynamic>?> getAttendanceLogs(
|
||||
String projectId, {
|
||||
DateTime? dateFrom,
|
||||
DateTime? dateTo,
|
||||
}) async {
|
||||
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),
|
||||
"projectId": projectId,
|
||||
if (dateFrom != null) "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
|
||||
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
||||
};
|
||||
|
||||
final response =
|
||||
await _getRequest("/attendance/project/log", queryParams: query);
|
||||
return response != null
|
||||
? _parseResponse(response, label: 'Attendance Logs')
|
||||
: null;
|
||||
final response = await _getRequest("/attendance/project/log", queryParams: query);
|
||||
return response != null ? _parseResponse(response, label: 'Attendance Logs') : null;
|
||||
}
|
||||
|
||||
static Future<List<dynamic>?> getAttendanceLogView(String id) async {
|
||||
final response = await _getRequest("/attendance/log/attendance/$id");
|
||||
return response != null
|
||||
? _parseResponse(response, label: 'Attendance Log Details')
|
||||
: null;
|
||||
return response != null ? _parseResponse(response, label: 'Log Details') : null;
|
||||
}
|
||||
|
||||
static Future<List<dynamic>?> getRegularizationLogs(String projectId) async {
|
||||
final response = await _getRequest("/attendance/regularize",
|
||||
queryParams: {"projectId": "$projectId"});
|
||||
return response != null
|
||||
? _parseResponse(response, label: 'Regularization')
|
||||
: null;
|
||||
queryParams: {"projectId": projectId});
|
||||
return response != null ? _parseResponse(response, label: 'Regularization Logs') : null;
|
||||
}
|
||||
|
||||
// ===== Upload Image =====
|
||||
|
||||
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, // <- add this flag
|
||||
}) async {
|
||||
final token = await _getToken();
|
||||
if (token == null) return false;
|
||||
|
||||
try {
|
||||
Map<String, dynamic>? imageObject;
|
||||
if (imageCapture && imageFile != null) {
|
||||
final bytes = await imageFile.readAsBytes();
|
||||
final base64Image = base64Encode(bytes);
|
||||
final fileSize = await imageFile.length();
|
||||
final contentType = "image/${imageFile.path.split('.').last}";
|
||||
|
||||
imageObject = {
|
||||
"fileName": '$imageName',
|
||||
"contentType": '$contentType',
|
||||
"fileSize": fileSize,
|
||||
"description": "Employee attendance photo",
|
||||
"base64Data": '$base64Image',
|
||||
};
|
||||
}
|
||||
// ===== Upload Attendance Image =====
|
||||
|
||||
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,
|
||||
}) async {
|
||||
final now = DateTime.now();
|
||||
|
||||
final body = {
|
||||
"id": id,
|
||||
"employeeId": employeeId,
|
||||
@ -147,40 +151,44 @@ class ApiService {
|
||||
"comment": comment,
|
||||
"action": action,
|
||||
"date": DateFormat('yyyy-MM-dd').format(now),
|
||||
if (imageCapture) "latitude": '$latitude',
|
||||
if (imageCapture) "longitude": '$longitude',
|
||||
};
|
||||
|
||||
// Only include latitude and longitude if imageCapture is true
|
||||
if (imageCapture) {
|
||||
body["latitude"] = '$latitude';
|
||||
body["longitude"] = '$longitude';
|
||||
if (imageCapture && imageFile != null) {
|
||||
try {
|
||||
final bytes = await imageFile.readAsBytes();
|
||||
final base64Image = base64Encode(bytes);
|
||||
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": base64Image,
|
||||
};
|
||||
} catch (e) {
|
||||
_log("Image encoding error: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Only add imageObject if it's not null
|
||||
if (imageObject != null) {
|
||||
body["image"] = imageObject;
|
||||
}
|
||||
final response = await _postRequest("/attendance/record-image", body);
|
||||
if (response == null) return false;
|
||||
|
||||
print("Attendance Image Upload Body: $body");
|
||||
final response = await http.post(
|
||||
Uri.parse("$baseUrl/attendance/record-image"),
|
||||
headers: _headers(token),
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
print("Attendance Image Upload Response: ${response.body}");
|
||||
final json = jsonDecode(response.body);
|
||||
if (response.statusCode == 200 && json['success'] == true) {
|
||||
return true;
|
||||
} else {
|
||||
print("Failed to upload image. API Error: ${json['message']}");
|
||||
_log("Failed to upload image: ${json['message'] ?? 'Unknown error'}");
|
||||
}
|
||||
} catch (e) {
|
||||
print("Exception during image upload: $e");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===== Utilities =====
|
||||
// ===== Utilities =====
|
||||
|
||||
static String generateImageName(String employeeId, int count) {
|
||||
final now = DateTime.now();
|
||||
|
@ -1,52 +1,104 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||
import 'package:marco/controller/permission_controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:logger/logger.dart'; // <-- Make sure this import is present
|
||||
|
||||
final Logger logger = Logger();
|
||||
|
||||
class AuthService {
|
||||
static const String _baseUrl = "https://stageapi.marcoaiot.com/api";
|
||||
static const Map<String, String> _headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
static bool isLoggedIn = false;
|
||||
|
||||
static Future<Map<String, String>?> loginUser(Map<String, dynamic> data) async {
|
||||
/// Logs in the user and stores tokens if successful.
|
||||
static Future<Map<String, String>?> loginUser(
|
||||
Map<String, dynamic> data) async {
|
||||
try {
|
||||
final response = await http.post(
|
||||
Uri.parse('https://stageapi.marcoaiot.com/api/auth/login'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
Uri.parse("$_baseUrl/auth/login"),
|
||||
headers: _headers,
|
||||
body: jsonEncode(data),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = jsonDecode(response.body);
|
||||
if (response.statusCode == 200 && responseData['data'] != null) {
|
||||
isLoggedIn = true;
|
||||
|
||||
// Parse the response to get the JWT and refresh tokens
|
||||
final responseData = jsonDecode(response.body);
|
||||
|
||||
// Adjusted for the actual response structure
|
||||
final jwtToken = responseData['data']['token']; // Ensure this matches your actual response
|
||||
|
||||
// Save the JWT token in local storage
|
||||
await LocalStorage.setJwtToken(jwtToken);
|
||||
print("JWT Token: $jwtToken");
|
||||
|
||||
// Optionally save refresh token if available
|
||||
final jwtToken = responseData['data']['token'];
|
||||
final refreshToken = responseData['data']['refreshToken'];
|
||||
|
||||
await LocalStorage.setJwtToken(jwtToken);
|
||||
await LocalStorage.setLoggedInUser(true);
|
||||
|
||||
if (refreshToken != null) {
|
||||
await LocalStorage.setRefreshToken(refreshToken);
|
||||
print("Refresh Token: $refreshToken");
|
||||
}
|
||||
|
||||
// Save the login state in local storage
|
||||
await LocalStorage.setLoggedInUser(true);
|
||||
Get.put(PermissionController());
|
||||
// Return null to indicate success
|
||||
return null;
|
||||
|
||||
logger.i("JWT Token: $jwtToken");
|
||||
if (refreshToken != null) logger.i("Refresh Token: $refreshToken");
|
||||
|
||||
return null; // Success
|
||||
} else if (response.statusCode == 401) {
|
||||
return {"password": "Invalid email or password"};
|
||||
} else {
|
||||
return {"error": "Something went wrong. Please try again."};
|
||||
return {
|
||||
"error": responseData['message'] ?? "Unexpected error occurred"
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
logger.e("Login error: $e");
|
||||
return {"error": "Network error. Please check your connection."};
|
||||
}
|
||||
}
|
||||
|
||||
/// Refreshes the JWT token using the refresh token.
|
||||
static Future<bool> refreshToken() async {
|
||||
final refreshToken = await LocalStorage.getRefreshToken();
|
||||
if (refreshToken == null || refreshToken.isEmpty) {
|
||||
logger.w("No refresh token available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final response = await http.post(
|
||||
Uri.parse("$_baseUrl/auth/refresh-token"),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({"refreshToken": refreshToken}),
|
||||
);
|
||||
|
||||
final data = jsonDecode(response.body);
|
||||
if (response.statusCode == 200 && data['success'] == true) {
|
||||
final newAccessToken = data['data']['accessToken'];
|
||||
final newRefreshToken = data['data']['refreshToken'];
|
||||
|
||||
// Check if the tokens are valid before saving them
|
||||
if (newAccessToken == null || newRefreshToken == null) {
|
||||
logger.w("Invalid tokens received during refresh.");
|
||||
return false;
|
||||
}
|
||||
|
||||
await LocalStorage.setJwtToken(newAccessToken);
|
||||
await LocalStorage.setRefreshToken(newRefreshToken);
|
||||
await LocalStorage.setLoggedInUser(true);
|
||||
|
||||
logger.i("Token refreshed successfully.");
|
||||
return true;
|
||||
} else {
|
||||
logger.w("Refresh failed: ${data['message']}");
|
||||
await LocalStorage.removeToken('jwt_token');
|
||||
await LocalStorage.removeToken('refresh_token');
|
||||
await LocalStorage.setLoggedInUser(false);
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.e("Exception during token refresh: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,25 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:marco/model/user_permission.dart';
|
||||
import 'package:marco/model/employee_info.dart';
|
||||
import 'package:marco/model/projects_model.dart';
|
||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||
import 'package:marco/helpers/services/auth_service.dart';
|
||||
|
||||
final Logger logger = Logger();
|
||||
|
||||
class PermissionService {
|
||||
// Cache to store the fetched user profile data per token
|
||||
static final Map<String, Map<String, dynamic>> _userDataCache = {};
|
||||
|
||||
// Method to fetch the user profile data (permissions, employee info, and projects)
|
||||
static Future<Map<String, dynamic>> fetchAllUserData(String token) async {
|
||||
// Check if the data for this token is already cached
|
||||
static Future<Map<String, dynamic>> fetchAllUserData(String token, {bool hasRetried = false}) async {
|
||||
// Return from cache if available
|
||||
if (_userDataCache.containsKey(token)) {
|
||||
return _userDataCache[token]!;
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch data from the API
|
||||
final response = await http.get(
|
||||
Uri.parse('https://stageapi.marcoaiot.com/api/user/profile'),
|
||||
headers: {'Authorization': 'Bearer $token'},
|
||||
@ -25,44 +28,50 @@ class PermissionService {
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body)['data'];
|
||||
|
||||
// Parse and extract relevant information
|
||||
final permissions = _parsePermissions(data['featurePermissions']);
|
||||
final employeeInfo = _parseEmployeeInfo(data['employeeInfo']);
|
||||
final projectsInfo = _parseProjectsInfo(data['projects']);
|
||||
|
||||
// Cache the processed data for later use
|
||||
final allUserData = {
|
||||
'permissions': permissions,
|
||||
'employeeInfo': employeeInfo,
|
||||
'projects': projectsInfo,
|
||||
final result = {
|
||||
'permissions': _parsePermissions(data['featurePermissions']),
|
||||
'employeeInfo': _parseEmployeeInfo(data['employeeInfo']),
|
||||
'projects': _parseProjectsInfo(data['projects']),
|
||||
};
|
||||
_userDataCache[token] = allUserData;
|
||||
|
||||
return allUserData;
|
||||
} else {
|
||||
final errorData = json.decode(response.body);
|
||||
throw Exception('Failed to load data: ${errorData['message']}');
|
||||
_userDataCache[token] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (response.statusCode == 401 && !hasRetried) {
|
||||
final refreshed = await AuthService.refreshToken();
|
||||
if (refreshed) {
|
||||
final newToken = await LocalStorage.getJwtToken();
|
||||
if (newToken != null) {
|
||||
return fetchAllUserData(newToken, hasRetried: true);
|
||||
}
|
||||
}
|
||||
|
||||
await _handleUnauthorized();
|
||||
throw Exception('Unauthorized. Token refresh failed.');
|
||||
}
|
||||
|
||||
final errorMessage = json.decode(response.body)['message'] ?? 'Unknown error';
|
||||
throw Exception('Failed to load data: $errorMessage');
|
||||
} catch (e) {
|
||||
print('Error fetching user data: $e');
|
||||
throw Exception('Error fetching user data: $e');
|
||||
logger.e('Error fetching user data: $e'); // <-- Use logger here
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to parse permissions from raw data
|
||||
static List<UserPermission> _parsePermissions(List<dynamic> featurePermissions) {
|
||||
return featurePermissions
|
||||
.map<UserPermission>((id) => UserPermission.fromJson({'id': id}))
|
||||
.toList();
|
||||
static Future<void> _handleUnauthorized() async {
|
||||
await LocalStorage.removeToken('jwt_token');
|
||||
await LocalStorage.removeToken('refresh_token');
|
||||
await LocalStorage.setLoggedInUser(false);
|
||||
Get.offAllNamed('/auth/login');
|
||||
}
|
||||
|
||||
// Helper method to parse employee info from raw data
|
||||
static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> employeeData) {
|
||||
return EmployeeInfo.fromJson(employeeData);
|
||||
}
|
||||
static List<UserPermission> _parsePermissions(List<dynamic> featurePermissions) =>
|
||||
featurePermissions.map((id) => UserPermission.fromJson({'id': id})).toList();
|
||||
|
||||
// Helper method to parse projects from raw data
|
||||
static List<ProjectInfo> _parseProjectsInfo(List<dynamic> projectIds) {
|
||||
return projectIds.map<ProjectInfo>((id) => ProjectInfo.fromJson(id)).toList();
|
||||
}
|
||||
static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> employeeData) =>
|
||||
EmployeeInfo.fromJson(employeeData);
|
||||
|
||||
static List<ProjectInfo> _parseProjectsInfo(List<dynamic> projects) =>
|
||||
projects.map((proj) => ProjectInfo.fromJson(proj)).toList();
|
||||
}
|
||||
|
@ -11,17 +11,23 @@ import 'package:marco/helpers/theme/theme_customizer.dart';
|
||||
import 'package:marco/routes.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_strategy/url_strategy.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
setPathUrlStrategy();
|
||||
|
||||
await LocalStorage.init();
|
||||
AppStyle.init();
|
||||
await ThemeCustomizer.init();
|
||||
try {
|
||||
await LocalStorage.init();
|
||||
await ThemeCustomizer.init();
|
||||
AppStyle.init();
|
||||
} catch (e) {
|
||||
print('Error during app initialization: $e');
|
||||
return;
|
||||
}
|
||||
|
||||
runApp(ChangeNotifierProvider<AppNotifier>(
|
||||
create: (context) => AppNotifier(),
|
||||
child: MyApp(),
|
||||
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ dependencies:
|
||||
permission_handler: ^11.3.0
|
||||
image: ^4.0.17
|
||||
image_picker: ^1.0.7
|
||||
|
||||
logger: ^2.0.2
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
Loading…
x
Reference in New Issue
Block a user