diff --git a/app/Http/Controllers/Admin/BookController.php b/app/Http/Controllers/Admin/BookController.php new file mode 100644 index 0000000..685e06f --- /dev/null +++ b/app/Http/Controllers/Admin/BookController.php @@ -0,0 +1,39 @@ +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' + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php new file mode 100644 index 0000000..fa10625 --- /dev/null +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -0,0 +1,23 @@ + 'Dashboard Admin', + 'user' => auth()->user(), + 'greeting' => 'Selamat Datang', + 'stats' => DummyDataService::getAdminDashboardStats(), + 'statistikBulanan' => DummyDataService::getStatistikPeminjamanAdmin(), + 'komposisiBuku' => DummyDataService::getKomposisiBukuAdmin(), + 'pengumuman' => DummyDataService::getPengumuman(), + 'aktivitasTerakhir' => DummyDataService::getAktivitasTerakhir(), + ]); + } +} diff --git a/app/Http/Controllers/Admin/PengumumanController.php b/app/Http/Controllers/Admin/PengumumanController.php new file mode 100644 index 0000000..8ceca56 --- /dev/null +++ b/app/Http/Controllers/Admin/PengumumanController.php @@ -0,0 +1,19 @@ + 'Manajemen Pengumuman', + 'semuaPengumuman' => $semuaPengumuman, + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..944eb9c --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,15 @@ + 'Manajemen Pengguna', + 'semuaSiswa' => $semuaSiswa + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index 0739e2e..3a959cc 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -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'); + } } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/RiwayatController.php b/app/Http/Controllers/RiwayatController.php new file mode 100644 index 0000000..472cb4e --- /dev/null +++ b/app/Http/Controllers/RiwayatController.php @@ -0,0 +1,35 @@ + '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, + ]); + } +} \ No newline at end of file diff --git a/app/Http/Middleware/CheckRole.php b/app/Http/Middleware/CheckRole.php new file mode 100644 index 0000000..9db1672 --- /dev/null +++ b/app/Http/Middleware/CheckRole.php @@ -0,0 +1,35 @@ +role == $role) { + return $next($request); + } + } + + // Jika role tidak cocok, tolak akses + abort(403, 'AKSES DITOLAK: Anda tidak memiliki hak untuk mengakses halaman ini.'); + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e52e3fa..ec5adfd 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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')); + }); } } -} diff --git a/app/Services/DummyDataService.php b/app/Services/DummyDataService.php index cd18a84..ffce8c1 100644 --- a/app/Services/DummyDataService.php +++ b/app/Services/DummyDataService.php @@ -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], + ]; + } } diff --git a/bootstrap/app.php b/bootstrap/app.php index a240aa5..9bc6f90 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -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(); \ No newline at end of file diff --git a/resources/js/app.js b/resources/js/app.js index 396c712..aa5c306 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -6,4 +6,6 @@ window.bootstrap = bootstrap; import Alpine from 'alpinejs'; window.Alpine = Alpine; -Alpine.start(); \ No newline at end of file +document.addEventListener('DOMContentLoaded', () => { + Alpine.start(); +}); \ No newline at end of file diff --git a/resources/scss/_variables.scss b/resources/scss/_variables.scss index d46ee7c..78b2707 100644 --- a/resources/scss/_variables.scss +++ b/resources/scss/_variables.scss @@ -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; -} \ No newline at end of file +} diff --git a/resources/scss/app.scss b/resources/scss/app.scss index 4e502dd..1b0f6a3 100644 --- a/resources/scss/app.scss +++ b/resources/scss/app.scss @@ -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; -} \ No newline at end of file + 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; +} diff --git a/resources/views/admin/buku/create.blade.php b/resources/views/admin/buku/create.blade.php new file mode 100644 index 0000000..6072106 --- /dev/null +++ b/resources/views/admin/buku/create.blade.php @@ -0,0 +1,66 @@ + + @section('page-title', $pageTitle) + +
+
+ + + +
Formulir Tambah Buku Baru
+
+
+
+ {{-- Form ini tidak akan berfungsi karena tidak ada backend --}} +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
+ + +
+
+ + +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
+
+
diff --git a/resources/views/admin/buku/index.blade.php b/resources/views/admin/buku/index.blade.php new file mode 100644 index 0000000..1b1e78a --- /dev/null +++ b/resources/views/admin/buku/index.blade.php @@ -0,0 +1,171 @@ + + @section('page-title', $pageTitle) + +
+
+
Daftar Buku
+ + Tambah Buku + +
+
+ + +
+ {{-- TAB UNTUK BUKU OFFLINE --}} +
+
+ + + + + + @forelse($bukuOffline as $buku) + + + + + + + + + @empty + + @endforelse + +
NoCoverJudulPenulisStatusAksi
{{ $loop->iteration }}{{ $buku['judul'] }}{{ $buku['judul'] }}{{ $buku['penulis'] }} + @if($buku['status'] == 'Tersedia') + Tersedia + @else + Dipinjam + @endif + + +
Tidak ada data buku offline.
+
+
+ + {{-- TAB UNTUK BUKU ONLINE --}} +
+
+ + + + + + @forelse($bukuOnline as $buku) + + + + + + + + + @empty + + @endforelse + +
NoCoverJudulPenulisFile PDFAksi
{{ $loop->iteration }}{{ $buku['judul'] }}{{ $buku['judul'] }}{{ $buku['penulis'] }}{{ $buku['file_pdf'] ?? 'N/A' }} + +
Tidak ada data buku online.
+
+
+
+
+
+ + + + @push('scripts') + + @endpush +
\ No newline at end of file diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php new file mode 100644 index 0000000..ddf6fe6 --- /dev/null +++ b/resources/views/admin/dashboard.blade.php @@ -0,0 +1,172 @@ + + @section('page-title', 'Dashboard Admin') + +
+

