MIF_E31230549/lib/bidan/jadwal_posyandu.dart

523 lines
21 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';
import '../layout/main_layout.dart';
import 'bidan_drawer.dart';
import '../bidan/crud_jadwal/riwayat_jadwal_posyandu.dart';
import '../bidan/crud_jadwal/edit_jadwal.dart';
// Import Dashboard Bidan agar navigasi PopScope tidak error
import '../bidan/dashboard_bidan.dart';
const String baseUrl = "http://ta.myhost.id/E31230549/mposyandu_api";
class Dusun {
final int id;
final String nama;
Dusun({required this.id, required this.nama});
}
class Kader {
final int id;
final String nama;
final int dunsunId;
Kader({required this.id, required this.nama, required this.dunsunId});
}
class DataJadwalPosyanduPage extends StatefulWidget {
const DataJadwalPosyanduPage({super.key});
@override
State<DataJadwalPosyanduPage> createState() => _DataJadwalPosyanduPageState();
}
class _DataJadwalPosyanduPageState extends State<DataJadwalPosyanduPage> {
final _tanggalController = TextEditingController();
final _jamMulaiController = TextEditingController();
final _jamSelesaiController = TextEditingController();
final _lokasiController = TextEditingController();
final _keteranganController = TextEditingController();
List<Dusun> daftarDusun = [];
List<Kader> daftarKader = [];
List<int> dunsunDipilih = [];
List<Map<String, dynamic>> _dataJadwalLokal = [];
bool _isLoading = false;
@override
void initState() {
super.initState();
initializeDateFormatting('id', null);
loadDusun();
loadLatestJadwal();
}
String _formatTanggal(String? dateStr) {
if (dateStr == null || dateStr == "-" || dateStr.isEmpty) return "-";
try {
DateTime dt = DateTime.parse(dateStr);
return DateFormat('dd MMMM yyyy', 'id').format(dt);
} catch (e) {
return dateStr;
}
}
Future<void> loadLatestJadwal() async {
try {
final res = await http
.get(Uri.parse("$baseUrl/jadwal_posyandu/get_latest_jadwal.php"));
if (res.statusCode == 200) {
final responseData = jsonDecode(res.body);
if (responseData['success'] == true && responseData['data'] != null) {
setState(() {
if (responseData['data'] is List) {
_dataJadwalLokal =
List<Map<String, dynamic>>.from(responseData['data']);
} else {
_dataJadwalLokal = [responseData['data']];
}
});
}
}
} catch (e) {
debugPrint("Error load latest jadwal: $e");
}
}
Future<void> loadDusun() async {
try {
final res = await http.get(Uri.parse("$baseUrl/dusun/get_dusun.php"));
if (res.statusCode == 200) {
final responseData = jsonDecode(res.body);
final List listData = responseData['data'];
setState(() {
daftarDusun = listData
.map((d) => Dusun(
id: int.parse(d['id'].toString()),
nama: d['nama_dusun'].toString(),
))
.toList();
});
}
} catch (e) {
debugPrint("Error load dunsun: $e");
}
}
Future<void> loadKaderByDusun() async {
if (dunsunDipilih.isEmpty) {
setState(() => daftarKader.clear());
return;
}
final String ids = dunsunDipilih.join(",");
try {
final res = await http
.get(Uri.parse("$baseUrl/petugas/get_kader_by_dusun.php?ids=$ids"));
if (res.statusCode == 200) {
final responseData = jsonDecode(res.body);
final List listData = responseData['data'] ?? [];
setState(() {
daftarKader = listData
.map((k) => Kader(
id: int.parse(k['id'].toString()),
nama: k['nama'].toString(),
dunsunId: int.parse(k['dusun_id'].toString()),
))
.toList();
});
}
} catch (e) {
debugPrint("Error load kader: $e");
}
}
Future<void> _simpanJadwal() async {
if (_tanggalController.text.isEmpty || dunsunDipilih.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Lengkapi Tanggal dan Dusun!",
style: GoogleFonts.poppins(fontSize: 12))),
);
return;
}
setState(() => _isLoading = true);
try {
final response = await http.post(
Uri.parse("$baseUrl/jadwal_posyandu/tambah_jadwal.php"),
body: {
"bidan_id": "1",
"tanggal": _tanggalController.text,
"jam_mulai": _jamMulaiController.text,
"jam_selesai": _jamSelesaiController.text,
"lokasi": _lokasiController.text,
"keterangan": _keteranganController.text,
"dusun_ids": dunsunDipilih.join(","),
},
);
final result = jsonDecode(response.body);
if (result['success']) {
loadLatestJadwal();
setState(() {
_tanggalController.clear();
_jamMulaiController.clear();
_jamSelesaiController.clear();
_lokasiController.clear();
_keteranganController.clear();
dunsunDipilih.clear();
daftarKader.clear();
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Jadwal Berhasil Disimpan",
style: GoogleFonts.poppins(fontSize: 12))),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Terjadi kesalahan: $e",
style: GoogleFonts.poppins(fontSize: 12))),
);
} finally {
setState(() => _isLoading = false);
}
}
InputDecoration _input(String label) => InputDecoration(
labelText: label,
labelStyle: GoogleFonts.poppins(fontSize: 12),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
);
Widget rowData(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Table(
columnWidths: const {
0: FixedColumnWidth(140),
1: FixedColumnWidth(15),
2: FlexColumnWidth()
},
children: [
TableRow(children: [
Text(label,
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w500)),
Text(" : ",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w500)),
Text(value.isEmpty ? "-" : value,
style: GoogleFonts.poppins(fontSize: 12)),
])
],
),
);
}
@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: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// FORM TAMBAH
Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 500),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(color: Colors.black12, blurRadius: 8)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Text("Tambah Jadwal",
style: GoogleFonts.poppins(
fontSize: 18, fontWeight: FontWeight.bold))),
const SizedBox(height: 15),
TextField(
controller: _tanggalController,
style: GoogleFonts.poppins(fontSize: 12),
readOnly: true,
onTap: () async {
DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2024),
lastDate: DateTime(2030));
if (picked != null)
setState(() => _tanggalController.text =
"${picked.year}-${picked.month.toString().padLeft(2, '0')}-${picked.day.toString().padLeft(2, '0')}");
},
decoration: _input("Tanggal").copyWith(
suffixIcon:
const Icon(Icons.calendar_today, size: 18)),
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: TextField(
controller: _jamMulaiController,
style: GoogleFonts.poppins(fontSize: 12),
readOnly: true,
onTap: () async {
TimeOfDay? t = await showTimePicker(
context: context,
initialTime: TimeOfDay.now());
if (t != null)
setState(() => _jamMulaiController.text =
"${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}");
},
decoration: _input("Jam Mulai"))),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _jamSelesaiController,
style: GoogleFonts.poppins(fontSize: 12),
readOnly: true,
onTap: () async {
TimeOfDay? t = await showTimePicker(
context: context,
initialTime: TimeOfDay.now());
if (t != null)
setState(() => _jamSelesaiController
.text =
"${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}");
},
decoration: _input("Jam Selesai"))),
],
),
const SizedBox(height: 10),
TextField(
controller: _lokasiController,
style: GoogleFonts.poppins(fontSize: 12),
decoration: _input("Lokasi Posyandu")),
const SizedBox(height: 15),
Text("Dusun Yang Dilayani",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 12)),
const Divider(),
...daftarDusun.map((d) => CheckboxListTile(
visualDensity: VisualDensity.compact,
contentPadding: EdgeInsets.zero,
title: Text(d.nama,
style: GoogleFonts.poppins(fontSize: 12)),
value: dunsunDipilih.contains(d.id),
onChanged: (val) {
setState(() {
if (val == true) {
dunsunDipilih.add(d.id);
} else {
dunsunDipilih.remove(d.id);
}
});
loadKaderByDusun();
},
)),
const SizedBox(height: 10),
Text("Kader Otomatis Bertugas:",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 12)),
const SizedBox(height: 5),
daftarKader.isEmpty
? Text("-",
style: GoogleFonts.poppins(
color: Colors.grey, fontSize: 12))
: Wrap(
spacing: 5,
runSpacing: 5,
children: daftarKader
.map((k) => Chip(
label: Text(k.nama,
style: GoogleFonts.poppins(
fontSize: 12)),
padding: EdgeInsets.zero,
backgroundColor: Colors.blue.shade50,
))
.toList(),
),
const SizedBox(height: 15),
TextField(
controller: _keteranganController,
style: GoogleFonts.poppins(fontSize: 12),
decoration: _input("Keterangan")),
const SizedBox(height: 20),
// BUTTON SIMPAN JADWAL
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: _isLoading ? null : _simpanJadwal,
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.blue),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: const StadiumBorder()),
child: _isLoading
? const SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(
color: Colors.blue, strokeWidth: 2))
: Text(
"Simpan Jadwal",
style: GoogleFonts.poppins(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
),
],
),
),
),
const SizedBox(height: 15),
// BUTTON RIWAYAT
Container(
constraints: const BoxConstraints(maxWidth: 500),
child: ElevatedButton.icon(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const RiwayatJadwalPosyanduPage())),
icon: const Icon(Icons.history,
size: 18, color: Colors.black87),
label: Text("Riwayat Jadwal Posyandu",
style: GoogleFonts.poppins(
color: Colors.black87, fontSize: 12)),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF1F1F1),
minimumSize: const Size(double.infinity, 45),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10))),
),
),
const SizedBox(height: 20),
// CARD HASIL
if (_dataJadwalLokal.isNotEmpty) ...[
Container(
constraints: const BoxConstraints(maxWidth: 500),
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text("Jadwal Posyandu Terbaru",
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold,
fontSize: 12,
color: Colors.green))),
const SizedBox(height: 8),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _dataJadwalLokal.length,
itemBuilder: (context, index) {
final item = _dataJadwalLokal[index];
return Card(
color: Colors.white,
elevation: 3,
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
const Icon(Icons.calendar_month,
size: 18, color: Colors.blue),
const SizedBox(width: 8),
Text("Data Jadwal Posyandu",
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold,
fontSize: 12)),
const Spacer(),
// BUTTON EDIT
OutlinedButton.icon(
onPressed: () async {
bool? refresh = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
EditJadwalPage(
data: item)));
if (refresh == true)
loadLatestJadwal();
},
icon: const Icon(Icons.edit,
size: 16, color: Colors.orange),
label: Text("Edit",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.orange)),
style: OutlinedButton.styleFrom(
side: const BorderSide(
color: Colors.orange),
padding: const EdgeInsets.symmetric(
horizontal: 12),
shape: const StadiumBorder()),
),
],
),
const Divider(),
rowData("Tanggal Posyandu",
_formatTanggal(item['tanggal'])),
rowData("Jam",
"${item['jam_mulai']} - ${item['jam_selesai']}"),
rowData("Lokasi", item['lokasi'] ?? "-"),
rowData("Dusun", item['dusun'] ?? "-"),
rowData(
"Kader Bertugas", item['kader'] ?? "-"),
rowData(
"Keterangan", item['keterangan'] ?? "-"),
],
),
),
);
},
),
],
),
),
]
],
),
),
),
);
}
}