352 lines
16 KiB
PHP
352 lines
16 KiB
PHP
@extends('admin.layouts.app')
|
|
|
|
@section('title', 'Dashboard Admin')
|
|
|
|
@push('styles')
|
|
<style>
|
|
.dash-sub { font-size: 13px; color: #94a3b8; margin-bottom: 24px; }
|
|
|
|
/* ── STAT CARDS ── */
|
|
.stat-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 18px;
|
|
margin-bottom: 24px;
|
|
}
|
|
.stat-card {
|
|
border-radius: 20px;
|
|
padding: 24px 22px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
animation: fadeUp 0.4s ease both;
|
|
text-decoration: none;
|
|
display: block;
|
|
}
|
|
.stat-card:hover { transform: translateY(-4px); box-shadow: 0 16px 40px rgba(0,0,0,0.12); }
|
|
.stat-card:nth-child(1) { background: linear-gradient(135deg,#fff7e6,#ffe4b2); animation-delay:0s; }
|
|
.stat-card:nth-child(2) { background: linear-gradient(135deg,#e8fff3,#b8f5d4); animation-delay:0.07s; }
|
|
.stat-card:nth-child(3) { background: linear-gradient(135deg,#eef4ff,#c5d9ff); animation-delay:0.14s; }
|
|
.stat-card:nth-child(4) { background: linear-gradient(135deg,#ffeef4,#ffc5db); animation-delay:0.21s; }
|
|
.stat-card::after { content:''; position:absolute; width:120px; height:120px; border-radius:50%; opacity:0.15; right:-30px; bottom:-30px; }
|
|
.stat-card:nth-child(1)::after { background:#f97316; }
|
|
.stat-card:nth-child(2)::after { background:#22c55e; }
|
|
.stat-card:nth-child(3)::after { background:#2b8ef3; }
|
|
.stat-card:nth-child(4)::after { background:#ec4899; }
|
|
.stat-label { font-size:11px; font-weight:700; letter-spacing:1px; text-transform:uppercase; margin-bottom:10px; }
|
|
.stat-card:nth-child(1) .stat-label { color:#c2651a; }
|
|
.stat-card:nth-child(2) .stat-label { color:#15803d; }
|
|
.stat-card:nth-child(3) .stat-label { color:#1d5fb8; }
|
|
.stat-card:nth-child(4) .stat-label { color:#be185d; }
|
|
.stat-num { font-size:42px; font-weight:800; line-height:1; color:#0f1f3d; margin-bottom:12px; }
|
|
.stat-link { font-size:11px; font-weight:700; display:inline-flex; align-items:center; gap:4px; opacity:0.6; transition:opacity 0.2s; }
|
|
.stat-card:hover .stat-link { opacity:1; }
|
|
.stat-card:nth-child(1) .stat-link { color:#c2651a; }
|
|
.stat-card:nth-child(2) .stat-link { color:#15803d; }
|
|
.stat-card:nth-child(3) .stat-link { color:#1d5fb8; }
|
|
.stat-card:nth-child(4) .stat-link { color:#be185d; }
|
|
.stat-icon { position:absolute; top:20px; right:20px; width:42px; height:42px; border-radius:12px; display:flex; align-items:center; justify-content:center; font-size:20px; opacity:0.7; }
|
|
.stat-card:nth-child(1) .stat-icon { background:rgba(249,115,22,0.15); }
|
|
.stat-card:nth-child(2) .stat-icon { background:rgba(34,197,94,0.15); }
|
|
.stat-card:nth-child(3) .stat-icon { background:rgba(43,142,243,0.15); }
|
|
.stat-card:nth-child(4) .stat-icon { background:rgba(236,72,153,0.15); }
|
|
|
|
/* ── DASH CARDS ── */
|
|
.dash-card { background:white; border-radius:20px; border:1.5px solid #e8f0fb; padding:24px; box-shadow:0 4px 20px rgba(43,142,243,0.06); }
|
|
.card-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:18px; flex-wrap:wrap; gap:8px; }
|
|
.card-title { font-size:15px; font-weight:700; color:#0f1f3d; }
|
|
.card-badge { font-size:11px; font-weight:700; background:#e8f4ff; color:#2b8ef3; padding:3px 10px; border-radius:99px; text-decoration:none; white-space:nowrap; }
|
|
.card-badge:hover { background:#dbeeff; }
|
|
|
|
/* ── BOTTOM GRID ── */
|
|
.dash-bottom {
|
|
display: grid;
|
|
grid-template-columns: 1fr 320px;
|
|
gap: 20px;
|
|
animation: fadeUp 0.4s ease 0.28s both;
|
|
margin-bottom: 20px;
|
|
}
|
|
.chart-wrap { position:relative; height:220px; }
|
|
|
|
/* ── CHALLENGE ── */
|
|
.challenge-item { background:#f8faff; border-radius:14px; padding:14px 16px; margin-bottom:10px; border-left:4px solid #2b8ef3; transition:all 0.2s; }
|
|
.challenge-item:nth-child(2) { border-left-color:#22c55e; }
|
|
.challenge-item:nth-child(3) { border-left-color:#f97316; }
|
|
.challenge-item:hover { background:#eef4ff; transform:translateX(3px); }
|
|
.ch-kelas { font-size:12px; font-weight:700; color:#2b8ef3; margin-bottom:3px; }
|
|
.challenge-item:nth-child(2) .ch-kelas { color:#16a34a; }
|
|
.challenge-item:nth-child(3) .ch-kelas { color:#ea580c; }
|
|
.ch-judul { font-size:13px; font-weight:600; color:#0f1f3d; margin-bottom:3px; }
|
|
.ch-meta { font-size:11px; color:#94a3b8; }
|
|
.btn-add-challenge { display:flex; align-items:center; justify-content:center; gap:6px; width:100%; padding:11px; background:white; border:2px dashed #dbeeff; border-radius:12px; color:#2b8ef3; font-size:13px; font-weight:700; text-decoration:none; margin-top:4px; transition:all 0.2s; }
|
|
.btn-add-challenge:hover { background:#e8f4ff; border-color:#2b8ef3; }
|
|
|
|
/* ── LEADERBOARD ── */
|
|
.lb-section { animation:fadeUp 0.4s ease 0.35s both; margin-bottom:20px; }
|
|
.lb-head-info { font-size:12px; color:#94a3b8; font-weight:500; }
|
|
.lb-table-wrap { overflow-x:auto; -webkit-overflow-scrolling:touch; }
|
|
.lb-table { width:100%; border-collapse:collapse; min-width:500px; }
|
|
.lb-table th { font-size:11px; font-weight:700; color:#94a3b8; text-transform:uppercase; letter-spacing:0.5px; padding:10px 14px; text-align:left; border-bottom:1.5px solid #e8f0fb; white-space:nowrap; }
|
|
.lb-table td { padding:11px 14px; font-size:13px; color:#1e293b; border-bottom:1px solid #f1f5f9; vertical-align:middle; }
|
|
.lb-table tr:last-child td { border-bottom:none; }
|
|
.lb-table tr:hover td { background:#f8faff; }
|
|
.rank-badge { display:inline-flex; align-items:center; justify-content:center; width:28px; height:28px; border-radius:8px; font-size:13px; font-weight:800; }
|
|
.rank-other { background:#f8fafc; color:#94a3b8; font-size:12px; }
|
|
.lb-avatar { width:30px; height:30px; border-radius:50%; object-fit:cover; border:2px solid #e8f0fb; }
|
|
.lb-avatar-placeholder { width:30px; height:30px; border-radius:50%; background:#e6f0ff; display:inline-flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; color:#2b8ef3; border:2px solid #e8f0fb; }
|
|
.exp-bar-wrap { display:flex; align-items:center; gap:8px; }
|
|
.exp-bar-bg { flex:1; height:6px; background:#f1f5f9; border-radius:99px; overflow:hidden; min-width:60px; }
|
|
.exp-bar-fill { height:100%; border-radius:99px; background:linear-gradient(90deg,#2b8ef3,#a855f7); transition:width 0.6s ease; }
|
|
.exp-num { font-size:12px; font-weight:700; color:#2b8ef3; white-space:nowrap; min-width:58px; text-align:right; }
|
|
.kelas-chip-lb { display:inline-block; background:#e6f0ff; color:#1d4ed8; font-size:11px; font-weight:600; padding:2px 8px; border-radius:99px; }
|
|
|
|
@keyframes fadeUp {
|
|
from { opacity:0; transform:translateY(16px); }
|
|
to { opacity:1; transform:translateY(0); }
|
|
}
|
|
|
|
/* ══════════════════════════════════
|
|
RESPONSIVE
|
|
══════════════════════════════════ */
|
|
@media (max-width: 1024px) {
|
|
.stat-grid { grid-template-columns: repeat(2, 1fr); }
|
|
.dash-bottom { grid-template-columns: 1fr; }
|
|
.stat-num { font-size: 36px; }
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.stat-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; }
|
|
.stat-card { padding: 18px 16px; border-radius: 16px; }
|
|
.stat-num { font-size: 28px; }
|
|
.stat-label { font-size: 10px; }
|
|
.stat-icon { width: 34px; height: 34px; top: 14px; right: 14px; }
|
|
.stat-icon img { width: 18px; height: 18px; }
|
|
.dash-card { padding: 18px 16px; }
|
|
.card-title { font-size: 14px; }
|
|
.chart-wrap { height: 200px; }
|
|
.lb-table th, .lb-table td { padding: 9px 10px; font-size: 12px; }
|
|
.dash-bottom { gap: 14px; }
|
|
.dash-sub { margin-bottom: 16px; font-size: 12px; }
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.stat-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
|
|
.stat-card { padding: 14px 12px; border-radius: 14px; }
|
|
.stat-num { font-size: 24px; margin-bottom: 8px; }
|
|
.stat-label { font-size: 9px; letter-spacing: 0.5px; margin-bottom: 6px; }
|
|
.stat-link { font-size: 10px; }
|
|
.stat-card::after { width: 80px; height: 80px; right: -20px; bottom: -20px; }
|
|
.dash-card { padding: 14px 12px; border-radius: 16px; }
|
|
.card-title { font-size: 13px; }
|
|
.chart-wrap { height: 180px; }
|
|
.challenge-item { padding: 10px 12px; }
|
|
.ch-judul { font-size: 12px; }
|
|
.exp-bar-bg { min-width: 40px; }
|
|
.exp-num { min-width: 44px; font-size: 11px; }
|
|
.lb-table th, .lb-table td { padding: 8px 8px; font-size: 11px; }
|
|
.lb-avatar, .lb-avatar-placeholder { width: 26px; height: 26px; }
|
|
.exp-bar-wrap { gap: 4px; }
|
|
}
|
|
|
|
@media (max-width: 360px) {
|
|
.stat-num { font-size: 20px; }
|
|
.dash-bottom { gap: 10px; }
|
|
}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
|
|
@php use Carbon\Carbon; @endphp
|
|
|
|
<div class="dash-sub">{{ Carbon::now()->isoFormat('dddd, D MMMM Y') }}</div>
|
|
|
|
{{-- STAT CARDS --}}
|
|
<div class="stat-grid">
|
|
<a href="{{ route('admin.guru.index') }}" class="stat-card">
|
|
<div class="stat-icon"><img src="{{ asset('images/icon/gurud/guru.png') }}" width="22" height="22" alt="Guru"></div>
|
|
<div class="stat-label">Total Guru</div>
|
|
<div class="stat-num">{{ $totalGuru }}</div>
|
|
<div class="stat-link">Lihat Data →</div>
|
|
</a>
|
|
<a href="{{ route('admin.kelas.index') }}" class="stat-card">
|
|
<div class="stat-icon"><img src="{{ asset('images/icon/gurud/school.png') }}" width="22" height="22" alt="Kelas"></div>
|
|
<div class="stat-label">Total Kelas</div>
|
|
<div class="stat-num">{{ $totalKelas }}</div>
|
|
<div class="stat-link">Lihat Data →</div>
|
|
</a>
|
|
<a href="{{ route('admin.siswa.index') }}" class="stat-card">
|
|
<div class="stat-icon"><img src="{{ asset('images/icon/gurud/siswa.png') }}" width="22" height="22" alt="Siswa"></div>
|
|
<div class="stat-label">Total Siswa</div>
|
|
<div class="stat-num">{{ $totalSiswa }}</div>
|
|
<div class="stat-link">Lihat Data →</div>
|
|
</a>
|
|
<a href="{{ route('admin.mapel.index') }}" class="stat-card">
|
|
<div class="stat-icon"><img src="{{ asset('images/icon/gurud/stacked.png') }}" width="22" height="22" alt="Mapel"></div>
|
|
<div class="stat-label">Total Mapel</div>
|
|
<div class="stat-num">{{ $totalMapel }}</div>
|
|
<div class="stat-link">Lihat Data →</div>
|
|
</a>
|
|
</div>
|
|
|
|
{{-- BOTTOM GRID: Chart + Challenge --}}
|
|
<div class="dash-bottom">
|
|
<div class="dash-card">
|
|
<div class="card-head">
|
|
<div class="card-title">Total Siswa Berdasarkan Kelas</div>
|
|
<span class="card-badge">{{ $chartData->count() }} Kelas</span>
|
|
</div>
|
|
<div class="chart-wrap">
|
|
<canvas id="kelasChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dash-card">
|
|
<div class="card-head">
|
|
<div class="card-title">Challenge Terbaru</div>
|
|
<a href="{{ route('admin.challenge.index') }}" class="card-badge">Lihat Semua →</a>
|
|
</div>
|
|
|
|
@forelse($latestChallenges as $ch)
|
|
<div class="challenge-item">
|
|
<div class="ch-kelas">
|
|
{{ $ch->kelas->pluck('nama_kelas')->join(', ') ?: 'Semua Kelas' }}
|
|
</div>
|
|
<div class="ch-judul">{{ $ch->judul_challenge }}</div>
|
|
<div class="ch-meta">
|
|
{{ $ch->soal->count() }} Soal
|
|
@if($ch->tenggat_waktu)
|
|
· Tenggat: {{ \Carbon\Carbon::parse($ch->tenggat_waktu)->isoFormat('D MMM Y') }}
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div style="text-align:center;padding:24px;color:#94a3b8;font-size:13px;">
|
|
Belum ada challenge dibuat.
|
|
</div>
|
|
@endforelse
|
|
|
|
<a href="{{ route('admin.challenge.index') }}" class="btn-add-challenge">
|
|
+ Tambahkan Challenge Baru
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- LEADERBOARD TOP 10 --}}
|
|
<div class="lb-section">
|
|
<div class="dash-card">
|
|
<div class="card-head">
|
|
<div>
|
|
<div class="card-title">Leaderboard Top 10 — Semua Kelas</div>
|
|
<div class="lb-head-info">
|
|
Semester {{ $semester }} • {{ $tahunAjaran }}
|
|
</div>
|
|
</div>
|
|
<a href="{{ route('admin.leaderboard.index') }}" class="card-badge">Lihat Semua →</a>
|
|
</div>
|
|
|
|
@if($leaderboard->count() > 0)
|
|
@php $maxExp = $leaderboard->max('total_exp') ?: 1; @endphp
|
|
<div class="lb-table-wrap">
|
|
<table class="lb-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:48px">Rank</th>
|
|
<th>Nama Siswa</th>
|
|
<th>Kelas</th>
|
|
<th>Total EXP</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($leaderboard as $row)
|
|
@php
|
|
$pct = $maxExp > 0 ? round(($row['total_exp'] / $maxExp) * 100) : 0;
|
|
@endphp
|
|
<tr>
|
|
<td>
|
|
@if($row['ranking'] === 1)
|
|
<img src="{{ asset('images/icon/siswal/1.png') }}" style="width:28px;height:28px;object-fit:contain" alt="Rank 1">
|
|
@elseif($row['ranking'] === 2)
|
|
<img src="{{ asset('images/icon/siswal/2.png') }}" style="width:28px;height:28px;object-fit:contain" alt="Rank 2">
|
|
@elseif($row['ranking'] === 3)
|
|
<img src="{{ asset('images/icon/siswal/3.png') }}" style="width:28px;height:28px;object-fit:contain" alt="Rank 3">
|
|
@else
|
|
<span class="rank-badge rank-other">{{ $row['ranking'] }}</span>
|
|
@endif
|
|
</td>
|
|
<td style="font-weight:600">{{ $row['nama'] }}</td>
|
|
<td><span class="kelas-chip-lb">{{ $row['kelas'] }}</span></td>
|
|
<td>
|
|
<div class="exp-bar-wrap">
|
|
<div class="exp-bar-bg">
|
|
<div class="exp-bar-fill" style="width:{{ $pct }}%"></div>
|
|
</div>
|
|
<span class="exp-num">{{ number_format($row['total_exp']) }} EXP</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@else
|
|
<div style="text-align:center;padding:40px 20px;color:#94a3b8;font-size:13px;">
|
|
<img src="{{ asset('images/icon/gurud/medal-pita.png') }}" style="width:36px;margin-bottom:8px" alt="">
|
|
<div>Belum ada data leaderboard.</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
<script>
|
|
const labels = @json($chartData->pluck('nama_kelas'));
|
|
const values = @json($chartData->pluck('siswa_count'));
|
|
const colors = [
|
|
'rgba(43,142,243,0.85)','rgba(34,197,94,0.85)','rgba(249,115,22,0.85)',
|
|
'rgba(168,85,247,0.85)','rgba(236,72,153,0.85)','rgba(234,179,8,0.85)',
|
|
];
|
|
|
|
const ctx = document.getElementById('kelasChart').getContext('2d');
|
|
const kelasChart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels,
|
|
datasets: [{
|
|
label: 'Jumlah Siswa',
|
|
data: values,
|
|
backgroundColor: labels.map((_, i) => colors[i % colors.length]),
|
|
borderRadius: 8,
|
|
borderSkipped: false,
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: { callbacks: { label: ctx => ` ${ctx.parsed.y} siswa` } }
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: { color: '#f1f5f9' },
|
|
ticks: { font: { size: 11 }, color: '#94a3b8', stepSize: 1 }
|
|
},
|
|
x: {
|
|
grid: { display: false },
|
|
ticks: { font: { size: 11 }, color: '#64748b', maxRotation: 35, autoSkip: true }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
document.getElementById('sidebarToggleBtn')?.addEventListener('click', () => {
|
|
setTimeout(() => kelasChart.resize(), 320);
|
|
});
|
|
window.addEventListener('resize', () => { kelasChart.resize(); });
|
|
</script>
|
|
@endpush |