Selamat Datang, {{ $user->nama_lengkap }}!

+

Berikut adalah ringkasan aktivitas perpustakaan hari ini.

+
+ + {{-- Kartu Statistik --}} +
+ @foreach ($stats as $stat) +
+
+
+
+
+
{{ $stat['label'] }}
+

{{ $stat['value'] }}

+
+
+
+ +
+
+
+
+
+
+ @endforeach +
+ +
+
+
+
+
Total Peminjaman per Bulan
+
+
+ +
+
+
+
+
+
+
Komposisi Status Buku
+
+
+
+ +
+
+
+
+ Tersedia + Dipinjam +
+
+
+
+
+ +
+
+
+
+
Pengumuman
+ + Kelola Semua + +
+
+ @forelse(collect($pengumuman)->take(2) as $item) +
+ +
+
{{ $item['title'] }}!
+
{{ $item['content'] }}
+
+
+ @empty +

Tidak ada pengumuman baru.

+ @endforelse +
+
+
+
+
+
+
Aktivitas Peminjaman Terkini
+
+
+
+ + + @forelse($aktivitasTerakhir as $aktivitas) + + + + + @empty + + @endforelse + +
+
{{ $aktivitas['nama'] }}
+
{{ $aktivitas['tipe'] }} buku "{{ $aktivitas['judul_buku'] }}"
+
+ @if($aktivitas['status'] == 'Dikembalikan' || $aktivitas['status'] == 'Selesai') + {{$aktivitas['status']}} + @else + {{$aktivitas['status']}} + @endif +
{{ $aktivitas['waktu'] }}
+

Tidak ada aktivitas terkini.

