import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import '../layout/main_layout.dart'; import 'kader_drawer.dart'; import '../pages/login_page.dart'; import '../kader/crud_balita/tambah_balita.dart'; import '../kader/crud_balita/edit_balita.dart'; import '../kader/dashboard_kader.dart'; class DataBalitaPage extends StatefulWidget { const DataBalitaPage({super.key}); @override State createState() => _DataBalitaPageState(); } class _DataBalitaPageState extends State { List _data = []; List _filteredData = []; bool _isLoading = true; int _currentPage = 0; final int _rowsPerPage = 5; final TextEditingController _searchC = TextEditingController(); Timer? _debounce; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _checkLogin(); _fetchData(); }); _searchC.addListener(_onSearchChanged); } @override void dispose() { _searchC.dispose(); _debounce?.cancel(); super.dispose(); } String _formatJenisKelamin(String jk) { if (jk.toLowerCase() == "p") return "Perempuan"; if (jk.toLowerCase() == "l") return "Laki-laki"; return jk; } String _formatTempatTanggal(String? tempat, String? tanggal) { String tempatLahir = (tempat == null || tempat.isEmpty) ? "-" : tempat; String tanggalLahirFormatted = "-"; if (tanggal != null && tanggal.isNotEmpty && tanggal != "-") { try { final date = DateTime.parse(tanggal); const bulanIndo = [ "", "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember" ]; tanggalLahirFormatted = "${date.day} ${bulanIndo[date.month]} ${date.year}"; } catch (e) { tanggalLahirFormatted = tanggal; } } return "$tempatLahir, $tanggalLahirFormatted"; } String _formatUsiaDariTanggal(String? tanggalLahir) { if (tanggalLahir == null || tanggalLahir.isEmpty || tanggalLahir == "-") return "-"; try { final lahir = DateTime.parse(tanggalLahir); final sekarang = DateTime.now(); int tahun = sekarang.year - lahir.year; int bulan = sekarang.month - lahir.month; int hari = sekarang.day - lahir.day; if (hari < 0) { final prevMonth = DateTime(sekarang.year, sekarang.month, 0); hari += prevMonth.day; bulan--; } if (bulan < 0) { bulan += 12; tahun--; } return "$tahun thn $bulan bln $hari hr"; } catch (e) { return "-"; } } void _onSearchChanged() { if (_debounce?.isActive ?? false) _debounce!.cancel(); _debounce = Timer(const Duration(milliseconds: 400), () { final keyword = _searchC.text.toLowerCase(); setState(() { _currentPage = 0; _filteredData = _data.where((item) { final nama = (item["nama"] ?? "").toString().toLowerCase(); final nik = (item["nik_balita"] ?? "").toString().toLowerCase(); final namaIbu = (item["nama_ibu"] ?? "").toString().toLowerCase(); return nama.contains(keyword) || nik.contains(keyword) || namaIbu.contains(keyword); }).toList(); }); }); } Future _checkLogin() async { final prefs = await SharedPreferences.getInstance(); final isLogin = prefs.getBool('isLogin') ?? false; if (!isLogin && mounted) { Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (_) => const LoginPage(fromGuard: true)), (route) => false, ); } } Future _fetchData() async { setState(() => _isLoading = true); try { final prefs = await SharedPreferences.getInstance(); final rawDusunId = prefs.get('dusun_id'); String dusunId = rawDusunId?.toString() ?? ""; final url = Uri.parse( "http://ta.myhost.id/E31230549/mposyandu_api/balita/get_balita.php?dusun_id=$dusunId"); final response = await http.get(url); final jsonData = json.decode(response.body); if (jsonData["success"] == true) { setState(() { _data = jsonData["data"]; _filteredData = _data; _isLoading = false; }); } else { setState(() { _data = []; _filteredData = []; _isLoading = false; }); } } catch (e) { debugPrint("Error: $e"); setState(() => _isLoading = false); } } Future _confirmDelete(String id) async { final yes = await showDialog( context: context, builder: (_) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), title: Text("Konfirmasi", style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.bold)), content: Text("Hapus data balita ini?", style: GoogleFonts.poppins(fontSize: 12)), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: Text("Batal", style: GoogleFonts.poppins(fontSize: 12))), OutlinedButton( onPressed: () => Navigator.pop(context, true), style: OutlinedButton.styleFrom( side: const BorderSide(color: Colors.red), shape: const StadiumBorder()), child: Text("Hapus", style: GoogleFonts.poppins( color: Colors.red, fontSize: 12, fontWeight: FontWeight.bold)), ), ], ), ); if (yes == true) _deleteData(id); } Future _deleteData(String id) async { try { final url = Uri.parse( "http://ta.myhost.id/E31230549/mposyandu_api/balita/hapus_balita.php"); final res = await http.post(url, body: {"id": id}); final data = json.decode(res.body); if (data["success"] == true) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Data dihapus", style: GoogleFonts.poppins(fontSize: 12)))); _fetchData(); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Gagal: $e", style: GoogleFonts.poppins(fontSize: 12)))); } } List get _paginatedData { final start = _currentPage * _rowsPerPage; final end = start + _rowsPerPage; if (start >= _filteredData.length) return []; return _filteredData.sublist( start, end > _filteredData.length ? _filteredData.length : end); } @override Widget build(BuildContext context) { final totalPages = _filteredData.isEmpty ? 1 : (_filteredData.length / _rowsPerPage).ceil(); return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (_) => const DashboardKaderPage()), (route) => false); }, child: MainLayout( title: "", drawer: const KaderDrawer(), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Text("Data Balita", style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w600)), const SizedBox(height: 16), Row( children: [ Expanded( child: TextField( controller: _searchC, style: GoogleFonts.poppins(fontSize: 12), decoration: InputDecoration( hintText: "Cari Nama/NIK/Ibu...", hintStyle: GoogleFonts.poppins(fontSize: 12), prefixIcon: const Icon(Icons.search, size: 20), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10)), contentPadding: const EdgeInsets.symmetric(vertical: 8), ), ), ), const SizedBox(width: 10), OutlinedButton.icon( onPressed: () async { final result = await Navigator.push( context, MaterialPageRoute( builder: (_) => const TambahBalitaPage())); if (result == true) _fetchData(); }, icon: const Icon(Icons.add, color: Colors.blueAccent, size: 18), label: Text("Tambah", style: GoogleFonts.poppins( color: Colors.blueAccent, fontSize: 12, fontWeight: FontWeight.bold)), style: OutlinedButton.styleFrom( side: const BorderSide( color: Colors.blueAccent, width: 1.5), shape: const StadiumBorder(), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 10), ), ), ], ), const SizedBox(height: 20), Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : _filteredData.isEmpty ? Center( child: Text("Tidak ada data balita", style: GoogleFonts.poppins(fontSize: 12))) : ListView.builder( itemCount: _paginatedData.length, itemBuilder: (context, index) { final item = _paginatedData[index]; String alamatLengkap = "Desa ${item['nama_desa']}, Dusun ${item['nama_dusun']}, ${item['alamat_detail']}"; String statusBalita = (item["status"] ?? "belum lolos") .toString() .toLowerCase(); bool isLolos = statusBalita == "lolos"; return Container( margin: const EdgeInsets.only(bottom: 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 6, offset: Offset(0, 3)) ], ), child: Column( children: [ Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: const BoxDecoration( color: Colors.blueAccent, borderRadius: BorderRadius.vertical( top: Radius.circular(12)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item["nama"] ?? "-", style: GoogleFonts.poppins( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 4), _rowHeader( "Ibu", item["nama_ibu"]), _rowHeader( "Ayah", item["nama_ayah"]), ], ), ), // VALIDASI STATUS (LABEL) Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: isLolos ? Colors.green : Colors.red, borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white, width: 1), ), child: Text( statusBalita.toUpperCase(), style: GoogleFonts.poppins( fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold), ), ) ], ), ), Padding( padding: const EdgeInsets.all(12), child: Column( children: [ _rowHeaderContent( "NIK Balita", item["nik_balita"], isDark: true), _rowHeaderContent( "Alamat", alamatLengkap, isDark: true), _rowContent( "Jenis Kelamin", _formatJenisKelamin( item["jenis_kelamin"] ?? "-")), _rowContent( "Tempat Tgl Lahir", _formatTempatTanggal( item["tempat_lahir"], item["tanggal_lahir"])), _rowContent( "Usia", _formatUsiaDariTanggal( item["tanggal_lahir"])), _rowContent("Anak Ke", item["anak_ke"]?.toString()), _rowContent("Berat Lahir", "${item["berat_lahir"] ?? "-"} Kg"), _rowContent("Panjang Lahir", "${item["panjang_lahir"] ?? "-"} Cm"), _rowContent( "Tgl Daftar", _formatTempatTanggal("", item["tanggal_daftar"]) .split(", ")[1]), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ _smallButton( text: "Edit", icon: Icons.edit, color: Colors.orange, onPressed: () async { final result = await Navigator.push( context, MaterialPageRoute( builder: (_) => EditBalitaPage( data: item))); if (result == true) _fetchData(); }, ), const SizedBox(width: 8), _smallButton( text: "Hapus", icon: Icons.delete, color: Colors.red, onPressed: () => _confirmDelete( item["id"].toString()), ), ], ) ], ), ), ], ), ); }, ), ), // Pagination Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( border: Border(top: BorderSide(color: Colors.grey.shade200))), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Halaman ${_currentPage + 1} dari $totalPages", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500)), Row( children: [ IconButton( icon: const Icon(Icons.chevron_left), onPressed: _currentPage == 0 ? null : () => setState(() => _currentPage--)), IconButton( icon: const Icon(Icons.chevron_right), onPressed: _currentPage >= totalPages - 1 ? null : () => setState(() => _currentPage++)), ], ), ], ), ), ], ), ), ), ); } Widget _rowHeader(String label, dynamic value) { return Row( children: [ SizedBox( width: 50, child: Text(label, style: GoogleFonts.poppins(fontSize: 12, color: Colors.white))), Text(": ", style: GoogleFonts.poppins(fontSize: 12, color: Colors.white)), Expanded( child: Text("${value ?? '-'}", style: GoogleFonts.poppins( fontSize: 12, color: Colors.white, fontWeight: FontWeight.w500))), ], ); } Widget _rowHeaderContent(String label, dynamic value, {bool isDark = false}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 110, child: Text(label, style: GoogleFonts.poppins( fontSize: 12, color: isDark ? Colors.black : Colors.white, fontWeight: FontWeight.w600))), Text(": ", style: GoogleFonts.poppins( fontSize: 12, color: isDark ? Colors.black : Colors.white)), Expanded( child: Text("${value ?? '-'}", style: GoogleFonts.poppins( fontSize: 12, color: isDark ? Colors.black : Colors.white))), ], ), ); } Widget _rowContent(String label, dynamic value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ SizedBox( width: 110, child: Text(label, style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, fontWeight: FontWeight.w500))), Text(": ", style: GoogleFonts.poppins(fontSize: 12, color: Colors.black)), Expanded( child: Text("${value ?? '-'}", style: GoogleFonts.poppins(fontSize: 12, color: Colors.black))), ], ), ); } Widget _smallButton( {required String text, required IconData icon, required Color color, required VoidCallback onPressed}) { return OutlinedButton.icon( onPressed: onPressed, icon: Icon(icon, size: 14, color: color), label: Text(text, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.bold, color: color)), style: OutlinedButton.styleFrom( side: BorderSide(color: color), shape: const StadiumBorder(), padding: const EdgeInsets.symmetric(horizontal: 12), minimumSize: const Size(0, 32)), ); } }