feat: Baca Buku Online

This commit is contained in:
zhadaarsita 2025-09-26 01:13:18 +07:00
parent 78ba8a0772
commit 542153ad8a
26 changed files with 458 additions and 130 deletions

View File

@ -0,0 +1,112 @@
<?php
namespace App\Http\Controllers;
use App\Services\DummyDataService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class BacaOnlineController extends Controller
{
public function index(Request $request): View
{
$filters = $request->only(['search', 'kategori', 'tahun', 'penulis']);
$filters['tipe_akses'] = 'online';
$semuaBuku = DummyDataService::getKatalogBuku($filters);
$filterOptions = DummyDataService::getFilterOptions();
return view('katalog.index', [
'semuaBuku' => $semuaBuku,
'filterOptions' => $filterOptions,
'input' => $request->query(),
'pageTitle' => 'Baca Buku Online',
'mode' => 'online',
]);
}
public function ringkasan(int $id): View
{
$book = $this->getBookOrFail($id);
return view('katalog.ringkasan', [
'buku' => $book,
'pageTitle' => 'Ringkasan Buku',
'actionRoute' => 'baca.request_code',
'actionButtonText' => 'Baca Sekarang',
'actionButtonIcon' => 'bi-book-half',
]);
}
public function showCodeRequestPage(int $id): View
{
$book = $this->getBookOrFail($id);
$sessionKey = 'access_code_for_book_' . $id;
if (session()->has($sessionKey)) {
$accessCode = session($sessionKey);
} else {
$accessCode = 'BCO-' . date('Ymd') . '-' . $book['id'] . '-' . Str::upper(Str::random(4));
session([$sessionKey => $accessCode]);
}
session(['book_verified_' . $id => false]);
return view('baca.request_code', [
'book' => $book,
'accessCode' => $accessCode
]);
}
public function verifyCode(Request $request, int $id): RedirectResponse
{
$request->validate(['kode_akses' => 'required|string']);
$correctCode = session('access_code_for_book_' . $id);
if ($request->input('kode_akses') === $correctCode) {
session(['book_verified_' . $id => true]);
session()->forget('access_code_for_book_' . $id);
return redirect()->route('baca.view_book', ['id' => $id]);
}
return back()->with('error', 'Kode akses yang Anda masukkan salah!');
}
public function viewBook(int $id): View|RedirectResponse
{
if (!session('book_verified_' . $id, false)) {
return redirect()->route('baca.request_code', ['id' => $id])
->with('error', 'Silakan masukkan kode akses terlebih dahulu.');
}
$book = $this->getBookOrFail($id);
return view('baca.view_book', ['book' => $book]);
}
public function streamPdf(int $id): BinaryFileResponse|Response
{
if (!session('book_verified_' . $id, false)) {
abort(403, 'Akses Ditolak.');
}
$book = $this->getBookOrFail($id);
$filePath = 'books/' . $book['file_pdf'];
$absolutePath = storage_path('app/' . $filePath);
if (!file_exists($absolutePath)) {
abort(404, 'GAGAL - PHP tidak dapat menemukan file di: ' . $absolutePath);
}
return response()->file($absolutePath);
}
private function getBookOrFail(int $id): array
{
$book = DummyDataService::getKatalogBuku()->firstWhere('id', $id);
if (!$book) {
abort(404, 'Buku tidak ditemukan.');
}
return $book;
}
}

View File