+
+
+
+
+
+ + + @push('scripts') + + + @endpush +
\ No newline at end of file diff --git a/resources/views/admin/pengguna/index.blade.php b/resources/views/admin/pengguna/index.blade.php new file mode 100644 index 0000000..49e13d0 --- /dev/null +++ b/resources/views/admin/pengguna/index.blade.php @@ -0,0 +1,28 @@ + + @section('page-title', $pageTitle) +
+
Daftar Semua Pengguna
+
+
+ + + + + + @forelse($semuaSiswa as $siswa) + + + + + + + + @empty + + @endforelse + +
NoNama LengkapEmailRoleAksi
{{ $loop->iteration }}{{ $siswa['nama_lengkap'] }}{{ $siswa['email'] }}{{ Str::title($siswa['role']) }}
Tidak ada data pengguna.
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/pengumuman/index.blade.php b/resources/views/admin/pengumuman/index.blade.php new file mode 100644 index 0000000..7df3a68 --- /dev/null +++ b/resources/views/admin/pengumuman/index.blade.php @@ -0,0 +1,42 @@ + + @section('page-title', $pageTitle) +
+
+
Kelola Pengumuman
+ +
+
+
+ + + + + + + + + + + + @foreach ($semuaPengumuman as $item) + + + + + + + + @endforeach + +
NoTipeJudulIsiAksi
{{ $loop->iteration }}{{ Str::title($item['type']) }} + {{ $item['title'] }}{{ $item['content'] }} + + +
+
+
+
+
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index c04d08a..9e67040 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -6,102 +6,14 @@ {{ config('app.name', 'Perpus') }} - - @vite(['resources/scss/app.scss', 'resources/js/app.js']) - - @@ -120,6 +32,45 @@ + + + @stack('scripts') diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index b4be40b..ef37d57 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -1,28 +1,108 @@ \ No newline at end of file + diff --git a/resources/views/layouts/sidebar.blade.php b/resources/views/layouts/sidebar.blade.php index faa381c..edf91ec 100644 --- a/resources/views/layouts/sidebar.blade.php +++ b/resources/views/layouts/sidebar.blade.php @@ -1,11 +1,12 @@ - diff --git a/resources/views/peminjaman/form.blade.php b/resources/views/peminjaman/form.blade.php index 75872f5..1aad1df 100644 --- a/resources/views/peminjaman/form.blade.php +++ b/resources/views/peminjaman/form.blade.php @@ -17,7 +17,7 @@
Informasi Peminjam
- Edit Profil + Edit Profil
diff --git a/resources/views/profile/index.blade.php b/resources/views/profile/index.blade.php index 53f84a8..f13196b 100644 --- a/resources/views/profile/index.blade.php +++ b/resources/views/profile/index.blade.php @@ -9,7 +9,6 @@
- {{-- Section Header Profil --}}
Foto Profil diff --git a/resources/views/riwayat/offline.blade.php b/resources/views/riwayat/offline.blade.php new file mode 100644 index 0000000..cf494a8 --- /dev/null +++ b/resources/views/riwayat/offline.blade.php @@ -0,0 +1,145 @@ +@section('page-title', $pageTitle) + +
+
+
{{ $pageTitle }}
+
+
+
+ + + + + + + + + + + + + + @php $counter = 1; @endphp + @forelse ($riwayatOffline as $transaksi) + @foreach ($transaksi['books'] as $buku) + + + + + + + + + + @endforeach + @empty + + + + @endforelse + +
NOID PEMINJAMANJUDUL BUKUTANGGAL PINJAMTANGGAL KEMBALISTATUSAKSI
{{ $counter++ }}{{ $transaksi['id_peminjaman'] }}{{ $buku['judul'] }}{{ $transaksi['tanggal_pinjam'] }}{{ $transaksi['tanggal_kembali'] }} + @if ($transaksi['status'] == 'Dikembalikan') + {{ $transaksi['status'] }} + @else + {{ $transaksi['status'] }} + @endif + + +
Tidak ada riwayat peminjaman.
+
+
+
+ + {{-- MODAL DETAIL RIWAYAT --}} + + + @push('scripts') + + @endpush +
diff --git a/resources/views/riwayat/online.blade.php b/resources/views/riwayat/online.blade.php new file mode 100644 index 0000000..a373f20 --- /dev/null +++ b/resources/views/riwayat/online.blade.php @@ -0,0 +1,127 @@ +@section('page-title', $pageTitle) + +
+
+
{{ $pageTitle }}
+
+
+
+ + + + + + + + + + + + + @php $counter = 1; @endphp + @forelse ($riwayatOnline as $transaksi) + @foreach ($transaksi['books'] as $buku) + + + + + + + + + @endforeach + @empty + + + + @endforelse + +
NOID BACAJUDUL BUKUTANGGAL AKSESSTATUSAKSI
{{ $counter++ }}{{ $transaksi['id_baca'] }}{{ $buku['judul'] }}{{ $transaksi['tanggal_akses'] }} + {{ $transaksi['status'] }} + + +
Tidak ada riwayat baca buku online.
+
+
+
+ + {{-- MODAL DETAIL RIWAYAT --}} + + + @push('scripts') + + @endpush +
diff --git a/routes/web.php b/routes/web.php index ddcc054..d17272e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,21 +1,27 @@ 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'; \ No newline at end of file