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; class EditPetugasPage extends StatefulWidget { final Map data; const EditPetugasPage({super.key, required this.data}); @override State createState() => _EditPetugasPageState(); } class _EditPetugasPageState extends State { final _nama = TextEditingController(); final _email = TextEditingController(); final _noHp = TextEditingController(); String? _role; String? _desaId; String? _dusunId; // State untuk menampung pesan error validasi String? _errNama, _errEmail, _errNoHp; bool _loading = false; List> desaList = []; List> dusunList = []; final String url = "http://ta.myhost.id/E31230549/mposyandu_api/petugas/update_petugas.php"; final String desaUrl = "http://ta.myhost.id/E31230549/mposyandu_api/desa/get_desa.php"; final String dusunUrl = "http://ta.myhost.id/E31230549/mposyandu_api/dusun/get_dusun.php"; @override void initState() { super.initState(); _nama.text = widget.data["nama"]?.toString() ?? ""; _email.text = widget.data["email"]?.toString() ?? ""; _noHp.text = widget.data["no_hp"]?.toString() ?? ""; _role = widget.data["role"]?.toString(); _desaId = (widget.data["desa_id"] == null || widget.data["desa_id"].toString() == "0") ? null : widget.data["desa_id"].toString(); _dusunId = (widget.data["dusun_id"] == null || widget.data["dusun_id"].toString() == "0") ? null : widget.data["dusun_id"].toString(); _initData(); } Future _initData() async { setState(() => _loading = true); await _fetchDesa(); if (_desaId != null && _desaId != "") { await _fetchDusun(_desaId!); } setState(() => _loading = false); } Future _fetchDesa() async { try { final res = await http.get(Uri.parse(desaUrl)); if (res.statusCode == 200) { final jsonData = json.decode(res.body); if (jsonData["success"] == true) { setState(() { desaList = List>.from(jsonData["data"]); }); } } } catch (e) { debugPrint("ERROR DESA: $e"); } } Future _fetchDusun(String dId) async { try { final res = await http.get(Uri.parse("$dusunUrl?desa_id=$dId")); if (res.statusCode == 200) { final jsonData = json.decode(res.body); if (jsonData["success"] == true) { setState(() { dusunList = List>.from(jsonData["data"]); }); } } } catch (e) { debugPrint("ERROR DUSUN: $e"); } } // Fungsi validasi sebelum melakukan update data bool _isValid() { setState(() { // 1. Validasi Nama _errNama = _nama.text.trim().isEmpty ? "Nama tidak boleh kosong" : null; // 2. Validasi Email final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (_email.text.trim().isEmpty) { _errEmail = "Email tidak boleh kosong"; } else if (!emailRegex.hasMatch(_email.text.trim())) { _errEmail = "Format email salah (misal: nama@email.com)"; } else { _errEmail = null; } // 3. Validasi No HP (Wajib berada di rentang 10 sampai 13 digit) final noHpLength = _noHp.text.trim().length; if (_noHp.text.trim().isEmpty) { _errNoHp = "Nomor HP tidak boleh kosong"; } else if (noHpLength < 10 || noHpLength > 13) { _errNoHp = "No HP harus berjumlah 10-13 digit"; } else { _errNoHp = null; } }); return _errNama == null && _errEmail == null && _errNoHp == null; } Future _update() async { if (!_isValid()) return; // Batalkan proses jika input tidak lolos validasi setState(() => _loading = true); try { final res = await http.post( Uri.parse(url), body: { "id": widget.data["id"].toString(), "nama": _nama.text.trim(), "email": _email.text.trim(), "no_hp": _noHp.text.trim(), "role": _role ?? "", "desa_id": _role == "kader" ? (_desaId ?? "") : "", "dusun_id": _role == "kader" ? (_dusunId ?? "") : "", }, ); final data = json.decode(res.body); if (data["success"] == true) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text("Berhasil update", style: GoogleFonts.poppins(fontSize: 12))), ); Navigator.pop(context, true); } } else { _showSimpleError(data["message"] ?? "Gagal update data"); } } catch (e) { debugPrint("Update Error: $e"); _showSimpleError("Terjadi kesalahan jaringan"); } finally { if (mounted) setState(() => _loading = false); } } void _showSimpleError(String msg) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(msg, style: GoogleFonts.poppins(fontSize: 12)))); } @override Widget build(BuildContext context) { final bool isKader = _role == "kader"; final bool isAdmin = widget.data["role"] == "admin"; return Scaffold( backgroundColor: const Color(0xfff5f6fa), appBar: AppBar( leading: const BackButton(color: Colors.white), title: Text("", style: GoogleFonts.poppins(color: Colors.white, fontSize: 16)), backgroundColor: Colors.blue, ), body: Center( child: SingleChildScrollView( child: Column( children: [ Text( "Edit Data Petugas", style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black, ), ), const SizedBox(height: 16), Container( width: 500, margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: const [ BoxShadow(color: Colors.black12, blurRadius: 12) ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _label("Nama Lengkap"), _input(_nama, error: _errNama), const SizedBox(height: 16), _label("Email"), _input(_email, error: _errEmail, type: TextInputType.emailAddress), const SizedBox(height: 16), _label("Role"), _dropdown( value: _role, items: const [ {"id": "admin", "nama": "admin"}, {"id": "kader", "nama": "kader"}, {"id": "bidan", "nama": "bidan"} ], onChanged: isAdmin ? null : (v) { setState(() { _role = v; if (_role != "kader") { _desaId = null; _dusunId = null; } }); }, disabled: isAdmin, ), const SizedBox(height: 16), _label("Desa"), _dropdown( value: isKader ? _desaId : null, items: desaList .map((e) => { "id": e["id"].toString(), "nama": e["nama_desa"] }) .toList(), onChanged: isKader ? (v) { setState(() { _desaId = v; _dusunId = null; dusunList = []; }); if (v != null) _fetchDusun(v); } : null, disabled: !isKader, hint: _loading ? "Memuat desa..." : "Pilih Desa", ), const SizedBox(height: 16), _label("Dusun"), _dropdown( value: isKader ? _dusunId : null, items: dusunList .map((e) => { "id": e["id"].toString(), "nama": e["nama_dusun"] }) .toList(), onChanged: isKader ? (v) => setState(() => _dusunId = v) : null, disabled: !isKader, hint: _loading ? "Memuat dusun..." : "Pilih Dusun", ), const SizedBox(height: 16), _label("No HP"), _input(_noHp, error: _errNoHp, type: TextInputType.number, limit: 13, isNumberOnly: true), const SizedBox(height: 32), SizedBox( width: double.infinity, child: OutlinedButton( onPressed: _loading ? null : _update, style: OutlinedButton.styleFrom( backgroundColor: Colors.white, side: const BorderSide(color: Colors.orange, width: 2), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), child: _loading ? const SizedBox( height: 15, width: 15, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.orange)) : Text("Simpan Perubahan", style: GoogleFonts.poppins( color: Colors.orange, fontWeight: FontWeight.bold, fontSize: 12)), ), ), ], ), ), const SizedBox(height: 20), ], ), ), ), ); } Widget _label(String t) => Padding( padding: const EdgeInsets.only(bottom: 6), child: Text(t, style: GoogleFonts.poppins(fontWeight: FontWeight.w600, fontSize: 12)), ); Widget _input( TextEditingController c, { String? error, TextInputType type = TextInputType.text, int? limit, bool isNumberOnly = false, }) => TextField( controller: c, maxLength: limit, keyboardType: type, inputFormatters: isNumberOnly ? [FilteringTextInputFormatter.digitsOnly] : null, style: GoogleFonts.poppins(fontSize: 12), decoration: InputDecoration( errorText: error, errorStyle: GoogleFonts.poppins(color: Colors.red, fontSize: 10), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: Colors.grey)), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: Colors.orange, width: 2)), ).copyWith(counterText: ""), ); Widget _dropdown({ required String? value, required List> items, required Function(String?)? onChanged, bool disabled = false, String hint = "Pilih", }) { String? validValue; if (value != null) { for (var item in items) { if (item["id"].toString() == value.toString()) { validValue = item["id"].toString(); break; } } } return DropdownButtonFormField( isExpanded: true, value: validValue, hint: Text(hint, style: GoogleFonts.poppins(fontSize: 12)), style: GoogleFonts.poppins( fontSize: 12, color: disabled ? Colors.grey : Colors.black), decoration: InputDecoration( filled: disabled, fillColor: disabled ? Colors.grey[200] : Colors.white, contentPadding: const EdgeInsets.symmetric(horizontal: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), ), items: items.map((e) { return DropdownMenuItem( value: e["id"].toString(), child: Text(e["nama"].toString(), overflow: TextOverflow.ellipsis), ); }).toList(), onChanged: onChanged, ); } }