479 lines
17 KiB
Dart
479 lines
17 KiB
Dart
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<String, dynamic> 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<String, dynamic> 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<ArtikelModel> 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<String, dynamic> 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<ArtikelModel>((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<ArtikelPagination> getArtikels({
|
|
int perPage = 10,
|
|
int page = 1,
|
|
String? search,
|
|
}) async {
|
|
try {
|
|
final Map<String, dynamic> 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<dynamic> 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<String, dynamic> 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<ArtikelModel> 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<String, dynamic>) {
|
|
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<List<ArtikelModel>> 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<dynamic> 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<ArtikelModel> _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<ArtikelModel> 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<ArtikelModel> 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<bool> 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');
|
|
}
|
|
}
|
|
}
|