feat: Fitur Laporan Minat Baca (Role Guru)

This commit is contained in:
zhadaarsita 2025-10-18 23:26:44 +07:00
parent 2b7d234475
commit 98679fec62
5 changed files with 346 additions and 60 deletions

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Guru;
use App\Http\Controllers\Controller;
use App\Services\DummyDataService;
use Illuminate\Http\Request;
class LaporanController extends Controller
{
public function index()
{
$laporan = DummyDataService::getLaporanMinatBaca();
$siswaTeraktif = DummyDataService::getSiswaTeraktif();
$aktivitasMingguan = DummyDataService::getAktivitasMingguan();
return view('guru.laporan.index', [
'pageTitle' => 'Laporan Minat Baca Siswa',
'laporan' => $laporan,
'siswaTeraktif' => $siswaTeraktif,
'aktivitasMingguan' => $aktivitasMingguan,
]);
}
}

View File

@ -4,6 +4,9 @@
class DummyDataService class DummyDataService
{ {
/**
* Data dummy untuk daftar siswa dan pengguna.
*/
public static function getAllSiswa(): array public static function getAllSiswa(): array
{ {
return [ return [
@ -56,11 +59,64 @@ public static function getAllSiswa(): array
'email' => 'rina.marlina@smkn1perpus.sch.id', 'email' => 'rina.marlina@smkn1perpus.sch.id',
'nomor_hp' => '081223344556', 'nomor_hp' => '081223344556',
'password' => 'password', 'password' => 'password',
'role' => 'penjaga perpus', 'role' => 'guru',
], ],
]; ];
} }
/**
* Data dummy untuk grafik aktivitas membaca mingguan di laporan guru.
*/
public static function getAktivitasMingguan(): array
{
return [
'labels' => ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu'],
'data' => [15, 22, 18, 25, 20, 30, 28],
];
}
/**
* Data dummy untuk menampilkan 10 siswa paling aktif di laporan guru.
*/
public static function getSiswaTeraktif(): array
{
return [
['nama' => 'Silvi Rahmawati', 'total_buku' => 45, 'kelas' => 'XII RPL'],
['nama' => 'Siti Nurhaliza', 'total_buku' => 33, 'kelas' => 'XII RPL A'],
['nama' => 'Andi Pratama', 'total_buku' => 30, 'kelas' => 'XII RPL B'],
['nama' => 'John Wick', 'total_buku' => 28, 'kelas' => 'XI TKJ'],
['nama' => 'Dewi Lestari', 'total_buku' => 25, 'kelas' => 'XII AKL'],
['nama' => 'Eko Prasetyo', 'total_buku' => 22, 'kelas' => 'XI MM'],
['nama' => 'Rina Marlina', 'total_buku' => 20, 'kelas' => 'XII TKJ A'],
['nama' => 'Budi Santoso', 'total_buku' => 18, 'kelas' => 'X OTKP'],
['nama' => 'Putri Amelia', 'total_buku' => 16, 'kelas' => 'XI RPL C'],
['nama' => 'Ahmad Haziq', 'total_buku' => 12, 'kelas' => 'X TKJ B'],
];
}
/**
* Data dummy untuk halaman Laporan Minat Baca khusus Guru.
*/
public static function getLaporanMinatBaca(): array
{
return [
'buku_terpopuler' => [
['judul' => 'Modul Ajar IPAS', 'penulis' => 'Tim Kemdikbud Ristek', 'total_pembaca' => 125, 'cover' => 'images/covers/ipas.jpg'],
['judul' => 'Ayah', 'penulis' => 'Andrea Hirata', 'total_pembaca' => 98, 'cover' => 'images/covers/ayah.png'],
['judul' => 'Si Anak Pintar', 'penulis' => 'Tere Liye', 'total_pembaca' => 92, 'cover' => 'images/covers/sianakpintar.jpg'],
],
'kategori_populer' => [
['nama' => 'Sains', 'total_pembaca' => 230, 'trend' => 'naik', 'icon' => 'bi-arrow-up-right'],
['nama' => 'Fiksi', 'total_pembaca' => 190, 'trend' => 'stabil', 'icon' => 'bi-arrow-right'],
['nama' => 'Pendidikan', 'total_pembaca' => 150, 'trend' => 'turun', 'icon' => 'bi-arrow-down-right'],
['nama' => 'Novel', 'total_pembaca' => 110, 'trend' => 'naik', 'icon' => 'bi-arrow-up-right'],
],
'insight' => 'Siswa menunjukkan minat baca tertinggi pada kategori Sains dan Fiksi. Buku-buku karangan Tere Liye dan Andrea Hirata masih menjadi favorit.',
];
}
public static function getAdminDashboardStats(): array public static function getAdminDashboardStats(): array
{ {
$allBooks = self::getAllBooks(); $allBooks = self::getAllBooks();

View File

@ -0,0 +1,184 @@
<x-app-layout>
@section('page-title', $pageTitle)
<div class="row g-4">
{{-- ========================= --}}
{{-- KOLOM KIRI (KONTEN UTAMA) --}}
{{-- ========================= --}}
<div class="col-lg-8">
<div class="d-flex flex-column gap-4">
{{-- GRAFIK AKTIVITAS MEMBACA --}}
<div class="card shadow-sm border-0 rounded-4 p-3">
<div class="card-header bg-white border-0 pb-0">
<h5 class="fw-semibold mb-2 text-primary">
<i class="bi bi-graph-up me-2"></i>Grafik Aktivitas Membaca (7 Hari Terakhir)
</h5>
</div>
<div class="card-body pt-3 pb-2">
<canvas id="lineChart" style="height: 280px;" data-stats='@json($aktivitasMingguan)'></canvas>
</div>
</div>
<div class="row g-4">
{{-- BUKU TERPOPULER --}}
<div class="col-md-6">
<div class="card shadow-sm border-0 rounded-4 p-3 h-100">
<div class="card-header bg-white border-0 pb-0">
<h5 class="fw-semibold mb-2 text-warning">
<i class="bi bi-trophy-fill me-2"></i>Buku Terpopuler
</h5>
</div>
<div class="card-body pt-2">
<div class="list-group list-group-flush">
@foreach ($laporan['buku_terpopuler'] as $buku)
<div
class="list-group-item d-flex align-items-center gap-3 px-0 border-0 py-2 hover-bg-light my-1">
<img src="{{ asset($buku['cover']) }}" class="rounded shadow-sm"
width="48" alt="Cover">
<div class="flex-grow-1">
<div class="fw-semibold small text-truncate">{{ $buku['judul'] }}</div>
</div>
<span class="badge bg-primary-subtle text-primary rounded-pill px-3">
{{ $buku['total_pembaca'] }}
</span>
</div>
@if (!$loop->last)
<hr class="my-0">
@endif
@endforeach
</div>
</div>
</div>
</div>
{{-- KATEGORI PALING DIMINATI --}}
<div class="col-md-6">
<div class="card shadow-sm border-0 rounded-4 p-3 h-100">
<div class="card-header bg-white border-0 pb-0">
<h5 class="fw-semibold mb-2 text-info">
<i class="bi bi-tags-fill me-2"></i>Kategori Paling Diminati
</h5>
</div>
<div class="card-body pt-2">
<div class="list-group list-group-flush">
@foreach ($laporan['kategori_populer'] as $kategori)
<div
class="list-group-item d-flex justify-content-between align-items-center px-0 border-0 py-2 hover-bg-light my-1">
<span class="small d-flex align-items-center">
<i
class="bi {{ $kategori['icon'] }} me-2
@if ($kategori['trend'] == 'naik') text-success
@elseif($kategori['trend'] == 'turun') text-danger @endif"></i>
{{ $kategori['nama'] }}
</span>
<span class="badge bg-info-subtle text-info rounded-pill px-3">
{{ $kategori['total_pembaca'] }}
</span>
</div>
@if (!$loop->last)
<hr class="my-0">
@endif
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- ========================= --}}
{{-- KOLOM KANAN (SISWA AKTIF) --}}
{{-- ========================= --}}
<div class="col-lg-4">
<div class="card shadow-sm border-0 rounded-4 p-3 h-100">
<div class="card-header bg-white border-0 pb-0">
<h5 class="fw-semibold mb-2 text-success">
<i class="bi bi-person-check-fill me-2"></i>Siswa Paling Aktif
</h5>
</div>
<div class="card-body pt-2">
@foreach ($siswaTeraktif as $siswa)
<div
class="list-group-item d-flex align-items-center gap-3 px-0 border-0 py-2 my-1 hover-bg-light">
<img src="https://ui-avatars.com/api/?name={{ urlencode($siswa['nama']) }}&background=random"
class="rounded-circle shadow-sm" width="42" height="42" alt="Avatar">
<div class="flex-grow-1">
<div class="fw-semibold">{{ $siswa['nama'] }}</div>
<small class="text-muted">{{ $siswa['kelas'] }}</small>
</div>
<span class="badge bg-success-subtle text-success rounded-pill px-3">
{{ $siswa['total_buku'] }}
</span>
</div>
@if (!$loop->last)
<hr class="my-0">
@endif
@endforeach
</div>
</div>
</div>
</div>
{{-- ========================= --}}
{{-- SCRIPT CHART.JS --}}
{{-- ========================= --}}
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (window.myLineChart) window.myLineChart.destroy();
const lineCanvas = document.getElementById('lineChart');
if (lineCanvas) {
const lineData = JSON.parse(lineCanvas.dataset.stats);
window.myLineChart = new Chart(lineCanvas, {
type: 'line',
data: {
labels: lineData.labels,
datasets: [{
label: 'Aktivitas Baca',
data: lineData.data,
fill: true,
backgroundColor: 'rgba(67, 94, 190, 0.1)',
borderColor: '#435ebe',
borderWidth: 2,
tension: 0.35,
pointRadius: 4,
pointBackgroundColor: '#435ebe'
}]
},
options: {
scales: {
y: {
beginAtZero: true,
grid: {
color: '#f0f0f0'
},
ticks: {
stepSize: 5
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
legend: {
display: false
}
},
responsive: true,
maintainAspectRatio: false
}
});
}
});
</script>
@endpush
</x-app-layout>

