import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; class TambahGiziBalitaPage extends StatefulWidget { final Map balita; const TambahGiziBalitaPage({super.key, required this.balita}); @override State createState() => _TambahGiziBalitaPageState(); } class _TambahGiziBalitaPageState extends State { final _formKey = GlobalKey(); bool _isSaving = false; bool _isLoadingPreview = true; // Menahan transisi widget selama proses hitung API // Controller text form input & field readonly final TextEditingController _bbUController = TextEditingController(); final TextEditingController _tbUController = TextEditingController(); final TextEditingController _bbTbController = TextEditingController(); final TextEditingController _tindakLanjutController = TextEditingController(); final TextEditingController _saranController = TextEditingController(); // Status klasifikasi gizi text yang dihitung dinamis oleh server PHP String? _selectedStatusBBU; String? _selectedStatusTBU; String? _selectedStatusBBTB; String keteranganBBU = "Menghitung..."; String keteranganTBU = "Menghitung..."; String keteranganBBTB = "Menghitung..."; // Master list referensi dropdown status gizi penyeimbang data final List _listBBU = [ 'Gizi Buruk', 'Gizi Kurang', 'Gizi Baik', 'Risiko Gizi Lebih' ]; final List _listTBU = ['Sangat Pendek', 'Pendek', 'Normal', 'Tinggi']; final List _listBBTB = [ 'Sangat Kurus', 'Kurus', 'Normal', 'Gemuk', 'Obesitas' ]; @override void initState() { super.initState(); _ambilPreviewKalkulasiServer(); } // Helper pembersih string angka timbangan gizi anak di card atas String formatAngka(dynamic value, String satuan) { if (value == null || value.toString().trim().isEmpty || value.toString().toLowerCase() == "null") { return "-"; } return "${value.toString()} $satuan"; } // ========================================================================= // Fungsi Pendukung Realtime Preview Kalkulasi Z-Score dari Database Server // ========================================================================= Future _ambilPreviewKalkulasiServer() async { try { final response = await http.post( Uri.parse( "http://ta.myhost.id/E31230549/mposyandu_api/gizi_balita/tambah_gizi_balita.php"), body: { "id_pemeriksaan": widget.balita["id_pemeriksaan"].toString(), "id_balita": widget.balita["id"].toString(), "mode": "preview" // Triger parameter pembeda request mode di PHP }, ); final result = json.decode(response.body); if (result['success'] == true) { double zBBU = double.tryParse(result['zscore_bb_u']?.toString() ?? '0.0') ?? 0.0; double zTBU = double.tryParse(result['zscore_tb_u']?.toString() ?? '0.0') ?? 0.0; double zBBTB = double.tryParse(result['zscore_bb_tb']?.toString() ?? '0.0') ?? 0.0; _bbUController.text = zBBU.toStringAsFixed(2); _tbUController.text = zTBU.toStringAsFixed(2); _bbTbController.text = zBBTB.toStringAsFixed(2); String serverBBU = result['status_bbu']?.toString() ?? 'Gizi Baik'; String serverTBU = result['status_tbu']?.toString() ?? 'Normal'; String serverBBTB = result['status_bbtb']?.toString() ?? 'Normal'; // Penyelaras kata kunci jika seandainya backend mengirimkan teks "Normal" pada kategori BB/U if (serverBBU == "Normal") serverBBU = "Gizi Baik"; setState(() { // Sistem pengaman asersi: hanya masukkan value ke dropdown jika string terdaftar di list widget _selectedStatusBBU = _listBBU.contains(serverBBU) ? serverBBU : null; _selectedStatusTBU = _listTBU.contains(serverTBU) ? serverTBU : null; _selectedStatusBBTB = _listBBTB.contains(serverBBTB) ? serverBBTB : null; keteranganBBU = _getSDStatus(zBBU); keteranganTBU = _getSDStatus(zTBU); keteranganBBTB = _getSDStatus(zBBTB); _isLoadingPreview = false; // Mematikan putaran loading & memunculkan form komponen gizi }); } else { setState(() => _isLoadingPreview = false); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Info: ${result['message']}"))); } } } catch (e) { setState(() { keteranganBBU = "Gagal memuat"; keteranganTBU = "Gagal memuat"; keteranganBBTB = "Gagal memuat"; _isLoadingPreview = false; }); } } // ========================================================================= // Perbaikan Inti: Logika Pemisah Angka Minus & Plus Terhadap Nilai Median // ========================================================================= String _getSDStatus(double z) { if (z < -3) return "Di bawah -3 SD"; if (z < -2) return "-3 SD s/d -2 SD"; // Jika nilai desimal minus (misal -0.07), langsung kunci ke rentang kiri median if (z < 0) return "-2 SD s/d < Median"; // Jika nilai desimal nol atau positif kecil, masukkan ke rentang kanan median if (z <= 1) return "Median s/d +1 SD"; if (z <= 2) return "Di atas +1 SD s/d +2 SD"; return "Di atas +2 SD"; } // ========================================================================= // Fungsi Penyimpanan Data Gizi Transaksional Bersih ke Server API // ========================================================================= Future _simpanData() async { if (!_formKey.currentState!.validate()) return; setState(() => _isSaving = true); try { final response = await http.post( Uri.parse( "http://ta.myhost.id/E31230549/mposyandu_api/gizi_balita/tambah_gizi_balita.php"), body: { "id_pemeriksaan": widget.balita["id_pemeriksaan"].toString(), "id_balita": widget.balita["id"].toString(), "tindak_lanjut": _tindakLanjutController.text, "saran": _saranController.text, "status_bbu": _selectedStatusBBU, "status_tbu": _selectedStatusTBU, "status_bbtb": _selectedStatusBBTB, }, ); final result = json.decode(response.body); if (result['success'] == true) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Data Berhasil Disimpan"))); Navigator.pop(context, true); } else { throw result['message']; } } catch (e) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text("Error: $e"))); } finally { if (mounted) setState(() => _isSaving = false); } } @override Widget build(BuildContext context) { final bb = formatAngka(widget.balita['bb'], "kg"); final tb = formatAngka(widget.balita['tb'], "cm"); return Scaffold( backgroundColor: Colors.grey[100], appBar: AppBar( backgroundColor: Colors.blue, iconTheme: const IconThemeData(color: Colors.white), elevation: 0, ), body: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500), child: Form( key: _formKey, child: Column( children: [ // Judul Besar Halaman Tengah Atas Text( "Tambah Data Gizi Balita", style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black), ), const SizedBox(height: 15), // Card Informasi Identitas Balita (Warna Biru) Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(12)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.balita["nama"] ?? "-", style: GoogleFonts.poppins( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15), ), const SizedBox(height: 4), const Divider(color: Colors.white54, thickness: 1), const SizedBox(height: 4), Text("BB / TB: $bb / $tb", style: GoogleFonts.poppins( color: Colors.white, fontSize: 13)), ], ), ), const SizedBox(height: 15), // Blok Container Putih Pembungkus Seluruh Form Gizi Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10) ], ), child: _isLoadingPreview ? const Padding( padding: EdgeInsets.all(30.0), child: Center(child: CircularProgressIndicator()), ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 1. Seksi Z-Score BB/U _buildSection( "Z-Score BB/U", _bbUController, keteranganBBU, _listBBU, _selectedStatusBBU, (v) => setState(() => _selectedStatusBBU = v)), // 2. Seksi Z-Score TB/U _buildSection( "Z-Score TB/U", _tbUController, keteranganTBU, _listTBU, _selectedStatusTBU, (v) => setState(() => _selectedStatusTBU = v)), // 3. Seksi Z-Score BB/TB _buildSection( "Z-Score BB/TB", _bbTbController, keteranganBBTB, _listBBTB, _selectedStatusBBTB, (v) => setState(() => _selectedStatusBBTB = v)), const Divider(height: 30, thickness: 1), // Input Field Catatan Tindak Lanjut _buildLabel("Tindak Lanjut"), _buildTextField(_tindakLanjutController, "Rencana tindakan..."), const SizedBox(height: 10), // Input Field Catatan Saran _buildLabel("Saran"), _buildTextField( _saranController, "Saran untuk orang tua..."), const SizedBox(height: 25), // Tombol Aksi Simpan Data Bergaya Outline (Putih-Biru Stadium) SizedBox( width: double.infinity, child: OutlinedButton( onPressed: _isSaving ? null : _simpanData, style: OutlinedButton.styleFrom( backgroundColor: Colors.white, side: const BorderSide( color: Colors.blue, width: 2), shape: const StadiumBorder(), padding: const EdgeInsets.symmetric( vertical: 14)), child: _isSaving ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.blue)) : Text("Simpan Data Gizi", style: GoogleFonts.poppins( color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 14)), ), ), ], ), ), ], ), ), ), ), ), ); } // Widget generator baris isian Z-score (Form, Label SD, dan Dropdown Kategori) Widget _buildSection(String title, TextEditingController controller, String sd, List items, String? val, Function(String?) onChanged) { // Penentu warna latar background dinamis teks keterangan rentang SD Color bgKuningMuda = sd.contains('atas') ? Colors.orange[50]! : Colors.amber[50]!; Color borderKuning = sd.contains('atas') ? Colors.orange[200]! : Colors.amber[200]!; Color textKuningTua = sd.contains('atas') ? Colors.orange[900]! : Colors.amber[900]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLabel(title), Row( children: [ // Sisi Kiri: Nilai Angka Z-Score (Read-Only) Expanded( flex: 2, child: TextFormField( controller: controller, readOnly: true, decoration: _inputDeco(isFilled: true), style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.bold))), const SizedBox(width: 10), // Sisi Kanan: Teks Status Deviasi (Warna Kuning/Orange Kotak) Expanded( flex: 3, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 11), decoration: BoxDecoration( color: bgKuningMuda, borderRadius: BorderRadius.circular(8), border: Border.all(color: borderKuning)), child: Text(sd, style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.bold, color: textKuningTua)))), ], ), const SizedBox(height: 10), // Dropdown Pemilihan Status Klasifikasi Dibawahnya DropdownButtonFormField( value: val, hint: Text("Pilih Status $title", style: GoogleFonts.poppins(fontSize: 12, color: Colors.grey[600])), isExpanded: true, items: items .map((e) => DropdownMenuItem( value: e, child: Text(e, style: GoogleFonts.poppins(fontSize: 12)))) .toList(), onChanged: onChanged, decoration: _inputDeco(), validator: (v) => v == null ? "Pilih salah satu status" : null, ), const SizedBox(height: 10), ], ); } Widget _buildLabel(String t) => Padding( padding: const EdgeInsets.only(top: 10, bottom: 6), child: Text(t, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87))); Widget _buildTextField(TextEditingController c, String h) => TextFormField( controller: c, maxLines: 2, style: GoogleFonts.poppins(fontSize: 12), decoration: _inputDeco(hint: h)); InputDecoration _inputDeco({String? hint, bool isFilled = false}) => InputDecoration( hintText: hint, hintStyle: GoogleFonts.poppins(fontSize: 11, color: Colors.grey[400]), filled: true, fillColor: isFilled ? Colors.blue[50] : Colors.white, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey[300]!)), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Colors.blue)), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Colors.red)), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Colors.red, width: 2)), ); }