import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; class EditIbuPage extends StatefulWidget { final Map data; const EditIbuPage({super.key, required this.data}); @override State createState() => _EditIbuPageState(); } class _EditIbuPageState extends State { final _formKey = GlobalKey(); // ================= CONTROLLER ================= final nikC = TextEditingController(); final noKkC = TextEditingController(); final namaC = TextEditingController(); final namaSuamiC = TextEditingController(); final tempatLahirC = TextEditingController(); final tglC = TextEditingController(); final pendidikanC = TextEditingController(); final pekerjaanC = TextEditingController(); final alamatC = TextEditingController(); final hpC = TextEditingController(); final emailC = TextEditingController(); final desaC = TextEditingController(); final dusunC = TextEditingController(); String? selectedGolDarah; String? selectedAgama; int? selectedDesa; int? selectedDusun; bool _isSaving = false; bool isKader = false; String? _emailError; final List golDarahList = const ['A', 'B', 'AB', 'O', '-']; final List agamaList = const [ 'Islam', 'Kristen', 'Katolik', 'Hindu', 'Buddha', 'Konghucu' ]; @override void initState() { super.initState(); nikC.text = widget.data["nik"]?.toString() ?? ""; noKkC.text = widget.data["no_kk"]?.toString() ?? ""; namaC.text = widget.data["nama"]?.toString() ?? ""; namaSuamiC.text = widget.data["nama_suami"]?.toString() ?? ""; tempatLahirC.text = widget.data["tempat_lahir"]?.toString() ?? ""; tglC.text = widget.data["tanggal_lahir"]?.toString() ?? ""; pendidikanC.text = widget.data["pendidikan"]?.toString() ?? ""; pekerjaanC.text = widget.data["pekerjaan"]?.toString() ?? ""; alamatC.text = widget.data["alamat_detail"]?.toString() ?? ""; hpC.text = widget.data["no_hp"]?.toString() ?? ""; emailC.text = widget.data["email"]?.toString() ?? ""; selectedGolDarah = widget.data["golongan_darah"]; selectedAgama = widget.data["agama"]; selectedDesa = int.tryParse(widget.data["desa_id"].toString()); selectedDusun = int.tryParse(widget.data["dusun_id"].toString()); _checkUserRole(); } @override void dispose() { nikC.dispose(); noKkC.dispose(); namaC.dispose(); namaSuamiC.dispose(); tempatLahirC.dispose(); tglC.dispose(); pendidikanC.dispose(); pekerjaanC.dispose(); alamatC.dispose(); hpC.dispose(); emailC.dispose(); desaC.dispose(); dusunC.dispose(); super.dispose(); } Future _checkUserRole() async { final prefs = await SharedPreferences.getInstance(); final role = prefs.getString('role'); if (role == 'kader') setState(() => isKader = true); _getNamaWilayah(); } Future _getNamaWilayah() async { try { final resDesa = await http .get(Uri.parse("http://ta.myhost.id/E31230549/mposyandu_api/desa/get_desa.php")); final dataDesa = _safeJson(resDesa.body); if (dataDesa != null) { final list = dataDesa is List ? dataDesa : dataDesa['data']; final d = list.firstWhere( (e) => int.parse(e['id'].toString()) == selectedDesa, orElse: () => null); if (d != null) desaC.text = d['nama_desa'].toString(); } if (selectedDesa != null) { final resDusun = await http.get(Uri.parse( "http://ta.myhost.id/E31230549/mposyandu_api/dusun/get_dusun.php?desa_id=$selectedDesa")); final dataDusun = _safeJson(resDusun.body); if (dataDusun != null) { final list = dataDusun is List ? dataDusun : dataDusun['data']; final du = list.firstWhere( (e) => int.parse(e['id'].toString()) == selectedDusun, orElse: () => null); if (du != null) dusunC.text = du['nama_dusun'].toString(); } } } catch (e) { debugPrint("Gagal memuat wilayah: $e"); } } dynamic _safeJson(String body) { try { if (body.trim().startsWith("<")) return null; return json.decode(body); } catch (e) { return null; } } Future _update() async { setState(() => _emailError = null); if (!_formKey.currentState!.validate()) return; setState(() => _isSaving = true); final Map bodyData = { "id": widget.data["id"].toString(), "nik": nikC.text.trim(), "no_kk": noKkC.text.trim(), "nama": namaC.text.trim(), "nama_suami": namaSuamiC.text.trim(), "tempat_lahir": tempatLahirC.text.trim(), "tanggal_lahir": tglC.text.trim(), "golongan_darah": selectedGolDarah ?? "-", "pendidikan": pendidikanC.text.trim(), "pekerjaan": pekerjaanC.text.trim(), "agama": selectedAgama ?? "", "no_hp": hpC.text.trim(), "email": emailC.text.trim(), "desa_id": selectedDesa.toString(), "dusun_id": selectedDusun.toString(), "alamat_detail": alamatC.text.trim(), }; try { final res = await http.post( Uri.parse("http://ta.myhost.id/E31230549/mposyandu_api/ibu/update_ibu.php"), body: bodyData, ); final responseData = _safeJson(res.body); if (responseData != null) { if (responseData["success"] == true) { if (!mounted) return; _showMsg("Data berhasil diperbarui"); Navigator.pop(context, true); } else { String serverMsg = responseData["message"] ?? "Gagal memperbarui"; if (serverMsg.toLowerCase().contains("email")) { setState(() => _emailError = "Email sudah terdaftar"); } else { _showMsg("Gagal: $serverMsg"); } } } else { _showMsg("Terjadi kesalahan pada server (Format Response Salah)"); } } catch (e) { _showMsg("Error Koneksi: $e"); } finally { if (mounted) setState(() => _isSaving = false); } } void _showMsg(String msg) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(msg, style: GoogleFonts.poppins(fontSize: 12))), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.blue, foregroundColor: Colors.white, elevation: 0, title: Text("", style: GoogleFonts.poppins(fontSize: 18, fontWeight: FontWeight.w600)), ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 950), child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: const [ BoxShadow(blurRadius: 10, color: Colors.black12) ]), child: Form( key: _formKey, child: Column( children: [ // ================= JUDUL DI DALAM CARD ================= Padding( padding: const EdgeInsets.only(bottom: 30), child: Text( "Edit Data Ibu", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 18, color: Colors.black, fontWeight: FontWeight.bold, ), ), ), // ======================================================== Wrap( spacing: 20, runSpacing: 20, alignment: WrapAlignment.center, children: [ _input("NIK", nikC, isNikOrKk: true), _input("No KK", noKkC, isNikOrKk: true), _input("Nama Ibu", namaC), _input("Nama Suami", namaSuamiC), _input("Tempat Lahir", tempatLahirC), _dateInput(), _dropdown("Gol. Darah", selectedGolDarah, golDarahList, (v) => setState(() => selectedGolDarah = v)), _dropdown("Agama", selectedAgama, agamaList, (v) => setState(() => selectedAgama = v)), _input("Pendidikan", pendidikanC), _input("Pekerjaan", pekerjaanC), _input("No HP", hpC, isPhone: true), _input("Email", emailC, isEmail: true), _input("Desa", desaC, readOnly: true), _input("Dusun", dusunC, readOnly: true), _input("Alamat Detail", alamatC, lines: 2), ], ), const SizedBox(height: 40), SizedBox( width: 300, // Ukuran tombol disesuaikan agar rapi height: 50, child: OutlinedButton( onPressed: _isSaving ? null : _update, style: OutlinedButton.styleFrom( side: const BorderSide(color: Colors.blue, width: 2), shape: const StadiumBorder(), ), child: _isSaving ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( color: Colors.blue, strokeWidth: 2)) : Text("Simpan Perubahan", style: GoogleFonts.poppins( color: Colors.blue, fontSize: 14, fontWeight: FontWeight.bold, )), ), ) ], ), ), ), ), ), ), ); } // Widget Helper _input, _dateInput, _dropdown tetap sama seperti sebelumnya... Widget _input(String label, TextEditingController c, {bool isNikOrKk = false, bool isPhone = false, bool isEmail = false, bool readOnly = false, int lines = 1}) { return SizedBox( width: 280, child: TextFormField( controller: c, readOnly: readOnly, onChanged: (v) { if (isEmail && _emailError != null) { setState(() => _emailError = null); } }, keyboardType: (isNikOrKk || isPhone) ? TextInputType.number : (isEmail ? TextInputType.emailAddress : TextInputType.text), maxLines: lines, inputFormatters: [ if (isNikOrKk || isPhone) FilteringTextInputFormatter.digitsOnly, if (isNikOrKk) LengthLimitingTextInputFormatter(16), if (isPhone) LengthLimitingTextInputFormatter(13), ], style: GoogleFonts.poppins(fontSize: 12), decoration: InputDecoration( labelText: label, filled: readOnly, fillColor: readOnly ? Colors.grey[100] : Colors.white, errorText: isEmail ? _emailError : null, labelStyle: GoogleFonts.poppins(fontSize: 12), errorStyle: GoogleFonts.poppins(color: Colors.red, fontSize: 10), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10))), validator: (v) { if (v == null || v.isEmpty) return "Wajib diisi"; if (isNikOrKk && v.length != 16) return "Harus 16 digit angka"; if (isPhone && (v.length < 10 || v.length > 13)) return "Harus 10 - 13 digit"; if (isEmail) { final emailRegExp = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegExp.hasMatch(v)) return "Format email tidak valid"; } return null; }, ), ); } Widget _dateInput() { return SizedBox( width: 280, child: TextFormField( controller: tglC, readOnly: true, style: GoogleFonts.poppins(fontSize: 12), decoration: InputDecoration( labelText: "Tanggal Lahir", labelStyle: GoogleFonts.poppins(fontSize: 12), errorStyle: GoogleFonts.poppins(color: Colors.red, fontSize: 10), suffixIcon: const Icon(Icons.calendar_today, size: 18), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10))), onTap: () async { DateTime initial = DateTime.tryParse(tglC.text) ?? DateTime(1990, 1, 1); DateTime? p = await showDatePicker( context: context, initialDate: initial, firstDate: DateTime(1950), lastDate: DateTime.now()); if (p != null) { setState(() => tglC.text = p.toIso8601String().split("T").first); } }, validator: (v) => (v == null || v.isEmpty) ? "Wajib diisi" : null, ), ); } Widget _dropdown( String label, String? val, List items, Function(String?) onChg) { return SizedBox( width: 280, child: DropdownButtonFormField( value: val, style: GoogleFonts.poppins(color: Colors.black, fontSize: 12), items: items .map((e) => DropdownMenuItem( value: e, child: Text(e, style: GoogleFonts.poppins(fontSize: 12)))) .toList(), onChanged: onChg, decoration: InputDecoration( labelText: label, labelStyle: GoogleFonts.poppins(fontSize: 12), errorStyle: GoogleFonts.poppins(color: Colors.red, fontSize: 10), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10))), validator: (v) => (v == null || v.isEmpty) ? "Wajib dipilih" : null, ), ); } }