372 lines
11 KiB
Dart
372 lines
11 KiB
Dart
import 'package:posyandu/services/api_service.dart';
|
|
import 'package:posyandu/services/anak_service.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
class DashboardService {
|
|
final ApiService _apiService = ApiService();
|
|
final AnakService _anakService = AnakService();
|
|
|
|
// Singleton pattern
|
|
static final DashboardService _instance = DashboardService._internal();
|
|
|
|
factory DashboardService() {
|
|
return _instance;
|
|
}
|
|
|
|
DashboardService._internal();
|
|
|
|
/// Mendapatkan daftar anak untuk pemilihan di dashboard
|
|
Future<List<dynamic>> getAnakListForSelection() async {
|
|
try {
|
|
final anakList = await _anakService.getAnakList();
|
|
|
|
// Beri label aktif pada anak yang terakhir dipilih user
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final lastSelectedAnakId = prefs.getInt('last_selected_anak_id');
|
|
|
|
final enhancedList = anakList.map((anak) {
|
|
final isSelected = anak['id'] == lastSelectedAnakId;
|
|
return {
|
|
...anak,
|
|
'is_selected': isSelected,
|
|
};
|
|
}).toList();
|
|
|
|
return enhancedList;
|
|
} catch (e) {
|
|
print('Error loading anak list for selection: $e');
|
|
throw Exception('Gagal memuat daftar anak: $e');
|
|
}
|
|
}
|
|
|
|
/// Mengatur anak yang dipilih sebagai default di dashboard
|
|
Future<void> setSelectedAnak(int anakId) async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setInt('last_selected_anak_id', anakId);
|
|
print('Berhasil menyimpan anak terpilih dengan ID $anakId');
|
|
} catch (e) {
|
|
print('Error saving selected anak: $e');
|
|
}
|
|
}
|
|
|
|
/// Mendapatkan ID anak yang terakhir dipilih
|
|
Future<int?> getLastSelectedAnakId() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getInt('last_selected_anak_id');
|
|
} catch (e) {
|
|
print('Error getting last selected anak ID: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Mendapatkan data ringkasan untuk dashboard
|
|
Future<Map<String, dynamic>> getDashboardSummary({int? anakId}) async {
|
|
try {
|
|
// Jika tidak ada anakId yang diberikan, gunakan ID yang terakhir dipilih
|
|
final targetAnakId = anakId ?? await getLastSelectedAnakId();
|
|
|
|
if (targetAnakId == null) {
|
|
// Jika tidak ada anak yang dipilih, berikan respons kosong
|
|
return {
|
|
'success': false,
|
|
'message': 'Belum ada anak yang dipilih',
|
|
'data': null,
|
|
};
|
|
}
|
|
|
|
// Gunakan Future.wait untuk melakukan fetch data secara paralel
|
|
final futures = [
|
|
_anakService.getAnakDetail(targetAnakId),
|
|
_getLatestGrowthData(targetAnakId),
|
|
_getUpcomingSchedule(targetAnakId),
|
|
];
|
|
|
|
final results = await Future.wait(futures).timeout(
|
|
Duration(seconds: 2),
|
|
onTimeout: () {
|
|
// Jika timeout, gunakan data dummy
|
|
print('Fetching dashboard data timed out, using defaults');
|
|
return [
|
|
{'nama_anak': 'Anak', 'jenis_kelamin': 'Laki-laki', 'tanggal_lahir': DateTime.now().subtract(Duration(days: 365)).toString()},
|
|
{'tinggi_badan': 75.0, 'berat_badan': 9.0, 'tanggal': DateTime.now().toString()},
|
|
{'jenis': 'Imunisasi DPT', 'tanggal': DateTime.now().add(Duration(days: 14)).toString(), 'jam': '09:00 - 12:00', 'lokasi': 'Posyandu Melati'}
|
|
];
|
|
}
|
|
);
|
|
|
|
final anakDetail = results[0];
|
|
final growth = results[1];
|
|
final schedule = results[2];
|
|
|
|
// Hitung statistik
|
|
final stats = _calculateStatistics(anakDetail, growth);
|
|
|
|
// Buat ringkasan data gabungan
|
|
return {
|
|
'success': true,
|
|
'data': {
|
|
'anak': anakDetail,
|
|
'pertumbuhan': growth,
|
|
'jadwal': schedule,
|
|
'statistik': stats,
|
|
}
|
|
};
|
|
} catch (e) {
|
|
print('Error getting dashboard summary: $e');
|
|
return {
|
|
'success': false,
|
|
'message': 'Gagal memuat data dashboard: $e',
|
|
'data': null,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Mendapatkan semua data untuk cards di dashboard
|
|
Future<Map<String, dynamic>> getDashboardCards() async {
|
|
try {
|
|
final response = await _apiService.get('dashboard/summary');
|
|
|
|
if (response['success'] == true) {
|
|
return response;
|
|
} else {
|
|
// Generate dummy data jika API belum tersedia
|
|
return {
|
|
'success': true,
|
|
'data': {
|
|
'total_anak': 56,
|
|
'kunjungan_bulan_ini': 128,
|
|
'anak_stunting': 7,
|
|
'jadwal_hari_ini': 3,
|
|
}
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print('Error getting dashboard cards: $e');
|
|
// Fallback data jika API gagal
|
|
return {
|
|
'success': true,
|
|
'data': {
|
|
'total_anak': 42,
|
|
'kunjungan_bulan_ini': 95,
|
|
'anak_stunting': 5,
|
|
'jadwal_hari_ini': 2,
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Mendapatkan daftar artikel kesehatan terbaru
|
|
Future<List<dynamic>> getLatestHealthArticles() async {
|
|
try {
|
|
// Karena endpoint API belum dibuat, langsung kembali ke dummy data
|
|
// Hilangkan panggilan API yang menyebabkan error 404
|
|
return _getDummyArticles();
|
|
|
|
/* Kode di bawah ini akan diaktifkan nanti ketika API sudah tersedia
|
|
final response = await _apiService.get('articles/latest');
|
|
|
|
if (response['success'] == true) {
|
|
return response['articles'] ?? [];
|
|
} else {
|
|
// Generate dummy data jika API belum tersedia
|
|
return _getDummyArticles();
|
|
}
|
|
*/
|
|
} catch (e) {
|
|
print('Error getting latest articles: $e');
|
|
// Fallback ke data dummy
|
|
return _getDummyArticles();
|
|
}
|
|
}
|
|
|
|
/// Menghasilkan artikel kesehatan dummy (sementara)
|
|
List<dynamic> _getDummyArticles() {
|
|
return [
|
|
{
|
|
'id': 1,
|
|
'title': 'Pentingnya ASI Eksklusif',
|
|
'category': 'Gizi',
|
|
'excerpt': 'ASI eksklusif selama 6 bulan pertama sangat penting untuk perkembangan bayi...',
|
|
'image_url': null,
|
|
'created_at': '2025-05-01',
|
|
},
|
|
{
|
|
'id': 2,
|
|
'title': 'Mencegah Stunting Sejak Dini',
|
|
'category': 'Tumbuh Kembang',
|
|
'excerpt': 'Stunting dapat dicegah dengan pemberian nutrisi yang tepat dan pemantauan pertumbuhan...',
|
|
'image_url': null,
|
|
'created_at': '2025-05-05',
|
|
},
|
|
{
|
|
'id': 3,
|
|
'title': 'Jadwal Imunisasi Lengkap',
|
|
'category': 'Imunisasi',
|
|
'excerpt': 'Imunisasi lengkap dapat melindungi anak dari berbagai penyakit berbahaya...',
|
|
'image_url': null,
|
|
'created_at': '2025-05-10',
|
|
},
|
|
];
|
|
}
|
|
|
|
/// Helper: Mendapatkan data pertumbuhan terbaru
|
|
Future<Map<String, dynamic>> _getLatestGrowthData(int anakId) async {
|
|
try {
|
|
final response = await _apiService.get('perkembangan/anak/$anakId/latest');
|
|
|
|
if (response['success'] == true) {
|
|
return response['data'] ?? {};
|
|
}
|
|
|
|
// Fallback: coba dapatkan dari list, ambil yang terakhir
|
|
final perkembanganList = await _apiService.get('perkembangan/anak/$anakId');
|
|
|
|
if (perkembanganList['status'] == 'success' &&
|
|
perkembanganList['perkembangan'] is List &&
|
|
(perkembanganList['perkembangan'] as List).isNotEmpty) {
|
|
|
|
// Sort berdasarkan tanggal terbaru
|
|
final list = perkembanganList['perkembangan'] as List;
|
|
list.sort((a, b) {
|
|
final dateA = DateTime.parse(a['tanggal']);
|
|
final dateB = DateTime.parse(b['tanggal']);
|
|
return dateB.compareTo(dateA); // Descending (terbaru dulu)
|
|
});
|
|
|
|
return list.first;
|
|
}
|
|
|
|
// Jika tidak ada data, berikan nilai default
|
|
return {
|
|
'tinggi_badan': 0,
|
|
'berat_badan': 0,
|
|
'tanggal': DateFormat('yyyy-MM-dd').format(DateTime.now()),
|
|
};
|
|
} catch (e) {
|
|
print('Error getting latest growth data: $e');
|
|
|
|
// Data dummy jika error
|
|
return {
|
|
'tinggi_badan': 0,
|
|
'berat_badan': 0,
|
|
'tanggal': DateFormat('yyyy-MM-dd').format(DateTime.now()),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Helper: Mendapatkan jadwal imunisasi terdekat
|
|
Future<Map<String, dynamic>> _getUpcomingSchedule(int anakId) async {
|
|
try {
|
|
final response = await _apiService.get('jadwal/upcoming/$anakId');
|
|
|
|
if (response['success'] == true) {
|
|
return response['data'] ?? {};
|
|
}
|
|
|
|
// Data dummy jika API belum tersedia
|
|
return {
|
|
'jenis': 'Imunisasi DPT',
|
|
'tanggal': DateFormat('yyyy-MM-dd').format(DateTime.now().add(Duration(days: 14))),
|
|
'jam': '09:00 - 12:00',
|
|
'lokasi': 'Posyandu Melati',
|
|
};
|
|
} catch (e) {
|
|
print('Error getting upcoming schedule: $e');
|
|
|
|
// Data dummy jika error
|
|
return {
|
|
'jenis': 'Imunisasi DPT',
|
|
'tanggal': DateFormat('yyyy-MM-dd').format(DateTime.now().add(Duration(days: 14))),
|
|
'jam': '09:00 - 12:00',
|
|
'lokasi': 'Posyandu Melati',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Helper: Hitung statistik pertumbuhan
|
|
Map<String, dynamic> _calculateStatistics(Map<String, dynamic> anakDetail, Map<String, dynamic> growth) {
|
|
final tanggalLahir = DateTime.parse(anakDetail['tanggal_lahir']);
|
|
final now = DateTime.now();
|
|
final ageInDays = now.difference(tanggalLahir).inDays;
|
|
final ageInMonths = (ageInDays / 30).floor(); // Approximate
|
|
|
|
// Dapatkan tinggi dan berat badan
|
|
double? height, weight;
|
|
try {
|
|
height = double.parse(growth['tinggi_badan'].toString());
|
|
weight = double.parse(growth['berat_badan'].toString());
|
|
|
|
// Tambahkan pengecekan apakah nilai adalah 0
|
|
if (height == 0 && weight == 0) {
|
|
return {
|
|
'height': {
|
|
'value': 0,
|
|
'status': 'Belum Ada Data',
|
|
},
|
|
'weight': {
|
|
'value': 0,
|
|
'status': 'Belum Ada Data',
|
|
},
|
|
'age': '$ageInMonths bulan',
|
|
'is_stunting': false,
|
|
'overall_status': 'Belum Ada Data',
|
|
};
|
|
}
|
|
} catch (e) {
|
|
// Default values jika parsing gagal
|
|
height = 0;
|
|
weight = 0;
|
|
print('Error parsing height/weight: $e');
|
|
|
|
return {
|
|
'height': {
|
|
'value': 0,
|
|
'status': 'Belum Ada Data',
|
|
},
|
|
'weight': {
|
|
'value': 0,
|
|
'status': 'Belum Ada Data',
|
|
},
|
|
'age': '$ageInMonths bulan',
|
|
'is_stunting': false,
|
|
'overall_status': 'Belum Ada Data',
|
|
};
|
|
}
|
|
|
|
// Hitung status berdasarkan standar WHO (implementasi sederhana)
|
|
String statusBB = 'Normal';
|
|
if (weight < 8.0) {
|
|
statusBB = 'Kurang';
|
|
} else if (weight > 15.0) {
|
|
statusBB = 'Lebih';
|
|
}
|
|
|
|
String statusTB = 'Normal';
|
|
if (height < 75.0) {
|
|
statusTB = 'Pendek';
|
|
} else if (height > 95.0) {
|
|
statusTB = 'Tinggi';
|
|
}
|
|
|
|
// Tentukan status stunting
|
|
bool isStunting = statusTB == 'Pendek' && statusBB == 'Kurang';
|
|
|
|
return {
|
|
'height': {
|
|
'value': height,
|
|
'status': statusTB,
|
|
},
|
|
'weight': {
|
|
'value': weight,
|
|
'status': statusBB,
|
|
},
|
|
'age': '$ageInMonths bulan',
|
|
'is_stunting': isStunting,
|
|
'overall_status': isStunting ? 'Stunting' : 'Normal',
|
|
};
|
|
}
|
|
}
|