import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../models/stunting_model.dart'; import '../../controllers/stunting_controller.dart'; import '../../services/stunting_service.dart'; import '../../services/perkembangan_service.dart'; import '../../services/profile_service.dart'; import '../../services/auth_service.dart'; import '../../services/dashboard_service.dart'; import '../../services/api_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'dart:math'; class StuntingScreen extends StatefulWidget { final int? anakId; StuntingScreen({this.anakId}); @override _StuntingScreenState createState() => _StuntingScreenState(); } class _StuntingScreenState extends State with SingleTickerProviderStateMixin { final _formKey = GlobalKey(); final Map _controllers = { 'Nama Anak': TextEditingController(), 'Usia (bulan)': TextEditingController(), 'Tinggi Badan (cm)': TextEditingController(), 'Berat Badan (kg)': TextEditingController(), }; bool _isLoading = false; late AnimationController _animationController; late Animation _fadeAnimation; late StuntingController _stuntingController; late StuntingService _stuntingService; late PerkembanganService _perkembanganService; late ProfileService _profileService; late AuthService _authService; late DashboardService _dashboardService; String _selectedAnakName = ''; int? _selectedAnakId; int? _selectedPerkembanganId; String _selectedGender = 'L'; // Default laki-laki // For date selection DateTime _tanggalPemeriksaan = DateTime.now(); List _riwayatStunting = []; @override void initState() { super.initState(); _stuntingController = StuntingController(); _stuntingService = StuntingService(); _perkembanganService = PerkembanganService(); _profileService = ProfileService(); _authService = AuthService(); _dashboardService = DashboardService(); _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: 1000), ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Interval(0.1, 1.0, curve: Curves.easeOut), )); _animationController.forward(); // Inisialisasi anakId jika ada _selectedAnakId = widget.anakId; _initializeData(); } Future _initializeData() async { setState(() { _isLoading = true; }); try { final prefs = await SharedPreferences.getInstance(); // Coba refresh token terlebih dahulu untuk memastikan autentikasi berfungsi try { final tokenRefreshed = await _refreshToken(); print('Token refresh status: ${tokenRefreshed ? "berhasil" : "gagal"}'); } catch (e) { print('Error saat refresh token: $e'); } // Jika anakId tidak diset, coba ambil dari shared preferences if (_selectedAnakId == null) { // Coba dapatkan dari selected_anak_id _selectedAnakId = prefs.getInt('selected_anak_id'); // Jika masih null, coba ambil dari last_selected_anak if (_selectedAnakId == null) { _selectedAnakId = prefs.getInt('last_selected_anak'); } // Jika masih null, coba ambil dari anak_id_active if (_selectedAnakId == null) { _selectedAnakId = prefs.getInt('anak_id_active'); } } print('Selected anak ID: $_selectedAnakId'); // Jika masih null, coba ambil ID anak pertama dari daftar anak if (_selectedAnakId == null) { // Coba mendapatkan ID anak dari DashboardService try { final lastSelectedAnakId = await _dashboardService.getLastSelectedAnakId(); if (lastSelectedAnakId != null) { _selectedAnakId = lastSelectedAnakId; print('Berhasil mendapatkan ID anak dari DashboardService: $_selectedAnakId'); } } catch (e) { print('Error mengambil lastSelectedAnakId: $e'); } } // Jika memiliki ID anak, coba dapatkan data anak if (_selectedAnakId != null) { // Coba dapatkan data anak dari dashboard summary try { final dashboardData = await _dashboardService.getDashboardSummary(anakId: _selectedAnakId!); if (dashboardData['success'] == true && dashboardData['data'] != null) { final childData = dashboardData['data']['anak'] ?? {}; final statsData = dashboardData['data']['statistik'] ?? {}; final growthData = dashboardData['data']['pertumbuhan'] ?? {}; // Debug respons API print('Debug respons dashboard:'); print('childData: $childData'); print('statsData: $statsData'); print('growthData: $growthData'); // Set nama anak _selectedAnakName = childData['nama_anak'] ?? childData['nama'] ?? ''; _selectedGender = childData['jenis_kelamin'] ?? 'L'; // Set ke preferences await prefs.setString('selected_anak_name', _selectedAnakName); await prefs.setString('selected_anak_gender', _selectedGender); // Pre-fill nama _controllers['Nama Anak']!.text = _selectedAnakName; // Try to get height and weight double tinggiBadan = 0; double beratBadan = 0; String usia = ''; // Dari pertumbuhan terbaru if (growthData is Map && growthData.isNotEmpty) { print('Mencoba ambil data dari growthData: $growthData'); if (growthData.containsKey('tinggi_badan')) { tinggiBadan = double.tryParse(growthData['tinggi_badan'].toString()) ?? 0; print('Tinggi badan dari growthData: $tinggiBadan'); } if (growthData.containsKey('berat_badan')) { beratBadan = double.tryParse(growthData['berat_badan'].toString()) ?? 0; print('Berat badan dari growthData: $beratBadan'); } } // Dari statistik if (tinggiBadan <= 0 && statsData is Map && statsData.isNotEmpty) { print('Mencoba ambil data dari statsData: $statsData'); if (statsData.containsKey('height') && statsData['height'] is Map) { tinggiBadan = double.tryParse(statsData['height']['value'].toString()) ?? 0; print('Tinggi badan dari statsData: $tinggiBadan'); } else if (statsData.containsKey('tinggi_badan')) { tinggiBadan = double.tryParse(statsData['tinggi_badan'].toString()) ?? 0; print('Tinggi badan dari statsData (tinggi_badan): $tinggiBadan'); } if (statsData.containsKey('weight') && statsData['weight'] is Map) { beratBadan = double.tryParse(statsData['weight']['value'].toString()) ?? 0; print('Berat badan dari statsData: $beratBadan'); } else if (statsData.containsKey('berat_badan')) { beratBadan = double.tryParse(statsData['berat_badan'].toString()) ?? 0; print('Berat badan dari statsData (berat_badan): $beratBadan'); } } // Dari data anak if (tinggiBadan <= 0 && childData is Map) { print('Mencoba ambil data dari childData: $childData'); // Coba semua kemungkinan field untuk tinggi badan final possibleTinggiFields = ['tinggi_badan', 'tinggi', 'height', 'tb']; for (var field in possibleTinggiFields) { if (childData.containsKey(field) && childData[field] != null) { final nilai = childData[field].toString().trim(); if (nilai.isNotEmpty && nilai != '0' && nilai != 'null') { final parsed = double.tryParse(nilai); if (parsed != null && parsed > 0) { tinggiBadan = parsed; print('Tinggi badan dari childData[$field]: $tinggiBadan'); break; } } } } // Coba semua kemungkinan field untuk berat badan final possibleBeratFields = ['berat_badan', 'berat', 'weight', 'bb']; for (var field in possibleBeratFields) { if (childData.containsKey(field) && childData[field] != null) { final nilai = childData[field].toString().trim(); if (nilai.isNotEmpty && nilai != '0' && nilai != 'null') { final parsed = double.tryParse(nilai); if (parsed != null && parsed > 0) { beratBadan = parsed; print('Berat badan dari childData[$field]: $beratBadan'); break; } } } } } // Jika masih belum dapat data, coba ambil dari semua property di dashboardData if (tinggiBadan <= 0 || beratBadan <= 0) { print('Mencari nilai TB dan BB di seluruh struktur data dashboard'); _searchValueInNestedMap(dashboardData, 'tinggi_badan', (value) { if (tinggiBadan <= 0) { final parsed = double.tryParse(value.toString()); if (parsed != null && parsed > 0) { print('Menemukan tinggi_badan di struktur data: $parsed'); tinggiBadan = parsed; } } }); _searchValueInNestedMap(dashboardData, 'berat_badan', (value) { if (beratBadan <= 0) { final parsed = double.tryParse(value.toString()); if (parsed != null && parsed > 0) { print('Menemukan berat_badan di struktur data: $parsed'); beratBadan = parsed; } } }); } // Get usia usia = childData['usia']?.toString() ?? statsData['age']?.toString() ?? childData['umur_bulan']?.toString() ?? ''; // Pre-fill form fields if (tinggiBadan > 0) { _controllers['Tinggi Badan (cm)']!.text = tinggiBadan.toString(); await prefs.setDouble('latest_tinggi_badan_${_selectedAnakId}', tinggiBadan); } if (beratBadan > 0) { _controllers['Berat Badan (kg)']!.text = beratBadan.toString(); await prefs.setDouble('latest_berat_badan_${_selectedAnakId}', beratBadan); } // Jika masih belum mendapatkan tinggi dan berat, coba ambil dari API perkembangan if ((tinggiBadan <= 0 || beratBadan <= 0) && _selectedAnakId != null) { try { print('Mencoba ambil data dari API perkembangan karena TB/BB masih 0'); final perkembanganList = await _perkembanganService.getPerkembanganByAnakId(_selectedAnakId!); if (perkembanganList.isNotEmpty) { // Sort berdasarkan tanggal terbaru perkembanganList.sort((a, b) { final dateA = a['tanggal'] != null ? DateTime.parse(a['tanggal'].toString()) : DateTime(2000); final dateB = b['tanggal'] != null ? DateTime.parse(b['tanggal'].toString()) : DateTime(2000); return dateB.compareTo(dateA); // Descending (terbaru dulu) }); // Ambil data perkembangan terbaru final latestData = perkembanganList.first; print('Data perkembangan terbaru: $latestData'); // Simpan ID perkembangan untuk referensi _selectedPerkembanganId = latestData['id']; if (tinggiBadan <= 0 && latestData.containsKey('tinggi_badan')) { final height = double.tryParse(latestData['tinggi_badan'].toString()); if (height != null && height > 0) { tinggiBadan = height; _controllers['Tinggi Badan (cm)']!.text = height.toString(); await prefs.setDouble('latest_tinggi_badan_${_selectedAnakId}', height); print('Tinggi badan dari perkembangan: $height'); } } if (beratBadan <= 0 && latestData.containsKey('berat_badan')) { final weight = double.tryParse(latestData['berat_badan'].toString()); if (weight != null && weight > 0) { beratBadan = weight; _controllers['Berat Badan (kg)']!.text = weight.toString(); await prefs.setDouble('latest_berat_badan_${_selectedAnakId}', weight); print('Berat badan dari perkembangan: $weight'); } } } else { print('Tidak ada data perkembangan untuk anak ini'); } } catch (e) { print('Error saat mengambil data perkembangan: $e'); } } if (usia.isNotEmpty) { // Coba ekstrak angka bulan dari string usia final RegExp regExp = RegExp(r'(\d+)'); final match = regExp.firstMatch(usia); if (match != null) { _controllers['Usia (bulan)']!.text = match.group(1) ?? ''; } else { _controllers['Usia (bulan)']!.text = usia; } await prefs.setString('latest_usia_${_selectedAnakId}', usia); } print('Berhasil mendapatkan data anak dari dashboard: ${_selectedAnakName}, TB: $tinggiBadan, BB: $beratBadan'); } } catch (e) { print('Error mengambil dashboard summary: $e'); // Jika gagal mendapatkan data dari API, coba ambil data dari SharedPreferences _fallbackToStoredData(); } } else { print('Tidak ada ID anak yang ditemukan, mencoba ambil dari user_data'); // Tidak ada ID anak yang ditemukan, coba ambil dari user_data await _fetchChildDataFromUserData(); } // Ambil riwayat stunting jika ada ID anak yang dipilih if (_selectedAnakId != null) { try { _riwayatStunting = await _stuntingService.getStuntingByAnakId(_selectedAnakId!); // Urutkan data stunting berdasarkan tanggal terbaru _riwayatStunting.sort((a, b) => b.tanggalPemeriksaan.compareTo(a.tanggalPemeriksaan)); // Debug urutan data dari API if (_riwayatStunting.isNotEmpty) { print('Setelah pengurutan di _initializeData, urutan data:'); for (int i = 0; i < min(3, _riwayatStunting.length); i++) { print('${i+1}. Tanggal: ${_riwayatStunting[i].tanggalPemeriksaan}, Status: ${_riwayatStunting[i].status}'); } } // Tambahkan nama anak ke riwayat stunting yang tidak memiliki nama if (_riwayatStunting.isNotEmpty) { _riwayatStunting = _riwayatStunting.map((data) { if (data.namaPasien == null || data.namaPasien!.isEmpty) { return StuntingData( id: data.id, anakId: data.anakId, perkembanganId: data.perkembanganId, tanggalPemeriksaan: data.tanggalPemeriksaan, usia: data.usia, tinggiBadan: data.tinggiBadan, beratBadan: data.beratBadan, status: data.status, catatan: data.catatan, gender: data.gender, namaPasien: _selectedAnakName, lingkarKepala: data.lingkarKepala, ); } return data; }).toList(); } } catch (e) { print('Error mengambil riwayat stunting: $e'); } } } catch (e) { print('Error dalam _initializeData: $e'); // Coba ambil data dari SharedPreferences jika ada kesalahan _fallbackToStoredData(); } finally { setState(() { _isLoading = false; }); } } // Metode untuk mengambil data anak dari user_data yang tersimpan Future _fetchChildDataFromUserData() async { try { final prefs = await SharedPreferences.getInstance(); final userData = prefs.getString('user_data'); if (userData != null && userData.contains('"anak"')) { print('Mencoba mendapatkan data anak dari user_data...'); try { final parsedUserData = json.decode(userData); if (parsedUserData != null && parsedUserData['anak'] != null && parsedUserData['anak'] is List && (parsedUserData['anak'] as List).isNotEmpty) { final children = parsedUserData['anak'] as List; if (children.isNotEmpty) { // Ambil data anak pertama final firstChild = children.first; if (firstChild != null && firstChild['id'] != null) { _selectedAnakId = firstChild['id']; _selectedAnakName = firstChild['nama'] ?? ''; _selectedGender = firstChild['jenis_kelamin'] ?? 'L'; // Simpan data anak ke SharedPreferences await prefs.setInt('selected_anak_id', _selectedAnakId!); await prefs.setString('selected_anak_name', _selectedAnakName); await prefs.setString('selected_anak_gender', _selectedGender); print('Berhasil mendapatkan ID anak dari user_data: $_selectedAnakId, nama: $_selectedAnakName'); // Pre-fill nama _controllers['Nama Anak']!.text = _selectedAnakName; // Fetch anak data manually dari user_data if (firstChild['tinggi_badan'] != null || firstChild['tinggi'] != null) { final tinggiStr = firstChild['tinggi_badan'] ?? firstChild['tinggi'] ?? '0'; final tinggi = double.tryParse(tinggiStr.toString()) ?? 0.0; if (tinggi > 0) { _controllers['Tinggi Badan (cm)']!.text = tinggi.toString(); await prefs.setDouble('latest_tinggi_badan_${_selectedAnakId}', tinggi); } } if (firstChild['berat_badan'] != null || firstChild['berat'] != null) { final beratStr = firstChild['berat_badan'] ?? firstChild['berat'] ?? '0'; final berat = double.tryParse(beratStr.toString()) ?? 0.0; if (berat > 0) { _controllers['Berat Badan (kg)']!.text = berat.toString(); await prefs.setDouble('latest_berat_badan_${_selectedAnakId}', berat); } } if (firstChild['umur_bulan'] != null || firstChild['usia'] != null) { final usiaStr = firstChild['umur_bulan'] ?? firstChild['usia'] ?? ''; _controllers['Usia (bulan)']!.text = usiaStr.toString(); await prefs.setString('latest_usia_${_selectedAnakId}', usiaStr.toString()); } } } } } catch (e) { print('Error parsing user_data: $e'); } } else { print('user_data tidak ditemukan atau tidak berisi data anak'); _fallbackToStoredData(); } } catch (e) { print('Error dalam _fetchChildDataFromUserData: $e'); _fallbackToStoredData(); } } // Metode untuk menggunakan data yang sudah tersimpan di SharedPreferences void _fallbackToStoredData() async { try { final prefs = await SharedPreferences.getInstance(); // Coba ambil data tersimpan untuk anak yang dipilih if (_selectedAnakId != null) { print('Menggunakan data tersimpan untuk anak $_selectedAnakId'); // Ambil nama anak _selectedAnakName = prefs.getString('selected_anak_name') ?? ''; _selectedGender = prefs.getString('selected_anak_gender') ?? 'L'; if (_selectedAnakName.isNotEmpty) { _controllers['Nama Anak']!.text = _selectedAnakName; } // Ambil tinggi, berat, dan usia tersimpan final tinggi = prefs.getDouble('latest_tinggi_badan_${_selectedAnakId}'); final berat = prefs.getDouble('latest_berat_badan_${_selectedAnakId}'); final usia = prefs.getString('latest_usia_${_selectedAnakId}'); if (tinggi != null && tinggi > 0) { _controllers['Tinggi Badan (cm)']!.text = tinggi.toString(); } if (berat != null && berat > 0) { _controllers['Berat Badan (kg)']!.text = berat.toString(); } if (usia != null && usia.isNotEmpty) { _controllers['Usia (bulan)']!.text = usia; } } else { print('Tidak ada ID anak yang dipilih untuk fallback data'); } } catch (e) { print('Error dalam _fallbackToStoredData: $e'); } } // Refresh token untuk memastikan autentikasi berfungsi Future _refreshToken() async { try { // Gunakan getCurrentUser yang sudah ada di AuthService untuk refresh token secara tidak langsung final result = await _authService.getCurrentUser(); if (result['success'] == true) { print('Token berhasil di-refresh melalui getCurrentUser'); return true; } else { // Jika getCurrentUser gagal, coba dengan pendekatan alternatif final prefs = await SharedPreferences.getInstance(); final savedNik = prefs.getString('nik'); final savedPassword = prefs.getString('saved_password'); if (savedNik != null && savedPassword != null) { try { final loginResult = await _authService.login( nik: savedNik, password: savedPassword, ); if (loginResult['success'] == true) { print('Token berhasil di-refresh melalui login ulang'); return true; } } catch (e) { print('Error saat login ulang: $e'); } } print('Gagal refresh token'); return false; } } catch (e) { print('Error saat refresh token: $e'); return false; } } @override void dispose() { _controllers.forEach((key, controller) => controller.dispose()); _animationController.dispose(); super.dispose(); } Future _selectDate(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: _tanggalPemeriksaan, firstDate: DateTime(2020), lastDate: DateTime.now(), builder: (context, child) { return Theme( data: ThemeData.light().copyWith( colorScheme: ColorScheme.light( primary: Colors.red.shade700, onPrimary: Colors.white, surface: Colors.white, onSurface: Colors.black, ), ), child: child!, ); }, ); if (picked != null && picked != _tanggalPemeriksaan) { setState(() { _tanggalPemeriksaan = picked; }); } } Future _submitForm() async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); try { // Jika anakId tidak ada, tampilkan error if (_selectedAnakId == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error: Data anak tidak tersedia'), backgroundColor: Colors.red, ), ); setState(() => _isLoading = false); return; } // Get form values double? tinggi = double.tryParse(_controllers['Tinggi Badan (cm)']!.text); String usia = _controllers['Usia (bulan)']!.text; double? berat = double.tryParse(_controllers['Berat Badan (kg)']!.text); // Check usia requirement - hanya anak berusia 24-60 bulan yang bisa dicek int? usiaInt = int.tryParse(usia); if (usiaInt == null || usiaInt < 24 || usiaInt > 60) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Pemeriksaan stunting hanya untuk anak berusia 24-60 bulan'), backgroundColor: Colors.red, duration: Duration(seconds: 5), ), ); setState(() => _isLoading = false); return; } // Jika tidak ada perkembanganId, gunakan nilai default if (_selectedPerkembanganId == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Peringatan: Data perkembangan terbaru tidak tersedia. Hasilnya mungkin tidak akurat.'), backgroundColor: Colors.orange, ), ); } if (tinggi != null && usia.isNotEmpty && berat != null) { // Hitung status stunting via API final result = await _stuntingService.calculateStuntingStatus( usia: usia, tinggiBadan: tinggi, beratBadan: berat, ); String status; if (result['success'] == true) { status = result['finalStatus']; print('Status stunting: $status'); } else { // Jika API gagal, gunakan perhitungan lokal status = _stuntingController.getZScoreStatus(tinggi, usiaInt, _selectedGender); print('API gagal, menggunakan status lokal: $status'); } // Simpan data stunting ke API final storeResult = await _stuntingService.createStuntingData( anakId: _selectedAnakId!, tanggal: _tanggalPemeriksaan.toIso8601String().split('T')[0], usia: usia, perkembanganId: _selectedPerkembanganId ?? 0, // Gunakan 0 jika tidak ada perkembanganId status: status, ); if (storeResult['success'] == true) { print('Data stunting berhasil disimpan'); } else { print('Gagal menyimpan data stunting: ${storeResult['message']}'); } // Perbarui riwayat stunting _riwayatStunting = await _stuntingService.getStuntingByAnakId(_selectedAnakId!); // Urutkan riwayat stunting berdasarkan tanggal terbaru _riwayatStunting.sort((a, b) => b.tanggalPemeriksaan.compareTo(a.tanggalPemeriksaan)); // Tambahkan nama anak ke riwayat stunting yang tidak memiliki nama if (_riwayatStunting.isNotEmpty) { _riwayatStunting = _riwayatStunting.map((data) { if (data.namaPasien == null || data.namaPasien!.isEmpty) { return StuntingData( id: data.id, anakId: data.anakId, perkembanganId: data.perkembanganId, tanggalPemeriksaan: data.tanggalPemeriksaan, usia: data.usia, tinggiBadan: data.tinggiBadan, beratBadan: data.beratBadan, status: data.status, catatan: data.catatan, gender: data.gender, namaPasien: _selectedAnakName, lingkarKepala: data.lingkarKepala, ); } return data; }).toList(); } // Tampilkan dialog hasil (menggunakan status dari API jika tersedia) _showResultDialog(status, tinggi, berat, usiaInt); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error: Data tidak valid'), backgroundColor: Colors.red, ), ); } } catch (e) { print('Error during form submission: $e'); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Terjadi kesalahan: $e'), backgroundColor: Colors.red, ), ); } finally { setState(() { _isLoading = false; }); } } } void _showResultDialog(String status, double tinggi, double berat, int usia) { final statusDetails = _stuntingService.getStatusDetails(status); showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), title: Row( children: [ Icon( statusDetails['icon'] == 'warning' ? Icons.warning : (statusDetails['icon'] == 'warning_amber' ? Icons.warning_amber : Icons.check_circle), color: Color(statusDetails['color']), ), SizedBox(width: 8), Text('Hasil Pemeriksaan', style: TextStyle(fontSize: 18)), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Status: $status', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: Color(statusDetails['color']), ), ), SizedBox(height: 8), Text('Tinggi: $tinggi cm'), Text('Berat: $berat kg'), Text('Usia: $usia bulan'), SizedBox(height: 16), Text(statusDetails['message']), ], ), actions: [ Container( width: double.infinity, child: TextButton( style: TextButton.styleFrom( backgroundColor: Colors.red.shade700, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), padding: EdgeInsets.symmetric(vertical: 12), ), onPressed: () => Navigator.pop(context), child: Text('TUTUP', style: TextStyle(fontWeight: FontWeight.bold)), ), ), ], ), ); } @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; return Scaffold( backgroundColor: Colors.grey[100], appBar: AppBar( backgroundColor: Colors.red.shade700, elevation: 0, title: Text( 'Stunting', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), leading: IconButton( icon: Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), ), actions: [ // Tambahkan tombol riwayat di AppBar if (_riwayatStunting.isNotEmpty) Container( margin: EdgeInsets.only(right: 8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8), ), child: IconButton( icon: Stack( alignment: Alignment.center, children: [ Icon( Icons.history_edu, color: Colors.white, size: 26, ), Positioned( right: 0, top: 0, child: Container( padding: EdgeInsets.all(2), decoration: BoxDecoration( color: Colors.amber, shape: BoxShape.circle, ), constraints: BoxConstraints( minWidth: 14, minHeight: 14, ), child: Text( _riwayatStunting.length.toString(), style: TextStyle( color: Colors.red.shade900, fontSize: 8, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), ), ], ), onPressed: () => _showRiwayatDialog(), tooltip: 'Lihat Riwayat Stunting', ), ), ], ), body: _isLoading ? Center(child: CircularProgressIndicator(color: Colors.red.shade700)) : RefreshIndicator( color: Colors.red.shade700, onRefresh: () async { // Tampilkan indikator memuat ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Memperbarui data...'), duration: Duration(seconds: 1), backgroundColor: Colors.red.shade700, ), ); // Reset form dan muat ulang data await _initializeData(); // Notifikasi selesai if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Data berhasil diperbarui'), backgroundColor: Colors.green, duration: Duration(seconds: 2), ), ); } }, child: FadeTransition( opacity: _fadeAnimation, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, decoration: BoxDecoration( color: Colors.red.shade700, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30), ), boxShadow: [ BoxShadow( color: Colors.red.shade200.withOpacity(0.5), spreadRadius: 2, blurRadius: 10, offset: Offset(0, 3), ), ], ), padding: EdgeInsets.fromLTRB(20, 0, 20, 30), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Form Pengukuran', style: TextStyle( fontSize: screenSize.width * 0.055, fontWeight: FontWeight.bold, color: Colors.white, ), ), SizedBox(height: 8), Text( _selectedAnakName.isNotEmpty ? 'Lengkapi form di bawah untuk mendeteksi stunting $_selectedAnakName' : 'Lengkapi form di bawah untuk mendeteksi stunting', style: TextStyle( fontSize: screenSize.width * 0.035, color: Colors.white.withOpacity(0.9), ), ), ], ), ), Padding( padding: EdgeInsets.fromLTRB(16, 24, 16, 16), child: Card( elevation: 8, shadowColor: Colors.grey.withOpacity(0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Padding( padding: EdgeInsets.all(20), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Date selector Text( "Tanggal Pemeriksaan", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey[700], ), ), SizedBox(height: 8), InkWell( onTap: () => _selectDate(context), child: Container( padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(10), ), child: Row( children: [ Icon(Icons.calendar_today, color: Colors.red.shade700), SizedBox(width: 12), Text( '${_tanggalPemeriksaan.day}/${_tanggalPemeriksaan.month}/${_tanggalPemeriksaan.year}', style: TextStyle(fontSize: 16), ), ], ), ), ), SizedBox(height: 24), // Form fields with better grouping _buildFormSection("Data Anak"), // Form fields ..._controllers.entries.map((entry) { final isNumberField = entry.key.contains('(kg)') || entry.key.contains('(cm)') || entry.key.contains('(bulan)'); IconData fieldIcon; if (entry.key.contains('Nama')) { fieldIcon = Icons.person; } else if (entry.key.contains('Usia')) { fieldIcon = Icons.calendar_month; } else if (entry.key.contains('Tinggi')) { fieldIcon = Icons.height; } else if (entry.key.contains('Berat')) { fieldIcon = Icons.monitor_weight; } else { fieldIcon = Icons.note; } return Padding( padding: const EdgeInsets.only(bottom: 16.0), child: TextFormField( controller: entry.value, decoration: InputDecoration( labelText: entry.key, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.red.shade700, width: 2), ), prefixIcon: Icon(fieldIcon, color: Colors.red.shade700), floatingLabelStyle: TextStyle(color: Colors.red.shade700), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), filled: true, fillColor: Colors.grey.shade100, // Semua field berwarna abu-abu enabled: false, // Semua field tidak bisa diubah disabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.grey.shade300), ), ), readOnly: true, // Semua field read only keyboardType: isNumberField ? TextInputType.number : TextInputType.text, maxLines: 1, inputFormatters: isNumberField ? [FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))] : null, validator: (value) { if (value == null || value.isEmpty) { return '${entry.key} tidak boleh kosong'; } if (isNumberField && value != null && value.isNotEmpty) { try { final numValue = double.parse(value); if (entry.key.contains('Tinggi')) { if (numValue < 30 || numValue > 200) { return 'Tinggi badan harus antara 30-200 cm'; } } if (entry.key.contains('Berat')) { if (numValue < 1 || numValue > 100) { return 'Berat badan harus antara 1-100 kg'; } } if (entry.key.contains('Usia')) { if (numValue < 0 || numValue > 60) { return 'Usia harus antara 0-60 bulan'; } } } catch (e) { return 'Masukkan angka yang valid'; } } return null; }, ), ); }).toList(), SizedBox(height: 24), // Submit button Container( width: double.infinity, height: 54, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red.shade700, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 4, ), onPressed: _isLoading ? null : _submitForm, child: _isLoading ? SizedBox( height: 24, width: 24, child: CircularProgressIndicator( strokeWidth: 2.5, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.save), SizedBox(width: 8), Text( 'SIMPAN & ANALISIS', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ), ), ], ), ), ), ), ), // Info panel Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Card( color: Colors.blue.shade50, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), elevation: 2, shadowColor: Colors.blue.withOpacity(0.3), child: Padding( padding: EdgeInsets.all(16), child: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.blue.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( Icons.info_outline, color: Colors.blue.shade700, size: 36, ), ), SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Apa itu stunting?', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: Colors.blue.shade900, ), ), SizedBox(height: 6), Text( 'Stunting adalah kondisi gagal tumbuh pada anak akibat kekurangan gizi kronis dan infeksi berulang yang terjadi pada 1000 hari pertama kehidupan.', style: TextStyle( fontSize: 14, color: Colors.blue.shade900, ), ), ], ), ), ], ), ), ), ), ], ), ), ), ), ); } // Helper untuk membuat section pada form Widget _buildFormSection(String title) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.red.shade800, ), ), Divider( color: Colors.red.shade200, thickness: 1, ), SizedBox(height: 16), ], ); } // Tambahkan metode baru untuk menampilkan dialog riwayat void _showRiwayatDialog() { if (_riwayatStunting.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Tidak ada riwayat pemeriksaan')) ); return; } // Pastikan data diurutkan berdasarkan tanggal terbaru sebelum ditampilkan List sortedData = List.from(_riwayatStunting); sortedData.sort((a, b) => b.tanggalPemeriksaan.compareTo(a.tanggalPemeriksaan)); // Debug urutan data print('Data terurut - urutan 3 data pertama:'); for (int i = 0; i < min(3, sortedData.length); i++) { print('${i+1}. Tanggal: ${sortedData[i].tanggalPemeriksaan}, Status: ${sortedData[i].status}'); } // Variabel untuk pagination int _currentPage = 0; final int _itemsPerPage = 3; final int _totalPages = (sortedData.length / _itemsPerPage).ceil(); showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setState) { // Hitung range data untuk halaman saat ini final startIndex = _currentPage * _itemsPerPage; final endIndex = min(startIndex + _itemsPerPage, sortedData.length); final currentItems = sortedData.sublist(startIndex, endIndex); return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Container( padding: EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row( children: [ Container( padding: EdgeInsets.all(4), decoration: BoxDecoration( color: Colors.red.shade50, shape: BoxShape.circle, ), child: Icon( Icons.history_edu, color: Colors.red.shade700, size: 24, ), ), SizedBox(width: 10), Text( 'Riwayat Pemeriksaan', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Spacer(), IconButton( icon: Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), ], ), Divider(), // Daftar item Container( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.5, ), child: ListView.builder( shrinkWrap: true, itemCount: currentItems.length, itemBuilder: (context, index) { final data = currentItems[index]; return _buildRiwayatStuntingDialogItem(data); }, ), ), // Pagination controls if (_totalPages > 1) Padding( padding: const EdgeInsets.only(top: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // Tombol Previous IconButton( icon: Icon(Icons.arrow_back_ios, size: 18), onPressed: _currentPage > 0 ? () { setState(() { _currentPage--; }); } : null, color: _currentPage > 0 ? Colors.red.shade700 : Colors.grey, ), // Indikator halaman Text( 'Halaman ${_currentPage + 1} dari $_totalPages', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.grey[800], ), ), // Tombol Next IconButton( icon: Icon(Icons.arrow_forward_ios, size: 18), onPressed: _currentPage < _totalPages - 1 ? () { setState(() { _currentPage++; }); } : null, color: _currentPage < _totalPages - 1 ? Colors.red.shade700 : Colors.grey, ), ], ), ), ], ), ), ); }, ), ); } // Widget untuk item riwayat dalam dialog Widget _buildRiwayatStuntingDialogItem(StuntingData data) { final statusDetails = _stuntingService.getStatusDetails(data.status); // Gunakan nama anak yang sedang aktif jika namaPasien null pada data final namaPasien = data.namaPasien ?? _selectedAnakName; return Card( elevation: 2, margin: EdgeInsets.symmetric(vertical: 6), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: ListTile( contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), leading: Container( width: 40, height: 40, decoration: BoxDecoration( color: Color(statusDetails['color']).withOpacity(0.1), shape: BoxShape.circle, ), child: Center( child: Icon( statusDetails['icon'] == 'warning' ? Icons.warning : (statusDetails['icon'] == 'warning_amber' ? Icons.warning_amber : Icons.check_circle), color: Color(statusDetails['color']), size: 24, ), ), ), title: Text( '${namaPasien.isNotEmpty ? namaPasien : _selectedAnakName}', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Status: ${data.status}', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Color(statusDetails['color']), ), ), Text( 'Tanggal: ${_formatDate(data.tanggalPemeriksaan)}', style: TextStyle(fontSize: 13), ), Text( 'TB: ${data.tinggiBadan} cm | BB: ${data.beratBadan} kg | Usia: ${data.usia}', style: TextStyle(fontSize: 13), ), ], ), trailing: Icon(Icons.arrow_forward_ios, size: 16), onTap: () { // Tutup dialog riwayat Navigator.pop(context); // Tampilkan detail stunting _showStuntingDetailDialog(data); }, ), ); } // Format tanggal String _formatDate(DateTime date) { return '${date.day}/${date.month}/${date.year}'; } // Dialog untuk menampilkan detail stunting yang dipilih void _showStuntingDetailDialog(StuntingData data) { final statusDetails = _stuntingService.getStatusDetails(data.status); // Gunakan nama anak yang sedang aktif jika namaPasien null pada data final namaPasien = data.namaPasien ?? _selectedAnakName; showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), title: Row( children: [ Icon( statusDetails['icon'] == 'warning' ? Icons.warning : (statusDetails['icon'] == 'warning_amber' ? Icons.warning_amber : Icons.check_circle), color: Color(statusDetails['color']), ), SizedBox(width: 8), Expanded( child: Text('Detail Pemeriksaan', style: TextStyle(fontSize: 18)), ), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Tanggal: ${_formatDate(data.tanggalPemeriksaan)}', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), SizedBox(height: 8), Text( 'Status: ${data.status}', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: Color(statusDetails['color']), ), ), SizedBox(height: 12), _detailRow('Nama', namaPasien.isNotEmpty ? namaPasien : _selectedAnakName), _detailRow('Tinggi Badan', '${data.tinggiBadan} cm'), _detailRow('Berat Badan', '${data.beratBadan} kg'), _detailRow('Usia', '${data.usia} bulan'), SizedBox(height: 16), Text( 'Keterangan:', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), ), SizedBox(height: 4), Text(statusDetails['message']), if (data.catatan != null && data.catatan!.isNotEmpty) ...[ SizedBox(height: 8), Text( 'Catatan Dokter:', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), ), SizedBox(height: 4), Text(data.catatan!), ], ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Tutup'), ), ], ), ); } // Helper untuk row pada detail dialog Widget _detailRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 100, child: Text( label, style: TextStyle( fontWeight: FontWeight.w500, color: Colors.grey[700], ), ), ), SizedBox(width: 8), Expanded( child: Text( value, style: TextStyle( fontWeight: FontWeight.w500, ), ), ), ], ), ); } // Helper untuk mencari nilai dalam struktur data bersarang void _searchValueInNestedMap(Map map, String key, Function(dynamic value) callback) { if (map.containsKey(key)) { callback(map[key]); } for (var value in map.values) { if (value is Map) { _searchValueInNestedMap(value, key, callback); } else if (value is List) { for (var item in value) { if (item is Map) { _searchValueInNestedMap(item, key, callback); } } } } } }