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 attendances = []; List projects = []; String? selectedProjectId; List employees = []; DateTime? startDateAttendance; DateTime? endDateAttendance; List attendanceLogs = []; List regularizationLogs = []; List attendenceLogsView = []; RxBool isLoading = false.obs; RxMap uploadingStates = {}.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 _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 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 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 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 captureAndUploadAttendance( String id, String employeeId, String projectId, { String comment = "Marked via mobile app", required int action, bool imageCapture = true, }) 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, ); 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 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 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> groupLogsByCheckInDate() { final groupedLogs = >{}; 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>.fromEntries(sortedEntries); log.i("Logs grouped and sorted by check-in date."); return sortedMap; } Future 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 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"); } } }