523 lines
21 KiB
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'] ?? "-"),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
]
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|