import 'dart:developer'; import 'package:absensi_qr/configs/api_constant.dart'; import 'package:absensi_qr/features/others/main_controller.dart'; import 'package:absensi_qr/models/model_merging/schedule_report_item.dart'; import 'package:absensi_qr/models/model_merging/schedule_student_attendance_report.dart'; import 'package:absensi_qr/services/endpoint_service.dart'; import 'package:absensi_qr/services/geolocation_service.dart'; import 'package:absensi_qr/utils/app_util.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get/get.dart'; class TeacherDashboardController extends GetxController { // service controller final EndpointService _httpService = Get.find(); final MainController mainController = Get.find(); final GeolocationService _geolocationService = Get.find(); final RxBool isLoading = false.obs; final RxBool isLoadingStatistic = false.obs; final RxList dataSchedule = [].obs; /// data profile final RxString name = ''.obs; final RxString firstName = ''.obs; final RxString email = ''.obs; final RxString subject = ''.obs; final RxString nip = ''.obs; final RxString profileImageURL = ''.obs; /// attendance history final RxBool isLoadingAttendanceHistory = false.obs; // data result store. final RxList attendanceHistoryResult = [].obs; final Rx selectedClassId = BigInt.from(-1).obs; // final DateTime selectedDate = DateTime.now(); final DateTime selectedDate = DateTime(2026, 7, 9); // testing only - Thursday final Rx statiscticAttendanceToday = Rx(null); /// data geolocation String get placemark => _geolocationService.outputPlacemark.value; // kota String get placemarkCity => _geolocationService.placemarkResult.value?.subAdministrativeArea ?? '(No Data)'; // jalan String get placemarkStreet => _geolocationService.placemarkResult.value?.street ?? '(No Data)'; // kecamatan String get placemarkLocality => _geolocationService.placemarkResult.value?.locality ?? ''; // desa String get placemarkVillage => _geolocationService.placemarkResult.value?.subLocality ?? ''; /// data geolocation END DateTime dateNow = DateTime.now(); // DateTime dateNow = DateTime(2026, 7, 9, 9, 20, 00); // testing only - Thursday String get dateNowFormatted => AppUtil.formatDateIndonesia(dateNow); @override void onInit() async { // TODO: implement onInit super.onInit(); // selectedDate.value = dateNow; _setProfileData(); ever(mainController.triggerUpdateProfile, (_) { _setProfileData(); }); await fetchTodayScheduleTeacher(); await fetchAttendanceHistory(); } // === SERVICE ZONE === _setProfileData() { final String? emailService = _httpService.userData != null ? (_httpService.userData!['email'] as String?) : null; name.value = _httpService.teacherData?.name ?? ''; firstName.value = _httpService.teacherData?.name != null ? _httpService.teacherData!.name.split(' ').first : ''; email.value = emailService ?? ''; subject.value = _httpService.teacherData?.subject ?? ''; nip.value = _httpService.teacherData?.nip ?? ''; final String? profilePicture = _httpService.userModel?.profilePicture; if (profilePicture != null) { profileImageURL.value = ApiConstant.getProfilePictureURL(profilePicture, 'teacher'); } } Future fetchTodayScheduleTeacher() async { isLoading.value = true; final result = await _httpService.teacherSchedulePersonal(); if (result.success) { final List? raw = result.data; if (raw != null) { final items = raw .map((e) => ScheduleReportItem.fromMap(e as Map)) .toList(); // sort by schedule.startTime items.sort((a, b) { return a.schedule.startTime.compareTo(b.schedule.startTime); }); dataSchedule.assignAll(items); log('Loaded ${items.length} schedule items'); } else { dataSchedule.clear(); } } else { dataSchedule.clear(); } isLoading.value = false; } Future fetchAttendanceHistory() async { // final now = DateTime.now(); isLoadingAttendanceHistory.value = true; // use dummy, change later to get from student data class id. // get filtered schedule for the current time and get the class id from there. final filteredSchedule = dataSchedule.where((item) { final normalizedStartTime = DateTime( dateNow.year, dateNow.month, dateNow.day, item.schedule.startTime.hour, item.schedule.startTime.minute, ); final normalizedEndTime = DateTime( dateNow.year, dateNow.month, dateNow.day, item.schedule.endTime.hour, item.schedule.endTime.minute, ); return normalizedStartTime.isBefore(dateNow) && normalizedEndTime.isAfter(dateNow); }).toList(); if (filteredSchedule.isNotEmpty) { final schedule = filteredSchedule.first; selectedClassId.value = schedule.classModel?.id ?? BigInt.from(-1); log('Auto-selected class ID: ${selectedClassId.value} based on current schedule'); } else { // No current class found, try to get upcoming class log('No active schedule found for current time. Looking for upcoming schedule...'); final upcomingSchedule = dataSchedule.where((item) { final normalizedStartTime = DateTime( dateNow.year, dateNow.month, dateNow.day, item.schedule.startTime.hour, item.schedule.startTime.minute, ); return normalizedStartTime.isAfter(dateNow); }).toList(); if (upcomingSchedule.isNotEmpty) { // Sort by startTime to get the nearest upcoming schedule upcomingSchedule.sort((a, b) { return a.schedule.startTime.compareTo(b.schedule.startTime); }); final schedule = upcomingSchedule.first; selectedClassId.value = schedule.classModel?.id ?? BigInt.from(-1); log('Auto-selected nearest upcoming class ID: ${selectedClassId.value} at ${schedule.schedule.startTime}'); } else { log('No upcoming schedule found.'); Fluttertoast.showToast(msg: 'Tidak ada kelas yang berjalan saat ini.'); isLoadingAttendanceHistory.value = false; isLoadingStatistic.value = false; return; } } if (selectedClassId.value == BigInt.from(-1)) { log('Invalid class ID: ${selectedClassId.value}. Cannot fetch attendance history.'); Fluttertoast.showToast(msg: 'Jadwal kosong.'); isLoadingAttendanceHistory.value = false; isLoadingStatistic.value = false; return; } final result = await _httpService.teacherClassesAttendance( classId: selectedClassId.value.toString(), date: selectedDate, ); isLoadingAttendanceHistory.value = false; if (result.success) { final List? rawList = result.data; if (rawList != null) { final List attendanceList = rawList .map((item) => ScheduleStudentAttendanceReport.fromMap(item)) .toList(); attendanceHistoryResult.value = attendanceList; log('Loaded attendance history for class $selectedClassId on date ${selectedDate.toIso8601String()}'); // jangan lupa tambahkan filter schedule hasilnya berdasarkan range waktu upcoming / ongoing schedule. dan tampilkan di dashboard statistik. /// filter logic here... filterClassAttendanceBySchedule(); } else { attendanceHistoryResult.clear(); Fluttertoast.showToast(msg: 'Data: No Data Found on This Date'); } Fluttertoast.showToast(msg: 'msg_success_fetch_attendance'); } else { Fluttertoast.showToast( msg: result.message ?? 'msg_failed_fetch_attendance'); } isLoadingAttendanceHistory.value = false; } // schedule student attendance filetring logic based on date now and schedule time (endtime and start time) void filterClassAttendanceBySchedule() { isLoadingStatistic.value = true; // final now = DateTime.now(); final filteredSchedule = attendanceHistoryResult.where((item) { final normalizedStartTime = DateTime( dateNow.year, dateNow.month, dateNow.day, item.schedule.startTime.hour, item.schedule.startTime.minute, ); final normalizedEndTime = DateTime( dateNow.year, dateNow.month, dateNow.day, item.schedule.endTime.hour, item.schedule.endTime.minute, ); isLoadingStatistic.value = false; return normalizedStartTime.isBefore(dateNow) && normalizedEndTime.isAfter(dateNow); }).toList(); isLoadingStatistic.value = false; if (filteredSchedule.isNotEmpty) { statiscticAttendanceToday.value = filteredSchedule.first; log('Filtered attendance history to ${filteredSchedule.length} items based on current schedule'); } else { log('No active schedule found for current time. Attendance history not filtered.'); // get upcoming schedule and filter based on that final upcomingSchedule = attendanceHistoryResult.where((item) { final normalizedStartTime = DateTime( dateNow.year, dateNow.month, dateNow.day, item.schedule.startTime.hour, item.schedule.startTime.minute, ); return normalizedStartTime.isAfter(dateNow); }).toList(); if (upcomingSchedule.isNotEmpty) { // Sort by startTime to get the nearest upcoming schedule upcomingSchedule.sort((a, b) { return a.schedule.startTime.compareTo(b.schedule.startTime); }); statiscticAttendanceToday.value = upcomingSchedule.first; log('Filtered attendance history to ${upcomingSchedule.length} items based on nearest upcoming schedule at ${upcomingSchedule.first.schedule.startTime}'); } else { statiscticAttendanceToday.value = null; log('No upcoming schedule found. Attendance history not filtered.'); } // log statistic attendance today log('Statistic attendance for today: ${statiscticAttendanceToday.value}'); log('Statistic attendance for today - schedule: ${statiscticAttendanceToday.value?.subject?.name ?? 'No Schedule'}'); log('Statistic attendance for today - class: ${statiscticAttendanceToday.value?.classModel ?? 'No Class'}'); log('Statistic attendance for today - class: ${statiscticAttendanceToday.value?.classModel?.name ?? 'No Class'}'); } } Future refreshData() async { await fetchTodayScheduleTeacher(); await fetchAttendanceHistory(); filterClassAttendanceBySchedule(); } }