MIF_E31230549/lib/bidan/data_gizi_balita.dart

532 lines
20 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 '../layout/main_layout.dart';
import 'bidan_drawer.dart';
import '../bidan/crud_data_gizi/tambah_gizi_balita.dart';
import '../bidan/crud_data_gizi/riwayat_gizi_balita.dart';
import '../bidan/dashboard_bidan.dart';
class DataGiziBalitaPage extends StatefulWidget {
const DataGiziBalitaPage({super.key});
@override
State<DataGiziBalitaPage> createState() => _DataGiziBalitaPageState();
}
class _DataGiziBalitaPageState extends State<DataGiziBalitaPage> {
List<Map<String, dynamic>> _dataBalita = [];
bool _isLoading = true;
int _currentPage = 0;
final int _rowsPerPage = 5;
String _searchQuery = "";
@override
void initState() {
super.initState();
fetchData();
}
String formatAngka(dynamic value, String satuan) {
if (value == null ||
value.toString().trim().isEmpty ||
value.toString().toLowerCase() == "null") {
return "-";
}
return "${value.toString()}$satuan";
}
String formatTanggal(String? tgl) {
if (tgl == null || tgl == "-" || tgl.isEmpty) return "-";
try {
DateTime dt = DateTime.parse(tgl);
List<String> bulanIndo = [
"",
"Januari",
"Februari",
"Maret",
"April",
"Mei",
"Juni",
"Juli",
"Agustus",
"September",
"Oktober",
"November",
"Desember"
];
return "${dt.day} ${bulanIndo[dt.month]} ${dt.year}";
} catch (e) {
return tgl;
}
}
// PERBAIKAN: Menghitung usia berdasarkan Tanggal Pemeriksaan, bukan Tanggal Sekarang
String hitungUsia(String? tglLahir, String? tglPeriksa) {
if (tglLahir == null || tglLahir == "-" || tglLahir.isEmpty) return "-";
try {
DateTime lahir = DateTime.parse(tglLahir);
// Menggunakan tanggal pemeriksaan sebagai acuan utama, jika kosong baru pakai waktu sekarang
DateTime acuanPeriksa =
(tglPeriksa != null && tglPeriksa != "-" && tglPeriksa.isNotEmpty)
? DateTime.parse(tglPeriksa)
: DateTime.now();
// Hitung selisih bulan dasar
int bulan = (acuanPeriksa.year - lahir.year) * 12 +
acuanPeriksa.month -
lahir.month;
// Logika pembulatan hari: Jika sisa hari pada bulan berjalan > 15 hari, bulatkan ke atas (+1 bulan)
DateTime tanggalTargetUlangTahun =
DateTime(lahir.year, lahir.month + bulan, lahir.day);
Duration selisihHari = acuanPeriksa.difference(tanggalTargetUlangTahun);
if (selisihHari.inDays > 15) {
bulan += 1;
}
// Pengondisian jika hasil hitung bernilai negatif karena variasi tanggal
if (bulan < 0) bulan = 0;
return "$bulan Bulan";
} catch (e) {
return "-";
}
}
Future<void> fetchData() async {
try {
final response = await http.get(
Uri.parse(
"http://ta.myhost.id/E31230549/mposyandu_api/gizi_balita/get_gizi_balita.php"),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
_dataBalita = List<Map<String, dynamic>>.from(data);
_isLoading = false;
});
} else {
setState(() => _isLoading = false);
}
} catch (e) {
debugPrint("Error Fetch: $e");
setState(() => _isLoading = false);
}
}
List<Map<String, dynamic>> get _filteredData {
return _dataBalita
.where((item) => item["nama"]
.toString()
.toLowerCase()
.contains(_searchQuery.toLowerCase()))
.toList();
}
List<Map<String, dynamic>> 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.length / _rowsPerPage).ceil() == 0
? 1
: (_filteredData.length / _rowsPerPage).ceil();
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const DashboardBidanPage()),
(route) => false);
},
child: MainLayout(
title: "",
drawer: const BidanDrawer(),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Center(
child: Text("Data Gizi Balita",
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black87)),
),
const SizedBox(height: 12),
TextField(
onChanged: (value) => setState(() {
_searchQuery = value;
_currentPage = 0;
}),
style: GoogleFonts.poppins(fontSize: 12),
decoration: InputDecoration(
hintText: "Cari nama balita...",
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(height: 15),
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _filteredData.isEmpty
? Center(
child: Text("Data tidak tersedia",
style: GoogleFonts.poppins(fontSize: 12)))
: ListView.builder(
itemCount: _paginatedData.length,
itemBuilder: (context, index) {
final balita = _paginatedData[index];
final bb = formatAngka(balita["bb"], "kg");
final tb = formatAngka(balita["tb"], "cm");
final lk = formatAngka(balita["lk"], "cm");
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
offset: Offset(0, 4))
],
),
child: Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(15),
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.vertical(
top: Radius.circular(12)),
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
balita["nama"] ?? "-",
style: GoogleFonts.poppins(
color: Colors.white,
fontWeight:
FontWeight.bold,
fontSize: 13)),
),
_badgeHadir(
balita["status_hadir"]),
],
),
const Divider(color: Colors.white54),
_rowWhite("Orang Tua",
balita["nama_orang_tua"] ?? "-"),
_rowWhite("Alamat",
balita["alamat"] ?? "-"),
// PERBAIKAN: Mengirimkan dua parameter (tanggal lahir & tanggal pemeriksaan)
_rowWhite(
"Usia",
hitungUsia(
balita["tanggal_lahir"],
balita[
"tanggal_pemeriksaan"])),
_rowWhite(
"Tgl Pemeriksaan",
formatTanggal(balita[
"tanggal_pemeriksaan"])),
_rowWhite("BB / TB / LK",
"$bb / $tb / $lk"),
_rowWhite("Catatan Kader",
balita["catatan"] ?? "-"),
],
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.all(15),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(12)),
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text("Pemeriksaan Gizi Terakhir",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600,
fontSize: 12)),
const SizedBox(height: 8),
_rowGizi("Z-Score BB/U",
balita["zscore_bb_u"] ?? "-"),
_rowGizi("Z-Score TB/U",
balita["zscore_tb_u"] ?? "-"),
_rowGizi("Z-Score BB/TB",
balita["zscore_bb_tb"] ?? "-"),
const Divider(),
_rowStatus("Status Gizi", balita),
_rowGizi("Tindak Lanjut",
balita["tindak_lanjut"] ?? "-"),
_rowGizi(
"Saran", balita["saran"] ?? "-"),
const SizedBox(height: 12),
Row(
children: [
_smallButton(Icons.add, "Input",
Colors.blue, () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
TambahGiziBalitaPage(
balita:
balita)));
fetchData();
}),
_smallButton(
Icons.history,
"Riwayat",
Colors.deepPurple, () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
RiwayatGiziBalitaPage(
balita:
balita)));
fetchData();
}),
],
)
],
),
),
],
),
);
},
),
),
_buildPagination(totalPages),
],
),
),
),
);
}
Widget _badgeHadir(String? status) {
String text = status ?? "Belum Absen";
Color badgeColor;
if (text.toLowerCase() == "hadir") {
badgeColor = Colors.greenAccent[700]!;
} else if (text.toLowerCase() == "tidak hadir") {
badgeColor = Colors.redAccent;
} else {
badgeColor = Colors.orangeAccent;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: badgeColor.withOpacity(0.3),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white, width: 1),
),
child: Text(
text.toUpperCase(),
style: GoogleFonts.poppins(
fontSize: 10, fontWeight: FontWeight.bold, color: Colors.white),
),
);
}
Widget _rowWhite(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100,
child: Text(label,
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600))),
Text(" : ",
style: GoogleFonts.poppins(color: Colors.white, fontSize: 12)),
Expanded(
child: Text(value,
style:
GoogleFonts.poppins(color: Colors.white, fontSize: 12))),
],
),
);
}
Widget _rowGizi(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(label,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.grey[800]))),
const Text(" : ", style: TextStyle(fontSize: 12)),
Expanded(
child: Text(value,
style:
GoogleFonts.poppins(fontSize: 12, color: Colors.black))),
],
),
);
}
Widget _rowStatus(String label, Map<String, dynamic> balita) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 5),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.orange[50], borderRadius: BorderRadius.circular(8)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("$label :",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.orange[900])),
const SizedBox(height: 4),
Padding(
padding: const EdgeInsets.only(left: 12),
child: Column(
children: [
_rowStatusDetail("BB / U", balita["status_bbu"] ?? "-"),
_rowStatusDetail("TB / U", balita["status_tbu"] ?? "-"),
_rowStatusDetail("BB / TB", balita["status_bbtb"] ?? "-"),
],
),
),
],
),
);
}
Widget _rowStatusDetail(String indikator, String nilai) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
SizedBox(
width: 65,
child: Text(
indikator,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.orange[900]),
),
),
Text(
" : ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.orange[900]),
),
Expanded(
child: Text(
nilai,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.orange[900]),
),
),
],
),
);
}
Widget _smallButton(
IconData icon, String text, Color color, VoidCallback onPressed) {
return Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: OutlinedButton.icon(
onPressed: onPressed,
icon: Icon(icon, size: 14, color: color),
label: Text(text,
style: GoogleFonts.poppins(
fontSize: 12, color: color, fontWeight: FontWeight.w500)),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 8),
side: BorderSide(color: color.withOpacity(0.5)),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
),
);
}
Widget _buildPagination(int totalPages) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Halaman ${_currentPage + 1} dari $totalPages",
style: GoogleFonts.poppins(fontSize: 12)),
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++)),
],
),
],
),
);
}
}