@ -7,25 +7,19 @@
class KatalogController extends Controller
{
public function index(Request $request, $tipe = null)
public function index(Request $request)
{
$filters = [
'search' => $request->query('search'),
'kategori' => $request->query('kategori'),
'tahun' => $request->query('tahun'),
'penulis' => $request->query('penulis'),
];
$filters = $request->only(['search', 'kategori', 'tahun', 'penulis']);
$semuaBuku = DummyDataService::getKatalogBuku($filters);
$filterOptions = DummyDataService::getFilterOptions();
return view('katalog', [
return view('katalog.index', [
'semuaBuku' => $semuaBuku,
'filterOptions' => $filterOptions,
'input' => $filters,
'pageTitle' => 'Katalog Buku',
'mode' => 'umum',
]);
}
}

View File

@ -4,78 +4,65 @@
use App\Services\DummyDataService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class PeminjamanController extends Controller
{
/**
* Menampilkan daftar buku yang bisa dipinjam offline,
* dengan memanfaatkan filter dari getKatalogBuku.
*/
public function index(Request $request)
public function index(Request $request)
{
$filters = $request->only(['search', 'kategori', 'tahun', 'penulis']);
$filters['tipe_akses'] = 'offline';
$semuaBuku = DummyDataService::getKatalogBuku($filters);
$filterOptions = DummyDataService::getFilterOptions();
return view('peminjaman.index', [
return view('katalog.index', [
'semuaBuku' => $semuaBuku,
'filterOptions' => $filterOptions,
'input' => $request->query(),
'input' => $request->query(),
'pageTitle' => 'Peminjaman Buku Offline',
'mode' => 'offline',
]);
}
/**
* Menampilkan halaman ringkasan buku sebelum meminjam.
*/
public function ringkasan($id)
{
// Untuk simulasi, kita anggap user sudah login
// $user = Auth::user();
$user = DummyDataService::getAllSiswa()[0];
$user = Auth::user();
$buku = DummyDataService::getKatalogBuku()->firstWhere('id', $id);
return view('peminjaman.ringkasan', compact('user', 'buku'));
return view('katalog.ringkasan', [
'user' => $user,
'buku' => $buku,
'pageTitle' => 'Ringkasan Buku',
'actionRoute' => 'peminjaman.form',
'actionButtonText' => 'Lanjutkan ke Form Peminjaman',
'actionButtonIcon' => 'bi-file-earmark-text-fill',
]);
}
/**
* Menampilkan form peminjaman dengan semua buku offline untuk pilihan tambahan.
*/
public function form($id)
{
// $user = Auth::user();
$user = DummyDataService::getAllSiswa()[0];
// Buku yang dipilih pertama kali
$user = Auth::user();
$buku = DummyDataService::getKatalogBuku()->firstWhere('id', $id);
// Semua buku offline untuk pilihan tambahan
$filters = ['tipe_akses' => 'offline'];
$semuaBuku = DummyDataService::getKatalogBuku($filters);
return view('peminjaman.form', compact('user', 'buku', 'semuaBuku'));
}
/**
* Proses pengiriman form peminjaman multi buku
*/
public function store(Request $request)
{
// Validasi input
$request->validate([
'buku_ids' => 'required|array|min:1|max:3',
'buku_ids.*' => 'integer|exists:books,id' // Sesuaikan dengan tabel yang ada
'buku_ids.*' => 'integer'
]);
// Logic untuk menyimpan peminjaman multiple books
$bukuIds = $request->input('buku_ids');
// Contoh logic penyimpanan:
foreach ($bukuIds as $bukuId) {
// Simpan ke database peminjaman
// Di backend nanti kayak gini
// Peminjaman::create([
// 'user_id' => auth()->id(),
// 'user_id' => auth()->id(),
// 'book_id' => $bukuId,
// 'tanggal_pinjam' => now(),
// 'tanggal_kembali' => now()->addDays(7),
@ -86,4 +73,4 @@ public function store(Request $request)
return redirect()->route('dashboard')
->with('success', 'Berhasil meminjam ' . count($bukuIds) . ' buku!');
}
}
}

View File

@ -145,8 +145,10 @@ private static function getAllBooks()
'tahun' => 2022,
'status' => 'Tersedia',
'is_new' => true,
'tipe_akses' => 'online',
'tipe_akses' => ['online','offline'],
'file_pdf'=>'ipas.pdf',
'progress' => 75,
'sisa_hari' => 14,
'user_id' => 1,
],
[
@ -158,7 +160,8 @@ private static function getAllBooks()
'tahun' => 2023,
'status' => 'Tersedia',
'is_new' => false,
'tipe_akses' => ['online', 'offline'],
'tipe_akses' => 'offline',
'sisa_hari' => 3,
'progress' => 100,
'user_id' => [3, 1],
],
@ -210,7 +213,9 @@ private static function getAllBooks()
'tahun' => 2023,
'status' => 'Tersedia',
'is_new' => true,
'tipe_akses' => 'online',
'tipe_akses' => ['online','offline'],
'file_pdf'=>'mtk.pdf',
'sisa_hari' => 7,
'progress' => 40,
'user_id' => [1, 4, 5],
],
@ -223,22 +228,54 @@ private static function getAllBooks()
'tahun' => 2024,
'status' => 'Tersedia',
'is_new' => true,
'tipe_akses' => 'online',
'tipe_akses' => 'offline',
'sisa_hari' => 4,
'progress' => 0,
'user_id' => [3, 1]
],
[
'id' => 8,
'judul' => 'Buku Offline Tanpa Peminjam',
'penulis' => 'Penulis Misteri',
'cover' => 'images/covers/sosiologi.jpg',
'kategori' => 'Misteri',
'tahun' => 2020,
'judul' => 'Ayah',
'penulis' => 'Andrea Hirata',
'cover' => 'images/covers/ayah.png',
'kategori' => 'Novel',
'tahun' => 2015,
'status' => 'Tersedia',
'is_new' => false,
'tipe_akses' => 'offline',
'sisa_hari' => null
'is_new' => true,
'tipe_akses' => 'online',
'file_pdf' => 'ayah.pdf',
'progress' => 0,
'user_id' => [1, 2, 3],
],
[
'id' => 9,
'judul' => 'Senja, Hujan, & Cerita yang Telah Usai',
'penulis' => 'Boy Candra',
'cover' => 'images/covers/senja.png',
'kategori' => 'Novel',
'tahun' => 2015,
'status' => 'Tersedia',
'is_new' => true,
'tipe_akses' => ['online','offline'],
'file_pdf' => 'senja.pdf',
'progress' => 0,
'sisa_hari' => 14,
'user_id' => [1, 3],
],
[
'id' => 10,
'judul' => 'Hijrah itu Cinta',
'penulis' => 'Abay Adhitya',
'cover' => 'images/covers/hijrah.png',
'kategori' => 'Religi',
'tahun' => 2018,
'status' => 'Tersedia',
'is_new' => true,
'tipe_akses' => 'online',
'file_pdf' => 'hijrah.pdf',
'progress' => 0,
'user_id' => [2, 3],
]
]);
}
@ -297,36 +334,36 @@ public static function getBacaBukuOnline($user): array
*/
// app/Services/DummyDataService.php
public static function getKatalogBuku(array $filters = []): \Illuminate\Support\Collection
{
$buku = self::getAllBooks();
public static function getKatalogBuku(array $filters = []): \Illuminate\Support\Collection
{
$buku = self::getAllBooks();
$buku = $buku->when($filters['search'] ?? null, function ($query, $search) {
return $query->filter(fn($item) => str_contains(strtolower($item['judul']), strtolower($search)));
})->when($filters['kategori'] ?? null, function ($query, $kategori) {
return $query->where('kategori', $kategori);
})->when($filters['tahun'] ?? null, function ($query, $tahun) {
return $query->where('tahun', $tahun);
})->when($filters['penulis'] ?? null, function ($query, $penulis) {
return $query->where('penulis', $penulis);
})
->when($filters['tipe_akses'] ?? null, function ($query, $tipe) {
if ($tipe === 'offline') {
return $query->filter(function ($buku) {
return $buku['tipe_akses'] === 'offline' || (is_array($buku['tipe_akses']) && in_array('offline', $buku['tipe_akses']));
});
}
if ($tipe === 'online') {
return $query->filter(function ($buku) {
return $buku['tipe_akses'] === 'online' || (is_array($buku['tipe_akses']) && in_array('online', $buku['tipe_akses']));
});
}
return $query;
})
->sortByDesc('status');
$buku = $buku->when($filters['search'] ?? null, function ($query, $search) {
return $query->filter(fn($item) => str_contains(strtolower($item['judul']), strtolower($search)));
})->when($filters['kategori'] ?? null, function ($query, $kategori) {
return $query->where('kategori', $kategori);
})->when($filters['tahun'] ?? null, function ($query, $tahun) {
return $query->where('tahun', $tahun);
})->when($filters['penulis'] ?? null, function ($query, $penulis) {
return $query->where('penulis', $penulis);
})
->when($filters['tipe_akses'] ?? null, function ($query, $tipe) {
if ($tipe === 'offline') {
return $query->filter(function ($buku) {
return $buku['tipe_akses'] === 'offline' || (is_array($buku['tipe_akses']) && in_array('offline', $buku['tipe_akses']));
});
}
if ($tipe === 'online') {
return $query->filter(function ($buku) {
return $buku['tipe_akses'] === 'online' || (is_array($buku['tipe_akses']) && in_array('online', $buku['tipe_akses']));
});
}
return $query;
})
->sortByDesc('status');
return $buku;
}
return $buku;
}
/**
* Method baru untuk mengambil daftar unik untuk dropdown filter

0
bootstrap/cache/.gitignore vendored Normal file → Executable file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

View File

@ -1,13 +1,12 @@
@section('page-title', 'Peminjaman Buku')
@section('page-title', 'Baca Buku Online')
<x-app-layout>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Daftar Buku Peminjaman Offline</h1>
<h1 class="h2">Daftar Buku Baca Online</h1>
</div>
<!-- Filter & Pencarian -->
<div class="card mb-4">
<div class="card-body">
<form action="{{ route('peminjaman.index') }}" method="GET">
<form action="{{ route('baca.index') }}" method="GET">
<div class="row g-3 align-items-center">
<div class="col-md-4">
<input type="text" name="search" class="form-control"
@ -51,10 +50,8 @@
</div>
</div>
<p class="fw-bold">Daftar buku yang tersedia secara offline dan dapat diambil di perpustakaan dengan mengisi form
yang tersedia.</p>
<p class="fw-bold">Berikut adalah daftar buku yang tersedia untuk dibaca secara online melalui platform ini.</p>
<!-- Daftar Buku -->
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-5 g-4">
@forelse ($semuaBuku as $buku)
<div class="col">
@ -75,9 +72,9 @@ class="badge fw-normal {{ $buku['status'] == 'Tersedia' ? 'bg-success-subtle tex
<div class="d-grid mt-auto">
@if ($buku['status'] == 'Tersedia')
<a href="{{ route('peminjaman.ringkasan', $buku['id']) }}"
<a href="{{ route('baca.request_code', $buku['id']) }}"
class="btn btn-outline-primary btn-sm">
<i class="bi bi-arrow-down-up me-1"></i> Pinjam Offline
<i class="bi bi-book-half me-1"></i> Baca Sekarang
</a>
@else
<button class="btn btn-secondary btn-sm" disabled><i class="bi bi-x-circle me-1"></i>
@ -93,9 +90,9 @@ class="btn btn-outline-primary btn-sm">
<h4 class="alert-heading">Tidak Ada Hasil</h4>
<p>Tidak ada buku yang cocok dengan kriteria filter Anda. Coba reset atau ubah filter.</p>
<hr>
<a href="{{ route('peminjaman') }}" class="btn btn-primary">Reset Filter</a>
<a href="{{ route('baca.index') }}" class="btn btn-primary">Reset Filter</a>
</div>
</div>
@endforelse
</div>
</x-app-layout>
</x-app-layout>

View File

@ -0,0 +1,37 @@
@section('page-title', 'Kode Akses Buku')
<x-app-layout>
<div class="container text-center py-5">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<h2 class="fw-bold">KODE AKSES BUKU ANDA</h2>
<p class="text-muted">Kode unik telah diberikan secara otomatis. Perhatikan kode dibawah ini:</p>
<div class="my-4 p-4 rounded" style="border: 2px dashed #0d6efd; background-color: #f8f9fa; display: inline-block;">
<h3 class="fw-bold" style="font-family: monospace; letter-spacing: 2px;">{{ $accessCode }}</h3>
</div>
<p class="text-muted">Jangan bagikan kepada orang lain.</p>
<p>Masukkan kode akses ini pada kolom dibawah ini:</p>
@if(session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
<form action="{{ route('baca.verify_code', ['id' => $book['id']]) }}" method="POST" class="mt-3">
@csrf
<div class="form-group" style="max-width: 400px; margin-left: auto; margin-right: auto;">
<label for="kode_akses" class="form-label fw-bold">Kode Akses Buku:</label>
<input type="text" name="kode_akses" id="kode_akses" class="form-control form-control-lg text-center" placeholder="Masukkan kode akses..." required autofocus>
</div>
<div class="d-grid gap-2" style="max-width: 400px; margin-left: auto; margin-right: auto;">
<button type="submit" class="btn btn-primary btn-lg mt-3">Akses Buku</button>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,31 @@
@section('page-title', 'Membaca: ' . $book['judul'])
<x-app-layout>
<div class="container-fluid vh-100 d-flex flex-column p-3">
<div class="d-flex justify-content-between align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2 text-truncate">{{ $book['judul'] }}</h1>
<a href="{{ route('baca.index') }}" class="btn btn-outline-secondary flex-shrink-0">
<i class="bi bi-x-lg me-1"></i>
Tutup
</a>
</div>
<div class="flex-grow-1">
{{-- src memanggil route streamPdf yang aman, bukan direct link ke file PDF --}}
<iframe src="{{ route('baca.stream_pdf', ['id' => $book['id']]) }}" width="100%" height="100%"
class="rounded border" style="border: none;">
Browser Anda tidak mendukung iframe untuk menampilkan PDF.
</iframe>
</div>
</div>
</x-app-layout>
{{-- Override CSS agar layout <main> bisa memenuhi lebar container-fluid --}}
@push('styles')
<style>
main.container-fluid {
max-width: none !important;
}
</style>
@endpush

View File

@ -0,0 +1,129 @@
@section('page-title', $pageTitle)
<x-app-layout>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">{{ $pageTitle }}</h1>
</div>
<div class="card mb-4">
<div class="card-body">
<form action="{{ route(request()->route()->getName()) }}" method="GET">
<div class="row g-3 align-items-center">
<div class="col-md-4">
<input type="text" name="search" class="form-control"
placeholder="Cari buku berdasarkan judul..." value="{{ $input['search'] ?? '' }}">
</div>
<div class="col-md-2">
<select name="kategori" class="form-select">
<option value="">Semua Kategori</option>
@foreach ($filterOptions['kategori'] as $kategori)
<option value="{{ $kategori }}"
{{ ($input['kategori'] ?? '') == $kategori ? 'selected' : '' }}>{{ $kategori }}
</option>
@endforeach
</select>
</div>
<div class="col-md-2">
<select name="tahun" class="form-select">
<option value="">Semua Tahun</option>
@foreach ($filterOptions['tahun'] as $tahun)
<option value="{{ $tahun }}"
{{ ($input['tahun'] ?? '') == $tahun ? 'selected' : '' }}>{{ $tahun }}
</option>
@endforeach
</select>
</div>
<div class="col-md-2">
<select name="penulis" class="form-select">
<option value="">Semua Penulis</option>
@foreach ($filterOptions['penulis'] as $penulis)
<option value="{{ $penulis }}"
{{ ($input['penulis'] ?? '') == $penulis ? 'selected' : '' }}>{{ $penulis }}
</option>
@endforeach
</select>
</div>
<div class="col-md-2 d-grid">
<button type="submit" class="btn btn-primary">Filter</button>
</div>
</div>
</form>
</div>
</div>
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-5 g-4">
@forelse ($semuaBuku as $buku)
<div class="col">
<div class="card h-100 shadow-sm border-0">
<img src="{{ asset($buku['cover']) }}" class="card-img-top rounded-2 object-fit-cover"
style="height: 310px;" alt="Cover {{ $buku['judul'] }}">
<div class="card-body d-flex flex-column p-3">
<div class="flex-grow-1">
<div class="mb-2">
<span class="badge fw-normal text-primary border me-1">{{ $buku['kategori'] }}</span>
<span
class="badge fw-normal {{ $buku['status'] == 'Tersedia' ? 'bg-success-subtle text-success-emphasis' : 'bg-warning-subtle text-warning-emphasis' }} border">{{ $buku['status'] }}</span>
</div>
<h6 class="card-title fw-bold line-clamp-2">{{ $buku['judul'] }}</h6>
<p class="card-text small text-muted mb-2">{{ $buku['penulis'] }}</p>
</div>
<div class="d-flex gap-2 mt-auto pt-2 border-top">
@php
$bisaPinjam =
(is_array($buku['tipe_akses']) && in_array('offline', $buku['tipe_akses'])) ||
$buku['tipe_akses'] === 'offline';
$bisaBaca =
(is_array($buku['tipe_akses']) && in_array('online', $buku['tipe_akses'])) ||
$buku['tipe_akses'] === 'online';
@endphp
@if ($mode === 'offline')
{{-- Mode Peminjaman: hanya tampilkan tombol Pinjam --}}
<a href="{{ route('peminjaman.ringkasan', $buku['id']) }}"
class="btn btn-sm btn-outline-primary w-100">
<i class="bi bi-arrow-left-right me-1"></i> Pinjam Buku
</a>
@elseif($mode === 'online')
{{-- Mode Baca Online: hanya tampilkan tombol Baca Buku --}}
<a href="{{ route('baca.ringkasan', $buku['id']) }}"
class="btn btn-sm btn-primary w-100">
<i class="bi bi-search me-1"></i> Baca Buku
</a>
@else
{{-- Mode 'umum' atau default --}}
{{-- Mode Katalog Umum: Tampilkan kedua tombol (aktif/nonaktif) --}}
@if ($bisaPinjam && $buku['status'] == 'Tersedia')
<a href="{{ route('peminjaman.ringkasan', $buku['id']) }}"
class="btn btn-sm btn-outline-primary w-100"><i
class="bi bi-arrow-left-right me-1"></i> Pinjam</a>
@else
<button class="btn btn-sm btn-outline-secondary w-100" disabled><i
class="bi bi-arrow-left-right me-1"></i> Pinjam</button>
@endif
@if ($bisaBaca && $buku['status'] == 'Tersedia')
<a href="{{ route('baca.ringkasan', $buku['id']) }}"
class="btn btn-sm btn-primary w-100"><i class="bi bi-book-half me-1"></i>
Baca</a>
@else
<button class="btn btn-sm btn-secondary w-100" disabled><i
class="bi bi-book-half me-1"></i> Baca</button>
@endif
@endif
</div>
</div>
</div>
</div>
@empty
<div class="col-12">
<div class="alert alert-warning text-center">
<h4 class="alert-heading">Tidak Ada Hasil</h4>
<p>Tidak ada buku yang cocok dengan kriteria filter Anda. Coba reset atau ubah filter.</p>
<hr>
<a href="{{ route(request()->route()->getName()) }}" class="btn btn-primary">Reset Filter</a>
</div>
</div>
@endforelse
</div>
</x-app-layout>

View File

@ -1,27 +1,23 @@
@section('page-title', 'Ringkasan Buku')
@section('page-title', $pageTitle)
<x-app-layout>
<div class="d-flex flex-column" style="min-height: calc(100vh - 110px);">
{{-- Header --}}
<div class="d-flex align-items-center mb-4">
<a href="{{ route('peminjaman.index')}}" class="btn btn-outline-secondary me-3">
<a href="{{ url()->previous() }}" class="btn btn-outline-secondary me-3">
<i class="bi bi-arrow-left"></i>
</a>
<h1 class="h2 mb-0">Ringkasan Buku</h1>
</div>
{{-- Wrapper main content --}}
<main class="flex-grow-1 d-flex align-items-center justify-content-center">
<div class="container-fluid">
<div class="row g-5 align-items-center">
{{-- Kolom Kiri untuk Cover Buku --}}
<div class="col-lg-4 text-center">
<img src="{{ asset($buku['cover']) }}" class="img-fluid rounded-3 shadow-lg"
style="max-height: 450px; max-width: 300px; object-fit: cover;" alt="Cover {{ $buku['judul'] }}">
</div>
{{-- Kolom Kanan untuk Detail & Button --}}
<div class="col-lg-8">
<h1 class="display-5 fw-bold mb-2">{{ $buku['judul'] }}</h1>
<p class="h5 text-muted mb-4">oleh {{ $buku['penulis'] }}</p>
@ -34,9 +30,9 @@
</p>
<div class="mt-5">
<a href="{{ route('peminjaman.form', $buku['id']) }}" class="btn btn-primary btn-lg rounded-pill px-5 py-3">
<i class="bi bi-file-earmark-text-fill me-2"></i>
Lanjutkan ke Form Peminjaman
<a href="{{ route($actionRoute, $buku['id']) }}" class="btn btn-primary btn-lg rounded-pill px-5 py-3">
<i class="{{ $actionButtonIcon }} me-2"></i>
{{ $actionButtonText }}
</a>
</div>
</div>

View File

@ -5,7 +5,8 @@
<div class="d-flex align-items-center mt-md-2" style="gap: 0.75rem;">
<div class="vr bg-primary sidebar-title" style="width: 2px;"></div>
<img src="{{ asset('images/logo/name.svg') }}" alt="Perpus" style="height: 32px;" class="sidebar-title">
<img src="{{ asset('images/logo/name.svg') }}" alt="Perpus" style="height: 32px;"
class="sidebar-title">
</div>
</a>
@ -55,7 +56,7 @@ class="nav-link {{ request()->routeIs('peminjaman.*') ? 'active' : '' }}">
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link">
<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>

View File

@ -136,7 +136,6 @@ class="rounded me-3 form-book-cover">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content border-0 shadow-lg rounded-3">
<!-- Header -->
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold" id="pilihBukuModalLabel">
<i class="bi bi-book-half me-2 text-primary"></i>Pilih Buku Tambahan
@ -144,17 +143,13 @@ class="rounded me-3 form-book-cover">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Body -->
<div class="modal-body">
<!-- Alert Info -->
<div class="alert alert-info border-0 bg-info-subtle mb-4 d-flex align-items-center">
<i class="bi bi-info-circle-fill me-2"></i>
<span>Anda dapat memilih maksimal <strong><span id="sisaSlot">2</span> buku</strong> lagi.
Total maksimal 3 buku.</span>
</div>
<!-- Search Box -->
<div class="mb-3">
<div class="input-group shadow-sm">
<span class="input-group-text bg-white"><i class="bi bi-search"></i></span>
@ -177,7 +172,6 @@ class="rounded me-3 form-book-cover">
<div class="card-body p-3">
<div class="d-flex align-items-start">
<!-- Cover Buku -->
<img src="{{ asset($bukuItem['cover']) }}" alt="Cover"
class="rounded me-3 form-book-cover"
style="width: 60px; height: 80px; object-fit: cover;">
@ -197,7 +191,6 @@ class="bi bi-star-fill me-1"></i>Buku Utama</span>
</div>
</div>
<!-- Checkbox -->
<div class="form-check ms-2">
<input class="form-check-input book-checkbox" type="checkbox"
value="{{ $bukuItem['id'] }}" id="book{{ $bukuItem['id'] }}"

View File

@ -1,5 +1,6 @@
<?php
use App\Http\Controllers\BacaOnlineController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\KatalogController;
use App\Http\Controllers\PeminjamanController;
@ -10,23 +11,36 @@
return view('welcome');
});
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/katalog', [KatalogController::class, 'index'])->name('katalog');
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware(['auth'])->name('dashboard');
Route::get('/katalog', [KatalogController::class, 'index'])
->middleware('auth')->name('katalog');
// --- Fitur Peminjaman Buku Offline ---
Route::prefix('peminjaman-offline')->name('peminjaman.')->group(function () {
Route::get('/', [PeminjamanController::class, 'index'])->name('index');
Route::get('/{id}/ringkasan', [PeminjamanController::class, 'ringkasan'])->name('ringkasan');
Route::get('/{id}/form', [PeminjamanController::class, 'form'])->name('form');
Route::post('/store', [PeminjamanController::class, 'store'])->name('store');
});
// Route untuk peminjaman offline
Route::get('/peminjaman-offline', [PeminjamanController::class, 'index'])->name('peminjaman.index');
Route::get('/peminjaman-offline/{id}/ringkasan', [PeminjamanController::class, 'ringkasan'])->name('peminjaman.ringkasan');
Route::get('/peminjaman-offline/{id}/form', [PeminjamanController::class, 'form'])->name('peminjaman.form');
Route::post('/peminjaman/store', [PeminjamanController::class, 'store'])->name('peminjaman.store');
// --- Fitur Baca Buku Online ---
Route::prefix('baca-online')->name('baca.')->group(function () {
Route::get('/', [BacaOnlineController::class, 'index'])->name('index');
Route::get('/{id}/ringkasan', [BacaOnlineController::class, 'ringkasan'])->name('ringkasan'); // Rute baru
Route::get('/{id}/request', [BacaOnlineController::class, 'showCodeRequestPage'])->name('request_code');
Route::post('/{id}/verify', [BacaOnlineController::class, 'verifyCode'])->name('verify_code');
Route::get('/{id}/view', [BacaOnlineController::class, 'viewBook'])->name('view_book');
});
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'index'])->name('profile.index');
Route::get('/profile/edit', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('/secure-pdf/{id}', [BacaOnlineController::class, 'streamPdf'])->name('baca.stream_pdf');
// --- Manajemen Profil Pengguna ---
Route::prefix('profile')->name('profile.')->group(function () {
Route::get('/', [ProfileController::class, 'index'])->name('index');
Route::get('/edit', [ProfileController::class, 'edit'])->name('edit');
Route::patch('/', [ProfileController::class, 'update'])->name('update');
Route::delete('/', [ProfileController::class, 'destroy'])->name('destroy');
});
});
require __DIR__ . '/auth.php';
require __DIR__ . '/auth.php';

0
storage/app/.gitignore vendored Normal file → Executable file
View File

0
storage/app/private/.gitignore vendored Normal file → Executable file
View File

0
storage/app/public/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/cache/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/cache/data/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/sessions/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/testing/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/views/.gitignore vendored Normal file → Executable file
View File

0
storage/logs/.gitignore vendored Normal file → Executable file
View File