import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import 'package:image_picker/image_picker.dart'; import 'package:flutter/foundation.dart' show kIsWeb; class AbsensiApi { static const String baseUrl = 'https://ta.myhost.id/E31230906/api'; static const String absenMasukEndpoint = '/absensi/absen-masuk'; static const String absenKeluarEndpoint = '/absensi/absen-keluar'; static const String checkStatusEndpoint = '/absensi/check-status'; static const String riwayatEndpoint = '/absensi/riwayat'; static const String kalenderEndpoint = '/absensi/kalender'; static const String rekapEndpoint = '/absensi/rekap'; static const Duration timeoutDuration = Duration(seconds: 30); Future _getToken() async { try { final prefs = await SharedPreferences.getInstance(); return prefs.getString('access_token'); } catch (e) { print('Error getting token: $e'); return null; } } Future> _getMultipartHeaders() async { final token = await _getToken(); return { 'Accept': 'application/json', if (token != null) 'Authorization': 'Bearer $token', }; } Future> _getHeaders() async { final token = await _getToken(); return { 'Content-Type': 'application/json', 'Accept': 'application/json', if (token != null) 'Authorization': 'Bearer $token', }; } /// Absen masuk dengan support Web & Mobile Future> absenMasuk({ required int idTeknisi, XFile? fotoAbsenMasuk, String? status, String? keterangan, double? latitude, double? longitude, }) async { try { print('=== ABSEN MASUK REQUEST ==='); print('ID Teknisi: $idTeknisi'); print('Status: ${status ?? "null"}'); print('Keterangan: ${keterangan ?? "null"}'); print('Foto: ${fotoAbsenMasuk != null ? "Ada (${fotoAbsenMasuk.name})" : "TIDAK ADA"}'); var request = http.MultipartRequest( 'POST', Uri.parse('$baseUrl$absenMasukEndpoint'), ); request.headers.addAll(await _getMultipartHeaders()); request.fields['id_teknisi'] = idTeknisi.toString(); if (status != null && status.isNotEmpty) { request.fields['status'] = status; } if (keterangan != null && keterangan.isNotEmpty) { request.fields['keterangan'] = keterangan; } if (latitude != null) { request.fields['latitude'] = latitude.toString(); } if (longitude != null) { request.fields['longitude'] = longitude.toString(); } if (fotoAbsenMasuk != null) { if (kIsWeb) { final bytes = await fotoAbsenMasuk.readAsBytes(); print('📸 Foto size (Web): ${bytes.length} bytes'); request.files.add(http.MultipartFile.fromBytes( 'foto_absen_masuk', bytes, filename: fotoAbsenMasuk.name, )); } else { print('📸 Foto path (Mobile): ${fotoAbsenMasuk.path}'); request.files.add(await http.MultipartFile.fromPath( 'foto_absen_masuk', fotoAbsenMasuk.path, )); } } print('📤 Sending request to: $baseUrl$absenMasukEndpoint'); final streamedResponse = await request.send().timeout(timeoutDuration); final response = await http.Response.fromStream(streamedResponse); print('=== ABSEN MASUK RESPONSE ==='); print('Status Code: ${response.statusCode}'); print('Response Body: ${response.body}'); final data = jsonDecode(response.body); if (response.statusCode == 201 && data['success'] == true) { print('✅ ABSEN MASUK SUKSES!'); return {'success': true, 'message': data['message'], 'data': data['data']}; } else if (response.statusCode == 400) { print('❌ Error 400: ${data['message']}'); return {'success': false, 'message': data['message'] ?? 'Anda sudah melakukan absen masuk hari ini'}; } else if (response.statusCode == 422) { print('❌ Validation Error: ${data['errors']}'); String errorMessage = 'Validasi gagal'; if (data['errors'] != null) { final errors = data['errors'] as Map; errorMessage = errors.values.first[0]; } return {'success': false, 'message': errorMessage, 'errors': data['errors']}; } else { print('❌ Unknown Error: ${response.statusCode}'); return {'success': false, 'message': data['message'] ?? 'Gagal melakukan absen masuk'}; } } catch (e, stackTrace) { print('❌ EXCEPTION di absenMasuk: $e'); print(stackTrace); return {'success': false, 'message': 'Terjadi kesalahan: ${e.toString()}'}; } } /// Absen keluar dengan support Web & Mobile Future> absenKeluar({ required int idTeknisi, XFile? fotoAbsenKeluar, String? status, String? keterangan, double? latitude, double? longitude, }) async { try { print('=== ABSEN KELUAR REQUEST ==='); print('ID Teknisi: $idTeknisi'); print('Status: ${status ?? "null"}'); print('Keterangan: ${keterangan ?? "null"}'); print('Foto: ${fotoAbsenKeluar != null ? "Ada (${fotoAbsenKeluar.name})" : "TIDAK ADA"}'); var request = http.MultipartRequest( 'POST', Uri.parse('$baseUrl$absenKeluarEndpoint'), ); request.headers.addAll(await _getMultipartHeaders()); request.fields['id_teknisi'] = idTeknisi.toString(); if (status != null && status.isNotEmpty) { request.fields['status'] = status; } if (keterangan != null && keterangan.isNotEmpty) { request.fields['keterangan'] = keterangan; } if (latitude != null) { request.fields['latitude'] = latitude.toString(); } if (longitude != null) { request.fields['longitude'] = longitude.toString(); } if (fotoAbsenKeluar != null) { if (kIsWeb) { final bytes = await fotoAbsenKeluar.readAsBytes(); print('📸 Foto size (Web): ${bytes.length} bytes'); request.files.add(http.MultipartFile.fromBytes( 'foto_absen_keluar', bytes, filename: fotoAbsenKeluar.name, )); } else { print('📸 Foto path (Mobile): ${fotoAbsenKeluar.path}'); request.files.add(await http.MultipartFile.fromPath( 'foto_absen_keluar', fotoAbsenKeluar.path, )); } } print('📤 Sending request to: $baseUrl$absenKeluarEndpoint'); final streamedResponse = await request.send().timeout(timeoutDuration); final response = await http.Response.fromStream(streamedResponse); print('=== ABSEN KELUAR RESPONSE ==='); print('Status Code: ${response.statusCode}'); print('Response Body: ${response.body}'); final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) { print('✅ ABSEN KELUAR SUKSES!'); return {'success': true, 'message': data['message'], 'data': data['data']}; } else if (response.statusCode == 400) { print('❌ Error 400: ${data['message']}'); return {'success': false, 'message': data['message'] ?? 'Belum melakukan absen masuk atau sudah absen keluar'}; } else if (response.statusCode == 422) { print('❌ Validation Error: ${data['errors']}'); String errorMessage = 'Validasi gagal'; if (data['errors'] != null) { final errors = data['errors'] as Map; errorMessage = errors.values.first[0]; } return {'success': false, 'message': errorMessage, 'errors': data['errors']}; } else { print('❌ Unknown Error: ${response.statusCode}'); return {'success': false, 'message': data['message'] ?? 'Gagal melakukan absen keluar'}; } } catch (e, stackTrace) { print('❌ EXCEPTION di absenKeluar: $e'); print(stackTrace); return {'success': false, 'message': 'Terjadi kesalahan: ${e.toString()}'}; } } /// Cek status absensi hari ini Future> checkStatus(int idTeknisi) async { try { final token = await _getToken(); final response = await http.get( Uri.parse('$baseUrl$checkStatusEndpoint/$idTeknisi'), headers: { 'Accept': 'application/json', if (token != null) 'Authorization': 'Bearer $token', }, ).timeout(timeoutDuration); final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) { return {'success': true, 'message': data['message'], 'data': data['data']}; } else if (response.statusCode == 404) { return {'success': false, 'message': 'Teknisi tidak ditemukan'}; } else { return {'success': false, 'message': data['message'] ?? 'Gagal mengecek status absensi'}; } } catch (e) { return {'success': false, 'message': 'Terjadi kesalahan: ${e.toString()}'}; } } // ── Method baru ──────────────────────────────────────────────────────────── /// Ambil riwayat absensi per bulan /// Endpoint: GET /api/absensi/riwayat?id_teknisi=1&bulan=3&tahun=2026 Future> getRiwayat({ required int idTeknisi, required int bulan, required int tahun, }) async { try { final url = Uri.parse( '$baseUrl$riwayatEndpoint?id_teknisi=$idTeknisi&bulan=$bulan&tahun=$tahun'); print('📋 GET Riwayat: $url'); final response = await http.get(url, headers: await _getHeaders()).timeout(timeoutDuration); print('Riwayat status: ${response.statusCode}'); print('Riwayat body: ${response.body}'); final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) { return {'success': true, 'data': data['data'] ?? []}; } return { 'success': false, 'message': data['message'] ?? 'Gagal memuat riwayat', 'data': [], }; } catch (e) { print('❌ getRiwayat error: $e'); return {'success': false, 'message': e.toString(), 'data': []}; } } /// Ambil status absensi per tanggal dalam 1 bulan (untuk kalender) /// Endpoint: GET /api/absensi/kalender?id_teknisi=1&bulan=3&tahun=2026 /// Response data: { "1": "hadir", "5": "izin", "10": "alpha", ... } Future> getKalender({ required int idTeknisi, required int bulan, required int tahun, }) async { try { final url = Uri.parse( '$baseUrl$kalenderEndpoint?id_teknisi=$idTeknisi&bulan=$bulan&tahun=$tahun'); print('📅 GET Kalender: $url'); final response = await http.get(url, headers: await _getHeaders()).timeout(timeoutDuration); print('Kalender status: ${response.statusCode}'); print('Kalender body: ${response.body}'); final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) { return {'success': true, 'data': data['data'] ?? {}}; } return { 'success': false, 'message': data['message'] ?? 'Gagal memuat kalender', 'data': {}, }; } catch (e) { print('❌ getKalender error: $e'); return {'success': false, 'message': e.toString(), 'data': {}}; } } /// Ambil rekap absensi bulanan /// Endpoint: GET /api/absensi/rekap?id_teknisi=1&bulan=3&tahun=2026 /// Response data: { "bulan": "Maret 2026", "hadir": 14, "izin": 1, ... } Future> getRekap({ required int idTeknisi, required int bulan, required int tahun, }) async { try { final url = Uri.parse( '$baseUrl$rekapEndpoint?id_teknisi=$idTeknisi&bulan=$bulan&tahun=$tahun'); print('📊 GET Rekap: $url'); final response = await http.get(url, headers: await _getHeaders()).timeout(timeoutDuration); print('Rekap status: ${response.statusCode}'); print('Rekap body: ${response.body}'); final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) { return {'success': true, 'data': data['data'] ?? {}}; } return { 'success': false, 'message': data['message'] ?? 'Gagal memuat rekap', 'data': {}, }; } catch (e) { print('❌ getRekap error: $e'); return {'success': false, 'message': e.toString(), 'data': {}}; } } }