import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:flutter_quill/flutter_quill.dart' as quill; // Import Halaman CRUD import '../bidan/crud_edukasi/tambah_edukasi_balita.dart'; import '../bidan/crud_edukasi/tambah_edukasi_ibu_hamil.dart'; import '../bidan/crud_edukasi/edit_edukasi_balita.dart'; import '../bidan/crud_edukasi/edit_edukasi_ibu_hamil.dart'; // Import Dashboard Bidan agar navigasi berfungsi import '../bidan/dashboard_bidan.dart'; import '../layout/main_layout.dart'; import 'bidan_drawer.dart'; class DataEdukasiPage extends StatefulWidget { const DataEdukasiPage({super.key}); @override State createState() => _DataEdukasiPageState(); } class _DataEdukasiPageState extends State { // Base URL untuk API final String baseUrlBalita = "http://ta.myhost.id/E31230549/mposyandu_api/edukasi_balita/"; final String baseUrlIbu = "http://ta.myhost.id/E31230549/mposyandu_api/edukasi_ibu_hamil/"; // Base URL untuk Gambar - Mengarah ke hosting bukan localhost final String baseImageUrl = "http://ta.myhost.id/E31230549/mposyandu_api/upload/edukasi/"; List balitaList = []; List ibuList = []; List filteredBalita = []; List filteredIbu = []; bool isLoading = true; int _currentBalitaPage = 0; int _currentIbuPage = 0; final int _rowsPerPage = 5; final searchBalitaController = TextEditingController(); final searchIbuController = TextEditingController(); @override void initState() { super.initState(); loadAllData(); searchBalitaController.addListener(() { filterBalita(searchBalitaController.text); }); searchIbuController.addListener(() { filterIbu(searchIbuController.text); }); } Future loadAllData() async { setState(() => isLoading = true); await loadBalita(); await loadIbu(); setState(() => isLoading = false); } Future loadBalita() async { try { var response = await http.get(Uri.parse("${baseUrlBalita}get_edukasi_balita.php")); var data = jsonDecode(response.body); if (data["success"]) { setState(() { balitaList = data["data"]; filteredBalita = balitaList; }); } } catch (e) { debugPrint("Error Load Balita: $e"); } } Future loadIbu() async { try { var response = await http.get(Uri.parse("${baseUrlIbu}get_edukasi_ibu_hamil.php")); var data = jsonDecode(response.body); if (data["success"]) { setState(() { ibuList = data["data"]; filteredIbu = ibuList; }); } } catch (e) { debugPrint("Error Load Ibu: $e"); } } void filterBalita(String query) { setState(() { filteredBalita = balitaList .where((item) => item["judul"].toLowerCase().contains(query.toLowerCase())) .toList(); _currentBalitaPage = 0; }); } void filterIbu(String query) { setState(() { filteredIbu = ibuList .where((item) => item["judul"].toLowerCase().contains(query.toLowerCase())) .toList(); _currentIbuPage = 0; }); } Future hapusData(String id, String type) async { String url = type == "balita" ? "${baseUrlBalita}hapus_edukasi_balita.php" : "${baseUrlIbu}hapus_edukasi_ibu_hamil.php"; try { var response = await http.post(Uri.parse(url), body: {"id": id}); var data = jsonDecode(response.body); if (data["status"] == "success") { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Data berhasil dihapus"))); loadAllData(); } } catch (e) { debugPrint("Error Hapus: $e"); } } void confirmDelete(String id, String type) { showDialog( context: context, builder: (_) => AlertDialog( title: const Text("Konfirmasi"), content: const Text("Yakin ingin menghapus data ini?"), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text("Batal")), TextButton( onPressed: () { Navigator.pop(context); hapusData(id, type); }, child: const Text("Hapus", style: TextStyle(color: Colors.red)), ), ], ), ); } Widget buildDescriptionCell(String? description) { String cleanText = description ?? ""; if (cleanText.startsWith('[') && cleanText.endsWith(']')) { try { final List json = jsonDecode(cleanText); final doc = quill.Document.fromJson(json); cleanText = doc.toPlainText().trim(); } catch (e) { debugPrint("Gagal parsing JSON deskripsi: $e"); } } return Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: SizedBox( width: 300, child: Text( cleanText, style: GoogleFonts.poppins(fontSize: 12, height: 1.5), softWrap: true, maxLines: 4, overflow: TextOverflow.ellipsis, ), ), ); } List getPaginatedData(List data, int currentPage) { int start = currentPage * _rowsPerPage; int end = start + _rowsPerPage; if (start >= data.length) return []; return data.sublist(start, end > data.length ? data.length : end); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; // ✅ FIX FINAL: selalu kembali ke Dashboard Bidan Navigator.pushAndRemoveUntil( context, MaterialPageRoute( builder: (_) => const DashboardBidanPage(), ), (route) => false, ); }, child: MainLayout( title: "", drawer: const BidanDrawer(), body: isLoading ? const Center(child: CircularProgressIndicator()) : RefreshIndicator( onRefresh: loadAllData, child: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 20), child: Column( children: [ sectionBalita(), const SizedBox(height: 40), sectionIbu(), ], ), ), ), ), ); } Widget sectionBalita() { int totalPages = (filteredBalita.length / _rowsPerPage).ceil(); if (totalPages == 0) totalPages = 1; return Column( children: [ title("Edukasi Balita"), const SizedBox(height: 15), searchAndAddRow( controller: searchBalitaController, hint: "Cari Judul Edukasi Balita...", onAdd: () async { await Navigator.push( context, MaterialPageRoute( builder: (_) => const TambahEdukasiBalitaPage())); loadBalita(); }, ), const SizedBox(height: 20), tableData( data: getPaginatedData(filteredBalita, _currentBalitaPage), type: "balita", ), paginationControls(_currentBalitaPage, totalPages, (i) => setState(() => _currentBalitaPage = i)), ], ); } Widget sectionIbu() { int totalPages = (filteredIbu.length / _rowsPerPage).ceil(); if (totalPages == 0) totalPages = 1; return Column( children: [ title("Edukasi Ibu Hamil"), const SizedBox(height: 15), searchAndAddRow( controller: searchIbuController, hint: "Cari Judul Edukasi Ibu...", onAdd: () async { await Navigator.push( context, MaterialPageRoute( builder: (_) => const TambahEdukasiBumilPage())); loadIbu(); }, ), const SizedBox(height: 20), tableData( data: getPaginatedData(filteredIbu, _currentIbuPage), type: "ibu", ), paginationControls(_currentIbuPage, totalPages, (i) => setState(() => _currentIbuPage = i)), ], ); } Widget searchAndAddRow({ required TextEditingController controller, required String hint, required VoidCallback onAdd, }) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ searchField(controller, hint), const SizedBox(height: 12), Align( alignment: Alignment.centerRight, child: addBtn(onAdd), ), ], ), ); } Widget tableData({required List data, required String type}) { return Center( child: Container( constraints: const BoxConstraints(maxWidth: 1000), padding: const EdgeInsets.symmetric(horizontal: 16), child: tableCard( headers: const ["Gambar", "Judul", "Deskripsi", "Aksi"], rows: data .map((e) => [ imagePreview(e), SizedBox( width: 120, child: Text(e["judul"] ?? "", style: GoogleFonts.poppins(fontSize: 13))), buildDescriptionCell(e["deskripsi"]), actionButtons(e, type) ]) .toList(), ), ), ); } Widget imagePreview(Map item) { String? fileName = item["gambar"]; if (fileName == null || fileName.isEmpty) { return const Icon(Icons.image_not_supported, color: Colors.grey, size: 40); } String fullUrl = "$baseImageUrl$fileName"; return ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( fullUrl, width: 60, height: 60, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 60, height: 60, color: Colors.grey.shade200, child: const Icon(Icons.broken_image, color: Colors.red, size: 30), ); }, ), ); } Widget title(String text) { return Text(text, style: GoogleFonts.poppins(fontSize: 18, fontWeight: FontWeight.bold)); } Widget searchField(TextEditingController controller, String hint) { return TextField( controller: controller, style: GoogleFonts.poppins(fontSize: 13), decoration: InputDecoration( hintText: hint, prefixIcon: const Icon(Icons.search, size: 20), contentPadding: const EdgeInsets.symmetric(vertical: 15), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), filled: true, fillColor: Colors.white, ), ); } Widget addBtn(VoidCallback onTap) { return SizedBox( height: 45, child: OutlinedButton.icon( onPressed: onTap, style: OutlinedButton.styleFrom( side: const BorderSide(color: Colors.blue), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), icon: const Icon(Icons.add, size: 18, color: Colors.blue), label: Text("Tambah", style: GoogleFonts.poppins( fontSize: 13, color: Colors.blue, fontWeight: FontWeight.bold)), ), ); } Widget tableCard({ required List headers, required List> rows, }) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ) ], ), child: ClipRRect( borderRadius: BorderRadius.circular(15), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: DataTable( headingRowColor: MaterialStateProperty.all(Colors.blue), dataRowMaxHeight: 100, dataRowMinHeight: 70, columnSpacing: 15, columns: headers .map((h) => DataColumn( label: Text(h, style: GoogleFonts.poppins( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13)))) .toList(), rows: rows .map((row) => DataRow( cells: row.map((cell) { if (cell is Widget) return DataCell(cell); return DataCell(Text(cell.toString(), style: GoogleFonts.poppins(fontSize: 12))); }).toList())) .toList(), ), ), ), ); } Widget paginationControls( int currentPage, int totalPages, Function(int) onPageChanged) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Halaman ${currentPage + 1} dari $totalPages", style: GoogleFonts.poppins(fontSize: 12, color: Colors.black54), ), Row( children: [ IconButton( icon: const Icon(Icons.chevron_left), onPressed: currentPage == 0 ? null : () => onPageChanged(currentPage - 1), ), IconButton( icon: const Icon(Icons.chevron_right), onPressed: currentPage >= totalPages - 1 ? null : () => onPageChanged(currentPage + 1), ), ], ), ], ), ); } Widget actionButtons(Map data, String type) { return Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit, color: Colors.orange, size: 18), onPressed: () async { if (type == "balita") { await Navigator.push( context, MaterialPageRoute( builder: (_) => EditEdukasiBalitaPage(data: data))); loadBalita(); } else { await Navigator.push( context, MaterialPageRoute( builder: (_) => EditEdukasiBumilPage(data: data))); loadIbu(); } }), IconButton( icon: const Icon(Icons.delete, color: Colors.red, size: 18), onPressed: () => confirmDelete(data["id"].toString(), type)), ], ); } }