MIF_E31230549/lib/kader/data_kehadiran.dart

566 lines
24 KiB
Dart

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<DataKehadiranPosyanduPage> createState() =>
_DataKehadiranPosyanduPageState();
}
class _DataKehadiranPosyanduPageState extends State<DataKehadiranPosyanduPage> {
List<Map<String, dynamic>> _data = [];
List<Map<String, dynamic>> _filteredData = [];
bool _isLoading = true;
int _currentPage = 0;
final int _rowsPerPage = 5;
final Map<String, TextEditingController> _catatanController = {};
final TextEditingController _searchController = TextEditingController();
int? _userId;
String? _dusunId;
@override
void initState() {
super.initState();
_initializeData();
_searchController.addListener(_onSearch);
}
Future<void> _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<String> 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<void> _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<void> _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<void> _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<Map<String, dynamic>>((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<bool> _saveData(Map<String, dynamic> 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<Map<String, dynamic>> 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<String>(
value: "Hadir",
groupValue: item["status"],
onChanged: (isEditing && !isLolos)
? (v) => setState(
() => item["status"] = v)
: null,
),
Text("Hadir",
style: GoogleFonts.poppins(
fontSize: 12)),
Radio<String>(
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),
),
),
),
);
}
}