marco.pms.mobileapp/lib/controller/dashboard/attendance_screen_controller.dart

316 lines
9.2 KiB
Dart

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:logger/logger.dart';
import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/model/attendance_model.dart';
import 'package:marco/model/project_model.dart';
import 'package:marco/model/employee_model.dart';
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';
final Logger log = Logger();
class AttendanceController extends GetxController {
List<AttendanceModel> attendances = [];
List<ProjectModel> projects = [];
String? selectedProjectId;
List<EmployeeModel> employees = [];
DateTime? startDateAttendance;
DateTime? endDateAttendance;
List<AttendanceLogModel> attendanceLogs = [];
List<RegularizationLogModel> regularizationLogs = [];
List<AttendanceLogViewModel> attendenceLogsView = [];
RxBool isLoading = false.obs;
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
@override
void onInit() {
super.onInit();
_initializeDefaults();
}
void _initializeDefaults() {
_setDefaultDateRange();
fetchProjects();
}
void _setDefaultDateRange() {
final today = DateTime.now();
startDateAttendance = today.subtract(const Duration(days: 7));
endDateAttendance = today;
log.i("Default date range set: $startDateAttendance to $endDateAttendance");
}
Future<bool> _handleLocationPermission() async {
LocationPermission permission;
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
log.w('Location permissions are denied');
return false;
}
}
if (permission == LocationPermission.deniedForever) {
log.e('Location permissions are permanently denied');
return false;
}
return true;
}
Future<void> fetchProjects() async {
isLoading.value = true;
final response = await ApiService.getProjects();
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 {
log.w("No project data found or API call failed.");
}
}
Future<void> fetchProjectData(String? projectId) async {
if (projectId == null) return;
isLoading.value = true;
await Future.wait([
fetchEmployeesByProject(projectId),
fetchAttendanceLogs(projectId,
dateFrom: startDateAttendance, dateTo: endDateAttendance),
fetchRegularizationLogs(projectId),
]);
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;
final response = await ApiService.getEmployeesByProject(projectId);
isLoading.value = false;
if (response != null) {
employees = response.map((json) => EmployeeModel.fromJson(json)).toList();
// Initialize per-employee uploading state
for (var emp in employees) {
uploadingStates[emp.id] = false.obs;
}
log.i("Employees fetched: ${employees.length} employees for project $projectId");
update();
} else {
log.e("Failed to fetch employees for project $projectId");
}
}
Future<bool> captureAndUploadAttendance(
String id,
String employeeId,
String projectId, {
String comment = "Marked via mobile app",
required int action,
bool imageCapture = true,
String? markTime,
}) async {
try {
uploadingStates[employeeId]?.value = true;
XFile? image;
if (imageCapture) {
image = await ImagePicker().pickImage(
source: ImageSource.camera,
imageQuality: 80,
);
if (image == null) {
log.w("Image capture cancelled.");
uploadingStates[employeeId]?.value = false;
return false;
}
}
final hasLocationPermission = await _handleLocationPermission();
if (!hasLocationPermission) {
uploadingStates[employeeId]?.value = false;
return false;
}
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
final imageName = imageCapture
? ApiService.generateImageName(employeeId, employees.length + 1)
: "";
final result = await ApiService.uploadAttendanceImage(
id,
employeeId,
image,
position.latitude,
position.longitude,
imageName: imageName,
projectId: projectId,
comment: comment,
action: action,
imageCapture: imageCapture,
markTime: markTime,
);
log.i("Attendance uploaded for $employeeId, action: $action");
return result;
} catch (e, stacktrace) {
log.e("Error uploading attendance", error: e, stackTrace: stacktrace);
return false;
} finally {
uploadingStates[employeeId]?.value = false;
}
}
Future<void> selectDateRangeForAttendance(
BuildContext context,
AttendanceController controller,
) async {
final picked = await showDateRangePicker(
context: context,
firstDate: DateTime(2022),
lastDate: DateTime.now(),
initialDateRange: DateTimeRange(
start: startDateAttendance ??
DateTime.now().subtract(const Duration(days: 7)),
end: endDateAttendance ?? DateTime.now(),
),
);
if (picked != null) {
startDateAttendance = picked.start;
endDateAttendance = picked.end;
log.i("Date range selected: $startDateAttendance to $endDateAttendance");
await controller.fetchAttendanceLogs(
controller.selectedProjectId,
dateFrom: picked.start,
dateTo: picked.end,
);
}
}
Future<void> fetchAttendanceLogs(
String? projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) async {
if (projectId == null) return;
isLoading.value = true;
final response = await ApiService.getAttendanceLogs(
projectId,
dateFrom: dateFrom,
dateTo: dateTo,
);
isLoading.value = false;
if (response != null) {
attendanceLogs =
response.map((json) => AttendanceLogModel.fromJson(json)).toList();
log.i("Attendance logs fetched: ${attendanceLogs.length}");
update();
} else {
log.e("Failed to fetch attendance logs for project $projectId");
}
}
Map<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
final groupedLogs = <String, List<AttendanceLogModel>>{};
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);
}
// Sort by date descending
final sortedEntries = groupedLogs.entries.toList()
..sort((a, b) {
if (a.key == 'Unknown') return 1;
if (b.key == 'Unknown') return -1;
final dateA = DateFormat('dd MMM yyyy').parse(a.key);
final dateB = DateFormat('dd MMM yyyy').parse(b.key);
return dateB.compareTo(dateA);
});
final sortedMap =
Map<String, List<AttendanceLogModel>>.fromEntries(sortedEntries);
log.i("Logs grouped and sorted by check-in date.");
return sortedMap;
}
Future<void> fetchRegularizationLogs(
String? projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) async {
if (projectId == null) return;
isLoading.value = true;
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 {
log.e("Failed to fetch regularization logs for project $projectId");
}
}
Future<void> fetchLogsView(String? id) async {
if (id == null) return;
isLoading.value = true;
final response = await ApiService.getAttendanceLogView(id);
isLoading.value = false;
if (response != null) {
attendenceLogsView = response
.map((json) => AttendanceLogViewModel.fromJson(json))
.toList();
// Sort by activityTime field (latest first)
attendenceLogsView.sort((a, b) {
if (a.activityTime == null || b.activityTime == null) return 0; // Handle null values if any
return b.activityTime!.compareTo(a.activityTime!); // Sort descending (latest first)
});
log.i("Attendance log view fetched for ID: $id");
update();
} else {
log.e("Failed to fetch attendance log view for ID $id");
}
}
}