import 'package:posyandu/services/api_service.dart'; import 'dart:convert'; import 'dart:async'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:io'; class ArtikelModel { final int id; final String judul; final String? gambarArtikel; final String isiArtikel; final DateTime tanggal; final String? gambarUrl; final DateTime? createdAt; final DateTime? updatedAt; ArtikelModel({ required this.id, required this.judul, this.gambarArtikel, required this.isiArtikel, required this.tanggal, this.gambarUrl, this.createdAt, this.updatedAt, }); factory ArtikelModel.fromJson(Map json) { return ArtikelModel( id: json['id'], judul: json['judul'], gambarArtikel: json['gambar_artikel'], isiArtikel: json['isi_artikel'], tanggal: DateTime.parse(json['tanggal']), gambarUrl: json['gambar_url'], createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null, updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']) : null, ); } Map toJson() { return { 'id': id, 'judul': judul, 'gambar_artikel': gambarArtikel, 'isi_artikel': isiArtikel, 'tanggal': tanggal.toIso8601String().split('T')[0], 'gambar_url': gambarUrl, 'created_at': createdAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(), }; } } class ArtikelPagination { final List data; final int currentPage; final int lastPage; final int perPage; final int total; ArtikelPagination({ required this.data, required this.currentPage, required this.lastPage, required this.perPage, required this.total, }); factory ArtikelPagination.fromJson(Map json) { try { print('Memparsing ArtikelPagination dari: $json'); final dataMap = json['data']; print('Data map: $dataMap'); if (dataMap == null) { print('dataMap adalah null'); throw Exception('Format data tidak valid: dataMap adalah null'); } // Handle jika data adalah array dan bukan objek paginasi if (dataMap is List) { print('dataMap adalah List, bukan objek paginasi'); return ArtikelPagination( data: dataMap.map((item) => ArtikelModel.fromJson(item)).toList(), currentPage: 1, lastPage: 1, perPage: dataMap.length, total: dataMap.length, ); } // Pastikan semua field yang dibutuhkan ada if (!dataMap.containsKey('data') || !dataMap.containsKey('current_page') || !dataMap.containsKey('last_page') || !dataMap.containsKey('per_page') || !dataMap.containsKey('total')) { print('Format data tidak lengkap: $dataMap'); // Jika format tidak sesuai dengan yang diharapkan, coba buat format alternatif return ArtikelPagination( data: (dataMap['data'] ?? []).map((item) => ArtikelModel.fromJson(item)).toList(), currentPage: dataMap['current_page'] ?? 1, lastPage: dataMap['last_page'] ?? 1, perPage: dataMap['per_page'] ?? 10, total: dataMap['total'] ?? 0, ); } return ArtikelPagination( data: (dataMap['data'] as List).map((item) => ArtikelModel.fromJson(item)).toList(), currentPage: dataMap['current_page'], lastPage: dataMap['last_page'], perPage: dataMap['per_page'], total: dataMap['total'], ); } catch (e) { print('Error saat parsing ArtikelPagination: $e'); // Return empty pagination in case of error return ArtikelPagination( data: [], currentPage: 1, lastPage: 1, perPage: 10, total: 0, ); } } } class ArtikelService { final ApiService _apiService = ApiService(); // Singleton pattern static final ArtikelService _instance = ArtikelService._internal(); factory ArtikelService() { return _instance; } ArtikelService._internal(); /// Mendapatkan daftar artikel dengan paginasi Future getArtikels({ int perPage = 10, int page = 1, String? search, }) async { try { final Map queryParameters = { 'per_page': perPage.toString(), 'page': page.toString(), }; if (search != null && search.isNotEmpty) { queryParameters['search'] = search; } print('Mengambil data artikel dengan parameter: $queryParameters'); // Buat fungsi fallback untuk mengembalikan data kosong jika terjadi error ArtikelPagination createEmptyPagination() { print('Mengembalikan pagination kosong untuk fallback'); return ArtikelPagination( data: [], currentPage: page, lastPage: page, perPage: perPage, total: 0, ); } // Gunakan timeout yang lebih pendek untuk mencegah loading infinit final response = await _apiService.get('artikel', queryParameters: queryParameters) .timeout(Duration(seconds: 5), onTimeout: () { print('Timeout saat mengambil data artikel'); return { 'status': 'success', 'data': [], 'message': 'Timeout saat mengambil data' }; }); print('Response dari API artikel: $response'); // Coba mendapatkan data terlepas dari format respons if (response['status'] == 'success') { // Format response berhasil, coba parse sebagai ArtikelPagination try { return ArtikelPagination.fromJson(response); } catch (e) { print('Error parsing ArtikelPagination: $e'); // Jika gagal, coba format alternatif // Jika respons adalah array, langsung gunakan sebagai data if (response['data'] is List) { final List dataList = response['data']; return ArtikelPagination( data: dataList.map((item) => ArtikelModel.fromJson(item)).toList(), currentPage: page, lastPage: dataList.isEmpty ? page : page + 1, // Asumsi ada halaman berikutnya jika ada data perPage: perPage, total: dataList.length, ); } // Format dengan pagination di dalam data if (response['data'] is Map && (response['data'] as Map).containsKey('data') && (response['data'] as Map)['data'] is List) { final Map paginationData = response['data']; return ArtikelPagination( data: (paginationData['data'] as List).map((item) => ArtikelModel.fromJson(item)).toList(), currentPage: paginationData['current_page'] ?? page, lastPage: paginationData['last_page'] ?? page, perPage: paginationData['per_page'] ?? perPage, total: paginationData['total'] ?? (paginationData['data'] as List).length, ); } // Jika semua format gagal, kembalikan pagination kosong print('Semua format gagal, mengembalikan pagination kosong'); return createEmptyPagination(); } } else { print('Error status dari API: ${response['message']}'); return createEmptyPagination(); } } catch (e) { print('Error lengkap pada getArtikels: $e'); // Kembalikan objek pagination kosong sebagai fallback return ArtikelPagination( data: [], currentPage: page, lastPage: page, perPage: perPage, total: 0, ); } } /// Mendapatkan detail artikel Future getArtikelDetail(int id) async { try { print('Memulai request untuk detail artikel dengan ID: $id'); final response = await _apiService.get('artikel/$id').timeout(Duration(seconds: 3), onTimeout: () { print('Timeout saat mengambil detail artikel dengan ID: $id'); throw TimeoutException('Waktu habis saat mengambil detail artikel'); }); print('Response dari API artikel detail: $response'); if (response['status'] == 'success') { // Periksa apakah data dalam format yang diharapkan if (response['data'] is Map) { return ArtikelModel.fromJson(response['data']); } else if (response['data'] is List && (response['data'] as List).isNotEmpty) { // Jika data adalah list, ambil item pertama return ArtikelModel.fromJson((response['data'] as List).first); } else { print('Format data detail artikel tidak dikenali: ${response['data'].runtimeType}'); return _createDummyDetail(id); } } else { print('Error status dari API detail artikel: ${response['message']}'); return _createDummyDetail(id); } } catch (e) { print('Error lengkap pada getArtikelDetail: $e'); return _createDummyDetail(id); } } /// Membuat model artikel dummy untuk fallback ArtikelModel _createDummyDetail(int id) { print('Membuat model artikel dummy untuk fallback dengan ID: $id'); return ArtikelModel( id: id, judul: 'Artikel Tidak Tersedia', isiArtikel: 'Maaf, artikel yang Anda cari tidak dapat diakses saat ini. Silakan coba lagi nanti.', tanggal: DateTime.now(), gambarUrl: null, ); } /// Mendapatkan artikel terbaru Future> getLatestArtikels({int limit = 5}) async { try { // Gunakan timeout yang lebih pendek final response = await _apiService.get('artikel', queryParameters: {'limit': limit.toString(), 'latest': 'true'}) .timeout(Duration(seconds: 5), onTimeout: () { print('Timeout saat mengambil artikel terbaru'); throw TimeoutException('Waktu habis saat mengambil artikel terbaru'); }); if (response['status'] == 'success' && response['data'] != null) { final dataMap = response['data']; try { final List dataList = dataMap['data'] != null ? dataMap['data'] : dataMap; return dataList.map((item) => ArtikelModel.fromJson(item)).toList(); } catch (e) { print('Error parsing artikel terbaru: $e'); throw Exception('Format data artikel terbaru tidak valid'); } } else { throw Exception(response['message'] ?? 'Gagal mengambil artikel terbaru'); } } catch (e) { print('Error pada getLatestArtikels: $e'); // Return artikel dummy untuk fallback return _getDummyLatestArtikels(limit); } } /// Membuat artikel terbaru dummy untuk fallback List _getDummyLatestArtikels(int limit) { print('Menggunakan artikel terbaru dummy untuk fallback'); final dummyArticles = [ ArtikelModel( id: 1, judul: 'Pentingnya ASI Eksklusif untuk Perkembangan Bayi', isiArtikel: 'ASI eksklusif adalah pemberian ASI saja pada bayi sampai usia 6 bulan. Manfaatnya sangat banyak untuk tumbuh kembang dan kekebalan tubuh bayi.', tanggal: DateTime.now().subtract(Duration(days: 2)), gambarUrl: 'https://img.freepik.com/free-photo/young-mother-showing-breastfeeding-her-baby_23-2149046913.jpg', ), ArtikelModel( id: 2, judul: 'Mencegah Stunting Sejak Dini pada Anak', isiArtikel: 'Stunting dapat dicegah dengan memperhatikan asupan gizi sejak masa kehamilan dan memberikan makanan bergizi seimbang pada anak.', tanggal: DateTime.now().subtract(Duration(days: 4)), gambarUrl: 'https://img.freepik.com/free-photo/doctor-check-up-little-boy-office_23-2148982292.jpg', ), ArtikelModel( id: 3, judul: 'Panduan Makanan Bergizi untuk Balita', isiArtikel: 'Makanan bergizi seimbang sangat penting untuk pertumbuhan optimal balita. Pelajari menu-menu sehat yang bisa diberikan sesuai usia anak.', tanggal: DateTime.now().subtract(Duration(days: 6)), gambarUrl: 'https://img.freepik.com/free-photo/close-up-young-attractive-smiling-mother-feeding-her-cute-baby-son-with-spoon-organic-healthy-baby-food-white-kitchen-with-big-window_8353-12056.jpg', ), ArtikelModel( id: 4, judul: 'Jadwal Imunisasi Lengkap untuk Anak', isiArtikel: 'Imunisasi adalah cara efektif untuk melindungi anak dari berbagai penyakit berbahaya. Ketahui jadwal imunisasi yang tepat untuk anak.', tanggal: DateTime.now().subtract(Duration(days: 10)), gambarUrl: 'https://img.freepik.com/free-photo/doctor-vaccinating-little-girl_23-2148982283.jpg', ), ArtikelModel( id: 5, judul: 'Tips Merawat Kesehatan Anak selama Musim Hujan', isiArtikel: 'Musim hujan meningkatkan risiko beberapa penyakit pada anak. Ikuti tips ini untuk menjaga kesehatan anak tetap optimal.', tanggal: DateTime.now().subtract(Duration(days: 15)), gambarUrl: 'https://img.freepik.com/free-photo/cute-child-with-umbrella_1149-537.jpg', ), ]; return dummyArticles.take(limit).toList(); } /// Membuat artikel baru (memerlukan hak akses admin) Future createArtikel({ required String judul, required File gambarArtikel, required String isiArtikel, required DateTime tanggal, }) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); if (token == null) { throw Exception('Tidak memiliki akses untuk membuat artikel'); } final uri = Uri.parse('${ApiService.baseUrl}/artikel'); // Membuat request multipart untuk upload file final request = http.MultipartRequest('POST', uri) ..headers.addAll({ 'Authorization': 'Bearer $token', 'Accept': 'application/json', }) ..fields['judul'] = judul ..fields['isi_artikel'] = isiArtikel ..fields['tanggal'] = tanggal.toIso8601String().split('T')[0] ..files.add(await http.MultipartFile.fromPath( 'gambar_artikel', gambarArtikel.path, )); final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); final responseData = json.decode(response.body); if (response.statusCode == 201 && responseData['status'] == 'success') { return ArtikelModel.fromJson(responseData['data']); } else { throw Exception(responseData['message'] ?? 'Gagal membuat artikel'); } } catch (e) { print('Error pada createArtikel: $e'); throw Exception('Gagal membuat artikel: $e'); } } /// Mengupdate artikel (memerlukan hak akses admin) Future updateArtikel({ required int id, required String judul, File? gambarArtikel, required String isiArtikel, required DateTime tanggal, }) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); if (token == null) { throw Exception('Tidak memiliki akses untuk mengupdate artikel'); } final uri = Uri.parse('${ApiService.baseUrl}/artikel/$id'); // Membuat request multipart untuk upload file final request = http.MultipartRequest('POST', uri) ..headers.addAll({ 'Authorization': 'Bearer $token', 'Accept': 'application/json', }) ..fields['judul'] = judul ..fields['isi_artikel'] = isiArtikel ..fields['tanggal'] = tanggal.toIso8601String().split('T')[0] ..fields['_method'] = 'PUT'; // Laravel menggunakan _method untuk form method spoofing // Tambahkan file gambar jika ada if (gambarArtikel != null) { request.files.add(await http.MultipartFile.fromPath( 'gambar_artikel', gambarArtikel.path, )); } final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); final responseData = json.decode(response.body); if (response.statusCode == 200 && responseData['status'] == 'success') { return ArtikelModel.fromJson(responseData['data']); } else { throw Exception(responseData['message'] ?? 'Gagal mengupdate artikel'); } } catch (e) { print('Error pada updateArtikel: $e'); throw Exception('Gagal mengupdate artikel: $e'); } } /// Menghapus artikel (memerlukan hak akses admin) Future deleteArtikel(int id) async { try { final response = await _apiService.delete('artikel/$id'); if (response['status'] == 'success') { return true; } else { throw Exception(response['message'] ?? 'Gagal menghapus artikel'); } } catch (e) { print('Error pada deleteArtikel: $e'); throw Exception('Gagal menghapus artikel: $e'); } } }