View File

@ -1,73 +1,94 @@
<aside id="sidebar" class="sidebar bg-white"> <aside id="sidebar" class="sidebar bg-white">
<div class="sidebar-header d-flex justify-content-between align-items-center px-3 py-3 py-md-2"> <div class="sidebar-header d-flex justify-content-between align-items-center px-3 py-3 py-md-2">
<a href="{{ session('user_data.role') == 'penjaga perpus' ? route('admin.dashboard') : route('dashboard') }}" <a href="{{ Auth::user()->role == 'penjaga perpus' ? route('admin.dashboard') : route('dashboard') }}"
class="d-flex align-items-center text-decoration-none" style="gap: 0.75rem;"> class="d-flex align-items-center text-decoration-none" style="gap: 0.75rem;">
<img src="{{ asset('images/logo/icon.svg') }}" alt="Ikon Perpus" style="height: 32px;" class="mt-md-2"> <img src="{{ asset('images/logo/icon.svg') }}" alt="Ikon Perpus" style="height: 32px;" class="mt-md-2">
<div class="d-flex align-items-center mt-md-2" style="gap: 0.75rem;"> <div class="d-flex align-items-center mt-md-2" style="gap: 0.75rem;">
<div class="vr bg-primary sidebar-title" style="width: 2px; height: 24px;"></div> <div class="vr bg-primary sidebar-title" style="width: 2px; height: 24px;"></div>
<img src="{{ asset('images/logo/name.svg') }}" alt="Perpus" style="height: 24px;" <img src="{{ asset('images/logo/name.svg') }}" alt="Perpus" style="height: 24px;"
class="sidebar-title"> class="sidebar-title">
</div> </div>
</a> </a>
{{-- Tombol close mobile --}}
<button type="button" class="btn-close d-lg-none" id="closeSidebarMobile"></button> <button type="button" class="btn-close d-lg-none" id="closeSidebarMobile"></button>
</div> </div>
<ul class="nav flex-column px-2 mt-2"> <ul class="nav flex-column px-2 mt-2">
@if (session('user_data.role') == 'penjaga perpus') @if (Auth::user()->role == 'penjaga perpus')
{{-- Menu untuk Penjaga Perpus --}} {{-- ================= MENU PENJAGA PERPUSTAKAAN ================= --}}
<li class="nav-item"> <li class="nav-item"><a href="{{ route('admin.dashboard') }}"
<a href="{{ route('admin.dashboard') }}" class="nav-link {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}"><i
class="nav-link {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}"> class="bi bi-grid-1x2-fill"></i><span class="nav-text ms-2">Dashboard</span></a>
<i class="bi bi-grid-1x2-fill"></i><span class="nav-text ms-2">Dashboard</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item"><a href="{{ route('admin.buku.index') }}"
<a href="{{ route('admin.buku.index') }}" class="nav-link {{ request()->routeIs('admin.buku.*') ? 'active' : '' }}"><i
class="nav-link {{ request()->routeIs('admin.buku.*') ? 'active' : '' }}"> class="bi bi-book-fill"></i><span class="nav-text ms-2">Manajemen Buku</span></a>
<i class="bi bi-book-fill"></i><span class="nav-text ms-2">Manajemen Buku</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item"><a href="{{ route('admin.pengumuman.index') }}"
<a href="{{ route('admin.pengumuman.index') }}" class="nav-link {{ request()->routeIs('admin.pengumuman.*') ? 'active' : '' }}"><i
class="nav-link {{ request()->routeIs('admin.pengumuman.*') ? 'active' : '' }}"> class="bi bi-megaphone-fill"></i><span class="nav-text ms-2">Pengumuman</span></a>
<i class="bi bi-megaphone-fill"></i><span class="nav-text ms-2">Pengumuman</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item"><a href="{{ route('admin.pengguna.index') }}"
<a href="{{ route('admin.pengguna.index') }}" class="nav-link {{ request()->routeIs('admin.pengguna.*') ? 'active' : '' }}"><i
class="nav-link {{ request()->routeIs('admin.pengguna.*') ? 'active' : '' }}"> class="bi bi-people-fill"></i><span class="nav-text ms-2">Manajemen Pengguna</span></a>
<i class="bi bi-people-fill"></i><span class="nav-text ms-2">Manajemen Pengguna</span>
</a>
</li> </li>
@elseif (Auth::user()->role == 'guru')
{{-- ================= MENU GURU (Siswa + Fitur Khusus) ================= --}}
<li class="nav-item"><a href="{{ route('dashboard') }}"
class="nav-link {{ request()->routeIs('dashboard') ? 'active' : '' }}"><i
class="bi bi-grid-1x2-fill"></i><span class="nav-text ms-2">Dashboard</span></a>
</li>
<li class="nav-item"><a href="{{ route('katalog.index') }}"
class="nav-link {{ request()->routeIs('katalog.*') ? 'active' : '' }}"><i
class="bi bi-search"></i><span class="nav-text ms-2">Katalog Buku</span></a>
</li>
<li class="nav-item"><a href="{{ route('peminjaman.index') }}"
class="nav-link {{ request()->routeIs('peminjaman.*') ? 'active' : '' }}"><i
class="bi bi-arrow-left-right"></i><span class="nav-text ms-2">Peminjaman Offline</span></a>
</li>
<li class="nav-item"><a href="{{ route('baca.index') }}"
class="nav-link {{ request()->routeIs('baca.*') ? 'active' : '' }}"><i
class="bi bi-globe"></i><span class="nav-text ms-2">Baca Buku Online</span></a>
</li>
<li class="nav-item"><a href="{{ route('guru.laporan.index') }}"
class="nav-link {{ request()->routeIs('guru.laporan.*') ? 'active' : '' }}"><i
class="bi bi-graph-up-arrow"></i><span class="nav-text ms-2">Laporan Minat Baca</span></a></li>
<li class="nav-item">
<a class="nav-link collapsed d-flex align-items-center {{ request()->routeIs('riwayat.*') ? 'active' : '' }}"
href="#riwayat-collapse" data-bs-toggle="collapse" aria-expanded="false">
<span><i class="bi bi-clock-history"></i><span class="nav-text ms-2">Riwayat</span></span>
<i class="bi bi-chevron-down ms-auto"></i>
</a>
<div class="collapse {{ request()->routeIs('riwayat.*') ? 'show' : '' }}" id="riwayat-collapse">
<ul class="nav flex-column ms-4">
<li class="nav-item"><a
class="nav-link py-1 {{ request()->routeIs('riwayat.offline') ? 'active' : '' }}"
href="{{ route('riwayat.offline') }}">Peminjaman Offline</a></li>
<li class="nav-item"><a
class="nav-link py-1 {{ request()->routeIs('riwayat.online') ? 'active' : '' }}"
href="{{ route('riwayat.online') }}">Baca Online</a></li>
</ul>
</div>
</li>
@else @else
{{-- Menu untuk Siswa --}} {{-- ================= MENU SISWA ================= --}}
<li class="nav-item"> <li class="nav-item"><a href="{{ route('dashboard') }}"
<a href="{{ route('dashboard') }}" class="nav-link {{ request()->routeIs('dashboard') ? 'active' : '' }}"><i
class="nav-link {{ request()->routeIs('dashboard') ? 'active' : '' }}"> class="bi bi-grid-1x2-fill"></i><span class="nav-text ms-2">Dashboard</span></a>
<i class="bi bi-grid-1x2-fill"></i><span class="nav-text ms-2">Dashboard</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item"><a href="{{ route('katalog.index') }}"
<a href="{{ route('katalog.index') }}" class="nav-link {{ request()->routeIs('katalog.*') ? 'active' : '' }}"><i
class="nav-link {{ request()->routeIs('katalog.*') ? 'active' : '' }}"> class="bi bi-search"></i><span class="nav-text ms-2">Katalog Buku</span></a>
<i class="bi bi-search"></i><span class="nav-text ms-2">Katalog Buku</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item"><a href="{{ route('peminjaman.index') }}"
<a href="{{ route('peminjaman.index') }}" class="nav-link {{ request()->routeIs('peminjaman.*') ? 'active' : '' }}"><i
class="nav-link {{ request()->routeIs('peminjaman.*') ? 'active' : '' }}"> class="bi bi-arrow-left-right"></i><span class="nav-text ms-2">Peminjaman Offline</span></a>
<i class="bi bi-arrow-left-right"></i><span class="nav-text ms-2">Peminjaman Offline</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item"><a href="{{ route('baca.index') }}"
<a href="{{ route('baca.index') }}" class="nav-link {{ request()->routeIs('baca.*') ? 'active' : '' }}"><i
class="nav-link {{ request()->routeIs('baca.*') ? 'active' : '' }}"> class="bi bi-globe"></i><span class="nav-text ms-2">Baca Buku Online</span></a>
<i class="bi bi-globe "></i><span class="nav-text ms-2">Baca Buku Online</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link collapsed d-flex align-items-center {{ request()->routeIs('riwayat.*') ? 'active' : '' }}" <a class="nav-link collapsed d-flex align-items-center {{ request()->routeIs('riwayat.*') ? 'active' : '' }}"
@ -75,24 +96,19 @@ class="nav-link {{ request()->routeIs('baca.*') ? 'active' : '' }}">
<span><i class="bi bi-clock-history"></i><span class="nav-text ms-2">Riwayat</span></span> <span><i class="bi bi-clock-history"></i><span class="nav-text ms-2">Riwayat</span></span>
<i class="bi bi-chevron-down ms-auto"></i> <i class="bi bi-chevron-down ms-auto"></i>
</a> </a>
<div class="collapse {{ request()->routeIs('riwayat.*') ? 'show' : '' }}" id="riwayat-collapse"> <div class="collapse {{ request()->routeIs('riwayat.*') ? 'show' : '' }}" id="riwayat-collapse">
<ul class="nav flex-column ms-4"> <ul class="nav flex-column ms-4">
<li class="nav-item"> <li class="nav-item"><a
<a class="nav-link {{ request()->routeIs('riwayat.offline') ? 'active' : '' }}" class="nav-link py-1 {{ request()->routeIs('riwayat.offline') ? 'active' : '' }}"
href="{{ route('riwayat.offline') }}"> href="{{ route('riwayat.offline') }}">Peminjaman Offline</a>
<span class="nav-text">Peminjaman Offline</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item"><a
<a class="nav-link {{ request()->routeIs('riwayat.online') ? 'active' : '' }}" class="nav-link py-1 {{ request()->routeIs('riwayat.online') ? 'active' : '' }}"
href="{{ route('riwayat.online') }}"> href="{{ route('riwayat.online') }}">Baca Online</a>
<span class="nav-text">Baca Online</span>
</a>
</li> </li>
</ul> </ul>
</div> </div>
</li> </li>
@endif @endif
</ul> </ul>
</aside> </aside>

View File

@ -11,6 +11,7 @@
use App\Http\Controllers\Admin\BookController as AdminBookController; use App\Http\Controllers\Admin\BookController as AdminBookController;
use App\Http\Controllers\Admin\PengumumanController; use App\Http\Controllers\Admin\PengumumanController;
use App\Http\Controllers\Admin\UserController as AdminUserController; use App\Http\Controllers\Admin\UserController as AdminUserController;
use App\Http\Controllers\Guru\LaporanController;
Route::get('/', function () { Route::get('/', function () {
return view('welcome'); return view('welcome');
@ -64,4 +65,9 @@
Route::get('/pengguna/{id}/edit', [AdminUserController::class, 'edit'])->name('pengguna.edit'); Route::get('/pengguna/{id}/edit', [AdminUserController::class, 'edit'])->name('pengguna.edit');
}); });
// GRUP RUTE KHUSUS UNTUK GURU
Route::middleware(['role:guru'])->prefix('guru')->name('guru.')->group(function () {
Route::get('/laporan-minat-baca', [LaporanController::class, 'index'])->name('laporan.index');
});
require __DIR__ . '/auth.php'; require __DIR__ . '/auth.php';