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_kehadiran/riwayat_kehadiran.dart'; import '../kader/dashboard_kader.dart'; class DataKehadiranPosyanduPage extends StatefulWidget { const DataKehadiranPosyanduPage({super.key}); @override State createState() => _DataKehadiranPosyanduPageState(); } class _DataKehadiranPosyanduPageState extends State { List> _data = []; List> _filteredData = []; bool _isLoading = true; int _currentPage = 0; final int _rowsPerPage = 5; final Map _catatanController = {}; final TextEditingController _searchController = TextEditingController(); int? _userId; String? _dusunId; @override void initState() { super.initState(); _initializeData(); _searchController.addListener(_onSearch); } Future _initializeData() async { debugPrint("=== Memulai Inisialisasi Data ==="); await _checkLogin(); await _getUser(); await _fetchData(); } @override void dispose() { _searchController.dispose(); _catatanController.forEach((_, c) => c.dispose()); super.dispose(); } String _formatTanggal(String? dateStr) { if (dateStr == null || dateStr == "-" || dateStr.isEmpty) return "-"; try { DateTime dateTime = DateTime.parse(dateStr); List months = [ "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember" ]; return "${dateTime.day} ${months[dateTime.month - 1]} ${dateTime.year}"; } catch (e) { return dateStr; } } Future _getUser() async { try { final prefs = await SharedPreferences.getInstance(); String? idString = prefs.getString('id_user'); dynamic rawDusunId = prefs.get('dusun_id'); if (mounted) { setState(() { _userId = int.tryParse(idString ?? ""); _dusunId = rawDusunId?.toString(); }); } } catch (e) { debugPrint("DEBUG ERROR (SharedPrefs): $e"); } } void _onSearch() { final query = _searchController.text.toLowerCase(); setState(() { _currentPage = 0; _filteredData = _data.where((item) { final nama = (item["nama_balita"] ?? "").toLowerCase(); final ibu = (item["ibu_suami"] ?? "").toLowerCase(); return nama.contains(query) || ibu.contains(query); }).toList(); }); } Future _checkLogin() async { final prefs = await SharedPreferences.getInstance(); if (!(prefs.getBool('isLogin') ?? false)) { if (!mounted) return; Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (_) => const LoginPage(fromGuard: true)), (route) => false); } } Future _fetchData() async { if (_dusunId == null || _dusunId == "0" || _dusunId == "null") { if (mounted) setState(() => _isLoading = false); return; } if (mounted) setState(() => _isLoading = true); try { final url = "http://ta.myhost.id/E31230549/mposyandu_api/kehadiran/get_kehadiran_balita.php?dusun_id=$_dusunId"; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final jsonData = json.decode(response.body); if (jsonData["success"] == true) { final List list = jsonData["data"] ?? []; _data = list.map>((e) { bool belumInput = (e["status_hadir"] == "-" || e["status_hadir"] == null); String idBalita = e["id_balita"].toString(); _catatanController[idBalita] = TextEditingController( text: (e["keterangan"] == "-" || e["keterangan"] == null) ? "" : e["keterangan"].toString()); return { ...e, "isEditing": belumInput, "sudahInput": !belumInput, "status": belumInput ? null : e["status_hadir"].toString(), }; }).toList(); _filteredData = List.from(_data); } } } catch (e) { debugPrint("ERROR FETCH: $e"); } if (mounted) setState(() => _isLoading = false); } Future _saveData(Map item) async { String idBalita = item["id_balita"].toString(); try { final response = await http.post( Uri.parse( "http://ta.myhost.id/E31230549/mposyandu_api/kehadiran/simpan_kehadiran_balita.php"), body: { "jadwal_id": item["id_jadwal"].toString(), "balita_id": idBalita, "status_hadir": item["status"] ?? "-", "keterangan": _catatanController[idBalita]?.text ?? "", "dicatat_oleh": _userId?.toString() ?? "0", }, ); final result = json.decode(response.body); return result["success"] == true; } catch (e) { return false; } } List> get _paginatedData { final start = _currentPage * _rowsPerPage; final end = (start + _rowsPerPage > _filteredData.length) ? _filteredData.length : start + _rowsPerPage; return _filteredData.isEmpty ? [] : _filteredData.sublist(start, 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: [ Center( child: Text("Data Kehadiran Posyandu", style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold))), const SizedBox(height: 14), TextField( controller: _searchController, style: GoogleFonts.poppins(fontSize: 12), decoration: InputDecoration( hintText: "Cari nama balita / ibu...", hintStyle: GoogleFonts.poppins(fontSize: 12), prefixIcon: const Icon(Icons.search, size: 20), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10)), isDense: true, ), ), const SizedBox(height: 14), Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: _paginatedData.isEmpty ? 1 : _paginatedData.length, itemBuilder: (context, index) { if (_paginatedData.isEmpty) { return Center( child: Text("Data tidak ditemukan", style: GoogleFonts.poppins(fontSize: 12))); } final item = _paginatedData[index]; String idBalita = item["id_balita"].toString(); bool isEditing = item["isEditing"] ?? false; bool sudahInput = item["sudahInput"] ?? false; // LOGIKA STATUS LOLOS / BELUM LOLOS String rawStatus = (item["status_balita"] ?? "") .toString() .toLowerCase(); bool isLolos = rawStatus == "lolos"; // Warna Header (Hijau jika lolos, Merah jika belum lolos) Color headerColor = isLolos ? Colors.green.shade600 : Colors.red.shade600; return Container( margin: const EdgeInsets.only(bottom: 14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: Colors.white, boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 8, offset: Offset(0, 4)) ], ), child: Column( children: [ Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: headerColor, borderRadius: const BorderRadius.vertical( top: Radius.circular(12))), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item["nama_balita"] ?? "-", style: GoogleFonts.poppins( color: Colors.white, fontSize: 13, fontWeight: FontWeight.w600)), Text( "Nama Orang Tua: ${item["ibu_suami"] ?? "-"}", style: GoogleFonts.poppins( color: Colors.white, fontSize: 11)), ], ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.white, width: 0.5)), child: Text( isLolos ? "LOLOS" : "BELUM LOLOS", style: GoogleFonts.poppins( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), ), ) ], ), ), Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _row("Tanggal", _formatTanggal(item["tanggal"])), _row("Lokasi", item["lokasi"] ?? "-"), _row("Jam", item["jam"] ?? "-"), const SizedBox(height: 8), Text("Status Kehadiran", style: GoogleFonts.poppins( fontWeight: FontWeight.w600, fontSize: 12)), Row( children: [ Radio( value: "Hadir", groupValue: item["status"], onChanged: (isEditing && !isLolos) ? (v) => setState( () => item["status"] = v) : null, ), Text("Hadir", style: GoogleFonts.poppins( fontSize: 12)), Radio( value: "Tidak Hadir", groupValue: item["status"], onChanged: (isEditing && !isLolos) ? (v) => setState( () => item["status"] = v) : null, ), Text("Tidak Hadir", style: GoogleFonts.poppins( fontSize: 12)), ], ), TextField( controller: _catatanController[idBalita], enabled: isEditing && !isLolos, style: GoogleFonts.poppins(fontSize: 12), maxLines: 2, decoration: InputDecoration( hintText: isLolos ? "Terkunci (Lolos)" : "Catatan...", hintStyle: GoogleFonts.poppins(fontSize: 12), isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8)), filled: isLolos, fillColor: isLolos ? Colors.grey.shade100 : Colors.transparent, ), ), const SizedBox(height: 12), Row( children: [ _smallButton( icon: Icons.history, text: "Riwayat", onTap: () async { await Navigator.push( context, MaterialPageRoute( builder: (_) => RiwayatKehadiranPage( balitaId: idBalita, namaBalita: item[ "nama_balita"]))); _fetchData(); }, ), _smallButton( icon: Icons.edit, text: "Edit", onTap: (isLolos || isEditing || !sudahInput) ? null : () => setState(() => item["isEditing"] = true), ), _smallButton( icon: Icons.save, text: "Simpan", onTap: (isLolos || !isEditing || !sudahInput) ? null : () async { final success = await _saveData(item); if (success) setState(() => item["isEditing"] = false); _showMsg(success ? "Perubahan disimpan" : "Gagal menyimpan"); }, ), _smallButton( icon: Icons.input, text: "Input", onTap: (isLolos || sudahInput || !isEditing) ? null : () async { if (item["status"] == null) { _showMsg( "Pilih status kehadiran dulu!"); return; } final success = await _saveData(item); if (success) setState(() { item["isEditing"] = false; item["sudahInput"] = true; }); _showMsg(success ? "Data berhasil diinput" : "Gagal input data"); }, ), ], ), ], ), ), ], ), ); }, ), ), // 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++), ), ], ), ], ), ), ], ), ), ), ); } void _showMsg(String msg) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(msg, style: GoogleFonts.poppins(fontSize: 12)))); } Widget _row(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ SizedBox( width: 100, child: Text(label, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600))), Expanded( child: Text(": $value", style: GoogleFonts.poppins(fontSize: 12))), ], ), ); } Widget _smallButton( {required IconData icon, required String text, required VoidCallback? onTap}) { Color color = onTap == null ? Colors.grey : Colors.blueAccent; return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 2), child: OutlinedButton.icon( onPressed: onTap, icon: Icon(icon, size: 14, color: color), label: Text(text, style: GoogleFonts.poppins( fontSize: 9, fontWeight: FontWeight.bold, color: color)), style: OutlinedButton.styleFrom( side: BorderSide(color: color, width: 1), shape: const StadiumBorder(), padding: EdgeInsets.zero, minimumSize: const Size(0, 32), ), ), ), ); } }