From 172ac9e3b743b97bfba81b810d18e2d724e0b7b8 Mon Sep 17 00:00:00 2001 From: zhadaarsita Date: Wed, 24 Sep 2025 17:01:20 +0700 Subject: [PATCH] feat: Feature peminjaman offline --- app/Http/Controllers/DashboardController.php | 15 +- app/Http/Controllers/KatalogController.php | 1 - app/Http/Controllers/PeminjamanController.php | 89 ++++ app/Services/DummyDataService.php | 66 ++- public/js/dashboard-charts.js | 83 +++ resources/js/app.js | 9 +- resources/js/peminjaman-form.js | 188 +++++++ resources/scss/_variables.scss | 486 +++++++++--------- resources/scss/app.scss | 5 +- .../views/components/book-card.blade.php | 26 + resources/views/dashboard.blade.php | 183 ++----- resources/views/katalog.blade.php | 41 +- resources/views/layouts/app.blade.php | 11 +- resources/views/layouts/sidebar.blade.php | 5 +- resources/views/peminjaman/form.blade.php | 304 +++++++++++ resources/views/peminjaman/index.blade.php | 100 ++++ .../views/peminjaman/ringkasan.blade.php | 52 ++ routes/web.php | 6 + 18 files changed, 1232 insertions(+), 438 deletions(-) create mode 100644 app/Http/Controllers/PeminjamanController.php create mode 100644 public/js/dashboard-charts.js create mode 100644 resources/js/peminjaman-form.js create mode 100644 resources/views/components/book-card.blade.php create mode 100644 resources/views/peminjaman/form.blade.php create mode 100644 resources/views/peminjaman/index.blade.php create mode 100644 resources/views/peminjaman/ringkasan.blade.php diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 85866e4..9de1b68 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -19,6 +19,16 @@ public function index() $bukuPinjamOffline = DummyDataService::getBukuPinjamOffline($user); $bacaBukuOnline = DummyDataService::getBacaBukuOnline($user); + $hour = date('H'); + $greeting = "Selamat Pagi"; + if ($hour >= 12 && $hour < 15) { + $greeting = "Selamat Siang"; + } elseif ($hour >= 15 && $hour < 18) { + $greeting = "Selamat Sore"; + } elseif ($hour >= 18) { + $greeting = "Selamat Malam"; + } + return view('dashboard', compact( 'user', 'stats', @@ -27,7 +37,8 @@ public function index() 'progressMembaca', 'statistikBulanan', 'bukuPinjamOffline', - 'bacaBukuOnline' + 'bacaBukuOnline', + 'greeting', )); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/KatalogController.php b/app/Http/Controllers/KatalogController.php index 089a36c..3c4f8b6 100644 --- a/app/Http/Controllers/KatalogController.php +++ b/app/Http/Controllers/KatalogController.php @@ -25,7 +25,6 @@ public function index(Request $request, $tipe = null) 'semuaBuku' => $semuaBuku, 'filterOptions' => $filterOptions, 'input' => $filters, - 'tipe' => $tipe, ]); } } diff --git a/app/Http/Controllers/PeminjamanController.php b/app/Http/Controllers/PeminjamanController.php new file mode 100644 index 0000000..5992ea2 --- /dev/null +++ b/app/Http/Controllers/PeminjamanController.php @@ -0,0 +1,89 @@ +only(['search', 'kategori', 'tahun', 'penulis']); + $filters['tipe_akses'] = 'offline'; + $semuaBuku = DummyDataService::getKatalogBuku($filters); + $filterOptions = DummyDataService::getFilterOptions(); + + return view('peminjaman.index', [ + 'semuaBuku' => $semuaBuku, + 'filterOptions' => $filterOptions, + 'input' => $request->query(), + ]); + } + + /** + * Menampilkan halaman ringkasan buku sebelum meminjam. + */ + public function ringkasan($id) + { + // Untuk simulasi, kita anggap user sudah login + // $user = Auth::user(); + $user = DummyDataService::getAllSiswa()[0]; + + $buku = DummyDataService::getKatalogBuku()->firstWhere('id', $id); + + return view('peminjaman.ringkasan', compact('user', 'buku')); + } + + /** + * Menampilkan form peminjaman dengan semua buku offline untuk pilihan tambahan. + */ + public function form($id) + { + // $user = Auth::user(); + $user = DummyDataService::getAllSiswa()[0]; + + // Buku yang dipilih pertama kali + $buku = DummyDataService::getKatalogBuku()->firstWhere('id', $id); + + // Semua buku offline untuk pilihan tambahan + $filters = ['tipe_akses' => 'offline']; + $semuaBuku = DummyDataService::getKatalogBuku($filters); + + return view('peminjaman.form', compact('user', 'buku', 'semuaBuku')); + } + + /** + * Proses pengiriman form peminjaman multi buku + */ + public function store(Request $request) + { + // Validasi input + $request->validate([ + 'buku_ids' => 'required|array|min:1|max:3', + 'buku_ids.*' => 'integer|exists:books,id' // Sesuaikan dengan tabel yang ada + ]); + + // Logic untuk menyimpan peminjaman multiple books + $bukuIds = $request->input('buku_ids'); + + // Contoh logic penyimpanan: + foreach ($bukuIds as $bukuId) { + // Simpan ke database peminjaman + // Peminjaman::create([ + // 'user_id' => auth()->id(), + // 'book_id' => $bukuId, + // 'tanggal_pinjam' => now(), + // 'tanggal_kembali' => now()->addDays(7), + // 'status' => 'dipinjam' + // ]); + } + + return redirect()->route('dashboard') + ->with('success', 'Berhasil meminjam ' . count($bukuIds) . ' buku!'); + } +} \ No newline at end of file diff --git a/app/Services/DummyDataService.php b/app/Services/DummyDataService.php index 0bd8db8..f75a498 100644 --- a/app/Services/DummyDataService.php +++ b/app/Services/DummyDataService.php @@ -10,11 +10,13 @@ public static function getAllSiswa(): array [ 'id' => 1, 'nisn' => '1234567890', - 'nama_lengkap' => 'John Doe', - 'email' => 'johnskuy@smkn1perpus.sch.id', - 'nomor_hp' => '081234567890', + 'nama_lengkap' => 'Silvi Rahmawati', + 'email' => 'silvi.rahmawati@smkn1perpus.sch.id', + 'nomor_hp' => '08123456789', 'password' => 'password', 'role' => 'siswa', + 'kelas' => 'XII RPL', + 'golongan' => 'A', ], [ 'id' => 2, @@ -33,6 +35,8 @@ public static function getAllSiswa(): array 'nomor_hp' => '081998877665', 'password' => 'password', 'role' => 'siswa', + 'kelas' => 'XII RPL A', + 'golongan' => 'A', ], [ 'id' => 4, @@ -42,6 +46,8 @@ public static function getAllSiswa(): array 'nomor_hp' => '081556677889', 'password' => 'password', 'role' => 'siswa', + 'kelas' => 'XII RPL A', + 'golongan' => 'A', ], [ 'id' => 5, @@ -152,7 +158,7 @@ private static function getAllBooks() 'tahun' => 2023, 'status' => 'Tersedia', 'is_new' => false, - 'tipe_akses' => 'online', + 'tipe_akses' => ['online', 'offline'], 'progress' => 100, 'user_id' => [3, 1], ], @@ -206,7 +212,7 @@ private static function getAllBooks() 'is_new' => true, 'tipe_akses' => 'online', 'progress' => 40, - 'user_id' => [1,4,5], + 'user_id' => [1, 4, 5], ], [ 'id' => 7, @@ -219,9 +225,9 @@ private static function getAllBooks() 'is_new' => true, 'tipe_akses' => 'online', 'progress' => 0, - 'user_id' => [3,1] + 'user_id' => [3, 1] ], - [ + [ 'id' => 8, 'judul' => 'Buku Offline Tanpa Peminjam', 'penulis' => 'Penulis Misteri', @@ -289,24 +295,38 @@ public static function getBacaBukuOnline($user): array /** * Mengambil daftar buku untuk katalog dengan filter. */ - public static function getKatalogBuku(array $filters = []): \Illuminate\Support\Collection - { - $buku = self::getAllBooks(); + // app/Services/DummyDataService.php - $buku = $buku->when($filters['search'] ?? null, function ($query, $search) { - return $query->filter(fn($item) => str_contains(strtolower($item['judul']), strtolower($search))); - })->when($filters['kategori'] ?? null, function ($query, $kategori) { - return $query->where('kategori', $kategori); - })->when($filters['tahun'] ?? null, function ($query, $tahun) { - return $query->where('tahun', $tahun); - })->when($filters['penulis'] ?? null, function ($query, $penulis) { - return $query->where('penulis', $penulis); - })->when($filters['tipe'] ?? null, function ($query, $tipe) { - return $query->where('tipe_akses', $tipe); - }); +public static function getKatalogBuku(array $filters = []): \Illuminate\Support\Collection +{ + $buku = self::getAllBooks(); - return $buku; - } + $buku = $buku->when($filters['search'] ?? null, function ($query, $search) { + return $query->filter(fn($item) => str_contains(strtolower($item['judul']), strtolower($search))); + })->when($filters['kategori'] ?? null, function ($query, $kategori) { + return $query->where('kategori', $kategori); + })->when($filters['tahun'] ?? null, function ($query, $tahun) { + return $query->where('tahun', $tahun); + })->when($filters['penulis'] ?? null, function ($query, $penulis) { + return $query->where('penulis', $penulis); + }) + ->when($filters['tipe_akses'] ?? null, function ($query, $tipe) { + if ($tipe === 'offline') { + return $query->filter(function ($buku) { + return $buku['tipe_akses'] === 'offline' || (is_array($buku['tipe_akses']) && in_array('offline', $buku['tipe_akses'])); + }); + } + if ($tipe === 'online') { + return $query->filter(function ($buku) { + return $buku['tipe_akses'] === 'online' || (is_array($buku['tipe_akses']) && in_array('online', $buku['tipe_akses'])); + }); + } + return $query; + }) + ->sortByDesc('status'); + + return $buku; +} /** * Method baru untuk mengambil daftar unik untuk dropdown filter diff --git a/public/js/dashboard-charts.js b/public/js/dashboard-charts.js new file mode 100644 index 0000000..ba23c9c --- /dev/null +++ b/public/js/dashboard-charts.js @@ -0,0 +1,83 @@ +// public/js/dashboard-charts.js +document.addEventListener('DOMContentLoaded', function () { + // Ambil elemen canvas Bar Chart + const barChartEl = document.getElementById('barChart'); + if (barChartEl) { + const statistikData = JSON.parse(barChartEl.dataset.stats); + + new Chart(barChartEl.getContext('2d'), { + type: 'bar', + data: { + labels: statistikData.labels, + datasets: [{ + label: 'Buku Dibaca', + backgroundColor: '#435ebe', + data: statistikData.data, + borderRadius: 8 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true, + grid: { + color: 'rgba(0,0,0,0.05)', + }, + ticks: { + color: '#6c757d', + font: { + size: 12 + } + } + }, + x: { + grid: { + display: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 12 + } + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + + const donutChartEl = document.getElementById('donutChart'); + if (donutChartEl) { + const progressData = JSON.parse(donutChartEl.dataset.progress); + + new Chart(donutChartEl.getContext('2d'), { + type: 'doughnut', + data: { + labels: ['Telah Dibaca', 'Belum Dibaca'], + datasets: [{ + data: [progressData.selesai, progressData.sisa], + backgroundColor: ['#435ebe', '#e9ecef'], + borderColor: ['#ffffff'], + borderWidth: 3, + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false + } + }, + cutout: '75%' + } + }); + } +}); \ No newline at end of file diff --git a/resources/js/app.js b/resources/js/app.js index 9b3b3cb..396c712 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,8 +1,9 @@ import './bootstrap'; -import 'bootstrap'; +import './peminjaman-form.js'; +import * as bootstrap from 'bootstrap'; + +window.bootstrap = bootstrap; import Alpine from 'alpinejs'; - window.Alpine = Alpine; - -Alpine.start(); +Alpine.start(); \ No newline at end of file diff --git a/resources/js/peminjaman-form.js b/resources/js/peminjaman-form.js new file mode 100644 index 0000000..cda8ece --- /dev/null +++ b/resources/js/peminjaman-form.js @@ -0,0 +1,188 @@ +const formPeminjamanElement = document.getElementById('formPeminjaman'); + +if (formPeminjamanElement) { + // Ambil data yang sudah disimpan di elemen HTML + const semuaBuku = JSON.parse(formPeminjamanElement.dataset.semuaBuku); + const bukuAwal = JSON.parse(formPeminjamanElement.dataset.bukuAwal); + + let bukuTerpilih = [bukuAwal.id]; + const maxBuku = 3; + + document.addEventListener('DOMContentLoaded', function () { + updateCounter(); + updateSelectedBooks(); + updateCheckboxes(); + updateBtnTambahBuku(); + updateDaftarBuku(); + }); + + function updateCounter() { + document.getElementById('counterBuku').textContent = bukuTerpilih.length; + document.getElementById('sisaSlot').textContent = maxBuku - bukuTerpilih.length; + } + + function updateBtnTambahBuku() { + const btnTambah = document.getElementById('btnTambahBuku'); + if (bukuTerpilih.length >= maxBuku) { + btnTambah.classList.add('disabled'); + btnTambah.innerHTML = 'Maksimal Buku Tercapai'; + } else { + btnTambah.classList.remove('disabled'); + btnTambah.innerHTML = 'Tambah Buku'; + } + } + + function updateSelectedBooks() { + const container = document.getElementById('selectedBooks'); + const selected = semuaBuku.filter(book => bukuTerpilih.includes(book.id)); + container.innerHTML = selected.map(book => `
  • ${book.judul}
  • `).join(''); + } + + function updateCheckboxes() { + document.querySelectorAll('.book-checkbox').forEach(checkbox => { + const bookId = parseInt(checkbox.value); + checkbox.checked = bukuTerpilih.includes(bookId); + + if (bukuTerpilih.length >= maxBuku && !bukuTerpilih.includes(bookId)) { + checkbox.disabled = true; + } else if (bookId !== bukuAwal.id) { + checkbox.disabled = false; + } + }); + } + + window.toggleBookSelection = function (bookId) { + if (bookId === bukuAwal.id) return; + + const checkbox = document.getElementById('book' + bookId); + if (!checkbox.disabled) { + checkbox.checked = !checkbox.checked; + handleCheckboxChange(checkbox); + } + } + + function handleCheckboxChange(checkbox) { + const bookId = parseInt(checkbox.value); + if (checkbox.checked) { + if (bukuTerpilih.length < maxBuku && !bukuTerpilih.includes(bookId)) { + bukuTerpilih.push(bookId); + } else { + checkbox.checked = false; + return; + } + } else { + bukuTerpilih = bukuTerpilih.filter(id => id !== bookId); + } + + updateCounter(); + updateSelectedBooks(); + updateCheckboxes(); + updateBtnTambahBuku(); + } + + document.addEventListener('change', function (e) { + if (e.target.classList.contains('book-checkbox')) { + if (e.target.value != bukuAwal.id) { + handleCheckboxChange(e.target); + } + } + }); + + document.getElementById('searchBuku')?.addEventListener('input', function (e) { + const searchTerm = e.target.value.toLowerCase(); + document.querySelectorAll('.book-option').forEach(option => { + const title = option.getAttribute('data-book-title'); + const author = option.getAttribute('data-book-author'); + + if (title.includes(searchTerm) || author.includes(searchTerm)) { + option.style.display = 'block'; + } else { + option.style.display = 'none'; + } + }); + }); + + function updateDaftarBuku() { + const container = document.getElementById('daftarBukuPinjam'); + const hiddenInputs = document.getElementById('hiddenInputs'); + const selected = semuaBuku.filter(book => bukuTerpilih.includes(book.id)); + + container.innerHTML = selected.map((book) => ` +
    +
    + + Cover + +
    +
    ${book.judul}
    +

    ${book.penulis}

    + ${book.kategori} +
    + +
    + ${book.id === bukuAwal.id + ? 'Buku Utama' + : 'Tambahan'} + + ${book.id !== bukuAwal.id + ? `` + : ''} +
    +
    +
    + `).join(''); + + + hiddenInputs.innerHTML = bukuTerpilih.map(id => + `` + ).join(''); + } + + window.removeBuku = function (bookId) { + if (bookId !== bukuAwal.id) { + bukuTerpilih = bukuTerpilih.filter(id => id !== bookId); + updateDaftarBuku(); + updateCounter(); + updateSelectedBooks(); + updateCheckboxes(); + updateBtnTambahBuku(); + } + } + + window.konfirmasiPilihanBuku = function () { + updateDaftarBuku(); + const modal = bootstrap.Modal.getInstance(document.getElementById('pilihBukuModal')); + modal.hide(); + } + + document.getElementById('konfirmasiModal')?.addEventListener('show.bs.modal', function () { + const selected = semuaBuku.filter(book => bukuTerpilih.includes(book.id)); + const ringkasan = selected.map((book, index) => + `
    + ${index + 1}. ${book.judul} + ${book.penulis} +
    ` + ).join(''); + document.getElementById('ringkasanBuku').innerHTML = ringkasan; + }); + + window.kirimForm = function () { + document.getElementById('formPeminjaman').submit(); + } + + document.getElementById('pilihBukuModal')?.addEventListener('hidden.bs.modal', function () { + const searchInput = document.getElementById('searchBuku'); + if (searchInput) { + searchInput.value = ''; + document.querySelectorAll('.book-option').forEach(option => { + option.style.display = 'block'; + }); + } + }); +} \ No newline at end of file diff --git a/resources/scss/_variables.scss b/resources/scss/_variables.scss index 836e6c2..5ab1dfe 100644 --- a/resources/scss/_variables.scss +++ b/resources/scss/_variables.scss @@ -1,128 +1,139 @@ -$primary: #435ebe; -$secondary: #6c757d; -$success: #198754; -$info: #0dcaf0; -$warning: #ffc107; -$danger: #dc3545; -$light: #f4f7f8; -$dark: #212529; +// =================================== +// VARIABLES & MAPS +// =================================== -// Derived colors untuk background light -$primary-light: rgba($primary, 0.1); -$success-light: rgba($success, 0.1); -$warning-light: rgba($warning, 0.1); -$danger-light: rgba($danger, 0.1); -$info-light: rgba($info, 0.1); -$secondary-light: rgba($secondary, 0.1); +// 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%), +); -// Border radius +// Gray Colors Map +$grays: ( + "light": #f4f7f8, + "dark": #4c5053 +); + +// Spacing & Sizing $border-radius: 0.5rem; -$card-box-shadow: 0 0 1.25rem rgba(33,37,41,.05); - $border-radius-sm: 0.25rem; $border-radius-lg: 1rem; -$shadow-sm: 0 2px 4px rgba(0,0,0,0.08); -$shadow-md: 0 4px 8px rgba(0,0,0,0.12); -$shadow-lg: 0 8px 20px rgba(0,0,0,0.15); + +// Shadows & Transitions +$card-box-shadow: 0 0 1.25rem rgba(33, 37, 41, 0.05); +$shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.08); +$shadow-md: 0 4px 8px rgba(0, 0, 0, 0.12); +$shadow-lg: 0 8px 20px rgba(0, 0, 0, 0.15); $transition: all 0.3s ease; // =================================== -// CARD COMPONENTS +// UTILITIES (GENERATED FROM MAPS) // =================================== - -.card { - box-shadow: $card-box-shadow; - - .card-header { - &.bg-white { - background-color: #fff !important; - border-bottom: 1px solid rgba($secondary, 0.125); - } +@each $color, $value in $theme-colors { + .bg-#{$color}-light { + background-color: rgba($value, 0.25) !important; + } + .bg-#{$color}-soft { + background-color: rgba($value, 0.25); + color: $value; + } + .alert-#{$color} { + background-color: rgba($value, 0.2); + color: darken($value, 25%); + border-color: rgba($value, 0.3); } } // =================================== -// ICON COMPONENTS +// BASE COMPONENTS // =================================== -.icon-circle { - width: 50px; - height: 50px; +.card { + box-shadow: $card-box-shadow; + .card-header { + background-color: #fff; + border-bottom: 1px solid rgba(map-get($grays, "dark"), 0.1); // DIPERBAIKI + } +} + +.alert { border-radius: $border-radius; + box-shadow: $shadow-sm; + .alert-icon { + width: 24px; + display: flex; + align-items: center; + justify-content: center; + } +} + +.modal { + .modal-content { + border: none; + border-radius: $border-radius-lg; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); + .modal-header, + .modal-footer { + border-color: rgba(map-get($grays, "dark"), 0.1); + padding: 1.5rem; + } + .modal-title { + color: map-get($grays, "dark"); // DIPERBAIKI + font-weight: 700; + } + } +} + +.btn{ + padding: 0.5rem 1.5rem; + font-weight: 500; + &.btn-sm { + padding: 0.375rem 1.25rem; + font-size: 0.875rem; + } +} + +.badge.rounded-pill { + font-weight: 500; + padding: 0.5rem 1rem; +} + +// =================================== +// CUSTOM COMPONENTS +// =================================== + +.icon-circle, .icon-box { display: flex; align-items: center; justify-content: center; transition: $transition; } -.section-icon, -.chart-icon, -.notification-icon, -.activity-icon { +.icon-circle { + width: 50px; + height: 50px; + border-radius: 50%; +} + +.icon-box { width: 40px; height: 40px; border-radius: 10px; - background-color: $primary-light; - display: flex; - align-items: center; - justify-content: center; - transition: $transition; - i { font-size: 1.2rem; } } -// =================================== -// BACKGROUND COLORS -// =================================== - -// Light backgrounds for icon circles -.bg-primary-light { background-color: $primary-light; } -.bg-success-light { background-color: $success-light; } -.bg-warning-light { background-color: $warning-light; } -.bg-danger-light { background-color: $danger-light; } -.bg-info-light { background-color: $info-light; } -.bg-secondary-light { background-color: $secondary-light; } - -// Soft badge colors -.bg-primary-soft { - background-color: $primary-light; - color: $primary; -} -.bg-success-soft { - background-color: $success-light; - color: $success; -} -.bg-warning-soft { - background-color: $warning-light; - color: $warning; -} -.bg-danger-soft { - background-color: $danger-light; - color: $danger; -} -.bg-info-soft { - background-color: $info-light; - color: $info; -} -.bg-secondary-soft { - background-color: $secondary-light; - color: $secondary; -} - -// =================================== -// BOOK COMPONENTS -// =================================== - .book-card { transition: $transition; box-shadow: $card-box-shadow; - &:hover { transform: translateY(-2px); - box-shadow: $shadow-md !important; - + box-shadow: $shadow-md; .book-cover { transform: scale(1.03); } @@ -132,180 +143,169 @@ $transition: all 0.3s ease; .book-cover { transition: $transition; border-radius: $border-radius-sm; - &-container { - background: linear-gradient(135deg, rgba($light, 0.5) 0%, rgba($light, 0.8) 100%); + background: linear-gradient( + 135deg, + rgba(map-get($grays, "light"), 0.5) 0%, // DIPERBAIKI + rgba(map-get($grays, "light"), 0.8) 100% // DIPERBAIKI + ); border-radius: $border-radius-sm 0 0 $border-radius-sm; } } - -// =================================== -// ALERT COMPONENTS -// =================================== - -.alert { - border-radius: $border-radius; - box-shadow: $shadow-sm; - - .alert-icon { - width: 24px; - display: flex; - align-items: center; - justify-content: center; - } - - &.alert-primary { - background-color: rgba($primary, 0.1); - color: darken($primary, 10%); - } - - &.alert-success { - background-color: rgba($success, 0.1); - color: darken($success, 10%); - } - - &.alert-warning { - background-color: rgba($warning, 0.1); - color: darken($warning, 20%); - } - - &.alert-danger { - background-color: rgba($danger, 0.1); - color: darken($danger, 10%); - } - - &.alert-info { - background-color: rgba($info, 0.1); - color: darken($info, 10%); - } -} - -// =================================== -// SECTION COMPONENTS -// =================================== - .section-header { padding-bottom: 15px; - border-bottom: 2px solid $light; - + border-bottom: 2px solid map-get($grays, "light"); // DIPERBAIKI h5 { - color: $dark; + color: map-get($grays, "dark"); // DIPERBAIKI font-weight: 700; } } -// =================================== -// MODAL COMPONENTS -// =================================== - -.modal { - .modal-content { - border: none; - border-radius: $border-radius-lg; - box-shadow: 0 20px 40px rgba(0,0,0,0.15); - - .modal-header { - border-bottom: 1px solid rgba($secondary, 0.125); - padding: 1.5rem; - - .modal-title { - color: $dark; - font-weight: 700; - } - } - - .modal-footer { - border-top: 1px solid rgba($secondary, 0.125); - padding: 1.5rem; - } - } -} - -// =================================== -// UTILITY CLASSES -// =================================== - -.line-clamp-2 { - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - line-height: 1.4; -} - -.line-clamp-3 { - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; - line-height: 1.4; -} - -// Rounded buttons -.btn { - &.rounded-pill { - border-radius: 50px; - padding: 0.5rem 1.5rem; - font-weight: 500; - - &.btn-sm { - padding: 0.375rem 1.25rem; - font-size: 0.875rem; - } - } -} - -// Badge -.badge { - &.rounded-pill { - font-weight: 500; - padding: 0.5rem 1rem; - } -} - -// =================================== -// SCROLLBAR CUSTOMIZATION -// =================================== - -.modal-dialog-scrollable .modal-body { - &::-webkit-scrollbar { - width: 6px; - } - - &::-webkit-scrollbar-track { - background: #f1f1f1; - border-radius: 3px; - } - - &::-webkit-scrollbar-thumb { - background: #c1c1c1; - border-radius: 3px; - - &:hover { - background: #a8a8a8; - } - } -} - -// =================================== -// EMPTY STATES -// =================================== - .empty-state { text-align: center; padding: 3rem 1rem; - i { font-size: 4rem; - color: $secondary; + color: map-get($grays, "dark"); // DIPERBAIKI margin-bottom: 1rem; - opacity: 0.5; + opacity: 0.25; } - p { - color: $secondary; + color: map-get($grays, "dark"); // DIPERBAIKI + opacity: 0.7; font-size: 1rem; margin: 0; } +} + +.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%)}; +} + +// =================================== +// UTILITIES & CUSTOMIZATIONS +// =================================== + +.line-clamp-2, +.line-clamp-3 { + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + line-height: 1.4; +} +.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"); // DIPERBAIKI + border-radius: 3px; +} +::-webkit-scrollbar-thumb { + background: rgba(map-get($grays, "dark"), 0.25); // DIPERBAIKI + border-radius: 3px; + &:hover { + background: rgba(map-get($grays, "dark"), 0.4); // DIPERBAIKI + } +} + +// =================================== +// Style untuk kartu pemilihan buku di dalam modal +// =================================== + +.card-book-select { + cursor: pointer; + position: relative; + transition: $transition; + + &:hover { + transform: translateY(-5px); + box-shadow: $shadow-md; + } + + .card-select-overlay { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + background-color: rgba(#435ebe, 0.7); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: $transition; + border-radius: $border-radius-sm; + + i { + 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; + } + } + + &.disabled { + cursor: not-allowed; + opacity: 0.6; + } +} + + +// =================================== +// Styling untuk Form Peminjaman +// =================================== + +.form-book-cover { + width: 60px; + height: 80px; + object-fit: cover; +} + +.book-item { + background-color: map-get($grays, "light"); + border-left: 4px solid map-get($theme-colors, "primary"); +} + +// Styling untuk kartu buku di dalam modal pemilihan +.book-card { + cursor: pointer; + transition: $transition; + border: 2px solid transparent; + + &:hover { + border-color: map-get($theme-colors, "primary"); + box-shadow: $shadow-md; + } + + // Style khusus ketika checkbox di dalamnya terpilih + &:has(.book-checkbox:checked) { + border-color: map-get($theme-colors, "success"); + background-color: rgba(map-get($theme-colors, "success"), 0.05); + } +} +.remove-book { + padding: 2px 6px; + font-size: 12px; +} + +.btn.disabled { + pointer-events: none; + 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 540a669..4e502dd 100644 --- a/resources/scss/app.scss +++ b/resources/scss/app.scss @@ -1,5 +1,6 @@ -@import 'variables'; -@import 'bootstrap/scss/bootstrap'; +@import "bootstrap/scss/functions"; +@import "variables"; +@import "bootstrap/scss/bootstrap"; body { background-color: $light; diff --git a/resources/views/components/book-card.blade.php b/resources/views/components/book-card.blade.php new file mode 100644 index 0000000..d06e037 --- /dev/null +++ b/resources/views/components/book-card.blade.php @@ -0,0 +1,26 @@ +@props(['buku']) + +
    +
    +
    +
    + Cover {{ $buku['judul'] }} +
    +
    +
    +
    +
    +
    + {{ $buku['judul'] }} +
    +

    + {{ $buku['penulis'] }} +

    +
    +
    + {{ $slot }} +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index bd57d9c..45c89dc 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -7,15 +7,15 @@ --}}
    -

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

    +

    {{ $greeting }}, {{ $user->nama_lengkap }} !

    Apa yang ingin kamu baca hari ini?

    @foreach ($stats as $stat)
    -
    -
    +
    +
    {{ $stat['label'] }}
    @@ -46,7 +46,8 @@
    - + [], 'data' => []])'> +
    @@ -62,7 +63,8 @@
    - + 0, 'sisa' => 0])'> +
    @@ -174,45 +176,22 @@ class="badge bg-{{ $item['type'] }}-soft text-{{ $item['type'] }} rounded-pill p
    Buku Pinjam Offline
    -
    @forelse($bukuPinjamOffline as $buku) -
    -
    -
    -
    -
    - Cover {{ $buku['judul'] }} -
    -
    -
    -
    -
    -
    - {{ $buku['judul'] }}
    -

    - {{ $buku['penulis'] }} -

    -
    -
    -
    - - Sisa: {{ $buku['sisa_hari'] }} - hari -
    -
    -
    -
    +
    + +
    + + Sisa: {{ $buku['sisa_hari'] }} hari
    -
    +
    @empty
    @@ -233,7 +212,9 @@ class="alert alert-danger border-0 py-2 px-3 mb-0 d-flex align-items-center">
    Baca Buku Online
    - @@ -241,45 +222,23 @@ class="alert alert-danger border-0 py-2 px-3 mb-0 d-flex align-items-center">
    @forelse($bacaBukuOnline as $buku)
    -
    -
    -
    -
    - Cover {{ $buku['judul'] }} -
    + + +
    +
    + Progress + {{ $buku['progress'] }}%
    -
    -
    -
    -
    - {{ $buku['judul'] }}
    -

    - {{ $buku['penulis'] }} -

    -
    -
    -
    -
    - Progress - {{ $buku['progress'] }}% -
    -
    -
    -
    -
    -
    -
    +
    +
    -
    + +
    @empty
    @@ -368,87 +327,13 @@ class="badge bg-{{ $item['type'] }}-soft text-{{ $item['type'] }} rounded-pill p
    - + + diff --git a/resources/views/katalog.blade.php b/resources/views/katalog.blade.php index 3710d95..b46837a 100644 --- a/resources/views/katalog.blade.php +++ b/resources/views/katalog.blade.php @@ -61,27 +61,48 @@
    + {{-- Badge diletakkan di atas judul --}}
    - {{ $buku['kategori'] }} + {{ $buku['kategori'] }} {{ $buku['status'] }}
    -
    {{ $buku['judul'] }}
    +
    {{ $buku['judul'] }}

    {{ $buku['penulis'] }}

    -
    - @if ($buku['status'] == 'Dipinjam') - - @else - @if ($buku['tipe_akses'] == 'online') + @if ($buku['status'] == 'Tersedia') + @if (is_array($buku['tipe_akses'])) +
    + + Pinjam + + + +
    + @elseif ($buku['tipe_akses'] == 'online') @else - + + Pinjam Offline + + @endif + @else + @if (is_array($buku['tipe_akses'])) +
    + + +
    + @else + @endif @endif
    diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index cb170c7..bdd74aa 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -10,9 +10,15 @@ @vite(['resources/scss/app.scss', 'resources/js/app.js']) + + + + + +