TIFNJK_E41222758/lib/presentation/controllers/absen_controller.dart

326 lines
8.8 KiB
Dart

// ignore_for_file: unused_local_variable
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:sidak_desa_mobile/presentation/views/main_screen.dart';
import '../../data/fetch/attedance_api.dart';
import '../../data/model/user_model.dart';
import '../../data/shared/auth_storage.dart';
import '../../utils/device_id.dart';
class AbsenController extends GetxController {
AbsenController({required Map<String, dynamic> qrData}) {
this.qrData.value = qrData;
}
final _api = AttedanceApi();
final _storage = AuthStorage();
final user = Rxn<UserModel>();
final qrData = <String, dynamic>{}.obs;
final nameController = TextEditingController();
final isSubmitting = false.obs;
final isLoadingDaily = false.obs;
final isLoadingLocation = false.obs;
final latitude = RxnDouble();
final longitude = RxnDouble();
final locationError = RxnString();
final selectedDate = ''.obs;
final daily = Rxn<Map<String, dynamic>>();
@override
void onInit() {
_init();
super.onInit();
}
Future<void> _init() async {
selectedDate.value = _todayDate();
await loadUser();
await fetchDaily();
await getCurrentLocation();
}
Future<void> loadUser() async {
user.value = await _storage.getUser();
nameController.text = user.value?.name ?? '';
}
@override
void onClose() {
nameController.dispose();
super.onClose();
}
String get checkInText => _formatTime(daily.value?['check_in']);
String get checkOutText => _formatTime(daily.value?['check_out']);
Future<void> fetchDaily() async {
final uid = user.value?.id;
if (uid == null) return;
isLoadingDaily.value = true;
try {
final res = await _api.getDailyAttendance(
userId: uid,
date: selectedDate.value,
);
if (res['ok'] == true) {
if (res['data'] != null && res['data'] is Map<String, dynamic>) {
daily.value = Map<String, dynamic>.from(res['data']);
} else {
daily.value = null;
}
} else {
daily.value = null;
}
} catch (_) {
daily.value = null;
} finally {
isLoadingDaily.value = false;
}
}
Future<void> submitAbsen() async {
if (isSubmitting.value) return;
final token = (qrData['raw'] ?? '').toString().trim();
final uid = user.value?.id;
if (token.isEmpty || uid == null) return;
isSubmitting.value = true;
try {
final deviceId = await DeviceIdUtil.getDeviceId();
final response = await _api.verifyAttendance(
token: token,
userId: uid,
deviceInfo: deviceId,
latitude: latitude.value,
longitude: longitude.value,
);
await fetchDaily();
// ✅ DIALOG BERHASIL
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 80,
height: 80,
decoration: const BoxDecoration(
color: Color(0xFF0F766E), // hijau teal
shape: BoxShape.circle,
),
child: const Icon(Icons.check, color: Colors.white, size: 45),
),
const SizedBox(height: 16),
const Text(
"Absensi Berhasil",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
"Absensi berhasil dilakukan",
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Get.offAll(() => const MainScreen());
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
),
child: const Text(
"Oke",
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
),
barrierDismissible: false,
);
} catch (e) {
// ❌ DIALOG DITOLAK
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 80,
height: 80,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: const Icon(Icons.close, color: Colors.white, size: 45),
),
const SizedBox(height: 16),
const Text(
"Absen Ditolak",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
e.toString().replaceAll('Exception:', ''),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => Get.back(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text(
"Tutup",
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
),
barrierDismissible: false,
);
} finally {
isSubmitting.value = false;
}
}
Future<void> getCurrentLocation() async {
isLoadingLocation.value = true;
locationError.value = null;
try {
final isServiceEnabled = await Geolocator.isLocationServiceEnabled();
if (!isServiceEnabled) {
locationError.value = 'Layanan lokasi tidak aktif';
return;
}
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
if (permission == LocationPermission.denied ||
permission == LocationPermission.deniedForever) {
locationError.value = 'Izin lokasi ditolak';
return;
}
final position = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
),
);
latitude.value = position.latitude;
longitude.value = position.longitude;
} catch (_) {
locationError.value = 'Gagal mengambil lokasi terkini';
} finally {
isLoadingLocation.value = false;
}
}
String _todayDate() {
final now = DateTime.now();
final yyyy = now.year.toString().padLeft(4, '0');
final mm = now.month.toString().padLeft(2, '0');
final dd = now.day.toString().padLeft(2, '0');
return '$yyyy-$mm-$dd';
}
String _formatTime(dynamic value) {
if (value == null) return '-';
final s = value.toString().trim();
if (s.isEmpty || s == 'null') return '-';
if (s.contains('T')) {
try {
final dt = DateTime.parse(s).toLocal();
final hh = dt.hour.toString().padLeft(2, '0');
final mm = dt.minute.toString().padLeft(2, '0');
return '$hh:$mm';
} catch (_) {}
}
final parts = s.split(':');
if (parts.length >= 2) {
return '${parts[0].padLeft(2, '0')}:${parts[1].padLeft(2, '0')}';
}
return s;
}
String get tanggalFormatted {
final now = DateTime.now();
return "${now.day} ${_bulanIndo(now.month)} ${now.year}";
}
String _bulanIndo(int bulan) {
const bulanList = [
"",
"Januari",
"Februari",
"Maret",
"April",
"Mei",
"Juni",
"Juli",
"Agustus",
"September",
"Oktober",
"November",
"Desember",
];
return bulanList[bulan];
}
String get statusText {
if (daily.value == null) return "-";
if (daily.value?['check_in'] != null) {
return "Hadir";
}
return "-";
}
String get keteranganText {
if (daily.value == null) return "-";
final checkIn = daily.value?['check_in'];
if (checkIn == null) return "-";
return "Tepat Waktu";
}
}