MIF_E31230549/lib/bidan/edukasi.dart

512 lines
15 KiB
Dart

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<DataEdukasiPage> createState() => _DataEdukasiPageState();
}
class _DataEdukasiPageState extends State<DataEdukasiPage> {
// 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<void> 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<dynamic> 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<dynamic> 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<String, dynamic> 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<String> headers,
required List<List<dynamic>> 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<String, dynamic> 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)),
],
);
}
}