369 lines
14 KiB
Dart
369 lines
14 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:intl/intl.dart';
|
|
import 'package:intl/date_symbol_data_local.dart';
|
|
|
|
class RiwayatKehadiranPage extends StatefulWidget {
|
|
final String balitaId;
|
|
final String namaBalita;
|
|
|
|
const RiwayatKehadiranPage({
|
|
super.key,
|
|
required this.balitaId,
|
|
required this.namaBalita,
|
|
});
|
|
|
|
@override
|
|
State<RiwayatKehadiranPage> createState() => _RiwayatKehadiranPageState();
|
|
}
|
|
|
|
class _RiwayatKehadiranPageState extends State<RiwayatKehadiranPage> {
|
|
List _riwayatOriginal = [];
|
|
List _riwayatFiltered = [];
|
|
bool _isLoading = true;
|
|
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
/// ================= PAGINATION =================
|
|
int _currentPage = 0;
|
|
final int _rowsPerPage = 5;
|
|
|
|
List get _paginatedData {
|
|
if (_riwayatFiltered.isEmpty) return [];
|
|
final start = _currentPage * _rowsPerPage;
|
|
final end = start + _rowsPerPage;
|
|
if (start >= _riwayatFiltered.length) return [];
|
|
return _riwayatFiltered.sublist(
|
|
start,
|
|
end > _riwayatFiltered.length ? _riwayatFiltered.length : end,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
initializeDateFormatting('id_ID', null);
|
|
_fetchRiwayat();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
String _formatTanggal(String? tanggal) {
|
|
if (tanggal == null || tanggal == "" || tanggal == "0000-00-00") {
|
|
return "-";
|
|
}
|
|
try {
|
|
DateTime dt = DateTime.parse(tanggal);
|
|
return DateFormat('d MMMM yyyy', 'id_ID').format(dt);
|
|
} catch (e) {
|
|
return tanggal;
|
|
}
|
|
}
|
|
|
|
void _filterData(String query) {
|
|
setState(() {
|
|
if (query.isEmpty) {
|
|
_riwayatFiltered = _riwayatOriginal;
|
|
} else {
|
|
_riwayatFiltered = _riwayatOriginal.where((item) {
|
|
String tglFormatted =
|
|
_formatTanggal(item["tanggal_posyandu"]).toLowerCase();
|
|
String tglDatabase =
|
|
(item["tanggal_posyandu"] ?? "").toString().toLowerCase();
|
|
return tglFormatted.contains(query.toLowerCase()) ||
|
|
tglDatabase.contains(query.toLowerCase());
|
|
}).toList();
|
|
}
|
|
_currentPage = 0;
|
|
});
|
|
}
|
|
|
|
Future<void> _fetchRiwayat() async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse(
|
|
"http://ta.myhost.id/E31230549/mposyandu_api/kehadiran/get_riwayat_balita.php?balita_id=${widget.balitaId}"),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final res = json.decode(response.body);
|
|
if (res["success"] == true) {
|
|
setState(() {
|
|
_riwayatOriginal = res["data"];
|
|
_riwayatFiltered = res["data"];
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error Fetch Riwayat: ${e.toString()}");
|
|
} finally {
|
|
if (mounted) setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _hapusData(String id) async {
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse(
|
|
"http://ta.myhost.id/E31230549/mposyandu_api/kehadiran/hapus_kehadiran.php"),
|
|
body: {"id": id},
|
|
);
|
|
final res = json.decode(response.body);
|
|
if (res["success"] == true) {
|
|
_fetchRiwayat();
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("Data berhasil dihapus")),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error Hapus: ${e.toString()}");
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final totalPages = _riwayatFiltered.isEmpty
|
|
? 1
|
|
: (_riwayatFiltered.length / _rowsPerPage).ceil();
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xfff4f6fb),
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.blueAccent,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
centerTitle: true,
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () => Navigator.pop(context, true),
|
|
),
|
|
),
|
|
body: _isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: Column(
|
|
children: [
|
|
const SizedBox(height: 14),
|
|
Text(
|
|
"Riwayat Kehadiran",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 18, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
/// SEARCH
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: TextField(
|
|
controller: _searchController,
|
|
onChanged: _filterData,
|
|
style: GoogleFonts.poppins(fontSize: 12),
|
|
decoration: InputDecoration(
|
|
hintText: "Cari tanggal...",
|
|
hintStyle: GoogleFonts.poppins(fontSize: 12),
|
|
prefixIcon: const Icon(Icons.search, size: 18),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 10),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10)),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 15),
|
|
|
|
Expanded(
|
|
child: _riwayatFiltered.isEmpty
|
|
? Center(
|
|
child: Text("Data tidak ditemukan",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12, color: Colors.grey)))
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
itemCount: _paginatedData.length,
|
|
itemBuilder: (context, index) {
|
|
final item = _paginatedData[index];
|
|
return Card(
|
|
elevation: 2,
|
|
color: Colors.white, // Warna card putih
|
|
margin: const EdgeInsets.only(bottom: 14),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12)),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.blueAccent,
|
|
borderRadius: BorderRadius.vertical(
|
|
top: Radius.circular(12)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
Text(widget.namaBalita,
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.white,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold)),
|
|
Text(
|
|
_formatTanggal(
|
|
item["tanggal_posyandu"]),
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.white,
|
|
fontSize: 12)),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
children: [
|
|
_infoRowAligned("Status Kehadiran",
|
|
item["status_hadir"] ?? "-"),
|
|
const SizedBox(height: 4),
|
|
_infoRowAligned("Catatan",
|
|
item["keterangan"] ?? "-"),
|
|
const Divider(),
|
|
const SizedBox(height: 4),
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: SizedBox(
|
|
width: 110,
|
|
child: OutlinedButton.icon(
|
|
onPressed: () =>
|
|
_showDeleteDialog(
|
|
item["id"].toString()),
|
|
icon: const Icon(
|
|
Icons.delete_outline,
|
|
size: 14,
|
|
color: Colors.red),
|
|
style: OutlinedButton.styleFrom(
|
|
side: const BorderSide(
|
|
color: Colors.red,
|
|
width: 1.5),
|
|
shape: const StadiumBorder(),
|
|
padding:
|
|
const EdgeInsets.symmetric(
|
|
vertical: 8),
|
|
),
|
|
label: Text(
|
|
"Hapus",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
color: Colors.red,
|
|
fontWeight:
|
|
FontWeight.bold),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
/// PAGINATION
|
|
Container(
|
|
width: double.infinity,
|
|
padding:
|
|
const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
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++),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _infoRowAligned(String label, String value) {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 120,
|
|
child: Text(label,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12, fontWeight: FontWeight.w500))),
|
|
const Text(" : ", style: TextStyle(fontSize: 12)),
|
|
Expanded(
|
|
child: Text(value,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12, fontWeight: FontWeight.w600))),
|
|
],
|
|
);
|
|
}
|
|
|
|
void _showDeleteDialog(String id) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
|
title: Text("Konfirmasi",
|
|
style:
|
|
GoogleFonts.poppins(fontWeight: FontWeight.bold, fontSize: 12)),
|
|
content: Text("Data riwayat ini akan dihapus permanen.",
|
|
style: GoogleFonts.poppins(fontSize: 12)),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text("Batal",
|
|
style:
|
|
GoogleFonts.poppins(color: Colors.grey, fontSize: 12))),
|
|
OutlinedButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
_hapusData(id);
|
|
},
|
|
style: OutlinedButton.styleFrom(
|
|
side: const BorderSide(color: Colors.red),
|
|
shape: const StadiumBorder()),
|
|
child: Text("Hapus",
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.red,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 12)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|