MIF_E31231033/lib/screens/dashboard/admin/admin_home.dart

586 lines
20 KiB
Dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../../services/auth_service.dart';
import '../../../services/notification_admin_service.dart';
import 'notification_admin_screen.dart';
import 'data_petugas_screen.dart';
import 'laporan_admin_screen.dart';
class AdminHome extends StatefulWidget {
const AdminHome({super.key});
@override
State<AdminHome> createState() => _AdminHomeState();
}
class _AdminHomeState extends State<AdminHome> {
Map<String, dynamic> userData = {};
late String _timeString;
late Timer _timer;
late Timer _notifTimer;
int totalPetugas = 0;
int laporanMasuk = 0;
int laporanDisetujui = 0;
int laporanPending = 0;
int notifCount = 0;
@override
void initState() {
super.initState();
_timeString = DateFormat('HH:mm:ss').format(DateTime.now());
_timer = Timer.periodic(
const Duration(seconds: 1),
(Timer t) => _getCurrentTime(),
);
// 🔥 AUTO REFRESH NOTIF
_notifTimer = Timer.periodic(
const Duration(seconds: 5),
(Timer t) => loadNotif(),
);
fetchDashboard();
loadProfile();
loadNotif(); // pertama kali load
}
@override
void dispose() {
_timer.cancel();
_notifTimer.cancel();
super.dispose();
}
void _getCurrentTime() {
final String formattedDateTime = DateFormat(
'HH:mm:ss',
).format(DateTime.now());
setState(() {
_timeString = formattedDateTime;
});
}
Future<void> loadNotif() async {
final count = await NotificationAdminService.getUnreadCount(); // ✅ FIX
if (!mounted) return;
setState(() {
notifCount = count;
});
}
String getTanggalHari() {
final now = DateTime.now();
return DateFormat('EEEE, dd MMMM yyyy', 'id_ID').format(now);
}
Future<void> fetchDashboard() async {
try {
String? token = await AuthService.getToken();
final response = await http.get(
Uri.parse("${AuthService.baseUrl}/dashboard-statistik"),
headers: {
"Accept": "application/json",
"Authorization": "Bearer $token",
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
setState(() {
totalPetugas = data['total_petugas'] ?? 0;
laporanMasuk = data['laporan_masuk'] ?? 0;
laporanPending = data['laporan_pending'] ?? 0;
laporanDisetujui = data['laporan_disetujui'] ?? 0;
});
}
} catch (e) {
debugPrint("Error dashboard: $e");
}
}
Future<void> loadProfile() async {
final result = await AuthService.getProfile();
if (!mounted) return;
if (result['success']) {
setState(() {
userData = result['data'];
});
}
}
// ================= STAT CARD (PERCANTIK) =================
Widget _buildStatCard(
String title,
String count,
Color color,
IconData icon,
) {
return Expanded(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.08),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
border: Border.all(color: color.withOpacity(0.1), width: 1.5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: color, size: 20),
),
const SizedBox(height: 12),
Text(
count,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
// ================= MENU CARD (PERCANTIK) =================
Widget _buildMenuCard(
BuildContext context,
String title,
IconData icon,
Color color,
VoidCallback onTap,
) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(24),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 15,
offset: const Offset(0, 6),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(icon, color: color, size: 30),
),
const SizedBox(height: 12),
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Colors.black87,
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F7FB),
body: SingleChildScrollView(
child: Column(
children: [
// ================= HEADER =================
Stack(
clipBehavior: Clip.none,
children: [
Container(
height: 240,
width: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF1A39B1), Color(0xFF4263EB)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(40),
bottomRight: Radius.circular(40),
),
),
padding: const EdgeInsets.fromLTRB(25, 60, 25, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Patroli Kampus',
style: TextStyle(
color: Colors.white,
fontSize: 26,
fontWeight: FontWeight.bold,
letterSpacing: 0.8,
),
),
Text(
'Politeknik Negeri Jember',
style: TextStyle(
color: Colors.white70,
fontSize: 13,
),
),
],
),
Row(
children: [
Stack(
children: [
IconButton(
icon: const Icon(
Icons.notifications_rounded,
color: Colors.white,
size: 28,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder:
(_) =>
const NotificationAdminScreen(),
),
).then((_) => loadNotif());
},
),
if (notifCount > 0)
Positioned(
right: 6,
top: 6,
child: Container(
padding: const EdgeInsets.all(5),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
constraints: const BoxConstraints(
minWidth: 18,
minHeight: 18,
),
child: Text(
notifCount > 9
? '9+'
: notifCount.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
),
],
),
const SizedBox(width: 10),
Hero(
tag: 'logo_polije',
child: Image.asset(
'assets/images/logopolije.png',
height: 60,
),
),
],
),
],
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.calendar_today_rounded,
size: 14,
color: Colors.white,
),
const SizedBox(width: 8),
Text(
getTanggalHari(),
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 15),
const VerticalDivider(
color: Colors.white54,
thickness: 1,
),
const Icon(
Icons.watch_later_rounded,
size: 14,
color: Colors.white,
),
const SizedBox(width: 8),
Text(
_timeString,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
// ================= CARD PROFIL =================
Padding(
padding: const EdgeInsets.only(top: 185, left: 20, right: 20),
child: Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFF4263EB),
width: 2,
),
),
child: CircleAvatar(
radius: 26,
backgroundColor: const Color(0xFFF0F3FF),
backgroundImage:
(userData['foto'] != null &&
userData['foto'].toString().isNotEmpty)
? NetworkImage(userData['foto'])
: null,
child:
(userData['foto'] == null ||
userData['foto'].toString().isEmpty)
? const Icon(
Icons.admin_panel_settings_rounded,
color: Color(0xFF1A39B1),
size: 30,
)
: null,
),
),
const SizedBox(width: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Selamat Datang kembali,',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
fontWeight: FontWeight.w500,
),
),
Text(
userData['role'] == 'super_admin' ? 'Super Admin' : 'Admin',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
),
],
),
],
),
),
),
],
),
const SizedBox(height: 45),
// ================= CONTENT =================
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Ringkasan Laporan',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
),
const SizedBox(height: 16),
Row(
children: [
_buildStatCard(
"Petugas",
totalPetugas.toString(),
Colors.blue,
Icons.people_rounded,
),
const SizedBox(width: 14),
_buildStatCard(
"Masuk",
laporanMasuk.toString(),
Colors.purple,
Icons.assignment_returned_rounded,
),
],
),
const SizedBox(height: 14),
Row(
children: [
_buildStatCard(
"Disetujui",
laporanDisetujui.toString(),
Colors.green,
Icons.check_circle_rounded,
),
const SizedBox(width: 14),
_buildStatCard(
"Pending",
laporanPending.toString(),
Colors.orange,
Icons.hourglass_empty_rounded,
),
],
),
const SizedBox(height: 32),
const Text(
'Menu Utama',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
),
const SizedBox(height: 16),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: 18,
mainAxisSpacing: 18,
childAspectRatio: 1.25,
children: [
_buildMenuCard(
context,
"Data Petugas",
Icons.badge_rounded,
Colors.teal,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const DataPetugasScreen(),
),
);
},
),
_buildMenuCard(
context,
"Riwayat Aktivitas",
Icons.history_edu_rounded,
Colors.deepOrangeAccent,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const LaporanAdminScreen(),
),
);
},
),
],
),
const SizedBox(height: 40),
],
),
),
],
),
),
);
}
}