feat: role management & notification modal
This commit is contained in:
parent
f5f33124d3
commit
6455f0cc06
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\DummyDataService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BookController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$filters = $request->only(['search']);
|
||||
$semuaBuku = DummyDataService::getKatalogBuku($filters);
|
||||
|
||||
// Memisahkan buku menjadi dua koleksi: online dan offline
|
||||
[$bukuOnline, $bukuOffline] = $semuaBuku->partition(function ($buku) {
|
||||
$tipe = $buku['tipe_akses'];
|
||||
return $tipe === 'online' || (is_array($tipe) && in_array('online', $tipe));
|
||||
});
|
||||
|
||||
return view('admin.buku.index', [
|
||||
'pageTitle' => 'Manajemen Buku',
|
||||
'bukuOnline' => $bukuOnline,
|
||||
'bukuOffline' => $bukuOffline,
|
||||
'input' => $filters
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan halaman form untuk menambah buku baru.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('admin.buku.create', [
|
||||
'pageTitle' => 'Tambah Buku Baru'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\DummyDataService;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('admin.dashboard', [
|
||||
'pageTitle' => 'Dashboard Admin',
|
||||
'user' => auth()->user(),
|
||||
'greeting' => 'Selamat Datang',
|
||||
'stats' => DummyDataService::getAdminDashboardStats(),
|
||||
'statistikBulanan' => DummyDataService::getStatistikPeminjamanAdmin(),
|
||||
'komposisiBuku' => DummyDataService::getKomposisiBukuAdmin(),
|
||||
'pengumuman' => DummyDataService::getPengumuman(),
|
||||
'aktivitasTerakhir' => DummyDataService::getAktivitasTerakhir(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\DummyDataService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PengumumanController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$semuaPengumuman = DummyDataService::getPengumuman();
|
||||
return view('admin.pengumuman.index', [
|
||||
'pageTitle' => 'Manajemen Pengumuman',
|
||||
'semuaPengumuman' => $semuaPengumuman,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
namespace App\Http\Controllers\Admin;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\DummyDataService;
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$semuaSiswa = DummyDataService::getAllSiswa();
|
||||
return view('admin.pengguna.index', [
|
||||
'pageTitle' => 'Manajemen Pengguna',
|
||||
'semuaSiswa' => $semuaSiswa
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the registration view.
|
||||
* Menampilkan halaman registrasi.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
|
|
@ -23,28 +23,35 @@ public function create(): View
|
|||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* Menangani permintaan registrasi yang masuk.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255'],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
'role' => ['required', 'in:penjaga perpus,siswa'],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
$user = new User();
|
||||
$user->forceFill([
|
||||
'id' => rand(100, 999),
|
||||
'nama_lengkap' => $request->name,
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
'password' => $request->password,
|
||||
'role' => $request->role,
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect(route('dashboard', absolute: false));
|
||||
if ($user->role === 'penjaga perpus') {
|
||||
return redirect()->route('admin.dashboard');
|
||||
} else {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\DummyDataService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RiwayatController extends Controller
|
||||
{
|
||||
/**
|
||||
* Menampilkan halaman riwayat peminjaman offline.
|
||||
*/
|
||||
public function offlineIndex()
|
||||
{
|
||||
$riwayatOffline = DummyDataService::getRiwayatOffline();
|
||||
|
||||
return view('riwayat.offline', [
|
||||
'pageTitle' => 'Riwayat Peminjaman Offline',
|
||||
'riwayatOffline' => $riwayatOffline,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan halaman riwayat baca online.
|
||||
*/
|
||||
public function onlineIndex()
|
||||
{
|
||||
$riwayatOnline = DummyDataService::getRiwayatOnline();
|
||||
|
||||
return view('riwayat.online', [
|
||||
'pageTitle' => 'Riwayat Baca Online',
|
||||
'riwayatOnline' => $riwayatOnline,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckRole
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, ...$roles): Response
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
return redirect('login');
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
// Loop melalui role yang diizinkan (misal: 'guru', 'admin')
|
||||
foreach ($roles as $role) {
|
||||
if ($user->role == $role) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
// Jika role tidak cocok, tolak akses
|
||||
abort(403, 'AKSES DITOLAK: Anda tidak memiliki hak untuk mengakses halaman ini.');
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\DummyDataService;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -19,9 +21,16 @@ public function register(): void
|
|||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
if ($this->app->environment('production')) {
|
||||
URL::forceScheme('https');
|
||||
{
|
||||
if ($this->app->environment('production')) {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
|
||||
View::composer('*', function ($view) {
|
||||
$notifikasi = collect(DummyDataService::getNotifikasi());
|
||||
$unreadNotificationsCount = $notifikasi->where('read', false)->count();
|
||||
|
||||
$view->with(compact('notifikasi', 'unreadNotificationsCount'));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public static function getAllSiswa(): array
|
|||
'email' => 'budi.santoso@smkn1perpus.sch.id',
|
||||
'nomor_hp' => '081122334455',
|
||||
'password' => 'password',
|
||||
'role' => 'guru',
|
||||
'role' => 'penjaga perpus',
|
||||
],
|
||||
[
|
||||
'id' => 3,
|
||||
|
|
@ -56,11 +56,59 @@ public static function getAllSiswa(): array
|
|||
'email' => 'rina.marlina@smkn1perpus.sch.id',
|
||||
'nomor_hp' => '081223344556',
|
||||
'password' => 'password',
|
||||
'role' => 'guru',
|
||||
'role' => 'penjaga perpus',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function getAdminDashboardStats(): array
|
||||
{
|
||||
$allBooks = self::getAllBooks();
|
||||
$allUsers = self::getAllSiswa();
|
||||
$bukuDipinjam = $allBooks->filter(fn($buku) => $buku['status'] === 'Dipinjam')->count();
|
||||
return [
|
||||
['label' => 'Total Buku', 'value' => $allBooks->count(), 'icon' => 'bi-journal-bookmark-fill', 'color' => 'primary'],
|
||||
['label' => 'Total Anggota', 'value' => count($allUsers), 'icon' => 'bi-people-fill', 'color' => 'success'],
|
||||
['label' => 'Buku Dipinjam', 'value' => $bukuDipinjam, 'icon' => 'bi-arrow-up-right-circle-fill', 'color' => 'warning'],
|
||||
['label' => 'Denda Menunggu', 'value' => 0, 'icon' => 'bi-cash-coin', 'color' => 'danger'],
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Data untuk bar chart di dashboard admin (total peminjaman per bulan).
|
||||
*/
|
||||
public static function getStatistikPeminjamanAdmin(): array
|
||||
{
|
||||
return [
|
||||
'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul'],
|
||||
'data' => [65, 59, 80, 81, 56, 55, 70],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data untuk donut chart di dashboard admin (komposisi buku).
|
||||
*/
|
||||
public static function getKomposisiBukuAdmin(): array
|
||||
{
|
||||
$allBooks = self::getAllBooks();
|
||||
return [
|
||||
'tersedia' => $allBooks->where('status', 'Tersedia')->count(),
|
||||
'dipinjam' => $allBooks->where('status', 'Dipinjam')->count(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data untuk tabel aktivitas terakhir di dashboard admin.
|
||||
*/
|
||||
public static function getAktivitasTerakhir(): array
|
||||
{
|
||||
return [
|
||||
['nama' => 'Silvi Rahmawati', 'judul_buku' => 'Perahu Kertas', 'tipe' => 'Peminjaman', 'waktu' => '5 menit yang lalu', 'status' => 'Dipinjam'],
|
||||
['nama' => 'Andi Pratama', 'judul_buku' => 'The Last Spell Breather', 'tipe' => 'Pengembalian', 'waktu' => '1 jam yang lalu', 'status' => 'Dikembalikan'],
|
||||
['nama' => 'Siti Nurhaliza', 'judul_buku' => 'Ayah', 'tipe' => 'Baca Online', 'waktu' => '3 jam yang lalu', 'status' => 'Selesai'],
|
||||
['nama' => 'Rina Marlina', 'judul_buku' => 'Modul Ajar IPAS', 'tipe' => 'Peminjaman', 'waktu' => 'Kemarin', 'status' => 'Dipinjam'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data untuk 4 kartu statistik
|
||||
*/
|
||||
|
|
@ -133,7 +181,7 @@ public static function getStatistikBulanan(): array
|
|||
* Master list untuk semua buku
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
private static function getAllBooks()
|
||||
public static function getAllBooks()
|
||||
{
|
||||
return collect([
|
||||
[
|
||||
|
|
@ -145,8 +193,8 @@ private static function getAllBooks()
|
|||
'tahun' => 2022,
|
||||
'status' => 'Tersedia',
|
||||
'is_new' => true,
|
||||
'tipe_akses' => ['online','offline'],
|
||||
'file_pdf'=>'ipas.pdf',
|
||||
'tipe_akses' => ['online', 'offline'],
|
||||
'file_pdf' => 'ipas.pdf',
|
||||
'progress' => 75,
|
||||
'sisa_hari' => 14,
|
||||
'user_id' => 1,
|
||||
|
|
@ -213,8 +261,8 @@ private static function getAllBooks()
|
|||
'tahun' => 2023,
|
||||
'status' => 'Tersedia',
|
||||
'is_new' => true,
|
||||
'tipe_akses' => ['online','offline'],
|
||||
'file_pdf'=>'mtk.pdf',
|
||||
'tipe_akses' => ['online', 'offline'],
|
||||
'file_pdf' => 'mtk.pdf',
|
||||
'sisa_hari' => 7,
|
||||
'progress' => 40,
|
||||
'user_id' => [1, 4, 5],
|
||||
|
|
@ -243,9 +291,9 @@ private static function getAllBooks()
|
|||
'status' => 'Tersedia',
|
||||
'is_new' => true,
|
||||
'tipe_akses' => 'online',
|
||||
'file_pdf' => 'ayah.pdf',
|
||||
'file_pdf' => 'ayah.pdf',
|
||||
'progress' => 0,
|
||||
'user_id' => [1, 2, 3],
|
||||
'user_id' => [1, 2, 3],
|
||||
],
|
||||
[
|
||||
'id' => 9,
|
||||
|
|
@ -256,8 +304,8 @@ private static function getAllBooks()
|
|||
'tahun' => 2015,
|
||||
'status' => 'Tersedia',
|
||||
'is_new' => true,
|
||||
'tipe_akses' => ['online','offline'],
|
||||
'file_pdf' => 'senja.pdf',
|
||||
'tipe_akses' => ['online', 'offline'],
|
||||
'file_pdf' => 'senja.pdf',
|
||||
'progress' => 0,
|
||||
'sisa_hari' => 14,
|
||||
'user_id' => [1, 3],
|
||||
|
|
@ -274,7 +322,7 @@ private static function getAllBooks()
|
|||
'tipe_akses' => 'online',
|
||||
'file_pdf' => 'hijrah.pdf',
|
||||
'progress' => 0,
|
||||
'user_id' => [2, 3],
|
||||
'user_id' => [2, 3],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
|
@ -377,4 +425,105 @@ public static function getFilterOptions(): array
|
|||
'penulis' => $buku->pluck('penulis')->unique()->sort()->values(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data untuk riwayat peminjaman offline.
|
||||
* Setiap item mewakili satu transaksi peminjaman.
|
||||
*/
|
||||
public static function getRiwayatOffline(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'id' => 1,
|
||||
'id_peminjaman' => 'PIN-20240520-001',
|
||||
'judul_utama' => 'Yuk, Mari Sekolah',
|
||||
'tanggal_pinjam' => '20/05/2024',
|
||||
'tanggal_kembali' => '27/05/2024',
|
||||
'status' => 'Dikembalikan',
|
||||
'books' => [
|
||||
[
|
||||
'id' => 1,
|
||||
'judul' => 'Yuk, Mari Sekolah',
|
||||
'cover' => 'images/covers/ipas.jpg',
|
||||
'deskripsi' => 'Buku ini berisi ajakan kepada anak-anak untuk semangat pergi ke sekolah dan menuntut ilmu.',
|
||||
'kategori' => 'Pendidikan',
|
||||
'tahun' => 2022,
|
||||
'keterangan' => 'Buku dikembalikan dalam kondisi baik.'
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'id_peminjaman' => 'PIN-20240527-002',
|
||||
'judul_utama' => 'Perahu Kertas & 1 lainnya',
|
||||
'tanggal_pinjam' => '27/05/2024',
|
||||
'tanggal_kembali' => '04/06/2024',
|
||||
'status' => 'Dipinjam',
|
||||
'books' => [
|
||||
[
|
||||
'id' => 8,
|
||||
'judul' => 'Perahu Kertas',
|
||||
'cover' => 'images/covers/ayah.png',
|
||||
'deskripsi' => 'Cerita penggambaran pasang surut hubungan dua anak manusia, yaitu Kugy dan Keenan.',
|
||||
'kategori' => 'Fiksi',
|
||||
'tahun' => 2022,
|
||||
'keterangan' => null,
|
||||
],
|
||||
[
|
||||
'id' => 7,
|
||||
'judul' => 'The Last Spell Breather',
|
||||
'cover' => 'images/covers/thelastspellbreather.jpg',
|
||||
'deskripsi' => 'Sebuah petualangan fantasi di dunia sihir yang menakjubkan.',
|
||||
'kategori' => 'Fantasi',
|
||||
'tahun' => 2024,
|
||||
'keterangan' => null,
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data untuk riwayat baca buku online.
|
||||
*/
|
||||
public static function getRiwayatOnline(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'id' => 1,
|
||||
'id_baca' => 'BCO-20240527-002',
|
||||
'judul_buku' => 'Ayah',
|
||||
'tanggal_akses' => '27/05/2024',
|
||||
'status' => 'Selesai',
|
||||
'books' => [
|
||||
[
|
||||
'id' => 9,
|
||||
'judul' => 'Ayah',
|
||||
'cover' => 'images/covers/ayah.png',
|
||||
'deskripsi' => 'Novel yang mengisahkan perjuangan dan kasih sayang seorang ayah.',
|
||||
'kategori' => 'Novel',
|
||||
'tahun' => 2015,
|
||||
'keterangan' => null
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data untuk fitur notifikasi.
|
||||
*/
|
||||
public static function getNotifikasi(): array
|
||||
{
|
||||
return [
|
||||
['icon' => 'bi-check2-circle', 'color' => 'success', 'title' => 'Buku "Perahu Kertas" berhasil dipinjam.', 'time' => '5 menit yang lalu', 'read' => false],
|
||||
['icon' => 'bi-exclamation-triangle', 'color' => 'danger', 'title' => 'Buku "Sosiologi" akan jatuh tempo besok!', 'time' => '1 jam yang lalu', 'read' => false],
|
||||
['icon' => 'bi-book-half', 'color' => 'info', 'title' => '5 buku baru ditambahkan ke kategori Fiksi.', 'time' => '3 jam yang lalu', 'read' => false],
|
||||
['icon' => 'bi-arrow-repeat', 'color' => 'primary', 'title' => 'Peminjaman buku "Modul IPAS" telah diperpanjang.', 'time' => 'Kemarin', 'read' => true],
|
||||
['icon' => 'bi-check-circle', 'color' => 'success', 'title' => 'Anda telah mengembalikan buku "The Last Spell Breather".', 'time' => 'Kemarin', 'read' => true],
|
||||
['icon' => 'bi-info-circle', 'color' => 'info', 'title' => 'Perpustakaan akan mengadakan acara baca buku bersama.', 'time' => '2 hari yang lalu', 'read' => true],
|
||||
['icon' => 'bi-person-check', 'color' => 'primary', 'title' => 'Profil Anda berhasil diperbarui.', 'time' => '2 hari yang lalu', 'read' => true],
|
||||
['icon' => 'bi-exclamation-triangle', 'color' => 'warning', 'title' => 'Sistem akan maintenance pada pukul 23:00.', 'time' => '3 hari yang lalu', 'read' => true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,13 @@
|
|||
$middleware->web(append: [
|
||||
\App\Http\Middleware\AuthenticateFromSessionData::class,
|
||||
]);
|
||||
$middleware->alias([
|
||||
'role' => \App\Http\Middleware\CheckRole::class,
|
||||
]);
|
||||
})
|
||||
->withProviders([
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
])
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
})->create();
|
||||
})->create();
|
||||
|
|
@ -6,4 +6,6 @@ window.bootstrap = bootstrap;
|
|||
|
||||
import Alpine from 'alpinejs';
|
||||
window.Alpine = Alpine;
|
||||
Alpine.start();
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
Alpine.start();
|
||||
});
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
@use "sass:color";
|
||||
|
||||
// ===================================
|
||||
// VARIABLES & MAPS
|
||||
// ===================================
|
||||
|
|
@ -5,17 +7,17 @@
|
|||
// Theme Colors Map
|
||||
$theme-colors: (
|
||||
"primary": #435ebe,
|
||||
"secondary": mix(#6c757d, #ffffff, 80%),
|
||||
"success": mix(#198754, #ffffff, 85%),
|
||||
"info": mix(#0dcaf0, #ffffff, 80%),
|
||||
"warning": mix(#ffc107, #ffffff, 80%),
|
||||
"danger": mix(#dc3545, #ffffff, 80%),
|
||||
"secondary": color.mix(#6c757d, #ffffff, 80%),
|
||||
"success": color.mix(#198754, #ffffff, 85%),
|
||||
"info": color.mix(#0dcaf0, #ffffff, 80%),
|
||||
"warning": color.mix(#ffc107, #ffffff, 80%),
|
||||
"danger": color.mix(#dc3545, #ffffff, 80%),
|
||||
);
|
||||
|
||||
// Gray Colors Map
|
||||
$grays: (
|
||||
"light": #f4f7f8,
|
||||
"dark": #4c5053
|
||||
"light": #f4f7f8,
|
||||
"dark": #4c5053,
|
||||
);
|
||||
|
||||
// Spacing & Sizing
|
||||
|
|
@ -43,7 +45,7 @@ $transition: all 0.3s ease;
|
|||
}
|
||||
.alert-#{$color} {
|
||||
background-color: rgba($value, 0.2);
|
||||
color: darken($value, 25%);
|
||||
color: color.adjust($value, $lightness: -25%);
|
||||
border-color: rgba($value, 0.3);
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +58,7 @@ $transition: all 0.3s ease;
|
|||
box-shadow: $card-box-shadow;
|
||||
.card-header {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid rgba(map-get($grays, "dark"), 0.1);
|
||||
border-bottom: 1px solid rgba(map-get($grays, "dark"), 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,17 +80,17 @@ $transition: all 0.3s ease;
|
|||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||
.modal-header,
|
||||
.modal-footer {
|
||||
border-color: rgba(map-get($grays, "dark"), 0.1);
|
||||
border-color: rgba(map-get($grays, "dark"), 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.modal-title {
|
||||
color: map-get($grays, "dark");
|
||||
color: map-get($grays, "dark");
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn{
|
||||
.btn {
|
||||
padding: 0.5rem 1.5rem;
|
||||
font-weight: 500;
|
||||
&.btn-sm {
|
||||
|
|
@ -106,7 +108,8 @@ $transition: all 0.3s ease;
|
|||
// CUSTOM COMPONENTS
|
||||
// ===================================
|
||||
|
||||
.icon-circle, .icon-box {
|
||||
.icon-circle,
|
||||
.icon-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -128,26 +131,14 @@ $transition: all 0.3s ease;
|
|||
}
|
||||
}
|
||||
|
||||
.book-card {
|
||||
transition: $transition;
|
||||
box-shadow: $card-box-shadow;
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: $shadow-md;
|
||||
.book-cover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.book-cover {
|
||||
transition: $transition;
|
||||
border-radius: $border-radius-sm;
|
||||
&-container {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(map-get($grays, "light"), 0.5) 0%,
|
||||
rgba(map-get($grays, "light"), 0.8) 100%
|
||||
rgba(map-get($grays, "light"), 0.5) 0%,
|
||||
rgba(map-get($grays, "light"), 0.8) 100%
|
||||
);
|
||||
border-radius: $border-radius-sm 0 0 $border-radius-sm;
|
||||
}
|
||||
|
|
@ -155,9 +146,9 @@ $transition: all 0.3s ease;
|
|||
|
||||
.section-header {
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid map-get($grays, "light");
|
||||
border-bottom: 2px solid map-get($grays, "light");
|
||||
h5 {
|
||||
color: map-get($grays, "dark");
|
||||
color: map-get($grays, "dark");
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
|
@ -167,12 +158,12 @@ $transition: all 0.3s ease;
|
|||
padding: 3rem 1rem;
|
||||
i {
|
||||
font-size: 4rem;
|
||||
color: map-get($grays, "dark");
|
||||
color: map-get($grays, "dark");
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.25;
|
||||
}
|
||||
p {
|
||||
color: map-get($grays, "dark");
|
||||
color: map-get($grays, "dark");
|
||||
opacity: 0.7;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
|
|
@ -180,12 +171,18 @@ $transition: all 0.3s ease;
|
|||
}
|
||||
|
||||
.btn-primary-soft {
|
||||
--bs-btn-color: #{map-get($grays, "dark")};
|
||||
--bs-btn-bg: #{map-get($theme-colors, "primary")};
|
||||
--bs-btn-border-color: #{map-get($theme-colors, "primary")};
|
||||
--bs-btn-hover-color: #{map-get($grays, "dark")};
|
||||
--bs-btn-hover-bg: #{darken(map-get($theme-colors, "primary"), 5%)};
|
||||
--bs-btn-hover-border-color: #{darken(map-get($theme-colors, "primary"), 7.5%)};
|
||||
--bs-btn-color: #{map-get($grays, "dark")};
|
||||
--bs-btn-bg: #{map-get($theme-colors, "primary")};
|
||||
--bs-btn-border-color: #{map-get($theme-colors, "primary")};
|
||||
--bs-btn-hover-color: #{map-get($grays, "dark")};
|
||||
--bs-btn-hover-bg: #{color.adjust(
|
||||
map-get($theme-colors, "primary"),
|
||||
$lightness: -5%
|
||||
)};
|
||||
--bs-btn-hover-border-color: #{color.adjust(
|
||||
map-get($theme-colors, "primary"),
|
||||
$lightness: -7.5%
|
||||
)};
|
||||
}
|
||||
|
||||
// ===================================
|
||||
|
|
@ -199,22 +196,26 @@ $transition: all 0.3s ease;
|
|||
overflow: hidden;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.line-clamp-2 { -webkit-line-clamp: 2; }
|
||||
.line-clamp-3 { -webkit-line-clamp: 3; }
|
||||
.line-clamp-2 {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
.line-clamp-3 {
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
// Custom Scrollbar
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: map-get($grays, "light");
|
||||
background: map-get($grays, "light");
|
||||
border-radius: 3px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(map-get($grays, "dark"), 0.25);
|
||||
background: rgba(map-get($grays, "dark"), 0.25);
|
||||
border-radius: 3px;
|
||||
&:hover {
|
||||
background: rgba(map-get($grays, "dark"), 0.4);
|
||||
background: rgba(map-get($grays, "dark"), 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +226,7 @@ $transition: all 0.3s ease;
|
|||
.card-book-select {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: $transition;
|
||||
transition: $transition;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
|
|
@ -234,7 +235,10 @@ $transition: all 0.3s ease;
|
|||
|
||||
.card-select-overlay {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(#435ebe, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -242,15 +246,15 @@ $transition: all 0.3s ease;
|
|||
opacity: 0;
|
||||
transition: $transition;
|
||||
border-radius: $border-radius-sm;
|
||||
|
||||
|
||||
i {
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border: 2px solid map-get($theme-colors, "primary");
|
||||
|
||||
|
||||
.card-select-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
@ -262,7 +266,6 @@ $transition: all 0.3s ease;
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// ===================================
|
||||
// Styling untuk Form Peminjaman
|
||||
// ===================================
|
||||
|
|
@ -283,10 +286,15 @@ $transition: all 0.3s ease;
|
|||
cursor: pointer;
|
||||
transition: $transition;
|
||||
border: 2px solid transparent;
|
||||
box-shadow: $card-box-shadow;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: map-get($theme-colors, "primary");
|
||||
box-shadow: $shadow-md;
|
||||
.book-cover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
}
|
||||
|
||||
// Style khusus ketika checkbox di dalamnya terpilih
|
||||
|
|
@ -295,6 +303,7 @@ $transition: all 0.3s ease;
|
|||
background-color: rgba(map-get($theme-colors, "success"), 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.remove-book {
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
|
|
@ -305,7 +314,6 @@ $transition: all 0.3s ease;
|
|||
opacity: 0.65;
|
||||
}
|
||||
|
||||
|
||||
.book-option[style*="display: none"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,217 @@
|
|||
@import "bootstrap/scss/functions";
|
||||
@import "variables";
|
||||
@import "variables";
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
|
||||
// =========================================
|
||||
// Custom Styling untuk Navbar dan Sidebar
|
||||
// =========================================
|
||||
|
||||
body {
|
||||
background-color: $light;
|
||||
}
|
||||
background-color: map-get($grays, "light");
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1050;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1049;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.sidebar {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin-left: 250px;
|
||||
}
|
||||
|
||||
.sidebar.minimized {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.sidebar.minimized .nav-text,
|
||||
.sidebar.minimized .sidebar-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar.minimized .nav-link {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.main-wrapper.sidebar-minimized {
|
||||
margin-left: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
left: -250px;
|
||||
}
|
||||
|
||||
.sidebar.active {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
margin: 0.3rem 0;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background: rgba(67, 94, 190, 0.1);
|
||||
color: #435ebe;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover:not(.active) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.sidebar.minimized .nav-link .ms-auto {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// NAVBAR & NOTIFICATIONS
|
||||
// ===================================
|
||||
|
||||
nav {
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
color: var(--bs-dark-text-emphasis) !important;
|
||||
}
|
||||
|
||||
.navbar .nav-link,
|
||||
.navbar .btn-light {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--bs-light-bg-subtle);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.dropdown-item i {
|
||||
width: 1.25em;
|
||||
}
|
||||
|
||||
.notification-dropdown .dropdown-menu {
|
||||
width: 390px;
|
||||
border-radius: 0.75rem;
|
||||
padding: 0.5rem !important;
|
||||
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.notification-wrapper {
|
||||
max-height: 450px;
|
||||
}
|
||||
|
||||
.notification-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
padding: 0.8rem 1rem;
|
||||
transition: background-color 0.2s ease;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.notification-item:hover {
|
||||
background-color: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
.notification-item.unread {
|
||||
background-color: rgba(var(--bs-primary-rgb), 0.1);
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.notification-icon i {
|
||||
text-align: center;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.notification-content p {
|
||||
margin-bottom: 0.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--bs-body-color);
|
||||
font-size: 0.925rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.notification-content small {
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.unread-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--bs-primary);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<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.buku.index') }}" class="btn btn-light me-2">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
</a>
|
||||
<h5 class="my-0 fw-bold">Formulir Tambah Buku Baru</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="#" method="POST">
|
||||
{{-- Form ini tidak akan berfungsi karena tidak ada backend --}}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="mb-3">
|
||||
<label for="judul" class="form-label">Judul Buku</label>
|
||||
<input type="text" class="form-control" id="judul" placeholder="Masukkan judul buku">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="penulis" class="form-label">Penulis</label>
|
||||
<input type="text" class="form-control" id="penulis"
|
||||
placeholder="Masukkan nama penulis">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="kategori" class="form-label">Kategori</label>
|
||||
<input type="text" class="form-control" id="kategori"
|
||||
placeholder="Contoh: Fiksi, Sains">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="tahun" class="form-label">Tahun Terbit</label>
|
||||
<input type="number" class="form-control" id="tahun" placeholder="Contoh: 2024">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Tipe Akses</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tipe_offline">
|
||||
<label class="form-check-label" for="tipe_offline">Peminjaman Offline</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tipe_online">
|
||||
<label class="form-check-label" for="tipe_online">Baca Online</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="cover" class="form-label">Cover Buku</label>
|
||||
<input type="file" class="form-control" id="cover">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="file_pdf" class="form-label">File PDF (untuk buku online)</label>
|
||||
<input type="file" class="form-control" id="file_pdf">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-primary">Simpan Buku</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<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">Daftar Buku</h5>
|
||||
<a href="{{ route('admin.buku.create') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle-fill me-2"></i>Tambah Buku
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="nav nav-tabs" id="bukuTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="offline-tab" data-bs-toggle="tab" data-bs-target="#offline-tab-pane" type="button" role="tab">Peminjaman Offline ({{ $bukuOffline->count() }})</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="online-tab" data-bs-toggle="tab" data-bs-target="#online-tab-pane" type="button" role="tab">Baca Online ({{ $bukuOnline->count() }})</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="bukuTabContent">
|
||||
{{-- TAB UNTUK BUKU OFFLINE --}}
|
||||
<div class="tab-pane fade show active" id="offline-tab-pane" role="tabpanel">
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr><th>No</th><th>Cover</th><th>Judul</th><th>Penulis</th><th>Status</th><th>Aksi</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($bukuOffline as $buku)
|
||||
<tr>
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td><img src="{{ asset($buku['cover']) }}" alt="{{ $buku['judul'] }}" width="50" class="rounded"></td>
|
||||
<td>{{ $buku['judul'] }}</td>
|
||||
<td>{{ $buku['penulis'] }}</td>
|
||||
<td>
|
||||
@if($buku['status'] == 'Tersedia')
|
||||
<span class="badge bg-success-subtle text-success-emphasis">Tersedia</span>
|
||||
@else
|
||||
<span class="badge bg-warning-subtle text-warning-emphasis">Dipinjam</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#detailBukuModal"
|
||||
data-cover="{{ asset($buku['cover']) }}"
|
||||
data-judul="{{ $buku['judul'] }}"
|
||||
data-penulis="{{ $buku['penulis'] }}"
|
||||
data-kategori="{{ $buku['kategori'] }}"
|
||||
data-tahun="{{ $buku['tahun'] }}"
|
||||
data-status="{{ $buku['status'] }}">
|
||||
<i class="bi bi-eye-fill"></i> Detail
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center py-4">Tidak ada data buku offline.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- TAB UNTUK BUKU ONLINE --}}
|
||||
<div class="tab-pane fade" id="online-tab-pane" role="tabpanel">
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr><th>No</th><th>Cover</th><th>Judul</th><th>Penulis</th><th>File PDF</th><th>Aksi</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($bukuOnline as $buku)
|
||||
<tr>
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td><img src="{{ asset($buku['cover']) }}" alt="{{ $buku['judul'] }}" width="50" class="rounded"></td>
|
||||
<td>{{ $buku['judul'] }}</td>
|
||||
<td>{{ $buku['penulis'] }}</td>
|
||||
<td><span class="badge bg-info-subtle text-info-emphasis">{{ $buku['file_pdf'] ?? 'N/A' }}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#detailBukuModal"
|
||||
data-cover="{{ asset($buku['cover']) }}"
|
||||
data-judul="{{ $buku['judul'] }}"
|
||||
data-penulis="{{ $buku['penulis'] }}"
|
||||
data-kategori="{{ $buku['kategori'] }}"
|
||||
data-tahun="{{ $buku['tahun'] }}"
|
||||
data-status="Dapat Dibaca Online">
|
||||
<i class="bi bi-eye-fill"></i> Detail
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center py-4">Tidak ada data buku online.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="detailBukuModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold" id="modalJudul">Detail Buku</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<img src="" id="modalCover" class="img-fluid rounded shadow-sm" alt="Cover Buku">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h3 id="modalJudulContent" class="fw-bold"></h3>
|
||||
<p class="text-muted" id="modalPenulis"></p>
|
||||
<table class="table table-sm table-borderless">
|
||||
<tr><th width="100px">Kategori</th><td id="modalKategori"></td></tr>
|
||||
<tr><th>Tahun</th><td id="modalTahun"></td></tr>
|
||||
<tr><th>Status</th><td><span id="modalStatus" class="badge"></span></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const detailBukuModal = document.getElementById('detailBukuModal');
|
||||
detailBukuModal.addEventListener('show.bs.modal', event => {
|
||||
const button = event.relatedTarget; // Tombol yang memicu modal
|
||||
|
||||
// Ambil data dari atribut data-*
|
||||
const cover = button.getAttribute('data-cover');
|
||||
const judul = button.getAttribute('data-judul');
|
||||
const penulis = button.getAttribute('data-penulis');
|
||||
const kategori = button.getAttribute('data-kategori');
|
||||
const tahun = button.getAttribute('data-tahun');
|
||||
const status = button.getAttribute('data-status');
|
||||
|
||||
// Dapatkan elemen-elemen di dalam modal
|
||||
const modalJudul = detailBukuModal.querySelector('#modalJudul');
|
||||
const modalCover = detailBukuModal.querySelector('#modalCover');
|
||||
const modalJudulContent = detailBukuModal.querySelector('#modalJudulContent');
|
||||
const modalPenulis = detailBukuModal.querySelector('#modalPenulis');
|
||||
const modalKategori = detailBukuModal.querySelector('#modalKategori');
|
||||
const modalTahun = detailBukuModal.querySelector('#modalTahun');
|
||||
const modalStatus = detailBukuModal.querySelector('#modalStatus');
|
||||
|
||||
// Masukkan data ke dalam elemen modal
|
||||
modalJudul.textContent = judul;
|
||||
modalCover.src = cover;
|
||||
modalJudulContent.textContent = judul;
|
||||
modalPenulis.textContent = `oleh ${penulis}`;
|
||||
modalKategori.textContent = `: ${kategori}`;
|
||||
modalTahun.textContent = `: ${tahun}`;
|
||||
modalStatus.textContent = status;
|
||||
|
||||
// Atur warna badge status
|
||||
if (status === 'Tersedia' || status === 'Dapat Dibaca Online') {
|
||||
modalStatus.className = 'badge bg-success-subtle text-success-emphasis';
|
||||
} else {
|
||||
modalStatus.className = 'badge bg-warning-subtle text-warning-emphasis';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-app-layout>
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<x-app-layout>
|
||||
@section('page-title', 'Dashboard Admin')
|
||||
|
||||
<div class="mb-4">
|
||||
<h3>Selamat Datang, {{ $user->nama_lengkap }}!</h3>
|
||||
<p class="text-muted">Berikut adalah ringkasan aktivitas perpustakaan hari ini.</p>
|
||||
</div>
|
||||
|
||||
{{-- Kartu Statistik --}}
|
||||
<div class="row g-4 mb-4">
|
||||
@foreach ($stats as $stat)
|
||||
<div class="col-xl-3 col-lg-6 col-md-6">
|
||||
<div class="card rounded-2 bg-{{ $stat['color'] }}-light border-0 h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="text-muted fw-normal mb-2 text-uppercase small">{{ $stat['label'] }}</h6>
|
||||
<h3 class="fw-bold mb-0 text-dark">{{ $stat['value'] }}</h3>
|
||||
</div>
|
||||
<div class="icon-wrapper">
|
||||
<div class="icon-circle bg-{{ $stat['color'] }}-light">
|
||||
<i class="bi {{ $stat['icon'] }} text-{{ $stat['color'] }} fs-4"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white border-0 py-3">
|
||||
<h6 class="m-0 fw-bold text-dark"><i class="bi bi-bar-chart-fill text-primary me-2"></i>Total Peminjaman per Bulan</h6>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<canvas id="barChart" style="max-height: 300px;" data-stats='@json($statistikBulanan)'></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white border-0 py-3">
|
||||
<h6 class="m-0 fw-bold text-dark"><i class="bi bi-pie-chart-fill text-primary me-2"></i>Komposisi Status Buku</h6>
|
||||
</div>
|
||||
<div class="card-body text-center d-flex justify-content-center align-items-center p-4">
|
||||
<div style="position: relative; height: 200px; width: 200px;">
|
||||
<canvas id="donutChartAdmin" data-progress='@json($komposisiBuku)'></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center py-3 border-top">
|
||||
<div class="d-flex justify-content-center gap-4">
|
||||
<span class="badge bg-success-soft text-success px-3 py-2"><i class="bi bi-circle-fill me-1"></i> Tersedia</span>
|
||||
<span class="badge bg-warning-soft text-warning px-3 py-2"><i class="bi bi-circle-fill me-1"></i> Dipinjam</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<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-megaphone-fill text-warning me-2"></i>Pengumuman</h6>
|
||||
<a href="{{ route('admin.pengumuman.index') }}" class="btn btn-sm btn-outline-primary rounded-pill px-3">
|
||||
Kelola Semua
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
@forelse(collect($pengumuman)->take(2) as $item)
|
||||
<div class="alert alert-{{ $item['type'] }} border-0 d-flex align-items-start mb-3 shadow-sm">
|
||||
<i class="{{ $item['icon'] }} fs-5 me-3 mt-1"></i>
|
||||
<div>
|
||||
<div class="fw-bold mb-1">{{ $item['title'] }}!</div>
|
||||
<div class="small">{{ $item['content'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-4"><i class="bi bi-inbox text-muted fs-2 mb-3"></i><p class="text-muted mb-0">Tidak ada pengumuman baru.</p></div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<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-bell-fill text-info me-2"></i>Aktivitas Peminjaman Terkini</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<tbody>
|
||||
@forelse($aktivitasTerakhir as $aktivitas)
|
||||
<tr>
|
||||
<td class="ps-4 py-3">
|
||||
<div class="fw-semibold text-dark">{{ $aktivitas['nama'] }}</div>
|
||||
<div class="text-muted small truncate-text" style="max-width: 200px;">{{ $aktivitas['tipe'] }} buku "{{ $aktivitas['judul_buku'] }}"</div>
|
||||
</td>
|
||||
<td class="text-end pe-4 py-3">
|
||||
@if($aktivitas['status'] == 'Dikembalikan' || $aktivitas['status'] == 'Selesai')
|
||||
<span class="badge bg-success-subtle text-success-emphasis">{{$aktivitas['status']}}</span>
|
||||
@else
|
||||
<span class="badge bg-warning-subtle text-warning-emphasis">{{$aktivitas['status']}}</span>
|
||||
@endif
|
||||
<div class="text-muted small mt-1">{{ $aktivitas['waktu'] }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td class="text-center py-5"><p class="text-muted">Tidak ada aktivitas terkini.</p></td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
// Script untuk menginisialisasi chart di dashboard admin
|
||||
document.addEventListener('turbo:load', function() {
|
||||
if (window.myBarChart) window.myBarChart.destroy();
|
||||
if (window.myDonutChart) window.myDonutChart.destroy();
|
||||
|
||||
// Bar Chart
|
||||
const barCtx = document.getElementById('barChart');
|
||||
if (barCtx) {
|
||||
const barData = JSON.parse(barCtx.dataset.stats);
|
||||
window.myBarChart = new Chart(barCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: barData.labels,
|
||||
datasets: [{
|
||||
label: 'Jumlah Peminjaman',
|
||||
data: barData.data,
|
||||
backgroundColor: 'rgba(67, 94, 190, 0.7)',
|
||||
borderColor: 'rgba(67, 94, 190, 1)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 5
|
||||
}]
|
||||
},
|
||||
options: { scales: { y: { beginAtZero: true } }, responsive: true, maintainAspectRatio: false }
|
||||
});
|
||||
}
|
||||
|
||||
// Donut Chart
|
||||
const donutCtx = document.getElementById('donutChartAdmin');
|
||||
if (donutCtx) {
|
||||
const donutData = JSON.parse(donutCtx.dataset.progress);
|
||||
window.myDonutChart = new Chart(donutCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Tersedia', 'Dipinjam'],
|
||||
datasets: [{
|
||||
data: [donutData.tersedia, donutData.dipinjam],
|
||||
backgroundColor: ['#198754', '#ffc107'],
|
||||
borderWidth: 0,
|
||||
}]
|
||||
},
|
||||
options: { responsive: true, maintainAspectRatio: false, cutout: '75%' }
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-app-layout>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<x-app-layout>
|
||||
@section('page-title', $pageTitle)
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white"><h5 class="my-0 fw-bold">Daftar Semua Pengguna</h5></div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr><th>No</th><th>Nama Lengkap</th><th>Email</th><th>Role</th><th>Aksi</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($semuaSiswa as $siswa)
|
||||
<tr>
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td>{{ $siswa['nama_lengkap'] }}</td>
|
||||
<td>{{ $siswa['email'] }}</td>
|
||||
<td><span class="badge bg-primary-subtle text-primary-emphasis">{{ Str::title($siswa['role']) }}</span></td>
|
||||
<td><button class="btn btn-sm btn-outline-secondary">Detail</button></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="5" class="text-center">Tidak ada data pengguna.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<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 Pengumuman</h5>
|
||||
<button class="btn btn-primary"><i class="bi bi-plus-circle-fill me-2"></i>Buat Pengumuman</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Tipe</th>
|
||||
<th>Judul</th>
|
||||
<th>Isi</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($semuaPengumuman as $item)
|
||||
<tr>
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td><span
|
||||
class="badge bg-{{ $item['type'] }}-subtle text-{{ $item['type'] }}-emphasis">{{ Str::title($item['type']) }}</span>
|
||||
</td>
|
||||
<td>{{ $item['title'] }}</td>
|
||||
<td class="truncate-text" style="max-width: 300px;">{{ $item['content'] }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary"><i
|
||||
class="bi bi-pencil-fill"></i></button>
|
||||
<button class="btn btn-sm btn-outline-danger"><i
|
||||
class="bi bi-trash3-fill"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
|
@ -6,102 +6,14 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ config('app.name', 'Perpus') }}</title>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
@vite(['resources/scss/app.scss', 'resources/js/app.js'])
|
||||
|
||||
<!-- Flatpickr CSS & JS untuk calendar picker -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/material_blue.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||
<script src="https://npmcdn.com/flatpickr/dist/l10n/id.js"></script>
|
||||
|
||||
<style>
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1050;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1049;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.sidebar {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin-left: 250px;
|
||||
}
|
||||
|
||||
.sidebar.minimized {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.sidebar.minimized .nav-text,
|
||||
.sidebar.minimized .sidebar-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar.minimized .nav-link {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.main-wrapper.sidebar-minimized {
|
||||
margin-left: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
left: -250px;
|
||||
}
|
||||
|
||||
.sidebar.active {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
margin: .3rem 0;
|
||||
border-radius: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background: rgba(67, 94, 190, .1);
|
||||
color: #435ebe;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover:not(.active) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
@ -120,6 +32,45 @@
|
|||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="semuaNotifikasiModal" tabindex="-1" aria-labelledby="semuaNotifikasiModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-lg">
|
||||
<div class="modal-content border-0 shadow rounded-3">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title fw-bold text-dark" id="semuaNotifikasiModalLabel">
|
||||
<i class="bi bi-bell-fill text-primary me-2"></i>Semua Notifikasi
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body pt-0">
|
||||
<div class="notification-list px-2">
|
||||
@forelse ($notifikasi as $item)
|
||||
<a href="#"
|
||||
class="notification-item d-flex my-1 rounded-3 text-body text-decoration-none @if (!$item['read']) unread @endif">
|
||||
<div class="notification-icon bg-{{ $item['color'] }}-subtle">
|
||||
<i class="bi {{ $item['icon'] }} text-{{ $item['color'] }}-emphasis"></i>
|
||||
</div>
|
||||
<div class="notification-content">
|
||||
<p class="mb-0">{{ $item['title'] }}</p>
|
||||
<small>{{ $item['time'] }}</small>
|
||||
</div>
|
||||
@if (!$item['read'])
|
||||
<div class="unread-dot"></div>
|
||||
@endif
|
||||
</a>
|
||||
@empty
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-bell-slash fs-2 text-muted"></i>
|
||||
<p class="text-muted small mt-2 mb-0">Anda belum memiliki notifikasi.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
|
|
@ -157,6 +108,16 @@ function toggleDesktopSidebar() {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
const dropdownToggles = document.querySelectorAll('.sidebar .nav-link[data-bs-toggle="collapse"]');
|
||||
|
||||
dropdownToggles.forEach(function(toggle) {
|
||||
toggle.addEventListener('click', function() {
|
||||
if (isDesktop() && sidebar.classList.contains('minimized')) {
|
||||
toggleDesktopSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@stack('scripts')
|
||||
|
|
|
|||
|
|
@ -1,28 +1,108 @@
|
|||
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
|
||||
<div class="container-fluid">
|
||||
<!-- Tombol Toggle Sidebar -->
|
||||
{{-- Tombol Sidebar Toggle --}}
|
||||
<button class="btn btn-light me-2" type="button" id="sidebarToggle">
|
||||
<i class="bi bi-list"></i>
|
||||
<i class="bi bi-list fs-5"></i>
|
||||
</button>
|
||||
|
||||
<a class="navbar-brand fw-bold text-primary" href="#">
|
||||
{{-- Judul Halaman --}}
|
||||
<a class="navbar-brand fw-bold me-auto" href="#">
|
||||
@yield('page-title', 'Perpus')
|
||||
</a>
|
||||
|
||||
{{-- Menu kanan --}}
|
||||
<div class="dropdown ms-auto">
|
||||
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-person-circle me-1"></i> {{ Auth::user()->name }}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
|
||||
<li><a class="dropdown-item" href="{{ route('profile.index') }}">{{ __('Profile') }}</a></li>
|
||||
<li>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="dropdown-item">{{ __('Log Out') }}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
{{-- Container Ikon Kanan --}}
|
||||
<div class="d-flex align-items-center ms-auto gap-3 me-3">
|
||||
|
||||
{{-- DROPDOWN Notifikasi --}}
|
||||
<div class="dropdown notification-dropdown">
|
||||
<a href="#" class="nav-link" id="notificationDropdown" role="button" data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<i class="bi bi-bell fs-5 position-relative">
|
||||
@if ($unreadNotificationsCount > 0)
|
||||
<span
|
||||
class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
|
||||
<span class="visually-hidden">New alerts</span>
|
||||
</span>
|
||||
@endif
|
||||
</i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end border-0 shadow-lg" aria-labelledby="notificationDropdown">
|
||||
<div class="px-3 py-2 d-flex justify-content-between align-items-center">
|
||||
<h6 class="m-0 fw-bold text-dark">Notifikasi</h6>
|
||||
@if ($unreadNotificationsCount > 0)
|
||||
<span
|
||||
class="badge bg-primary-subtle text-primary-emphasis rounded-pill">{{ $unreadNotificationsCount }}
|
||||
Baru</span>
|
||||
@endif
|
||||
</div>
|
||||
<hr class="my-1">
|
||||
<div class="notification-wrapper overflow-auto px-2">
|
||||
@forelse($notifikasi->take(5) as $item)
|
||||
<a href="#"
|
||||
class="notification-item my-1 rounded-pill dropdown-item @if (!$item['read']) unread @endif">
|
||||
<div class="notification-icon bg-{{ $item['color'] }}-subtle">
|
||||
<i class="bi {{ $item['icon'] }} text-{{ $item['color'] }}-emphasis"></i>
|
||||
</div>
|
||||
<div class="notification-content">
|
||||
<p class="mb-0 text-truncate" style="max-width: 250px;">{{ $item['title'] }}</p>
|
||||
<small>{{ $item['time'] }}</small>
|
||||
</div>
|
||||
@if (!$item['read'])
|
||||
<div class="unread-dot"></div>
|
||||
@endif
|
||||
</a>
|
||||
@empty
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-bell-slash fs-2 text-muted"></i>
|
||||
<p class="text-muted small mt-2 mb-0">Tidak ada notifikasi baru.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
@if (count($notifikasi) > 0)
|
||||
<hr class="mt-1 mb-0">
|
||||
<div class="text-center py-2">
|
||||
<a href="#" class="fw-bold small dropdown-item justify-content-center"
|
||||
data-bs-toggle="modal" data-bs-target="#semuaNotifikasiModal">
|
||||
Lihat Semua
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Dropdown Profile --}}
|
||||
<div class="dropdown">
|
||||
<a href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<img src="{{ Auth::user()->avatar ?? 'https://ui-avatars.com/api/?name=' . urlencode(Auth::user()->name) . '&background=random' }}"
|
||||
alt="Avatar" class="profile-avatar">
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow-lg border-0" aria-labelledby="userDropdown">
|
||||
<li>
|
||||
<div class="px-3 py-2">
|
||||
<h6 class="mb-0 fw-bold">{{ Auth::user()->name }}</h6>
|
||||
<small class="text-muted">{{ Auth::user()->email }}</small>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ route('profile.index') }}">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<span>{{ __('Profile') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="dropdown-item text-danger">
|
||||
<i class="bi bi-box-arrow-right"></i>
|
||||
<span>{{ __('Log Out') }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
<aside id="sidebar" class="sidebar bg-white shadow-sm">
|
||||
<div class="sidebar-header d-flex justify-content-between align-items-center px-3 py-3 py-md-2 border-bottom">
|
||||
<a href="{{ route('dashboard') }}" class="d-flex align-items-center text-decoration-none" style="gap: 0.75rem;">
|
||||
<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">
|
||||
<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;">
|
||||
<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="vr bg-primary sidebar-title" style="width: 2px;"></div>
|
||||
<img src="{{ asset('images/logo/name.svg') }}" alt="Perpus" style="height: 32px;"
|
||||
<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;"
|
||||
class="sidebar-title">
|
||||
</div>
|
||||
</a>
|
||||
|
|
@ -14,58 +15,84 @@ class="sidebar-title">
|
|||
<button type="button" class="btn-close d-lg-none" id="closeSidebarMobile"></button>
|
||||
</div>
|
||||
|
||||
<ul class="nav flex-column px-2 mt-3">
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('dashboard') }}" class="nav-link {{ request()->routeIs('dashboard') ? 'active' : '' }}">
|
||||
<i class="bi bi-grid-fill"></i>
|
||||
<span class="nav-text ms-2">Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
{{-- <li class="nav-item">
|
||||
<a class="nav-link collapsed" data-bs-toggle="collapse" href="#katalog-collapse" role="button" aria-expanded="false" aria-controls="katalog-collapse">
|
||||
<i class="bi bi-book me-2"></i>
|
||||
<span class="nav-text">Katalog Buku</span>
|
||||
<i class="bi bi-chevron-down ms-auto"></i>
|
||||
</a>
|
||||
<div class="collapse {{ request()->routeIs('katalog.index') ? 'show' : '' }}" id="katalog-collapse">
|
||||
<ul class="nav flex-column ms-3">
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('katalog.index', ['tipe' => 'online']) }}" class="nav-link {{ request('tipe') == 'online' ? 'active' : '' }}">
|
||||
<span class="nav-text">Baca Buku Online</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('katalog.index', ['tipe' => 'offline']) }}" class="nav-link {{ request('tipe') == 'offline' ? 'active' : '' }}">
|
||||
<span class="nav-text">Pinjam Buku Offline</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li> --}}
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('katalog') }}" class="nav-link {{ request()->routeIs('katalog') ? 'active' : '' }}">
|
||||
<i class="bi bi-book me-2"></i>
|
||||
<span class="nav-text ms-2">Katalog</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="#" class="nav-link">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
<span class="nav-text ms-2">Riwayat</span>
|
||||
</a>
|
||||
</li>
|
||||
<ul class="nav flex-column px-2 mt-2">
|
||||
|
||||
@if (Auth::user()->role == 'penjaga perpus')
|
||||
{{-- Menu untuk Penjaga Perpus --}}
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="nav-link {{ request()->routeIs('admin.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('admin.buku.index') }}"
|
||||
class="nav-link {{ request()->routeIs('admin.buku.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-book-fill"></i><span class="nav-text ms-2">Manajemen Buku</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('admin.pengumuman.index') }}"
|
||||
class="nav-link {{ request()->routeIs('admin.pengumuman.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-megaphone-fill"></i><span class="nav-text ms-2">Pengumuman</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('admin.pengguna.index') }}"
|
||||
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>
|
||||
@else
|
||||
{{-- Menu untuk Siswa --}}
|
||||
<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 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 {{ request()->routeIs('riwayat.offline') ? 'active' : '' }}"
|
||||
href="{{ route('riwayat.offline') }}">
|
||||
<span class="nav-text">Peminjaman Offline</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ request()->routeIs('riwayat.online') ? 'active' : '' }}"
|
||||
href="{{ route('riwayat.online') }}">
|
||||
<span class="nav-text">Baca Online</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</aside>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="fw-bold m-0">Informasi Peminjam</h5>
|
||||
<a href="#" class="btn btn-sm btn-outline-secondary ">Edit Profil</a>
|
||||
<a href="{{ route('profile.edit') }}" class="btn btn-sm btn-outline-secondary ">Edit Profil</a>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-3">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
<div class="col-lg-8">
|
||||
<div class="card border-0 mb-4">
|
||||
<div class="card-body p-4">
|
||||
{{-- Section Header Profil --}}
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://ui-avatars.com/api/?name={{ urlencode($user->name) }}&background=435ebe&color=fff&size=80&rounded=true"
|
||||
alt="Foto Profil" class="rounded-circle flex-shrink-0">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
@section('page-title', $pageTitle)
|
||||
<x-app-layout>
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0 fw-bold">{{ $pageTitle }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">NO</th>
|
||||
<th scope="col">ID PEMINJAMAN</th>
|
||||
<th scope="col">JUDUL BUKU</th>
|
||||
<th scope="col">TANGGAL PINJAM</th>
|
||||
<th scope="col">TANGGAL KEMBALI</th>
|
||||
<th scope="col">STATUS</th>
|
||||
<th scope="col">AKSI</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php $counter = 1; @endphp
|
||||
@forelse ($riwayatOffline as $transaksi)
|
||||
@foreach ($transaksi['books'] as $buku)
|
||||
<tr>
|
||||
<td>{{ $counter++ }}</td>
|
||||
<td>{{ $transaksi['id_peminjaman'] }}</td>
|
||||
<td>{{ $buku['judul'] }}</td>
|
||||
<td>{{ $transaksi['tanggal_pinjam'] }}</td>
|
||||
<td>{{ $transaksi['tanggal_kembali'] }}</td>
|
||||
<td>
|
||||
@if ($transaksi['status'] == 'Dikembalikan')
|
||||
<span
|
||||
class="badge rounded-pill bg-success">{{ $transaksi['status'] }}</span>
|
||||
@else
|
||||
<span
|
||||
class="badge rounded-pill bg-warning text-dark">{{ $transaksi['status'] }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm text-white" data-bs-toggle="modal"
|
||||
data-bs-target="#detailModal" data-transaksi-id="{{ $transaksi['id'] }}"
|
||||
data-buku-id="{{ $buku['id'] }}">
|
||||
Detail
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">Tidak ada riwayat peminjaman.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- MODAL DETAIL RIWAYAT --}}
|
||||
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content border-0 shadow-lg">
|
||||
<div class="modal-header bg-light">
|
||||
<h5 class="modal-title fw-bold" id="detailModalLabel"><i
|
||||
class="bi bi-book-half me-2 text-primary"></i>Detail Riwayat</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div id="modal-content-placeholder" class="text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
const riwayatOfflineData = @json($riwayatOffline);
|
||||
const detailModal = document.getElementById('detailModal');
|
||||
|
||||
detailModal.addEventListener('show.bs.modal', event => {
|
||||
const button = event.relatedTarget;
|
||||
const transaksiId = button.getAttribute('data-transaksi-id');
|
||||
const bukuId = button.getAttribute('data-buku-id');
|
||||
const modalBody = detailModal.querySelector('#modal-content-placeholder');
|
||||
const modalTitle = detailModal.querySelector('#detailModalLabel');
|
||||
|
||||
const transaksiItem = riwayatOfflineData.find(item => item.id == transaksiId);
|
||||
if (!transaksiItem) return;
|
||||
|
||||
const buku = transaksiItem.books.find(b => b.id == bukuId);
|
||||
if (!buku) return;
|
||||
|
||||
modalTitle.textContent = `Detail Buku`;
|
||||
|
||||
const keteranganHtml = buku.keterangan ?
|
||||
`
|
||||
<tr>
|
||||
<td class="fw-bold align-text-top">Keterangan</td>
|
||||
<td class="align-text-top">:</td>
|
||||
<td class="text-start">${buku.keterangan}</td>
|
||||
</tr>
|
||||
` : '';
|
||||
|
||||
const contentHTML = `
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ asset('${buku.cover}') }}" alt="Cover ${buku.judul}" class="img-fluid rounded shadow-sm" style="max-width: 150px;">
|
||||
</div>
|
||||
|
||||
<h4 class="fw-bold text-center">${buku.judul}</h4>
|
||||
<p class="text-muted text-center">${buku.deskripsi}</p>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<table class="table table-borderless table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="fw-bold" style="width: 35%;">ID Peminjaman</td>
|
||||
<td style="width: 5%;">:</td>
|
||||
<td class="text-start">${transaksiItem.id_peminjaman}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">Kategori Buku</td>
|
||||
<td>:</td>
|
||||
<td class="text-start"><span class="badge bg-primary-subtle text-primary-emphasis rounded-pill px-3 py-2">${buku.kategori}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">Tahun Terbit</td>
|
||||
<td>:</td>
|
||||
<td class="text-start">${buku.tahun}</td>
|
||||
</tr>
|
||||
${keteranganHtml}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
modalBody.innerHTML = contentHTML;
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-app-layout>
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
@section('page-title', $pageTitle)
|
||||
<x-app-layout>
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0 fw-bold">{{ $pageTitle }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">NO</th>
|
||||
<th scope="col">ID BACA</th>
|
||||
<th scope="col">JUDUL BUKU</th>
|
||||
<th scope="col">TANGGAL AKSES</th>
|
||||
<th scope="col">STATUS</th>
|
||||
<th scope="col">AKSI</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php $counter = 1; @endphp
|
||||
@forelse ($riwayatOnline as $transaksi)
|
||||
@foreach ($transaksi['books'] as $buku)
|
||||
<tr>
|
||||
<td>{{ $counter++ }}</td>
|
||||
<td>{{ $transaksi['id_baca'] }}</td>
|
||||
<td>{{ $buku['judul'] }}</td>
|
||||
<td>{{ $transaksi['tanggal_akses'] }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill bg-success">{{ $transaksi['status'] }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm text-white" data-bs-toggle="modal"
|
||||
data-bs-target="#detailModal" data-transaksi-id="{{ $transaksi['id'] }}"
|
||||
data-buku-id="{{ $buku['id'] }}">
|
||||
Detail
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">Tidak ada riwayat baca buku online.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- MODAL DETAIL RIWAYAT --}}
|
||||
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content border-0 shadow-lg">
|
||||
<div class="modal-header bg-light">
|
||||
<h5 class="modal-title fw-bold" id="detailModalLabel"><i
|
||||
class="bi bi-book-half me-2 text-primary"></i>Detail Riwayat</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div id="modal-content-placeholder" class="text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
const riwayatOnlineData = @json($riwayatOnline);
|
||||
const detailModal = document.getElementById('detailModal');
|
||||
|
||||
detailModal.addEventListener('show.bs.modal', event => {
|
||||
const button = event.relatedTarget;
|
||||
const transaksiId = button.getAttribute('data-transaksi-id');
|
||||
const bukuId = button.getAttribute('data-buku-id');
|
||||
const modalBody = detailModal.querySelector('#modal-content-placeholder');
|
||||
const modalTitle = detailModal.querySelector('#detailModalLabel');
|
||||
|
||||
const transaksiItem = riwayatOnlineData.find(item => item.id == transaksiId);
|
||||
if (!transaksiItem) return;
|
||||
|
||||
const buku = transaksiItem.books.find(b => b.id == bukuId);
|
||||
if (!buku) return;
|
||||
|
||||
modalTitle.textContent = `Detail Buku`;
|
||||
|
||||
const contentHTML = `
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ asset('${buku.cover}') }}" alt="Cover ${buku.judul}" class="img-fluid rounded shadow-sm" style="max-width: 150px;">
|
||||
</div>
|
||||
|
||||
<h4 class="fw-bold text-center">${buku.judul}</h4>
|
||||
<p class="text-muted text-center">${buku.deskripsi}</p>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<table class="table table-borderless table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="fw-bold" style="width: 35%;">ID Baca</td>
|
||||
<td style="width: 1%;">:</td>
|
||||
<td class="text-start">${transaksiItem.id_baca}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">Kategori Buku</td>
|
||||
<td>:</td>
|
||||
<td class="text-start"><span class="badge bg-primary-subtle text-primary-emphasis rounded-pill px-3 py-2">${buku.kategori}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">Tahun Terbit</td>
|
||||
<td>:</td>
|
||||
<td class="text-start">${buku.tahun}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
modalBody.innerHTML = contentHTML;
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-app-layout>
|
||||
|
|
@ -1,21 +1,27 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\BacaOnlineController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\DashboardController;
|
||||
use App\Http\Controllers\KatalogController;
|
||||
use App\Http\Controllers\PeminjamanController;
|
||||
use App\Http\Controllers\BacaOnlineController;
|
||||
use App\Http\Controllers\RiwayatController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
// Import Controller Admin
|
||||
use App\Http\Controllers\Admin\DashboardController as AdminDashboardController;
|
||||
use App\Http\Controllers\Admin\BookController as AdminBookController;
|
||||
use App\Http\Controllers\Admin\PengumumanController;
|
||||
use App\Http\Controllers\Admin\UserController as AdminUserController;
|
||||
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
});
|
||||
|
||||
// --- RUTE UNTUK PENGGUNA TERAUTENTIKASI (SISWA & PENJAGA) ---
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
|
||||
Route::get('/katalog', [KatalogController::class, 'index'])->name('katalog');
|
||||
Route::get('/katalog', [KatalogController::class, 'index'])->name('katalog.index');
|
||||
|
||||
// --- 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');
|
||||
|
|
@ -23,15 +29,17 @@
|
|||
Route::post('/store', [PeminjamanController::class, 'store'])->name('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}/ringkasan', [BacaOnlineController::class, 'ringkasan'])->name('ringkasan');
|
||||
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::get('/riwayat/offline', [RiwayatController::class, 'offlineIndex'])->name('riwayat.offline');
|
||||
Route::get('/riwayat/online', [RiwayatController::class, 'onlineIndex'])->name('riwayat.online');
|
||||
|
||||
Route::get('/secure-pdf/{id}', [BacaOnlineController::class, 'streamPdf'])->name('baca.stream_pdf');
|
||||
|
||||
// --- Manajemen Profil Pengguna ---
|
||||
|
|
@ -43,4 +51,13 @@
|
|||
});
|
||||
});
|
||||
|
||||
// --- GRUP RUTE KHUSUS UNTUK ADMIN / PENJAGA PERPUSTAKAAN ---
|
||||
Route::middleware(['auth', 'role:penjaga perpus'])->prefix('admin')->name('admin.')->group(function () {
|
||||
Route::get('/dashboard', [AdminDashboardController::class, 'index'])->name('dashboard');
|
||||
Route::get('/buku', [AdminBookController::class, 'index'])->name('buku.index');
|
||||
Route::get('/buku/tambah', [AdminBookController::class, 'create'])->name('buku.create');
|
||||
Route::get('/pengguna', [AdminUserController::class, 'index'])->name('pengguna.index');
|
||||
Route::get('/pengumuman', [PengumumanController::class, 'index'])->name('pengumuman.index'); // <-- RUTE BARU
|
||||
});
|
||||
|
||||
require __DIR__ . '/auth.php';
|
||||
Loading…
Reference in New Issue