552 lines
18 KiB
Dart
552 lines
18 KiB
Dart
import 'dart:convert';
|
|
import 'dart:async';
|
|
import 'dart:io'; // Tambahkan ini
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart'; // Tambahkan ini untuk SystemNavigator.pop
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:audioplayers/audioplayers.dart';
|
|
import '../layout/main_layout.dart';
|
|
import 'kader_drawer.dart';
|
|
import '../pages/login_page.dart';
|
|
import '../kader/data_kehamilan.dart';
|
|
import '../kader/data_balita.dart';
|
|
|
|
class DashboardKaderPage extends StatefulWidget {
|
|
const DashboardKaderPage({super.key});
|
|
|
|
@override
|
|
State<DashboardKaderPage> createState() => _DashboardKaderPageState();
|
|
}
|
|
|
|
class _DashboardKaderPageState extends State<DashboardKaderPage> {
|
|
DateTime selectedDate = DateTime.now();
|
|
List jadwalList = [];
|
|
Timer? _pollingTimer;
|
|
final AudioPlayer _audioPlayer = AudioPlayer();
|
|
int _notificationCount = 0;
|
|
String userName = "Kader";
|
|
String _lastJadwalDataString = "";
|
|
String? keteranganKegiatan;
|
|
String? tempatKegiatan;
|
|
String? jamMulai;
|
|
String? jamSelesai;
|
|
bool isLoadingJadwal = true;
|
|
int jumlahIbu = 0;
|
|
int jumlahBalita = 0;
|
|
bool isLoading = true;
|
|
int? kaderId;
|
|
|
|
static const String baseUrl = "http://ta.myhost.id/E31230549/mposyandu_api";
|
|
final String dashboardUrl = "$baseUrl/dashboard_kader.php";
|
|
final String jadwalUrl = "$baseUrl/jadwal_posyandu/get_jadwal_by_kader.php";
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_audioPlayer.setReleaseMode(ReleaseMode.stop);
|
|
_initPage();
|
|
_pollingTimer = Timer.periodic(const Duration(seconds: 10), (timer) {
|
|
if (kaderId != null && kaderId != 0) {
|
|
_loadJadwalKader();
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pollingTimer?.cancel();
|
|
_audioPlayer.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _playNotifSound() async {
|
|
try {
|
|
await _audioPlayer.stop();
|
|
await _audioPlayer.play(AssetSource('sounds/notif.mp3'));
|
|
} catch (e) {
|
|
debugPrint("DEBUG AUDIO ERROR: $e");
|
|
}
|
|
}
|
|
|
|
Future<void> _initPage() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final isLogin = prefs.getBool('isLogin') ?? false;
|
|
|
|
if (!isLogin) {
|
|
if (!mounted) return;
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const LoginPage(fromGuard: true)),
|
|
(route) => false,
|
|
);
|
|
return;
|
|
}
|
|
|
|
String? idString = prefs.getString('id_user');
|
|
int currentId = int.tryParse(idString ?? "0") ?? 0;
|
|
|
|
setState(() {
|
|
userName = prefs.getString('nama') ?? "Kader";
|
|
kaderId = currentId;
|
|
_notificationCount = prefs.getInt('unread_notif_count_$idString') ?? 0;
|
|
_lastJadwalDataString =
|
|
prefs.getString('last_jadwal_data_string_$idString') ?? "";
|
|
});
|
|
|
|
if (currentId != 0) {
|
|
await _loadDashboard();
|
|
await _loadJadwalKader();
|
|
} else {
|
|
if (mounted) setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadDashboard() async {
|
|
if (kaderId == null || kaderId == 0) {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
kaderId = int.tryParse(prefs.getString('id_user') ?? "0");
|
|
}
|
|
|
|
if (kaderId == null || kaderId == 0) {
|
|
if (mounted) setState(() => isLoading = false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final res = await http
|
|
.get(Uri.parse("$dashboardUrl?id_kader=$kaderId"))
|
|
.timeout(const Duration(seconds: 10));
|
|
|
|
final data = jsonDecode(res.body);
|
|
|
|
if (data['success'] == true) {
|
|
if (mounted) {
|
|
setState(() {
|
|
jumlahIbu = int.tryParse(data['jumlah_ibu'].toString()) ?? 0;
|
|
jumlahBalita = int.tryParse(data['jumlah_balita'].toString()) ?? 0;
|
|
isLoading = false;
|
|
});
|
|
}
|
|
} else {
|
|
debugPrint("API Error: ${data['message']}");
|
|
if (mounted) setState(() => isLoading = false);
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error Dashboard Connection: $e");
|
|
if (mounted) setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadJadwalKader() async {
|
|
if (kaderId == null || kaderId == 0) return;
|
|
try {
|
|
final url = "$jadwalUrl?kader_id=$kaderId";
|
|
final res =
|
|
await http.get(Uri.parse(url)).timeout(const Duration(seconds: 10));
|
|
final data = jsonDecode(res.body);
|
|
|
|
if (data['success'] == true) {
|
|
List fetchedJadwal = data['data'] ?? [];
|
|
String currentDataString = jsonEncode(fetchedJadwal);
|
|
final prefs = await SharedPreferences.getInstance();
|
|
String idStr = kaderId.toString();
|
|
|
|
if (_lastJadwalDataString.isNotEmpty &&
|
|
currentDataString != _lastJadwalDataString) {
|
|
List oldList = jsonDecode(_lastJadwalDataString);
|
|
if (fetchedJadwal.length >= oldList.length) {
|
|
_playNotifSound();
|
|
setState(() {
|
|
_notificationCount += 1;
|
|
});
|
|
await prefs.setInt('unread_notif_count_$idStr', _notificationCount);
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: const Text("Jadwal diperbarui atau ditambah!"),
|
|
backgroundColor: Colors.blue.shade700,
|
|
duration: const Duration(seconds: 2),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
await prefs.setString(
|
|
'last_jadwal_data_string_$idStr', currentDataString);
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
jadwalList = fetchedJadwal;
|
|
_lastJadwalDataString = currentDataString;
|
|
isLoadingJadwal = false;
|
|
});
|
|
_updateJadwalByDate(selectedDate);
|
|
}
|
|
} else {
|
|
_resetJadwal();
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error polling: $e");
|
|
if (mounted) setState(() => isLoadingJadwal = false);
|
|
}
|
|
}
|
|
|
|
void _resetJadwal() {
|
|
if (mounted) {
|
|
setState(() {
|
|
keteranganKegiatan = null;
|
|
tempatKegiatan = null;
|
|
jamMulai = null;
|
|
jamSelesai = null;
|
|
isLoadingJadwal = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
String formatTanggal(DateTime date) {
|
|
return "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
|
|
}
|
|
|
|
void _updateJadwalByDate(DateTime date) {
|
|
String tanggalDipilih = formatTanggal(date);
|
|
Map<String, dynamic>? jadwalHariIni;
|
|
|
|
for (var j in jadwalList) {
|
|
if (j['tanggal'] != null) {
|
|
String tanggalApi = j['tanggal'].toString().substring(0, 10);
|
|
if (tanggalApi == tanggalDipilih) {
|
|
jadwalHariIni = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jadwalHariIni != null) {
|
|
setState(() {
|
|
keteranganKegiatan = jadwalHariIni!['keterangan'];
|
|
tempatKegiatan = jadwalHariIni['lokasi'];
|
|
jamMulai = jadwalHariIni['jam_mulai'];
|
|
jamSelesai = jadwalHariIni['jam_selesai'];
|
|
});
|
|
} else {
|
|
_resetJadwal();
|
|
}
|
|
}
|
|
|
|
Future<void> _showNotifDialog() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
String idStr = kaderId.toString();
|
|
setState(() {
|
|
_notificationCount = 0;
|
|
});
|
|
await prefs.setInt('unread_notif_count_$idStr', 0);
|
|
|
|
if (!mounted) return;
|
|
showDialog(
|
|
context: context,
|
|
builder: (_) {
|
|
return AlertDialog(
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
|
title: Text("Pemberitahuan Jadwal",
|
|
style: GoogleFonts.poppins(fontWeight: FontWeight.w600)),
|
|
content: SizedBox(
|
|
width: 300,
|
|
child: jadwalList.isEmpty
|
|
? const Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 20),
|
|
child:
|
|
Text("Belum ada jadwal", textAlign: TextAlign.center),
|
|
)
|
|
: ListView.builder(
|
|
shrinkWrap: true,
|
|
itemCount: jadwalList.length > 5 ? 5 : jadwalList.length,
|
|
itemBuilder: (_, index) {
|
|
final item = jadwalList[(jadwalList.length - 1) - index];
|
|
return Card(
|
|
elevation: 2,
|
|
margin: const EdgeInsets.symmetric(vertical: 6),
|
|
child: ListTile(
|
|
leading: const CircleAvatar(
|
|
backgroundColor: Colors.blue,
|
|
child: Icon(Icons.event,
|
|
color: Colors.white, size: 20),
|
|
),
|
|
title: Text(
|
|
item['keterangan'] ?? "Kegiatan Posyandu",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600, fontSize: 13),
|
|
),
|
|
subtitle: Text(
|
|
"Tgl: ${item['tanggal']?.toString().substring(0, 10) ?? '-'}\nLokasi: ${item['lokasi'] ?? '-'}",
|
|
style: GoogleFonts.poppins(fontSize: 11),
|
|
),
|
|
isThreeLine: true,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text("Tutup"))
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PopScope(
|
|
canPop: false, // Menahan aksi "back" standar
|
|
onPopInvokedWithResult: (didPop, result) {
|
|
if (didPop) return;
|
|
// Keluar dari aplikasi secara total
|
|
if (Platform.isAndroid) {
|
|
SystemNavigator.pop();
|
|
} else if (Platform.isIOS) {
|
|
exit(0);
|
|
}
|
|
},
|
|
child: Theme(
|
|
data: Theme.of(context).copyWith(
|
|
textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme),
|
|
dividerColor: Colors.transparent,
|
|
),
|
|
child: Stack(
|
|
children: [
|
|
MainLayout(
|
|
title: " ",
|
|
drawer: const KaderDrawer(),
|
|
body: _buildDashboard(),
|
|
),
|
|
Positioned(
|
|
top: MediaQuery.of(context).padding.top +
|
|
(kToolbarHeight - 48) / 2,
|
|
right: 8,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.notifications,
|
|
color: Colors.white, size: 26),
|
|
onPressed: _showNotifDialog,
|
|
),
|
|
if (_notificationCount > 0)
|
|
Positioned(
|
|
right: 8,
|
|
top: 8,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(2),
|
|
decoration: BoxDecoration(
|
|
color: Colors.red,
|
|
shape: BoxShape.circle,
|
|
border: Border.all(color: Colors.white, width: 1.5),
|
|
),
|
|
constraints:
|
|
const BoxConstraints(minWidth: 18, minHeight: 18),
|
|
child: Center(
|
|
child: Text(
|
|
_notificationCount > 9
|
|
? "9+"
|
|
: _notificationCount.toString(),
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 9,
|
|
fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDashboard() {
|
|
return RefreshIndicator(
|
|
onRefresh: () async {
|
|
await _loadDashboard();
|
|
await _loadJadwalKader();
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
child: Column(
|
|
children: [
|
|
Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: RichText(
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'Selamat Datang Kader $userName\n',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w700,
|
|
color: const Color.fromARGB(255, 19, 133, 226)),
|
|
),
|
|
TextSpan(
|
|
text: 'Tetap semangat melayani masyarakat ',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.black87),
|
|
),
|
|
const WidgetSpan(
|
|
child: Icon(Icons.favorite,
|
|
color: Colors.blue, size: 18)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 15),
|
|
Center(
|
|
child: Image.asset('assets/images/logoo.webp',
|
|
width: 300, height: 200, fit: BoxFit.contain)),
|
|
const SizedBox(height: 25),
|
|
isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: Row(
|
|
children: [
|
|
_infoBoxClickable(jumlahIbu.toString(),
|
|
'Jumlah Ibu Hamil', Colors.pink, () async {
|
|
await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const DataIbuHamilPage()));
|
|
_loadDashboard();
|
|
}),
|
|
const SizedBox(width: 10),
|
|
_infoBoxClickable(
|
|
jumlahBalita.toString(),
|
|
'Jumlah Balita',
|
|
const Color.fromARGB(255, 240, 220, 39), () async {
|
|
await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const DataBalitaPage()));
|
|
_loadDashboard();
|
|
}),
|
|
],
|
|
),
|
|
const SizedBox(height: 30),
|
|
const Text('Jadwal Posyandu',
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 15),
|
|
isLoadingJadwal
|
|
? const CircularProgressIndicator()
|
|
: keteranganKegiatan == null
|
|
? const Text("Tidak ada kegiatan posyandu hari ini",
|
|
style: TextStyle(color: Colors.red))
|
|
: Center(
|
|
child: IntrinsicWidth(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildDetailRow(Icons.access_time, "Jam",
|
|
"${jamMulai ?? '-'} s/d ${jamSelesai ?? '-'}"),
|
|
const SizedBox(height: 8),
|
|
_buildDetailRow(Icons.location_on, "Lokasi",
|
|
tempatKegiatan ?? "-"),
|
|
const SizedBox(height: 8),
|
|
_buildDetailRow(Icons.event_available,
|
|
"Kegiatan", keteranganKegiatan ?? "-"),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 25),
|
|
Container(
|
|
width: 260,
|
|
padding: const EdgeInsets.all(6),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withOpacity(0.2), blurRadius: 5)
|
|
],
|
|
),
|
|
child: Transform.scale(
|
|
scale: 0.8,
|
|
child: CalendarDatePicker(
|
|
initialDate: selectedDate,
|
|
firstDate: DateTime(2020),
|
|
lastDate: DateTime(2030),
|
|
onDateChanged: (date) {
|
|
setState(() => selectedDate = date);
|
|
_updateJadwalByDate(date);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDetailRow(IconData icon, String label, String value) {
|
|
return Row(
|
|
children: [
|
|
Icon(icon, size: 18, color: Colors.blue),
|
|
const SizedBox(width: 10),
|
|
SizedBox(
|
|
width: 90,
|
|
child: Text(label,
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.w600, fontSize: 12))),
|
|
const SizedBox(
|
|
width: 10,
|
|
child: Text(":", style: TextStyle(fontWeight: FontWeight.bold))),
|
|
Expanded(
|
|
child: Text(value,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(fontSize: 12))),
|
|
],
|
|
);
|
|
}
|
|
|
|
static Widget _infoBoxClickable(
|
|
String value, String label, Color color, VoidCallback onTap) {
|
|
return Expanded(
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(12),
|
|
onTap: onTap,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: color, borderRadius: BorderRadius.circular(12)),
|
|
child: Column(
|
|
children: [
|
|
Text(value,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 5),
|
|
Text(label,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(color: Colors.white)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|