feat: Build full multi-role UI with admin, guru, and recommendation features

This commit is contained in:
zhadaarsita 2025-10-22 00:38:06 +07:00
parent 187bb9d9af
commit 6f90936e3c
13 changed files with 466 additions and 88 deletions

View File

@ -149,9 +149,14 @@ ## 🔑 Informasi Login (Dummy Data)
* **NISN**: `1234567890`
* **Password**: `password`
Gunakan kredensial berikut untuk masuk sebagai guru:
* **email**: `rina.marlina@smkn1perpus.sch.id`
* **Password**: `password`
Gunakan kredensial berikut untuk masuk sebagai penjaga perpus:
* **NISN**: `2233445566`
* **email**: `budi.santoso@smkn1perpus.sch.id`
* **Password**: `password`
---

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Services\DummyDataService;
use Illuminate\Http\Request;
class AdminRekomendasiController extends Controller
{
/**
* Helper function untuk mengekstrak ID video dari URL YouTube.
*/
private function extractYouTubeId(string $url): ?string
{
preg_match('/(v=|vi=|youtu.be\/|embed\/|\/v\/|\?v=|\&v=)(.+?)\b/i', $url, $matches);
return $matches[2] ?? null;
}
public function index()
{
$rekomendasiMentah = DummyDataService::getRekomendasiPembelajaran();
// Menambahkan thumbnail YouTube ke setiap rekomendasi
$semuaRekomendasi = $rekomendasiMentah->map(function ($item) {
$videoId = $this->extractYouTubeId($item['youtube_link']);
if ($videoId) {
$item['thumbnail'] = "https://img.youtube.com/vi/{$videoId}/hqdefault.jpg";
} else {
$item['thumbnail'] = 'https://via.placeholder.com/150?text=No+Preview';
}
return $item;
});
return view('admin.rekomendasi.index', [
'pageTitle' => 'Manajemen Rekomendasi',
'semuaRekomendasi' => $semuaRekomendasi
]);
}
public function create()
{
return view('admin.rekomendasi.create', ['pageTitle' => 'Tambah Rekomendasi']);
}
public function edit($id)
{
$rekomendasi = DummyDataService::getRekomendasiPembelajaran()->firstWhere('id', (int)$id);
abort_if(!$rekomendasi, 404);
return view('admin.rekomendasi.edit', ['pageTitle' => 'Edit Rekomendasi', 'rekomendasi' => $rekomendasi]);
}
}

View File

