feat: expanse activity books

This commit is contained in:
zhadaarsita 2025-09-20 13:24:04 +07:00
parent 0e0250fec3
commit 4f624d179b
7 changed files with 227 additions and 128 deletions

View File

@ -16,8 +16,8 @@ public function index()
$pemberitahuan = DummyDataService::getPemberitahuan();
$progressMembaca = DummyDataService::getProgressMembaca();
$statistikBulanan = DummyDataService::getStatistikBulanan();
$bukuPinjamOffline = DummyDataService::getBukuPinjamOffline();
$bacaBukuOnline = DummyDataService::getBacaBukuOnline();
$bukuPinjamOffline = DummyDataService::getBukuPinjamOffline($user);
$bacaBukuOnline = DummyDataService::getBacaBukuOnline($user);
return view('dashboard', compact(
'user',

View File

@ -132,20 +132,21 @@ private static function getAllBooks()
return collect([
[
'id' => 1,
'judul' => 'Modul Ajar IPAS',
'penulis' => 'Tim Kemdikbud Ristek',
'judul' => 'Modul Ajar IPAS',
'penulis' => 'Tim Kemdikbud Ristek',
'cover' => 'images/covers/ipas.jpg',
'kategori' => 'Sains',
'tahun' => 2022,
'status' => 'Tersedia',
'is_new' => true,
'tipe_akses' => 'online',
'tipe_akses' => 'online',
'progress' => 75,
'user_id' => 1,
],
[
'id' => 2,
'judul' => 'Modul Ajar Pendidikan Pancasila',
'penulis' => 'Tim Guru Pancasila',
'judul' => 'Modul Ajar Pendidikan Pancasila',
'penulis' => 'Tim Guru Pancasila',
'cover' => 'images/covers/pancasila.jpg',
'kategori' => 'Pendidikan',
'tahun' => 2023,
@ -153,64 +154,103 @@ private static function getAllBooks()
'is_new' => false,
'tipe_akses' => 'online',
'progress' => 100,
'user_id' => [3, 1],
],
[
'id' => 3,
'judul' => 'Modul Belajar Sosiologi',
'penulis' => 'Tim Cendekia',
'judul' => 'Modul Belajar Sosiologi',
'penulis' => 'Tim Cendekia',
'cover' => 'images/covers/sosiologi.jpg',
'kategori' => 'Sosial',
'tahun' => 2021,
'status' => 'Dipinjam',
'is_new' => false,
'tipe_akses' => 'offline',
'tipe_akses' => 'offline',
'sisa_hari' => 8,
'user_id' => 3,
],
[
'id' => 4,
'judul' => 'Modul Pembelajaran Seni Budaya',
'penulis' => 'Cahya Wulan, S.Pd.',
'judul' => 'Modul Pembelajaran Seni Budaya',
'penulis' => 'Cahya Wulan, S.Pd.',
'cover' => 'images/covers/senbud.jpg',
'kategori' => 'Seni',
'tahun' => 2022,
'status' => 'Dipinjam',
'is_new' => false,
'tipe_akses' => 'offline',
'tipe_akses' => 'offline',
'sisa_hari' => 14,
'user_id' => [1, 3],
],
[
'id' => 5,
'judul' => 'Yuk, Mari SEKOLAH',
'penulis' => 'Dr. Budi Santoso',
'cover' => 'images/covers/ipas.jpg',
'judul' => 'Si Anak Pintar',
'penulis' => 'Tere Liye',
'cover' => 'images/covers/sianakpintar.jpg',
'kategori' => 'Fiksi',
'tahun' => 2022,
'status' => 'Tersedia',
'is_new' => false,
'tipe_akses' => null,
'tahun' => 2018,
'status' => 'Dipinjam',
'is_new' => true,
'tipe_akses' => 'offline',
'sisa_hari' => 5,
'user_id' => 1,
],
[
'id' => 6,
'judul' => 'Yuk, Mari SEKOLAH',
'penulis' => 'Dr. Budi Santoso',
'cover' => 'images/covers/ipas.jpg',
'kategori' => 'Fiksi',
'tahun' => 2022,
'judul' => 'Matematika Dasar',
'penulis' => 'Prof. Dr. Matematikus',
'cover' => 'images/covers/mtk.jpg',
'kategori' => 'Pendidikan',
'tahun' => 2023,
'status' => 'Tersedia',
'is_new' => true,
'tipe_akses' => 'online',
'progress' => 40,
'user_id' => [1,4,5],
],
[
'id' => 7,
'judul' => 'The Last Spell Breather',
'penulis' => 'Julie Pike',
'cover' => 'images/covers/thelastspellbreather.jpg',
'kategori' => 'Fantasi',
'tahun' => 2024,
'status' => 'Tersedia',
'is_new' => true,
'tipe_akses' => 'online',
'progress' => 0,
'user_id' => [3,1]
],
[
'id' => 8,
'judul' => 'Buku Offline Tanpa Peminjam',
'penulis' => 'Penulis Misteri',
'cover' => 'images/covers/sosiologi.jpg',
'kategori' => 'Misteri',
'tahun' => 2020,
'status' => 'Tersedia',
'is_new' => false,
'tipe_akses' => 'offline',
'sisa_hari' => 13
'sisa_hari' => null
],
]);
}
/**
* Data untuk buku pinjam offline (Diringkas)
* Data untuk buku pinjam offline
*/
public static function getBukuPinjamOffline(): array
public static function getBukuPinjamOffline($user): array
{
return self::getAllBooks()
->where('tipe_akses', 'offline')
->filter(function ($buku) use ($user) {
if (!isset($buku['user_id'])) return false;
if (is_array($buku['user_id'])) {
return in_array($user->id, $buku['user_id']);
}
return $buku['user_id'] == $user->id;
})
->map(fn($buku) => [
'judul' => $buku['judul'],
'penulis' => $buku['penulis'],
@ -222,12 +262,20 @@ public static function getBukuPinjamOffline(): array
}
/**
* Data untuk baca buku online (Diringkas)
* Data untuk baca buku online
*/
public static function getBacaBukuOnline(): array
public static function getBacaBukuOnline($user): array
{
return self::getAllBooks()
->where('tipe_akses', 'online')
->filter(function ($buku) use ($user) {
if (!isset($buku['user_id'])) return false;
if (is_array($buku['user_id'])) {
return in_array($user->id, $buku['user_id']);
}
return $buku['user_id'] == $user->id;
})
->map(fn($buku) => [
'judul' => $buku['judul'],
'penulis' => $buku['penulis'],

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@ -11,7 +11,6 @@
<p class="text-muted">Apa yang ingin kamu baca hari ini?</p>
</div>
<!-- Stats Cards Section -->
<div class="row g-4 mb-4">
@foreach ($stats as $stat)
<div class="col-xl-3 col-lg-6 col-md-6">
@ -165,20 +164,9 @@ class="badge bg-{{ $item['type'] }}-soft text-{{ $item['type'] }} rounded-pill p
<!-- Book Activities Section -->
<div class="card border-0 shadow-sm py-2">
{{-- INI YANG DIKOMENTAR TITLE "AKTIVITAS BUKU" bukan fungsi --}}
{{-- <div class="card-header bg-white border-0 py-3">
<div class="d-flex align-items-center">
<div class="activity-icon me-3">
<i class="bi bi-book-fill text-primary"></i>
</div>
<h6 class="m-0 fw-bold text-dark">Aktivitas Buku</h6>
</div>
</div> --}}
<div class="card-body px-3">
<!-- Offline Books Section -->
<div class="mb-5">
<div class="mb-5" x-data="{ expanded: false }">
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<div class="section-icon me-3">
@ -186,14 +174,14 @@ class="badge bg-{{ $item['type'] }}-soft text-{{ $item['type'] }} rounded-pill p
</div>
<h6 class="mb-0 fw-bold text-dark">Buku Pinjam Offline</h6>
</div>
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-3"
data-bs-toggle="modal" data-bs-target="#">
Lihat Semua
<button type-="button" :class="expanded ? 'btn btn-sm btn-primary rounded-pill px-3' : 'btn btn-sm btn-outline-primary rounded-pill px-3'"
@click="expanded = !expanded" x-show="{{ count($bukuPinjamOffline) > 2 }}">
<span x-text="expanded ? 'Tampilkan Lebih Sedikit' : 'Lihat Semua'">Lihat Semua</span>
</button>
</div>
<div class="row g-4">
@forelse($bukuPinjamOffline as $buku)
<div class="col-lg-6">
<div class="col-xl-4 col-md-6" x-show="{{ $loop->index }} < 2 || expanded" x-transition>
<div class="card border-0 shadow-sm h-100 book-card">
<div class="row g-0 h-100">
<div class="col-4">
@ -201,8 +189,7 @@ class="badge bg-{{ $item['type'] }}-soft text-{{ $item['type'] }} rounded-pill p
class="book-cover-container d-flex align-items-center justify-content-center p-3 h-100">
<img src="{{ asset($buku['cover']) }}"
class="img-fluid rounded shadow-sm book-cover"
alt="Cover {{ $buku['judul'] }}"
style="max-height: 120px; width: auto; object-fit: cover;">
alt="Cover {{ $buku['judul'] }}">
</div>
</div>
<div class="col-8">
@ -238,54 +225,54 @@ class="alert alert-danger border-0 py-2 px-3 mb-0 d-flex align-items-center">
</div>
</div>
<!-- Online Books Section -->
<div class="mt-5">
<div class="mt-5" x-data="{ expanded: false }">
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<div class="section-icon me-3">
<i class="bi bi-globe text-success"></i>
<div class="d-flex align-items-center">
<div class="section-icon me-3">
<i class="bi bi-globe text-success"></i>
</div>
<h6 class="mb-0 fw-bold text-dark">Baca Buku Online</h6>
</div>
<h6 class="mb-0 fw-bold text-dark">Baca Buku Online</h5>
<button type="button" :class="expanded ? 'btn btn-sm btn-primary rounded-pill px-3' : 'btn btn-sm btn-outline-primary rounded-pill px-3'"
@click="expanded = !expanded" x-show="{{ count($bacaBukuOnline) > 2 }}">
<span x-text="expanded ? 'Tampilkan Lebih Sedikit' : 'Lihat Semua'">Lihat Semua</span>
</button>
</div>
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-3"
data-bs-toggle="modal" data-bs-target="#">
Lihat Semua
</button>
</div>
<div class="row g-4">
@forelse($bacaBukuOnline as $buku)
<div class="col-lg-6">
<div class="card border-0 shadow-sm h-100 book-card">
<div class="row g-0 h-100">
<div class="col-4">
<div
class="book-cover-container d-flex align-items-center justify-content-center p-3 h-100">
<img src="{{ asset($buku['cover']) }}"
class="img-fluid rounded shadow-sm book-cover"
alt="Cover {{ $buku['judul'] }}"
style="max-height: 120px; width: auto; object-fit: cover;">
</div>
</div>
<div class="col-8">
<div class="card-body d-flex flex-column h-100 p-3">
<div class="flex-grow-1">
<h6 class="card-title fw-bold text-dark mb-2 line-clamp-2">
{{ $buku['judul'] }}</h6>
<p class="card-subtitle text-muted small mb-3">
<i class="bi bi-person-fill me-1"></i>{{ $buku['penulis'] }}
</p>
<div class="row g-4">
@forelse($bacaBukuOnline as $buku)
<div class="col-xl-4 col-md-6" x-show="{{ $loop->index }} < 2 || expanded" x-transition>
<div class="card border-0 shadow-sm h-100 book-card">
<div class="row g-0 h-100">
<div class="col-4">
<div
class="book-cover-container d-flex align-items-center justify-content-center p-3 h-100">
<img src="{{ asset($buku['cover']) }}"
class="img-fluid rounded shadow-sm book-cover"
alt="Cover {{ $buku['judul'] }}">
</div>
<div class="mt-auto">
<div class="progress-wrapper">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="small text-muted">Progress</span>
<span
class="small fw-bold text-primary">{{ $buku['progress'] }}%</span>
</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-gradient"
style="width: {{ $buku['progress'] }}%" role="progressbar">
</div>
<div class="col-8">
<div class="card-body d-flex flex-column h-100 p-3">
<div class="flex-grow-1">
<h6 class="card-title fw-bold text-dark mb-2 line-clamp-2">
{{ $buku['judul'] }}</h6>
<p class="card-subtitle text-muted small mb-3">
<i class="bi bi-person-fill me-1"></i>{{ $buku['penulis'] }}
</p>
</div>
<div class="mt-auto">
<div class="progress-wrapper">
<div
class="d-flex justify-content-between align-items-center mb-2">
<span class="small text-muted">Progress</span>
<span
class="small fw-bold text-primary">{{ $buku['progress'] }}%</span>
</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-gradient"
style="width: {{ $buku['progress'] }}%"
role="progressbar">
</div>
</div>
</div>
</div>
@ -294,20 +281,20 @@ class="small fw-bold text-primary">{{ $buku['progress'] }}%</span>
</div>
</div>
</div>
</div>
@empty
<div class="col-12">
<div class="text-center py-5">
<i class="bi bi-book text-muted mb-3" style="font-size: 4rem;"></i>
<p class="text-muted mb-0">Tidak ada buku yang sedang dibaca secara online.</p>
@empty
<div class="col-12">
<div class="text-center py-5">
<i class="bi bi-book text-muted mb-3" style="font-size: 4rem;"></i>
<p class="text-muted mb-0">Tidak ada buku yang sedang dibaca secara online.</p>
</div>
</div>
</div>
@endforelse
</div>
@endforelse
</div>
</div>
</div>
</div>
<!-- Modals -->
<div class="modal fade" id="pengumumanModal" tabindex="-1" aria-labelledby="pengumumanModalLabel"
aria-hidden="true">

View File

@ -1,39 +1,106 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<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'])
<style>
body { background-color: #f4f7f8; }
.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; }
body {
background-color: #f4f7f8;
}
.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 {
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; }
.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 {
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;
}
.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>
<div class="d-flex">
@include('layouts.sidebar')
@ -45,11 +112,11 @@
{{ $slot }}
</main>
<footer class="footer text-center py-3">
<span class="text-muted small">Copyright &copy; {{ date('Y') }} {{ config('app.name', 'Perpus') }}.</span>
<span class="text-muted small">Copyright &copy; {{ date('Y') }}
{{ config('app.name', 'Perpus') }}.</span>
</footer>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('sidebar');
@ -59,19 +126,16 @@
const closeSidebarMobile = document.getElementById('closeSidebarMobile');
const isDesktop = () => window.innerWidth >= 992;
// Fungsi untuk toggle sidebar di mobile
function toggleMobileSidebar() {
sidebar.classList.toggle('active');
overlay.classList.toggle('active');
}
// Fungsi untuk toggle sidebar di desktop
function toggleDesktopSidebar() {
sidebar.classList.toggle('minimized');
mainWrapper.classList.toggle('sidebar-minimized');
}
// Event listener untuk tombol hamburger utama
sidebarToggle.addEventListener('click', function() {
if (isDesktop()) {
toggleDesktopSidebar();
@ -80,7 +144,6 @@ function toggleDesktopSidebar() {
}
});
// Event listener untuk overlay & tombol close (hanya di mobile)
const closeButtons = [overlay, closeSidebarMobile];
closeButtons.forEach(el => {
if (el) {
@ -94,4 +157,5 @@ function toggleDesktopSidebar() {
});
</script>
</body>
</html>