sistem_rekomendasi/resources/views/admin/dashboard.blade.php

329 lines
11 KiB
PHP

@extends('admin.layout')
@section('content')
<link rel="stylesheet" href="{{ asset('css/dashboard-admin.css') }}">
<div class="dashboard-modern">
<section class="dashboard-hero">
<div class="dashboard-hero-content">
<div>
<div class="hero-label">
<i class="fas fa-chart-pie"></i>
Dashboard Admin
</div>
<h2 class="hero-title">Overview Statistik Aplikasi</h2>
<p class="hero-subtitle">
Pantau jumlah siswa, rekomendasi yang sudah diproses, rata-rata tingkat keyakinan prediksi,
serta distribusi jurusan yang paling banyak muncul pada sistem rekomendasi jurusan kuliah SMAN 4 Jember.
</p>
</div>
<div class="hero-date-card">
<span>Terakhir diperbarui</span>
<strong id="last-updated">Memuat...</strong>
</div>
</div>
</section>
<section class="modern-stats-grid">
<div class="modern-stat-card">
<div class="stat-card-content">
<div>
<p class="stat-label">Total Siswa</p>
<h3 class="stat-value" id="stat-total-siswa">0</h3>
<p class="stat-note">Jumlah akun siswa yang terdaftar.</p>
</div>
<div class="modern-stat-icon icon-blue">
<i class="fas fa-user-graduate"></i>
</div>
</div>
</div>
<div class="modern-stat-card">
<div class="stat-card-content">
<div>
<p class="stat-label">Rekomendasi Diproses</p>
<h3 class="stat-value" id="stat-total-rekomendasi">0</h3>
<p class="stat-note">Jumlah riwayat rekomendasi yang tersimpan.</p>
</div>
<div class="modern-stat-icon icon-green">
<i class="fas fa-check-circle"></i>
</div>
</div>
</div>
<div class="modern-stat-card">
<div class="stat-card-content">
<div>
<p class="stat-label">Rata-rata Keyakinan Prediksi</p>
<h3 class="stat-value" id="stat-rata-akurasi">0%</h3>
<p class="stat-note" id="akurasi-status">Menunggu data keyakinan prediksi.</p>
</div>
<div class="accuracy-ring" id="accuracy-ring">
<div class="accuracy-ring-inner" id="accuracy-ring-text">0%</div>
</div>
</div>
</div>
</section>
<section class="dashboard-card dashboard-chart-full">
<div class="card-header-modern">
<div>
<h5 class="card-title-modern">Distribusi Rekomendasi Jurusan</h5>
<p class="card-subtitle-modern">
Grafik ini menampilkan jumlah hasil rekomendasi berdasarkan jurusan yang muncul dari riwayat prediksi siswa.
Detail siswa dan hasil lengkap dapat dilihat pada menu <b>Riwayat Prediksi</b>.
</p>
</div>
<span class="chart-badge">
<i class="fas fa-chart-column me-1"></i>
Grafik Jurusan
</span>
</div>
<div class="chart-wrapper-modern">
<canvas id="distribusiChart" style="display: none;"></canvas>
<div id="chart-placeholder" class="dashboard-placeholder">
<div>
<i class="fas fa-chart-pie"></i>
<p>Memuat grafik distribusi...</p>
</div>
</div>
</div>
</section>
</div>
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
let distribusiChartInstance = null;
function setText(id, value) {
const element = document.getElementById(id);
if (element) {
element.innerText = value;
}
}
function formatNumber(value) {
const number = Number(value || 0);
return new Intl.NumberFormat('id-ID').format(number);
}
function formatPercent(value) {
if (value === null || value === undefined || value === '') {
return '0%';
}
const number = Number(value);
if (Number.isNaN(number)) {
return '0%';
}
return `${number.toFixed(number % 1 === 0 ? 0 : 2)}%`;
}
function updateAccuracyRing(value) {
const ring = document.getElementById('accuracy-ring');
const ringText = document.getElementById('accuracy-ring-text');
const number = Number(value || 0);
const degree = Math.max(0, Math.min(100, number)) * 3.6;
if (ring) {
ring.style.background = `conic-gradient(#2D7A47 ${degree}deg, #e2e8f0 ${degree}deg)`;
}
if (ringText) {
ringText.innerText = formatPercent(number);
}
}
function updateLastUpdated() {
const now = new Date();
setText('last-updated', now.toLocaleString('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
}));
}
function renderDistribusiChart(distribusi) {
const placeholder = document.getElementById('chart-placeholder');
const canvas = document.getElementById('distribusiChart');
if (!placeholder || !canvas) {
return;
}
if (!distribusi || distribusi.length === 0) {
placeholder.style.display = 'flex';
canvas.style.display = 'none';
placeholder.querySelector('p').innerText = 'Belum ada data riwayat rekomendasi.';
return;
}
placeholder.style.display = 'none';
canvas.style.display = 'block';
const labels = distribusi.map(item => item.jurusan || 'Tidak diketahui');
const counts = distribusi.map(item => Number(item.total || 0));
const ctx = canvas.getContext('2d');
if (distribusiChartInstance) {
distribusiChartInstance.destroy();
}
const gradient = ctx.createLinearGradient(0, 0, 0, 360);
gradient.addColorStop(0, 'rgba(45, 122, 71, 0.95)');
gradient.addColorStop(1, 'rgba(45, 122, 71, 0.55)');
distribusiChartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Jumlah Rekomendasi',
data: counts,
backgroundColor: gradient,
borderColor: 'rgba(45, 122, 71, 1)',
borderWidth: 1,
borderRadius: 14,
borderSkipped: false,
maxBarThickness: 74
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: {
top: 12,
right: 12,
bottom: 0,
left: 4
}
},
plugins: {
legend: {
display: false
},
tooltip: {
backgroundColor: '#0f172a',
titleColor: '#ffffff',
bodyColor: '#e2e8f0',
padding: 12,
cornerRadius: 12,
callbacks: {
label: function(context) {
return ` ${context.raw} rekomendasi`;
}
}
}
},
scales: {
x: {
grid: {
display: false
},
ticks: {
color: '#64748b',
font: {
size: 11,
weight: '700'
},
maxRotation: 0,
minRotation: 0
}
},
y: {
beginAtZero: true,
grid: {
color: '#edf2f7'
},
ticks: {
color: '#64748b',
precision: 0,
stepSize: 1,
font: {
size: 11,
weight: '700'
}
}
}
}
}
});
}
async function loadDashboardStats() {
const token = localStorage.getItem('access_token');
if (!token) {
window.location.href = '/login';
return;
}
updateLastUpdated();
try {
const response = await fetch('/api/admin/dashboard-stats', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
});
const result = await response.json();
if (!response.ok || !result.success) {
throw new Error(result.message || 'Gagal memuat data dashboard.');
}
const data = result.data || {};
const rataAkurasi = data.rata_rata_akurasi;
setText('stat-total-siswa', formatNumber(data.total_siswa));
setText('stat-total-rekomendasi', formatNumber(data.rekomendasi_diproses));
setText('stat-rata-akurasi', formatPercent(rataAkurasi));
setText('akurasi-status', data.akurasi_status || 'Data keyakinan prediksi berhasil dimuat.');
updateAccuracyRing(rataAkurasi || 0);
renderDistribusiChart(data.distribusi_jurusan || []);
} catch (error) {
console.error('Gagal memuat data dashboard:', error);
setText('akurasi-status', 'Gagal memuat data dashboard.');
updateAccuracyRing(0);
const placeholder = document.getElementById('chart-placeholder');
const canvas = document.getElementById('distribusiChart');
if (placeholder) {
placeholder.style.display = 'flex';
placeholder.querySelector('p').innerText = 'Gagal memuat grafik distribusi.';
}
if (canvas) {
canvas.style.display = 'none';
}
}
}
document.addEventListener('DOMContentLoaded', loadDashboardStats);
</script>
@endpush
@endsection