@ -18,6 +18,18 @@ public function index()
$statistikBulanan = DummyDataService::getStatistikBulanan();
$bukuPinjamOffline = DummyDataService::getBukuPinjamOffline($user);
$bacaBukuOnline = DummyDataService::getBacaBukuOnline($user);
$rekomendasiPembelajaran = DummyDataService::getRekomendasiPembelajaran();
// Menambahkan thumbnail YouTube ke setiap rekomendasi
$rekomendasiPembelajaran = DummyDataService::getRekomendasiPembelajaran()->map(function ($item) {
$videoId = $this->extractYouTubeId($item['youtube_link']);
if ($videoId) {
$item['thumbnail'] = "https://img.youtube.com/vi/{$videoId}/hqdefault.jpg";
} else {
$item['thumbnail'] = 'https://via.placeholder.com/150?text=No+Preview';
}
return $item;
});
$hour = date('H');
$greeting = "Selamat Pagi";
@ -39,6 +51,16 @@ public function index()
'bukuPinjamOffline',
'bacaBukuOnline',
'greeting',
'rekomendasiPembelajaran'
));
}
/**
* Helper function untuk mengekstrak ID video dari URL YouTube.
*/
private function extractYouTubeId(string $url): ?string
{
preg_match('/(v=|vi=|youtu.be\/|embed\/|\/v\/|\?v=|\&v=)(.+?)\b/i', $url, $matches);
return $matches[2] ?? null;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers;
use App\Services\DummyDataService;
class RekomendasiController extends Controller
{
/**
* Helper function untuk mengekstrak ID video dari URL YouTube.
*/
private function extractYouTubeId(string $url): ?string
{
preg_match('/(v=|vi=|youtu.be\/|embed\/|\/v\/|\?v=|\&v=)(.+?)\b/i', $url, $matches);
return $matches[2] ?? null;
}
public function show($id)
{
$rekomendasi = DummyDataService::getRekomendasiPembelajaran()->firstWhere('id', (int)$id);
abort_if(!$rekomendasi, 404);
// Menambahkan thumbnail YouTube ke setiap rekomendasi
$embedLink = null;
$videoId = $this->extractYouTubeId($rekomendasi['youtube_link']);
if ($videoId) {
$embedLink = "https://www.youtube.com/embed/" . $videoId;
}
$rekomendasi['youtube_embed_link'] = $embedLink;
return view('rekomendasiShow', [
'pageTitle' => $rekomendasi['judul'],
'rekomendasi' => $rekomendasi,
]);
}
}

View File

@ -24,7 +24,7 @@ public static function getAllSiswa(): array
[
'id' => 2,
'nama_lengkap' => 'Budi Santoso',
'email' => 'budi.santoso@smkn1perpus.sch.id',
'email' => 'budi.santoso@smkn1perpus.sch.id',
'password' => 'password',
'role' => 'penjaga perpus',
],
@ -53,7 +53,7 @@ public static function getAllSiswa(): array
[
'id' => 5,
'nama_lengkap' => 'Rina Marlina',
'email' => 'rina.marlina@smkn1perpus.sch.id',
'email' => 'rina.marlina@smkn1perpus.sch.id',
'password' => 'password',
'role' => 'guru',
],
@ -71,6 +71,85 @@ public static function getAktivitasMingguan(): array
];
}
/**
*Data Dummy untuk fitur Rekomendasi Pembelajaran.
*/
public static function getRekomendasiPembelajaran(): \Illuminate\Support\Collection
{
return collect([
[
'id' => 1,
'judul' => 'Sistem Tata Surya - Rangkuman Materi IPA Terpadu',
'kategori' => 'IPA',
'youtube_link' => 'https://www.youtube.com/watch?v=libKVRa01L8',
'deskripsi' => '<p>Video animasi seru yang menjelaskan tentang planet-planet di tata surya kita...</p>',
],
[
'id' => 2,
'judul' => 'Macam-macam Zat dan Perubahannya - IPA Kelas 7',
'kategori' => 'IPA',
'youtube_link' => 'https://www.youtube.com/watch?v=CfwPsKdC5w8',
'deskripsi' => '<p>Zat (materi) adalah sesuatu yang menempati ruang dan mempunyai massa...</p>',
],
[
'id' => 3,
'judul' => 'Sejarah Kerajaan Majapahit, Kerajaan Terbesar di Nusantara',
'kategori' => 'IPS',
'youtube_link' => 'https://www.youtube.com/watch?v=2Z9hqVqPY_s',
'deskripsi' => '<p>Pelajari sejarah salah satu kerajaan terbesar di Indonesia...</p>',
],
[
'id' => 4,
'judul' => 'Belajar HTML dari NOL untuk Pemula',
'kategori' => 'Informatika',
'youtube_link' => 'https://www.youtube.com/watch?v=NBZ9Ro6UKV8',
'deskripsi' => '<p>Ingin belajar membuat website? Mulai dari sini!...</p>',
],
[
'id' => 5,
'judul' => 'Rumus Cepat Teorema Pythagoras',
'kategori' => 'Matematika',
'youtube_link' => 'https://youtu.be/JJaptwjRbxc?si=XRxkrAf5G76iJ5CG',
'deskripsi' => '<p>Jangan takut lagi dengan soal Pythagoras!...</p>',
],
[
'id' => 6,
'judul' => 'Cara Menggambar Perspektif 1 Titik Hilang',
'kategori' => 'Seni Budaya',
'youtube_link' => 'https://youtu.be/SS7dLGDWUSs?si=lVw8jBkaWT---7ch',
'deskripsi' => '<p>Buat gambarmu terlihat lebih hidup dan realistis!...</p>',
],
[
'id' => 7,
'judul' => 'Sistem Peredaran Darah pada Manusia',
'kategori' => 'IPA',
'youtube_link' => 'https://youtu.be/QLoqMruGbkc?si=KiP5VZ4ByKSdFJK9',
'deskripsi' => '<p>Pahami bagaimana jantung memompa darah ke seluruh tubuh...</p>',
],
[
'id' => 8,
'judul' => 'Unsur-Unsur Intrinsik Cerpen',
'kategori' => 'Bahasa Indonesia',
'youtube_link' => 'https://youtu.be/PQNuvyQZYvI?si=Lr9AeTO_k6vY21ei',
'deskripsi' => '<p>Analisis sebuah cerita pendek menjadi lebih mudah...</p>',
],
[
'id' => 9,
'judul' => 'Apa itu Pemanasan Global?',
'kategori' => 'IPS',
'youtube_link' => 'https://youtu.be/pVjXm340tbw?si=GeMhYK1FSOGtV86X',
'deskripsi' => '<p>Mengapa suhu bumi semakin panas? Pelajari tentang penyebabnya...</p>',
],
[
'id' => 10,
'judul' => 'APA ITU LUBANG HITAM?',
'kategori' => 'Sains',
'youtube_link' => 'https://youtu.be/Tx87wEaDtxo?si=fNORkTYOeLiH9_xh',
'deskripsi' => '<p>Jelajahi salah satu objek paling misterius di alam semesta...</p>',
],
]);
}
/**
* Data dummy untuk menampilkan 10 siswa paling aktif di laporan guru.
*/
@ -111,8 +190,9 @@ public static function getLaporanMinatBaca(): array
];
}
/**
* Data dummy untuk Halaman Dashboard Statistik Petugas Perpus
*/
public static function getAdminDashboardStats(): array
{
$allBooks = self::getAllBooks();
@ -432,7 +512,6 @@ public static function getBacaBukuOnline($user): array
/**
* Mengambil daftar buku untuk katalog dengan filter.
*/
// app/Services/DummyDataService.php
public static function getKatalogBuku(array $filters = []): \Illuminate\Support\Collection
{

View File

@ -0,0 +1,24 @@
<x-app-layout>
@section('page-title', $pageTitle)
<div class="card shadow-sm border-0">
<div class="card-header bg-white d-flex align-items-center">
<a href="{{ route('admin.rekomendasi.index') }}" class="btn btn-light me-2"><i class="bi bi-arrow-left"></i></a>
<h5 class="my-0 fw-bold">Formulir Rekomendasi Baru</h5>
</div>
<div class="card-body">
<form action="#" method="POST">
<div class="mb-3"><label class="form-label">Judul</label><input type="text" class="form-control"></div>
<div class="mb-3"><label class="form-label">Kategori</label><input type="text" class="form-control" placeholder="Contoh: Teknologi, Sains, Biologi"></div>
<div class="mb-3"><label class="form-label">Link YouTube</label><input type="url" class="form-control" placeholder="https://www.youtube.com/watch?v=xxxxxx"></div>
<div class="mb-3"><label class="form-label">Deskripsi</label><textarea name="deskripsi" id="editor"></textarea></div>
<hr><div class="d-flex justify-content-end"><button type="submit" class="btn btn-primary">Simpan</button></div>
</form>
</div>
</div>
@push('scripts')
<script src="https://cdn.ckeditor.com/4.22.1/standard/ckeditor.js"></script>
<script>
CKEDITOR.replace('editor');
</script>
@endpush
</x-app-layout>

View File

@ -0,0 +1,24 @@
<x-app-layout>
@section('page-title', $pageTitle)
<div class="card shadow-sm border-0">
<div class="card-header bg-white d-flex align-items-center">
<a href="{{ route('admin.rekomendasi.index') }}" class="btn btn-light me-2"><i class="bi bi-arrow-left"></i></a>
<h5 class="my-0 fw-bold">Formulir Edit Rekomendasi</h5>
</div>
<div class="card-body">
<form action="#" method="POST">
<div class="mb-3"><label class="form-label">Judul</label><input type="text" class="form-control" value="{{ $rekomendasi['judul'] }}"></div>
<div class="mb-3"><label class="form-label">Kategori</label><input type="text" class="form-control" value="{{ $rekomendasi['kategori'] }}"></div>
<div class="mb-3"><label class="form-label">Link YouTube</label><input type="url" class="form-control" value="{{ $rekomendasi['youtube_link'] }}"></div>
<div class="mb-3"><label class="form-label">Deskripsi</label><textarea name="deskripsi" id="editor">{{ $rekomendasi['deskripsi'] }}</textarea></div>
<hr><div class="d-flex justify-content-end"><button type="submit" class="btn btn-primary">Simpan Perubahan</button></div>
</form>
</div>
</div>
@push('scripts')
<script src="https://cdn.ckeditor.com/4.22.1/standard/ckeditor.js"></script>
<script>
CKEDITOR.replace('editor');
</script>
@endpush
</x-app-layout>

View File

@ -0,0 +1,29 @@
<x-app-layout>
@section('page-title', $pageTitle)
<div class="card shadow-sm border-0">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="my-0 fw-bold">Kelola Rekomendasi Pembelajaran</h5>
<a href="{{ route('admin.rekomendasi.create') }}" class="btn btn-primary"><i class="bi bi-plus-circle-fill me-2"></i>Buat Rekomendasi</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead><tr><th>No</th><th>Thumbnail</th><th>Judul</th><th>Kategori</th><th>Aksi</th></tr></thead>
<tbody>
@foreach($semuaRekomendasi as $item)
<tr>
<td>{{ $loop->iteration }}</td>
<td><img src="{{ $item['thumbnail'] }}" width="120" class="rounded" alt="Thumbnail"></td>
<td>{{ $item['judul'] }}</td>
<td>{{ $item['kategori'] }}</td>
<td>
<a href="{{ route('admin.rekomendasi.edit', $item['id']) }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-pencil-fill"></i></a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</x-app-layout>

View File

@ -1,6 +1,6 @@
@section('page-title', 'Dashboard')
<x-app-layout>
{{-- <button type="button" class="btn btn-sm btn-primary end-0">
<i class="bi bi-plus-circle me-1"></i>
Pinjam Buku Baru
@ -164,97 +164,130 @@ class="badge bg-{{ $item['type'] }}-soft text-{{ $item['type'] }} rounded-pill p
</div>
</div>
<!-- Book Activities Section -->
<div class="card border-0 shadow-sm py-2">
<div class="card-body px-3">
<div class="mb-5" x-data="{ expanded: false }">
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<div class="section-icon me-3">
<i class="bi bi-book-half text-danger"></i>
</div>
<h6 class="mb-0 fw-bold text-dark">Buku Pinjam Offline</h6>
</div>
<!-- Buku Pinjam Offline Section -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0 py-3 d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold text-dark">
<i class="bi bi-book-half text-danger me-2"></i>Buku Pinjam Offline
</h6>
@if (count($bukuPinjamOffline) > 2)
<div x-data="{ expanded: false }">
<button type="button"
:class="expanded ? 'btn btn-sm btn-primary rounded-pill px-3' :
'btn btn-sm btn-outline-primary rounded-pill px-3'"
@click="expanded = !expanded" x-show="{{ count($bukuPinjamOffline) > 2 }}">
<span x-text="expanded ? 'Tampilkan Lebih Sedikit' : 'Lihat Semua'">Lihat Semua</span>
@click="expanded = !expanded">
<span x-text="expanded ? 'Sembunyikan' : 'Lihat Semua'"></span>
</button>
</div>
<div class="row g-4">
@forelse($bukuPinjamOffline as $buku)
<div class="col-xl-4 col-md-6" x-show="{{ $loop->index }} < 2 || expanded" x-transition>
<x-book-card :buku="$buku">
<div class="alert alert-danger border-0 py-2 px-3 mb-0 d-flex align-items-center">
<i class="bi bi-clock-fill me-2"></i>
<span class="fw-bold small">Sisa: {{ $buku['sisa_hari'] }} hari</span>
</div>
</x-book-card>
</div>
@empty
<div class="col-12">
<div class="text-center py-5">
<i class="bi bi-book text-muted mb-3" style="font-size: 4rem;"></i>
<p class="text-muted mb-0">Tidak ada buku yang sedang dipinjam secara offline.</p>
@endif
</div>
<div class="card-body">
<div class="row g-4" x-data="{ expanded: {{ count($bukuPinjamOffline) <= 2 ? 'true' : 'false' }} }">
@forelse($bukuPinjamOffline as $buku)
<div class="col-xl-4 col-md-6" x-show="expanded || {{ $loop->index }} < 2" x-transition>
<x-book-card :buku="$buku">
<div class="alert alert-danger border-0 py-2 px-3 mb-0 d-flex align-items-center">
<i class="bi bi-clock-fill me-2"></i>
<span class="fw-bold small">Sisa: {{ $buku['sisa_hari'] }} hari</span>
</div>
</div>
@endforelse
</div>
</div>
<div class="mt-5" x-data="{ expanded: false }">
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<div class="section-icon me-3">
<i class="bi bi-globe text-success"></i>
</div>
<h6 class="mb-0 fw-bold text-dark">Baca Buku Online</h6>
</x-book-card>
</div>
<button type="button"
:class="expanded ? 'btn btn-sm btn-primary rounded-pill px-3' :
'btn btn-sm btn-outline-primary rounded-pill px-3'"
@click="expanded = !expanded" x-show="{{ count($bacaBukuOnline) > 3 }}">
<span x-text="expanded ? 'Tampilkan Lebih Sedikit' : 'Lihat Semua'">Lihat Semua</span>
</button>
</div>
<div class="row g-4">
@forelse($bacaBukuOnline as $buku)
<div class="col-xl-4 col-md-6" x-show="{{ $loop->index }} < 3 || expanded" x-transition>
<x-book-card :buku="$buku">
<div class="progress-wrapper">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="small text-muted">Progress</span>
<span class="small fw-bold text-primary">{{ $buku['progress'] }}%</span>
</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-primary rounded-pill"
style="width: {{ $buku['progress'] }}%" role="progressbar"
aria-valuenow="{{ $buku['progress'] }}" aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
</div>
</x-book-card>
@empty
{{-- Jika tidak ada buku, akan menampilkan pesan ini di dalam card-body --}}
<div class="col-12">
<div class="text-center py-5">
<i class="bi bi-book text-muted mb-3" style="font-size: 4rem;"></i>
<p class="text-muted mb-0">Tidak ada buku yang sedang dipinjam secara offline.</p>
</div>
@empty
<div class="col-12">
<div class="text-center py-5">
<i class="bi bi-book text-muted mb-3" style="font-size: 4rem;"></i>
<p class="text-muted mb-0">Tidak ada buku yang sedang dibaca secara online.</p>
</div>
</div>
@endforelse
</div>
</div>
@endforelse
</div>
</div>
</div>
<!-- Baca Buku Online Section -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0 py-3 d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold text-dark">
<i class="bi bi-globe text-success me-2"></i>Baca Buku Online
</h6>
@if (count($bacaBukuOnline) > 3)
<div x-data="{ expanded: false }">
<button type="button"
:class="expanded ? 'btn btn-sm btn-primary rounded-pill px-3' :
'btn btn-sm btn-outline-primary rounded-pill px-3'"
@click="expanded = !expanded">
<span x-text="expanded ? 'Sembunyikan' : 'Lihat Semua'"></span>
</button>
</div>
@endif
</div>
<div class="card-body">
<div class="row g-4" x-data="{ expanded: {{ count($bacaBukuOnline) <= 3 ? 'true' : 'false' }} }">
@forelse($bacaBukuOnline as $buku)
<div class="col-xl-4 col-md-6" x-show="expanded || {{ $loop->index }} < 3" x-transition>
<x-book-card :buku="$buku">
<div class="progress-wrapper">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="small text-muted">Progress</span>
<span class="small fw-bold text-primary">{{ $buku['progress'] }}%</span>
</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-primary rounded-pill"
style="width: {{ $buku['progress'] }}%" role="progressbar"></div>
</div>
</div>
</x-book-card>
</div>
@empty
{{-- Jika tidak ada buku, akan menampilkan pesan ini di dalam card-body --}}
<div class="col-12">
<div class="text-center py-5">
<i class="bi bi-book text-muted mb-3" style="font-size: 4rem;"></i>
<p class="text-muted mb-0">Tidak ada buku yang sedang dibaca secara online.</p>
</div>
</div>
@endforelse
</div>
</div>
</div>
<!-- Modals -->
<!-- Rekomendasi Pembelajaran Section (Section ini hanya tampil saat Role yang login adalah Guru) -->
@if (Auth::user()->role == 'guru')
<div class="card border-0 shadow-sm mt-4">
<div class="card-header bg-white border-0 d-flex justify-content-between align-items-center py-3">
<h6 class="m-0 fw-bold text-dark">
<i class="bi bi-lightbulb-fill text-success me-2"></i>Rekomendasi Pembelajaran
</h6>
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-3"
data-bs-toggle="modal" data-bs-target="#rekomendasiModal">
Lihat Semua
</button>
</div>
<div class="card-body">
@forelse($rekomendasiPembelajaran->take(3) as $item)
<a href="{{ route('rekomendasi.show', $item['id']) }}"
class="d-flex gap-3 text-decoration-none text-dark mb-3">
<img src="{{ $item['thumbnail'] }}" class="rounded"
style="width: 120px; height: 70px; object-fit: cover;" alt="Thumbnail">
<div class="flex-grow-1">
<h6 class="fw-bold mb-1 text-black">{{ $item['judul'] }}</h6>
<span
class="badge fw-normal text-primary border me-1">{{ $item['kategori'] }}</span>
</div>
</a>
@if (!$loop->last)
<hr class="my-3">
@endif
@empty
<p class="text-muted text-center">Belum ada rekomendasi.</p>
@endforelse
</div>
</div>
@endif
<!-- Modals untuk Pengumuman-->
<div class="modal fade" id="pengumumanModal" tabindex="-1" aria-labelledby="pengumumanModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-lg">
@ -287,6 +320,7 @@ class="badge bg-{{ $item['type'] }}-soft text-{{ $item['type'] }} rounded-pill p
</div>
</div>
<!-- Modals untuk Pemberitahuan-->
<div class="modal fade" id="pemberitahuanModal" tabindex="-1" aria-labelledby="pemberitahuanModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-lg">
@ -327,6 +361,41 @@ class="badge bg-{{ $item['type'] }}-soft text-{{ $item['type'] }} rounded-pill p
</div>
</div>
<!-- Modals untuk Rekomendasi Pembelajaran-->
<div class="modal fade" id="rekomendasiModal" tabindex="-1" aria-labelledby="rekomendasiModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold text-dark" id="rekomendasiModalLabel">
<i class="bi bi-lightbulb-fill text-success me-2"></i>Semua Rekomendasi Pembelajaran
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body px-4">
<div class="list-group list-group-flush">
@foreach ($rekomendasiPembelajaran as $item)
<a href="{{ route('rekomendasi.show', $item['id']) }}"
class="list-group-item list-group-item-action d-flex gap-3 px-0 py-3">
<img src="{{ $item['thumbnail'] }}" class="rounded"
style="width: 120px; height: 70px; object-fit: cover;" alt="Thumbnail">
<div class="flex-grow-1">
<h6 class="fw-bold mb-1 text-black">{{ $item['judul'] }}</h6>
<span
class="badge fw-normal text-primary border me-1">{{ $item['kategori'] }}</span>
</div>
</a>
@endforeach
</div>
</div>
<div class="modal-footer border-0">
<button type="button" class="btn btn-secondary rounded-pill px-4"
data-bs-dismiss="modal">Tutup</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const statistikData = @json($statistikBulanan ?? ['labels' => [], 'data' => []]);

View File

@ -4,6 +4,7 @@
<h1 class="h2">{{ $pageTitle }}</h1>
</div>
{{-- Filter Section --}}
<div class="card mb-4">
<div class="card-body">
<form action="{{ route(request()->route()->getName()) }}" method="GET">
@ -50,6 +51,7 @@
</div>
</div>
{{-- Katalog Buku Section --}}
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-5 g-4">
@forelse ($semuaBuku as $buku)
<div class="col">

View File

@ -32,7 +32,12 @@ class="bi bi-megaphone-fill"></i><span class="nav-text ms-2">Pengumuman</span></
class="nav-link {{ request()->routeIs('admin.pengguna.*') ? 'active' : '' }}"><i
class="bi bi-people-fill"></i><span class="nav-text ms-2">Manajemen Pengguna</span></a>
</li>
<li class="nav-item">
<a href="{{ route('admin.rekomendasi.index') }}"
class="nav-link {{ request()->routeIs('admin.rekomendasi.*') ? 'active' : '' }}">
<i class="bi bi-lightbulb-fill"></i><span class="nav-text ms-2">Rekomendasi</span>
</a>
</li>
@elseif (Auth::user()->role == 'guru')
{{-- ================= MENU GURU (Siswa + Fitur Khusus) ================= --}}
<li class="nav-item"><a href="{{ route('dashboard') }}"
@ -71,7 +76,6 @@ class="nav-link py-1 {{ request()->routeIs('riwayat.online') ? 'active' : '' }}"
</ul>
</div>
</li>
@else
{{-- ================= MENU SISWA ================= --}}
<li class="nav-item"><a href="{{ route('dashboard') }}"

View File

@ -0,0 +1,25 @@
@section('page-title', 'Dashboard')
<x-app-layout>
@section('page-title', $pageTitle)
<div class="d-flex align-items-center mb-4">
<a href="{{ route('dashboard')}}" class="btn btn-outline-secondary me-3">
<i class="bi bi-arrow-left"></i>
</a>
<h4 class="h4 mb-0">Kembali</h4>
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-4">
<div class="ratio ratio-16x9 mb-4">
<iframe src="{{ $rekomendasi['youtube_embed_link'] }}" title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
<h2 class="fw-bold mb-3">{{ $rekomendasi['judul'] }}</h2>
<div class="text-muted">
{!! $rekomendasi['deskripsi'] !!}
</div>
</div>
</div>
</x-app-layout>

View File

@ -1,5 +1,6 @@
<?php
use App\Http\Controllers\Admin\AdminRekomendasiController;
use Illuminate\Support\Facades\Route;
// General Controllers
@ -15,7 +16,7 @@
use App\Http\Controllers\Admin\BookController as AdminBookController;
use App\Http\Controllers\Admin\UserController as AdminUserController;
use App\Http\Controllers\Admin\PengumumanController as AdminPengumumanController;
use App\Http\Controllers\RekomendasiController;
// Guru Controller
use App\Http\Controllers\Guru\LaporanController;
@ -39,6 +40,7 @@
// Rute Umum untuk Siswa & Guru
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/katalog', [KatalogController::class, 'index'])->name('katalog.index');
Route::get('/rekomendasi/{id}', [RekomendasiController::class, 'show'])->name('rekomendasi.show');
Route::prefix('peminjaman-offline')->name('peminjaman.')->group(function () {
Route::get('/', [PeminjamanController::class, 'index'])->name('index');
@ -89,6 +91,11 @@
Route::get('/pengumuman', [AdminPengumumanController::class, 'index'])->name('pengumuman.index');
Route::get('/pengumuman/tambah', [AdminPengumumanController::class, 'create'])->name('pengumuman.create');
Route::get('/pengumuman/{id}/edit', [AdminPengumumanController::class, 'edit'])->name('pengumuman.edit');
Route::get('/rekomendasi', [AdminRekomendasiController
::class, 'index'])->name('rekomendasi.index');
Route::get('/rekomendasi/tambah', [AdminRekomendasiController::class, 'create'])->name('rekomendasi.create');
Route::get('/rekomendasi/{id}/edit', [AdminRekomendasiController::class, 'edit'])->name('rekomendasi.edit');
});
// --- RUTE LOGIN KHUSUS ADMIN ---