412 lines
17 KiB
PHP
412 lines
17 KiB
PHP
@extends('guru.layouts.app')
|
|
|
|
@section('title', 'Dashboard Guru')
|
|
|
|
@push('styles')
|
|
<style>
|
|
.dash-sub { font-size: 13px; color: #94a3b8; margin-bottom: 16px; }
|
|
|
|
/* ── STAT CARDS ── */
|
|
.stat-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
}
|
|
@media (max-width: 576px) {
|
|
.stat-grid { grid-template-columns: 1fr; gap: 10px; }
|
|
}
|
|
@media (min-width: 577px) and (max-width: 768px) {
|
|
.stat-grid { grid-template-columns: repeat(3, 1fr); gap: 10px; }
|
|
}
|
|
|
|
.stat-card {
|
|
border-radius: 16px; padding: 16px 14px;
|
|
position: relative; overflow: hidden;
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
animation: fadeUp 0.4s ease both;
|
|
}
|
|
@media (min-width: 769px) { .stat-card { border-radius: 20px; padding: 24px 22px; } }
|
|
.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,#eef4ff,#c5d9ff); 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,#fff7e6,#ffe4b2); animation-delay:0.14s; }
|
|
.stat-card::after {
|
|
content:''; position:absolute; width:100px; height:100px;
|
|
border-radius:50%; opacity:0.15; right:-24px; bottom:-24px;
|
|
}
|
|
@media (min-width: 769px) { .stat-card::after { width: 120px; height: 120px; right:-30px; bottom:-30px; } }
|
|
.stat-card:nth-child(1)::after { background:#2b8ef3; }
|
|
.stat-card:nth-child(2)::after { background:#22c55e; }
|
|
.stat-card:nth-child(3)::after { background:#f97316; }
|
|
.stat-label {
|
|
font-size:10px; font-weight:700; letter-spacing:0.8px;
|
|
text-transform:uppercase; margin-bottom:6px;
|
|
}
|
|
@media (min-width: 769px) { .stat-label { font-size:11px; letter-spacing:1px; margin-bottom:10px; } }
|
|
.stat-card:nth-child(1) .stat-label { color:#1d5fb8; }
|
|
.stat-card:nth-child(2) .stat-label { color:#15803d; }
|
|
.stat-card:nth-child(3) .stat-label { color:#c2651a; }
|
|
.stat-num { font-size:30px; font-weight:800; line-height:1; color:#0f1f3d; }
|
|
@media (min-width: 769px) { .stat-num { font-size:42px; } }
|
|
.stat-icon {
|
|
position:absolute; top:14px; right:14px;
|
|
width:34px; height:34px; border-radius:10px;
|
|
display:flex; align-items:center; justify-content:center; opacity:0.7;
|
|
}
|
|
@media (min-width: 769px) { .stat-icon { top:20px; right:20px; width:42px; height:42px; border-radius:12px; } }
|
|
.stat-card:nth-child(1) .stat-icon { background:rgba(43,142,243,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(249,115,22,0.15); }
|
|
.stat-link {
|
|
display:inline-block; margin-top:8px;
|
|
font-size:10px; font-weight:700;
|
|
padding:3px 10px; border-radius:99px;
|
|
text-decoration:none; transition:opacity 0.2s;
|
|
}
|
|
@media (min-width: 769px) { .stat-link { margin-top:12px; font-size:11px; padding:4px 12px; } }
|
|
.stat-link:hover { opacity:0.75; }
|
|
.stat-card:nth-child(1) .stat-link { background:rgba(43,142,243,0.15); color:#1d5fb8; }
|
|
.stat-card:nth-child(2) .stat-link { background:rgba(34,197,94,0.15); color:#15803d; }
|
|
.stat-card:nth-child(3) .stat-link { background:rgba(249,115,22,0.15); color:#c2651a; }
|
|
|
|
/* ── DASH CARDS ── */
|
|
.dash-card {
|
|
background: white;
|
|
border-radius: 16px;
|
|
border: 1.5px solid #e8f0fb;
|
|
padding: 16px;
|
|
box-shadow: 0 4px 20px rgba(43,142,243,0.06);
|
|
}
|
|
@media (min-width: 769px) { .dash-card { border-radius: 20px; padding: 24px; } }
|
|
.card-head {
|
|
display:flex; align-items:center;
|
|
justify-content:space-between; margin-bottom:14px;
|
|
flex-wrap: wrap; gap: 8px;
|
|
}
|
|
@media (min-width: 769px) { .card-head { margin-bottom:18px; } }
|
|
.card-title { font-size:13px; font-weight:700; color:#0f1f3d; }
|
|
@media (min-width: 769px) { .card-title { font-size:15px; } }
|
|
.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;
|
|
gap: 14px;
|
|
margin-bottom: 14px;
|
|
animation: fadeUp 0.4s ease 0.2s both;
|
|
}
|
|
@media (min-width: 769px) {
|
|
.dash-bottom { grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
|
|
}
|
|
|
|
/* ── CHART ── */
|
|
.chart-wrap { position:relative; height:200px; }
|
|
@media (min-width: 769px) { .chart-wrap { height: 240px; } }
|
|
.chart-legend { display:flex; gap:14px; margin-top:10px; flex-wrap: wrap; }
|
|
.legend-item { display:flex; align-items:center; gap:6px; font-size:12px; color:#64748b; font-weight:500; }
|
|
.legend-dot { width:10px; height:10px; border-radius:3px; flex-shrink:0; }
|
|
|
|
/* ── MAPEL LIST ── */
|
|
.mapel-item {
|
|
display:flex; align-items:center; gap:10px;
|
|
padding:9px 12px; border-radius:12px;
|
|
margin-bottom:6px; background:#f8faff; transition:background 0.2s;
|
|
}
|
|
.mapel-item:hover { background:#eef4ff; }
|
|
.mapel-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
|
.mapel-name { font-size:12px; font-weight:600; color:#1e293b; flex:1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@media (min-width: 769px) { .mapel-name { font-size:13px; } }
|
|
.mapel-kelas { font-size:11px; color:#94a3b8; background:#f1f5f9; padding:2px 8px; border-radius:99px; white-space: nowrap; flex-shrink: 0; }
|
|
|
|
/* ── LEADERBOARD ── */
|
|
.lb-section {
|
|
animation: fadeUp 0.4s ease 0.3s both;
|
|
margin-bottom: 14px;
|
|
}
|
|
@media (min-width: 769px) { .lb-section { 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: 400px; }
|
|
.lb-table th {
|
|
font-size: 11px; font-weight: 700; color: #94a3b8;
|
|
text-transform: uppercase; letter-spacing: 0.5px;
|
|
padding: 8px 10px; text-align: left;
|
|
border-bottom: 1.5px solid #e8f0fb; white-space: nowrap;
|
|
}
|
|
.lb-table td {
|
|
padding: 9px 10px; font-size: 12px; color: #1e293b;
|
|
border-bottom: 1px solid #f1f5f9; vertical-align: middle;
|
|
}
|
|
@media (min-width: 769px) {
|
|
.lb-table th { padding: 10px 14px; }
|
|
.lb-table td { padding: 11px 14px; font-size: 13px; }
|
|
}
|
|
.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: 26px; height: 26px; border-radius: 8px;
|
|
font-size: 12px; font-weight: 800;
|
|
}
|
|
.rank-1 { background: #fef9c3; color: #854d0e; }
|
|
.rank-2 { background: #f1f5f9; color: #475569; }
|
|
.rank-3 { background: #fff7ed; color: #9a3412; }
|
|
.rank-other { background: #f8fafc; color: #94a3b8; font-size:11px; }
|
|
|
|
.lb-avatar { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; border: 2px solid #e8f0fb; }
|
|
.lb-avatar-placeholder { width: 28px; height: 28px; border-radius: 50%; background: #e6f0ff; display:inline-flex; align-items:center; justify-content:center; font-size: 10px; font-weight: 700; color: #2b8ef3; border: 2px solid #e8f0fb; }
|
|
.exp-bar-wrap { display: flex; align-items: center; gap: 6px; min-width: 100px; }
|
|
.exp-bar-bg { flex: 1; height: 5px; background: #f1f5f9; border-radius: 99px; overflow: hidden; min-width: 40px; }
|
|
.exp-bar-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, #2b8ef3, #a855f7); transition: width 0.6s ease; }
|
|
.exp-num { font-size: 11px; font-weight: 700; color: #2b8ef3; white-space: nowrap; min-width: 50px; text-align: right; }
|
|
@media (min-width: 769px) { .exp-num { font-size: 12px; min-width: 58px; } }
|
|
.kelas-chip-lb { display: inline-block; background: #e6f0ff; color: #1d4ed8; font-size: 10px; font-weight: 600; padding: 2px 7px; border-radius: 99px; white-space: nowrap; }
|
|
@media (min-width: 769px) { .kelas-chip-lb { font-size: 11px; } }
|
|
|
|
@keyframes fadeUp {
|
|
from { opacity:0; transform:translateY(16px); }
|
|
to { opacity:1; transform:translateY(0); }
|
|
}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
|
|
@php
|
|
use Carbon\Carbon;
|
|
use App\Models\Mengajar;
|
|
$guru = Auth::guard('guru')->user();
|
|
$mengajars = Mengajar::with(['mapel','kelas'])->where('id_guru', $guru->id_guru)->get();
|
|
$dotColors = ['#2b8ef3','#22c55e','#f97316','#a855f7','#ec4899','#eab308'];
|
|
@endphp
|
|
|
|
<div class="dash-sub">{{ Carbon::now()->isoFormat('dddd, D MMMM Y') }}</div>
|
|
|
|
{{-- STAT CARDS --}}
|
|
<div class="stat-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-icon">
|
|
<img src="{{ asset('images/icon/gurud/school.png') }}" class="icon-sm" alt="Kelas">
|
|
</div>
|
|
<div class="stat-label">Kelas Diampu</div>
|
|
<div class="stat-num">{{ $totalKelas }}</div>
|
|
<a href="{{ route('guru.kelas.index') }}" class="stat-link">Lihat →</a>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon">
|
|
<img src="{{ asset('images/icon/gurud/stacked.png') }}" class="icon-sm" alt="Mata Pelajaran">
|
|
</div>
|
|
<div class="stat-label">Mata Pelajaran</div>
|
|
<div class="stat-num">{{ $totalMapel }}</div>
|
|
<a href="{{ route('guru.mapel.index') }}" class="stat-link">Lihat →</a>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon">
|
|
<img src="{{ asset('images/icon/gurud/siswa.png') }}" class="icon-sm" alt="Siswa">
|
|
</div>
|
|
<div class="stat-label">Siswa Diajar</div>
|
|
<div class="stat-num">{{ $totalSiswa }}</div>
|
|
<a href="{{ route('guru.siswa.index') }}" class="stat-link">Lihat →</a>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- BOTTOM GRID: Chart + Mapel List --}}
|
|
<div class="dash-bottom">
|
|
|
|
{{-- Grouped Bar Chart --}}
|
|
<div class="dash-card">
|
|
<div class="card-head">
|
|
<div class="card-title">Pengumpulan Tugas per Mapel & Kelas</div>
|
|
<a href="{{ route('guru.tugas.history') }}" class="card-badge">Lihat Semua →</a>
|
|
</div>
|
|
|
|
@if(count($chartLabels) > 0)
|
|
<div class="chart-wrap">
|
|
<canvas id="tugasChart" role="img"
|
|
aria-label="Grouped bar chart pengumpulan tugas tepat waktu vs terlambat per mapel dan kelas">
|
|
</canvas>
|
|
</div>
|
|
<div class="chart-legend">
|
|
<div class="legend-item">
|
|
<div class="legend-dot" style="background:#2b8ef3"></div>
|
|
Tepat Waktu
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-dot" style="background:#f97316"></div>
|
|
Terlambat
|
|
</div>
|
|
</div>
|
|
@else
|
|
<div style="text-align:center;padding:40px 20px;color:#94a3b8;">
|
|
<img src="{{ asset('images/icon/gurud/buku1.png') }}" class="icon-sm" alt="" style="margin-bottom:8px">
|
|
<div style="font-size:13px">Belum ada data pengumpulan tugas.</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Daftar Mengajar --}}
|
|
<div class="dash-card">
|
|
<div class="card-head">
|
|
<div class="card-title">Mata Pelajaran & Kelas</div>
|
|
<a href="{{ route('guru.mapel.index') }}" class="card-badge">Lihat Semua →</a>
|
|
</div>
|
|
|
|
@forelse($mengajars->take(7) as $i => $m)
|
|
<div class="mapel-item">
|
|
<div class="mapel-dot" style="background:{{ $dotColors[$i % count($dotColors)] }}"></div>
|
|
<div class="mapel-name">{{ optional($m->mapel)->nama_mapel ?? '-' }}</div>
|
|
<div class="mapel-kelas">{{ optional($m->kelas)->nama_kelas ?? '-' }}</div>
|
|
</div>
|
|
@empty
|
|
<div style="text-align:center;padding:20px;color:#94a3b8;font-size:13px;">
|
|
Belum ada data mengajar.
|
|
</div>
|
|
@endforelse
|
|
</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('guru.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:44px">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:26px;height:26px;object-fit:contain" alt="Rank 1">
|
|
@elseif($row['ranking'] === 2)
|
|
<img src="{{ asset('images/icon/siswal/2.png') }}" style="width:26px;height:26px;object-fit:contain" alt="Rank 2">
|
|
@elseif($row['ranking'] === 3)
|
|
<img src="{{ asset('images/icon/siswal/3.png') }}" style="width:26px;height:26px;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:30px 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 untuk semester ini.</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>
|
|
@if(count($chartLabels) > 0)
|
|
const tugasCtx = document.getElementById('tugasChart').getContext('2d');
|
|
const tugasChart = new Chart(tugasCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: @json($chartLabels),
|
|
datasets: [
|
|
{
|
|
label: 'Tepat Waktu',
|
|
data: @json($chartTepat),
|
|
backgroundColor: 'rgba(43,142,243,0.85)',
|
|
borderRadius: { topLeft:6, topRight:6, bottomLeft:0, bottomRight:0 },
|
|
borderSkipped: false,
|
|
},
|
|
{
|
|
label: 'Terlambat',
|
|
data: @json($chartTerlambat),
|
|
backgroundColor: 'rgba(249,115,22,0.85)',
|
|
borderRadius: { topLeft:6, topRight:6, bottomLeft:0, bottomRight:0 },
|
|
borderSkipped: false,
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
callbacks: {
|
|
label: ctx => ` ${ctx.dataset.label}: ${ctx.parsed.y} siswa`
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: { display: false },
|
|
ticks: {
|
|
font: { family: 'Poppins', size: 10 },
|
|
color: '#64748b',
|
|
autoSkip: false,
|
|
maxRotation: 35,
|
|
}
|
|
},
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: { color: '#f1f5f9' },
|
|
ticks: {
|
|
font: { family: 'Poppins', size: 11 },
|
|
color: '#94a3b8',
|
|
stepSize: 1,
|
|
callback: v => Math.round(v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
document.getElementById('sidebarToggleBtn')?.addEventListener('click', () => {
|
|
setTimeout(() => tugasChart.resize(), 320);
|
|
});
|
|
@endif
|
|
</script>
|
|
@endpush |