diff --git a/app/Http/Controllers/Admin/AdminPeminjamanController.php b/app/Http/Controllers/Admin/AdminPeminjamanController.php index fc5a32a..bb17f7c 100644 --- a/app/Http/Controllers/Admin/AdminPeminjamanController.php +++ b/app/Http/Controllers/Admin/AdminPeminjamanController.php @@ -31,6 +31,7 @@ public function index(Request $request) 'tanggal_pinjam' => $firstLoan->borrowed_at, 'tenggat_kembali' => $firstLoan->due_at, 'status' => $firstLoan->status, + 'role' => $user->role, 'books' => $userLoans->map(fn($l) => [ 'id' => $l->book->id, 'judul' => $l->book->judul, @@ -148,26 +149,36 @@ public function dendaIndex() $tenggat = Carbon::parse($firstLoan->due_at); $hariTelat = $now->greaterThan($tenggat) ? (int) $tenggat->diffInDays($now) : 0; - $totalDenda = $hariTelat * 1000; + + $isGuru = $user->role === 'guru'; + $totalDenda = $isGuru ? 0 : ($hariTelat * 1000); // Link WA $hp = $user->phone ?? ''; $waLink = '#'; if ($hp) { if (substr($hp, 0, 1) == '0') $hp = '62' . substr($hp, 1); - $pesan = $hariTelat > 0 - ? "Halo {$user->nama_lengkap}, anda terlambat pengembalian buku. Total Denda: Rp " . number_format($totalDenda, 0, ',', '.') - : "Halo {$user->nama_lengkap}, akun anda sedang dinonaktifkan sementara. Mohon hubungi petugas."; + + if ($isGuru && $hariTelat > 0) { + $pesan = "Halo Bapak/Ibu {$user->nama_lengkap}, anda terlambat pengembalian buku selama {$hariTelat} hari. Mohon segera dikembalikan ke perpustakaan. Terima kasih."; + } else { + $pesan = $hariTelat > 0 + ? "Halo {$user->nama_lengkap}, anda terlambat pengembalian buku. Total Denda: Rp " . number_format($totalDenda, 0, ',', '.') + : "Halo {$user->nama_lengkap}, akun anda sedang dinonaktifkan sementara. Mohon hubungi petugas."; + } + $waLink = "https://wa.me/{$hp}?text=" . urlencode($pesan); } return [ 'id' => $firstLoan->id, + 'user_id' => $userId, 'peminjam' => $user->nama_lengkap, 'nomor_hp' => $user->phone ?? '-', 'kelas' => $user->kelas ?? 'Guru', 'hari_terlambat' => $hariTelat, 'total_denda' => $totalDenda, + 'is_guru' => $isGuru, 'wa_link' => $waLink, 'is_banned' => $user->is_banned, 'tenggat_kembali' => $firstLoan->due_at, @@ -189,7 +200,61 @@ public function dendaIndex() public function berikanSanksi(Request $request) { - // Actually implement banning logic here if needed - return response()->json(['status' => 'success']); + $validated = $request->validate([ + 'user_id' => 'required|exists:users,id', + 'action' => 'required|in:ban,unban', + ]); + + $user = User::findOrFail($validated['user_id']); + $user->is_banned = ($validated['action'] === 'ban'); + $user->save(); + + return response()->json([ + 'status' => 'success', + 'message' => $user->is_banned ? "Akun {$user->nama_lengkap} berhasil dibekukan." : "Akun {$user->nama_lengkap} telah diaktifkan kembali." + ]); + } + + public function kembalikan(Request $request) + { + $validated = $request->validate([ + 'user_id' => 'required|exists:users,id', + 'returns' => 'required|array', + 'returns.*.book_id' => 'required|exists:books,id', + 'returns.*.condition' => 'required|string', + 'returns.*.fine_damage' => 'required|integer', + 'returns.*.fine_overdue' => 'required|integer', + 'returns.*.notes' => 'nullable|string', + ]); + + \DB::beginTransaction(); + try { + foreach ($validated['returns'] as $item) { + $loan = Loan::where('user_id', $validated['user_id']) + ->where('book_id', $item['book_id']) + ->whereIn('status', ['Dipinjam', 'Terlambat']) + ->first(); + + if ($loan) { + $loan->update([ + 'status' => 'Dikembalikan', + 'returned_at' => now(), + 'condition' => $item['condition'], + 'fine_damage' => $item['fine_damage'], + 'fine_overdue' => $item['fine_overdue'], + 'return_notes' => $item['notes'], + ]); + + // Update book status + $loan->book->update(['status' => 'Tersedia']); + } + } + + \DB::commit(); + return response()->json(['status' => 'success', 'message' => 'Buku berhasil dikembalikan.']); + } catch (\Exception $e) { + \DB::rollBack(); + return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500); + } } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/BookController.php b/app/Http/Controllers/Admin/BookController.php index d47f9a4..caf1908 100644 --- a/app/Http/Controllers/Admin/BookController.php +++ b/app/Http/Controllers/Admin/BookController.php @@ -21,10 +21,13 @@ public function index(Request $request) $semuaBuku = $query->latest()->get(); - // Memisahkan buku menjadi dua koleksi: online dan offline - [$bukuOnline, $bukuOffline] = $semuaBuku->partition(function ($buku) { - $tipe = $buku->tipe_akses; - return in_array('online', $tipe ?? []); + // Memisahkan buku menjadi dua koleksi: online dan offline secara independen + $bukuOnline = $semuaBuku->filter(function ($buku) { + return in_array('online', $buku->tipe_akses ?? []); + }); + + $bukuOffline = $semuaBuku->filter(function ($buku) { + return in_array('offline', $buku->tipe_akses ?? []); }); return view('admin.buku.index', [ diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php index ae82837..ae77115 100644 --- a/app/Http/Controllers/Admin/DashboardController.php +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -22,7 +22,7 @@ public function index() ['label' => 'Total Buku', 'value' => $allBooks, 'icon' => 'bi-journal-bookmark-fill', 'color' => 'primary'], ['label' => 'Total Anggota', 'value' => $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' => Loan::where('status', 'Terlambat')->count(), 'icon' => 'bi-cash-coin', 'color' => 'danger'], + ['label' => 'Total Denda', 'value' => 'Rp ' . number_format(Loan::where('status', 'Terlambat')->sum('fine_overdue'), 0, ',', '.'), 'icon' => 'bi-cash-coin', 'color' => 'danger'], ]; // Monthly stats (last 7 months) diff --git a/app/Http/Controllers/BacaOnlineController.php b/app/Http/Controllers/BacaOnlineController.php index 6204780..1249cbe 100644 --- a/app/Http/Controllers/BacaOnlineController.php +++ b/app/Http/Controllers/BacaOnlineController.php @@ -9,6 +9,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Illuminate\View\View; use Symfony\Component\HttpFoundation\BinaryFileResponse; @@ -134,15 +135,15 @@ public function streamPdf(int $id): BinaryFileResponse|Response abort(403, 'Akses Ditolak.'); } $book = Book::findOrFail($id); - $filePath = 'books/' . ($book->file_pdf ?? 'sample.pdf'); - $absolutePath = storage_path('app/' . $filePath); - - // For demo purposes, if file doesn't exist, we might want to use a placeholder or handle it gracefully - if (!file_exists($absolutePath)) { - // Create a dummy file for testing if it doesn't exist? (Optional, maybe just abort) + $fileName = $book->file_pdf ?? 'sample.pdf'; + $filePath = 'books/' . $fileName; + + if (!Storage::disk('local')->exists($filePath)) { abort(404, 'File PDF tidak ditemukan di server.'); } + $absolutePath = Storage::disk('local')->path($filePath); + return response()->file($absolutePath); } } diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 44fb5b3..ef9da8e 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -57,10 +57,28 @@ public function index() ['type' => 'info', 'icon' => 'bi-bell-fill', 'title' => 'Selamat Datang', 'content' => 'Selamat datang di perpustakaan digital SMKN 1.', 'badge' => 'Baru'] ]); - $progressMembaca = ['selesai' => 70, 'sisa' => 30]; // Still dummy as we don't track pages yet + // Dynamic reading progress based on returned vs total books + $totalUserLoans = Loan::where('user_id', $user->id)->count(); + $returnedLoans = Loan::where('user_id', $user->id)->where('status', 'Dikembalikan')->count(); + $progressSelesai = $totalUserLoans > 0 ? round(($returnedLoans / $totalUserLoans) * 100) : 0; + + $progressMembaca = ['selesai' => $progressSelesai, 'sisa' => 100 - $progressSelesai]; + + // Dynamic monthly stats for the last 7 months + $labels = []; + $data = []; + for ($i = 6; $i >= 0; $i--) { + $month = Carbon::now()->subMonths($i); + $labels[] = $month->translatedFormat('M'); + $data[] = Loan::where('user_id', $user->id) + ->whereMonth('borrowed_at', $month->month) + ->whereYear('borrowed_at', $month->year) + ->count(); + } + $statistikBulanan = [ - 'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul'], - 'data' => [10, 15, 8, 20, 18, 25, 22], + 'labels' => $labels, + 'data' => $data, ]; // Online books (books with 'online' in tipe_akses) diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index fad62f6..32c6e2d 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -112,13 +112,15 @@ public function edit(Request $request): View */ public function update(ProfileUpdateRequest $request): RedirectResponse { - $request->user()->fill($request->validated()); + $user = $request->user(); + $user->fill($request->validated()); + $user->name = $user->nama_lengkap; // Sync for compatibility - if ($request->user()->isDirty('email')) { - $request->user()->email_verified_at = null; + if ($user->isDirty('email')) { + $user->email_verified_at = null; } - $request->user()->save(); + $user->save(); return Redirect::route('profile.edit')->with('status', 'profile-updated'); } diff --git a/app/Http/Requests/ProfileUpdateRequest.php b/app/Http/Requests/ProfileUpdateRequest.php index 3622a8f..f01b359 100644 --- a/app/Http/Requests/ProfileUpdateRequest.php +++ b/app/Http/Requests/ProfileUpdateRequest.php @@ -16,7 +16,7 @@ class ProfileUpdateRequest extends FormRequest public function rules(): array { return [ - 'name' => ['required', 'string', 'max:255'], + 'nama_lengkap' => ['required', 'string', 'max:255'], 'email' => [ 'required', 'string', @@ -25,6 +25,7 @@ public function rules(): array 'max:255', Rule::unique(User::class)->ignore($this->user()->id), ], + 'phone' => ['required', 'string', 'max:20'], ]; } } diff --git a/app/Models/Book.php b/app/Models/Book.php index 6942685..1caa369 100644 --- a/app/Models/Book.php +++ b/app/Models/Book.php @@ -8,7 +8,7 @@ class Book extends Model { protected $fillable = [ 'judul', 'penulis', 'cover', 'kode_buku', - 'category_id', 'tahun', 'status', 'is_new', 'tipe_akses' + 'category_id', 'tahun', 'status', 'is_new', 'tipe_akses', 'file_pdf' ]; protected $casts = [ diff --git a/app/Models/Loan.php b/app/Models/Loan.php index b825af0..d2c823e 100644 --- a/app/Models/Loan.php +++ b/app/Models/Loan.php @@ -8,7 +8,8 @@ class Loan extends Model { protected $fillable = [ 'user_id', 'book_id', 'loan_code', - 'borrowed_at', 'due_at', 'returned_at', 'status' + 'borrowed_at', 'due_at', 'returned_at', 'status', + 'fine_overdue', 'fine_damage', 'condition', 'return_notes' ]; protected $casts = [ diff --git a/database/migrations/2026_02_09_153905_add_file_pdf_to_books_table.php b/database/migrations/2026_02_09_153905_add_file_pdf_to_books_table.php new file mode 100644 index 0000000..ee95903 --- /dev/null +++ b/database/migrations/2026_02_09_153905_add_file_pdf_to_books_table.php @@ -0,0 +1,28 @@ +string('file_pdf')->nullable()->after('tipe_akses'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('books', function (Blueprint $table) { + $table->dropColumn('file_pdf'); + }); + } +}; diff --git a/database/migrations/2026_02_09_181448_add_return_details_to_loans_table.php b/database/migrations/2026_02_09_181448_add_return_details_to_loans_table.php new file mode 100644 index 0000000..44f644b --- /dev/null +++ b/database/migrations/2026_02_09_181448_add_return_details_to_loans_table.php @@ -0,0 +1,31 @@ +integer('fine_overdue')->default(0)->after('status'); + $table->integer('fine_damage')->default(0)->after('fine_overdue'); + $table->string('condition')->nullable()->after('fine_damage'); + $table->text('return_notes')->nullable()->after('condition'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('loans', function (Blueprint $table) { + $table->dropColumn(['fine_overdue', 'fine_damage', 'condition', 'return_notes']); + }); + } +}; diff --git a/resources/views/admin/buku/create.blade.php b/resources/views/admin/buku/create.blade.php index d83a82f..2b96f7f 100644 --- a/resources/views/admin/buku/create.blade.php +++ b/resources/views/admin/buku/create.blade.php @@ -40,7 +40,7 @@
+ placeholder="Contoh: 2024" min="0" required>
diff --git a/resources/views/admin/buku/index.blade.php b/resources/views/admin/buku/index.blade.php index 625544a..22a2fea 100644 --- a/resources/views/admin/buku/index.blade.php +++ b/resources/views/admin/buku/index.blade.php @@ -62,7 +62,7 @@ class="badge bg-warning-subtle text-warning-emphasis">Dipinjam data-judul="{{ $buku['judul'] }}" data-kode_buku="{{ $buku['kode_buku'] }}" data-penulis="{{ $buku['penulis'] }}" - data-kategori="{{ $buku['kategori'] }}" + data-kategori="{{ $buku->category->name ?? '-' }}" data-tahun="{{ $buku['tahun'] }}" data-status="{{ $buku['status'] }}"> Detail @@ -109,7 +109,7 @@ class="badge bg-info-subtle text-info-emphasis">{{ $buku['file_pdf'] ?? 'N/A' }} data-cover="{{ asset($buku['cover']) }}" data-judul="{{ $buku['judul'] }}" data-penulis="{{ $buku['penulis'] }}" - data-kategori="{{ $buku['kategori'] }}" + data-kategori="{{ $buku->category->name ?? '-' }}" data-tahun="{{ $buku['tahun'] }}" data-status="Dapat Dibaca Online"> Detail diff --git a/resources/views/admin/denda/index.blade.php b/resources/views/admin/denda/index.blade.php index d2199a8..0702fc1 100644 --- a/resources/views/admin/denda/index.blade.php +++ b/resources/views/admin/denda/index.blade.php @@ -82,9 +82,14 @@ class="badge bg-danger-subtle text-danger border border-danger-subtle rounded-pi -
Rp - {{ number_format($item['total_denda'], 0, ',', '.') }}
- Rp 1.000/hari + @if($item['is_guru']) +
Bebas Denda
+ Kebijakan Guru + @else +
Rp + {{ number_format($item['total_denda'], 0, ',', '.') }}
+ Rp 1.000/hari + @endif
@@ -98,12 +103,14 @@ class="btn btn-sm btn-success text-white" title="Tagih via WhatsApp"> @if ($item['is_banned']) {{-- Jika sudah dibekukan (Otomatis/Manual), muncul tombol AKTIFKAN --}} @else {{-- Jika belum dibekukan, muncul tombol SANKSI (Manual) --}} @@ -183,11 +190,12 @@ function(settings, data, dataIndex) { }); }); - // --- LOGIC TOMBOL SANKSI --- $(document).on('click', '.btn-sanksi', function() { const nama = $(this).data('nama'); + const userId = $(this).data('user-id'); + modernSwal.fire({ - title: 'Nonaktifkan Guru?', + title: 'Bekukan Akun?', text: `Apakah Anda yakin ingin memberikan sanksi pembekuan akun kepada ${nama}?`, icon: 'warning', showCancelButton: true, @@ -198,23 +206,40 @@ function(settings, data, dataIndex) { if (result.isConfirmed) { modernSwal.fire({ title: 'Memproses...', - timer: 1000, didOpen: () => Swal.showLoading() - }) - .then(() => { - Toast.fire({ - icon: 'success', - title: 'Sanksi Diterapkan', - text: `Akun ${nama} berhasil dibekukan.` - }); }); + + $.ajax({ + url: '{{ route('admin.denda.sanksi') }}', + method: 'POST', + data: { + _token: '{{ csrf_token() }}', + user_id: userId, + action: 'ban' + }, + success: function(response) { + if (response.status === 'success') { + Toast.fire({ + icon: 'success', + title: 'Sanksi Diterapkan', + text: response.message + }); + setTimeout(() => location.reload(), 1500); + } else { + modernSwal.fire('Gagal', response.message, 'error'); + } + }, + error: function() { + modernSwal.fire('Gagal', 'Terjadi kesalahan saat memproses data.', 'error'); + } + }); } }); }); - // Logic Tombol Aktifkan (Unban) $(document).on('click', '.btn-aktifkan', function() { const nama = $(this).data('nama'); + const userId = $(this).data('user-id'); modernSwal.fire({ title: 'Aktifkan Akun?', @@ -226,20 +251,35 @@ function(settings, data, dataIndex) { cancelButtonText: 'Batal' }).then((result) => { if (result.isConfirmed) { - // Simulasi Loading & Sukses modernSwal.fire({ title: 'Memproses...', - timer: 1000, didOpen: () => Swal.showLoading() - }) - .then(() => { - Toast.fire({ - icon: 'success', - title: 'Akun Diaktifkan', - text: `Status sanksi pada ${nama} telah dicabut.` - }); - $(this).closest('td').html('Aktif'); }); + + $.ajax({ + url: '{{ route('admin.denda.sanksi') }}', + method: 'POST', + data: { + _token: '{{ csrf_token() }}', + user_id: userId, + action: 'unban' + }, + success: function(response) { + if (response.status === 'success') { + Toast.fire({ + icon: 'success', + title: 'Akun Diaktifkan', + text: response.message + }); + setTimeout(() => location.reload(), 1500); + } else { + modernSwal.fire('Gagal', response.message, 'error'); + } + }, + error: function() { + modernSwal.fire('Gagal', 'Terjadi kesalahan saat memproses data.', 'error'); + } + }); } }); }); diff --git a/resources/views/admin/peminjaman/index.blade.php b/resources/views/admin/peminjaman/index.blade.php index 40be248..64edc22 100644 --- a/resources/views/admin/peminjaman/index.blade.php +++ b/resources/views/admin/peminjaman/index.blade.php @@ -108,7 +108,7 @@ class="badge bg-primary-subtle text-primary fw-bold">{{ $transaksi['id_peminjama
Daftar Buku yang Dikembalikan:
@foreach ($transaksi['books'] as $buku) -
+
{{ $buku['judul'] }}
@@ -159,17 +159,25 @@ class="form-control form-control-sm denda-rusak-input"
Denda Keterlambatan ({{ $statusText }}) @php + $isGuru = ($transaksi['role'] ?? '') === 'guru'; $dendaTelat = 0; - if ($isTerlambat) { + if ($isTerlambat && !$isGuru) { $hari = $tenggat->startOfDay()->diffInDays($now->startOfDay()); $dendaTelat = $hari * 1000; } @endphp {{-- Data Attribute untuk JS --}} - - Rp {{ number_format($dendaTelat, 0, ',', '.') }} - + @if($isGuru && $isTerlambat) + + Bebas Denda + + @else + + Rp {{ number_format($dendaTelat, 0, ',', '.') }} + + @endif
Batal
@@ -291,9 +300,11 @@ function hitungTotalDenda(modal) { $(document).on('click', '.btn-konfirmasi-kembali', function() { const nama = $(this).data('nama-peminjam'); + const hp = $(this).data('nomor-hp'); const modalEl = $(this).closest('.modal'); const modalInstance = bootstrap.Modal.getInstance(modalEl[0]); const isEmailChecked = modalEl.find('.email-toggle').is(':checked'); + const isWaChecked = modalEl.find('input[role="switch"][id^="waStrukToggle"]').is(':checked'); modalInstance.hide(); @@ -313,10 +324,58 @@ function hitungTotalDenda(modal) { timerProgressBar: true, didOpen: () => Swal.showLoading() }).then(() => { - // Loading Kirim Email (Jika dicentang) + // Prepare Request Data + const returnsData = []; + const booksList = []; + const dendaOverdueTotal = parseInt(modalEl.find('.denda-keterlambatan-display').data('denda-keterlambatan')); + const dendaOverduePerBook = dendaOverdueTotal / modalEl.find('.book-return-item').length; + + let totalDendaRusak = 0; + modalEl.find('.book-return-item').each(function() { + const bookId = $(this).data('book-id'); + const judul = $(this).find('h6').text(); + const condition = $(this).find('.radio-kondisi:checked').val(); + const fineDamage = parseInt($(this).find('.denda-rusak-input').val()) || 0; + const notes = $(this).find('textarea').val(); + + totalDendaRusak += fineDamage; + booksList.push(judul); + returnsData.push({ + book_id: bookId, + condition: condition, + fine_damage: fineDamage, + fine_overdue: Math.round(dendaOverduePerBook), + notes: notes + }); + }); + + const userId = modalEl.find('.btn-konfirmasi-kembali').data('user-id'); + const totalDenda = dendaOverdueTotal + totalDendaRusak; + + // Construct WA Message + let waLink = null; + if (isWaChecked && hp && hp !== '-') { + let phone = hp; + if (phone.startsWith('0')) phone = '62' + phone.substring(1); + + let message = `*BUKTI PENGEMBALIAN BUKU*\n\n`; + message += `Halo ${nama},\n`; + message += `Terima kasih telah mengembalikan buku:\n`; + booksList.forEach(b => message += `- ${b}\n`); + message += `\n*Status:* Berhasil Dikembalikan\n`; + if (totalDenda > 0) { + message += `*Total Denda:* Rp ${new Intl.NumberFormat('id-ID').format(totalDenda)}\n`; + } else { + message += `*Denda:* Bebas Denda\n`; + } + message += `\n_Simpan pesan ini sebagai bukti pengembalian._`; + + waLink = `https://wa.me/${phone}?text=${encodeURIComponent(message)}`; + } + + // Loading Kirim Email flow if (isEmailChecked) { - const dummyEmail = nama.replace(/\s+/g, '.').toLowerCase() + - '@sekolah.sch.id'; + const dummyEmail = nama.replace(/\s+/g, '.').toLowerCase() + '@sekolah.sch.id'; modernSwal.fire({ title: 'Mengirim Email...', html: `Mengirim nota ke: ${dummyEmail}`, @@ -324,10 +383,10 @@ function hitungTotalDenda(modal) { timerProgressBar: true, didOpen: () => Swal.showLoading() }).then(() => { - finishTransaction(true); + finishTransaction(returnsData, userId, waLink); }); } else { - finishTransaction(false); + finishTransaction(returnsData, userId, waLink); } }); } else { @@ -335,13 +394,35 @@ function hitungTotalDenda(modal) { } }); - function finishTransaction(withEmail) { - Toast.fire({ - icon: 'success', - title: 'Berhasil', - text: withEmail ? 'Buku kembali & Email terkirim.' : 'Buku berhasil dikembalikan.' + function finishTransaction(returnsData, userId, waLink) { + $.ajax({ + url: '{{ route('admin.peminjaman.kembali') }}', + method: 'POST', + data: { + _token: '{{ csrf_token() }}', + user_id: userId, + returns: returnsData + }, + success: function(response) { + if (response.status === 'success') { + if (waLink) { + window.open(waLink, '_blank'); + } + + Toast.fire({ + icon: 'success', + title: 'Berhasil', + text: 'Buku berhasil dikembalikan.' + }); + setTimeout(() => location.reload(), 1500); + } else { + modernSwal.fire('Gagal', response.message, 'error'); + } + }, + error: function(xhr) { + modernSwal.fire('Gagal', 'Terjadi kesalahan saat memproses data.', 'error'); + } }); - setTimeout(() => location.reload(), 1500); } }); diff --git a/resources/views/admin/pengguna/create.blade.php b/resources/views/admin/pengguna/create.blade.php index 443a4e0..5d57415 100644 --- a/resources/views/admin/pengguna/create.blade.php +++ b/resources/views/admin/pengguna/create.blade.php @@ -22,19 +22,12 @@
+ placeholder="Masukkan alamat email" required pattern="[^@\s]+@[^@\s]+\.[^@\s]+" title="Masukkan alamat email yang valid">
- -
-
- - -
-
- - +
@@ -45,6 +38,14 @@
+ +
+ + +
@@ -60,6 +61,33 @@
+ +
diff --git a/resources/views/admin/pengguna/edit.blade.php b/resources/views/admin/pengguna/edit.blade.php index c00d7f6..6c066ac 100644 --- a/resources/views/admin/pengguna/edit.blade.php +++ b/resources/views/admin/pengguna/edit.blade.php @@ -23,22 +23,12 @@
+ value="{{ old('email', $pengguna->email) }}" required pattern="[^@\s]+@[^@\s]+\.[^@\s]+" title="Masukkan alamat email yang valid">
- -
-
- - -
-
- - +
@@ -52,6 +42,16 @@ Penjaga Perpus
+ +
+ + +
@@ -68,6 +68,44 @@
+ +
diff --git a/resources/views/admin/pengguna/index.blade.php b/resources/views/admin/pengguna/index.blade.php index 9ff289e..1a0e8a1 100644 --- a/resources/views/admin/pengguna/index.blade.php +++ b/resources/views/admin/pengguna/index.blade.php @@ -39,7 +39,7 @@ data-nama="{{ $siswa['nama_lengkap'] }}" data-email="{{ $siswa['email'] }}" data-role="{{ Str::title($siswa['role']) }}" - data-nisn="{{ $siswa['nisn'] ?? 'N/A' }}"> + data-nisn="{{ $siswa['nomor_induk'] ?? 'N/A' }}"> Detail diff --git a/resources/views/admin/pengumuman/index.blade.php b/resources/views/admin/pengumuman/index.blade.php index 569a168..729404f 100644 --- a/resources/views/admin/pengumuman/index.blade.php +++ b/resources/views/admin/pengumuman/index.blade.php @@ -32,12 +32,20 @@ {{ $item['content'] }} - - - - +
+ + + + + +
@empty @@ -52,4 +60,27 @@
+ + @push('scripts') + + @endpush \ No newline at end of file diff --git a/resources/views/admin/rekomendasi/index.blade.php b/resources/views/admin/rekomendasi/index.blade.php index aa11efd..12cbbb1 100644 --- a/resources/views/admin/rekomendasi/index.blade.php +++ b/resources/views/admin/rekomendasi/index.blade.php @@ -17,7 +17,20 @@ {{ $item['judul'] }} {{ $item['kategori'] }} - +
+ + + + + +
@endforeach @@ -26,4 +39,27 @@ + + @push('scripts') + + @endpush \ No newline at end of file diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 3eb1e75..e8756e9 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -1,8 +1,8 @@ @if ($errors->has('forbidden')) - @@ -26,7 +29,10 @@ @error('nisn') -
{{ $message }}
+
+ + {{ $message }} +
@enderror @else @@ -34,7 +40,10 @@ @error('nip') -
{{ $message }}
+
+ + {{ $message }} +
@enderror @endif @@ -44,7 +53,10 @@ @error('password') -
{{ $message }}
+
+ + {{ $message }} +
@enderror diff --git a/resources/views/baca/request_code.blade.php b/resources/views/baca/request_code.blade.php index 1e2cee2..9e30089 100644 --- a/resources/views/baca/request_code.blade.php +++ b/resources/views/baca/request_code.blade.php @@ -36,8 +36,9 @@

Jangan bagikan kode ini kepada orang lain.

@if (session('error')) -
- {{ session('error') }} +
+ +
{{ session('error') }}
@endif diff --git a/resources/views/components/auth-session-status.blade.php b/resources/views/components/auth-session-status.blade.php index c4bd6e2..8502916 100644 --- a/resources/views/components/auth-session-status.blade.php +++ b/resources/views/components/auth-session-status.blade.php @@ -1,7 +1,8 @@ @props(['status']) @if ($status) -
merge(['class' => 'font-medium text-sm text-green-600']) }}> - {{ $status }} +
merge(['class' => 'alert alert-success d-flex align-items-center mb-4']) }} role="alert"> + +
{{ $status }}
@endif diff --git a/resources/views/components/input-error.blade.php b/resources/views/components/input-error.blade.php index 9e6da21..4e2bec2 100644 --- a/resources/views/components/input-error.blade.php +++ b/resources/views/components/input-error.blade.php @@ -1,9 +1,12 @@ @props(['messages']) @if ($messages) -
    merge(['class' => 'text-sm text-red-600 space-y-1']) }}> +
      merge(['class' => 'list-unstyled mt-1 mb-0']) }}> @foreach ((array) $messages as $message) -
    • {{ $message }}
    • +
    • + + {{ $message }} +
    • @endforeach
    @endif diff --git a/resources/views/katalog/index.blade.php b/resources/views/katalog/index.blade.php index b3ae114..842fbfa 100644 --- a/resources/views/katalog/index.blade.php +++ b/resources/views/katalog/index.blade.php @@ -113,11 +113,16 @@ class="btn btn-sm btn-primary w-100">
@empty
-
-

Tidak Ada Hasil

-

Tidak ada buku yang cocok dengan kriteria filter Anda. Coba reset atau ubah filter.

-
- Reset Filter +
+
+ +
+

Tidak Ada Hasil

+

Tidak ada buku yang cocok dengan kriteria filter Anda.
Coba reset atau ubah filter pencarian Anda.

+
+ + Reset Filter +
@endforelse diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 417a886..2626ef0 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -33,9 +33,40 @@ @include('layouts.navigation')
@if (session('error')) -
-

{{ $user['nomor_hp'] }}

+

{{ $user->phone ?? 'N/A' }}

@@ -275,9 +275,9 @@ class="bi bi-star-fill me-1">Buku Utama {{-- --}} {{-- Button Setuju jika belum ada backend, akan ter direct ke peminjaman.index --}} - +
@@ -286,7 +286,6 @@ class="bi bi-star-fill me-1">Buku Utama document.addEventListener("DOMContentLoaded", function() { // (Default Hari Ini + 2 Hari) - // Simpan instance-nya ke variabel const fpKembali = flatpickr("#tanggalKembali", { dateFormat: "d F Y", altInput: true, @@ -306,7 +305,6 @@ class="bi bi-star-fill me-1">Buku Utama locale: "id", minDate: "today", - // LOGIC : Saat Tanggal Pinjam Berubah onChange: function(selectedDates, dateStr) { if (selectedDates.length > 0) { const tglMulai = selectedDates[0]; @@ -315,11 +313,117 @@ class="bi bi-star-fill me-1">Buku Utama fpKembali.set("minDate", minDateBaru); fpKembali.set("maxDate", maxDateBaru); - fpKembali.setDate(maxDateBaru); } } }); + + // Inisialisasi data dari data-attributes + const form = document.getElementById('formPeminjaman'); + const arrayBuku = JSON.parse(form.dataset.semuaBuku); + const bukuUtama = JSON.parse(form.dataset.bukuAwal); + + let selectedIds = [bukuUtama.id]; + const MAX_BOOKS = 2; + + // Global functions + window.toggleBookSelection = function(id) { + const index = selectedIds.indexOf(id); + const checkbox = document.getElementById('book' + id); + + if (index > -1) { + if (id === bukuUtama.id) return; + selectedIds.splice(index, 1); + if (checkbox) checkbox.checked = false; + } else { + if (selectedIds.length >= MAX_BOOKS) { + alert('Maksimal peminjaman adalah ' + MAX_BOOKS + ' buku.'); + return; + } + selectedIds.push(id); + if (checkbox) checkbox.checked = true; + } + updateCounter(); + }; + + window.konfirmasiPilihanBuku = function() { + renderDaftarBuku(); + const modal = bootstrap.Modal.getInstance(document.getElementById('pilihBukuModal')); + modal.hide(); + }; + + function updateCounter() { + const counterEl = document.getElementById('counterBuku'); + const sisaSlotEl = document.getElementById('sisaSlot'); + if (counterEl) counterEl.textContent = selectedIds.length; + if (sisaSlotEl) sisaSlotEl.textContent = MAX_BOOKS - selectedIds.length; + } + + function renderDaftarBuku() { + const container = document.getElementById('daftarBukuPinjam'); + const hiddenInputs = document.getElementById('hiddenInputs'); + const ringkasan = document.getElementById('ringkasanBuku'); + + if (!container || !hiddenInputs || !ringkasan) return; + + container.innerHTML = ''; + hiddenInputs.innerHTML = ''; + ringkasan.innerHTML = ''; + } + + window.toggleSelectionAndRender = function(id) { + window.toggleBookSelection(id); + renderDaftarBuku(); + }; + + document.getElementById('searchBuku').addEventListener('input', function(e) { + const keyword = e.target.value.toLowerCase(); + document.querySelectorAll('.book-option').forEach(el => { + const title = el.dataset.bookTitle; + const author = el.dataset.bookAuthor; + if (title.includes(keyword) || author.includes(keyword)) { + el.style.display = 'block'; + } else { + el.style.display = 'none'; + } + }); + }); + + renderDaftarBuku(); + updateCounter(); }); diff --git a/resources/views/profile/index.blade.php b/resources/views/profile/index.blade.php index a62a2ed..81dc7ca 100644 --- a/resources/views/profile/index.blade.php +++ b/resources/views/profile/index.blade.php @@ -110,6 +110,22 @@ class="btn btn-outline-primary rounded-pill w-100 w-md-auto ms-md-auto"> Edit Profil +
+
Informasi Personal
+
+
+ NIP / NUPTK +

{{ $user->nuptk ?? ($user->nomor_induk ?? 'N/A') }}

+
+
+ Email +

{{ $user->email }}

+
+
+ Nomor HP +

{{ $user->phone ?? 'N/A' }}

+
+
{{-- Ringkasan Laporan Minat Baca --}} @@ -192,7 +208,7 @@ class="btn btn-outline-primary rounded-pill ms-md-auto">
NISN -

{{ $user->nisn ?? 'N/A' }}

+

{{ $user->nomor_induk ?? 'N/A' }}

Email @@ -200,7 +216,7 @@ class="btn btn-outline-primary rounded-pill ms-md-auto">
Nomor HP -

{{ $user->nomor_hp ?? 'N/A' }}

+

{{ $user->phone ?? 'N/A' }}

Kelas diff --git a/resources/views/profile/partials/update-profile-information-form.blade.php b/resources/views/profile/partials/update-profile-information-form.blade.php index fea16f4..64bb793 100644 --- a/resources/views/profile/partials/update-profile-information-form.blade.php +++ b/resources/views/profile/partials/update-profile-information-form.blade.php @@ -55,43 +55,41 @@ class="form-control @error('email') is-invalid @enderror" value="{{ old('email',
{{-- Bagian Nomor Telepon --}} - @if ($user->nomor_hp) -
- - - @error('nomor_hp') -
{{ $message }}
- @enderror - *Nomor telepon wajib disi -
- @endif +
+ + + @error('phone') +
{{ $message }}
+ @enderror + *Nomor telepon wajib diisi +

{{-- Bagian Info Spesifik Role (Tidak Dapat Diubah) --}} @if ($user->role == 'siswa')
- - + + NISN tidak dapat diubah.
- +
- +
@else {{-- Untuk Guru atau Penjaga Perpus --}}
- - - NIP tidak dapat diubah. + + + ID Kepegawaian tidak dapat diubah.
@endif diff --git a/routes/web.php b/routes/web.php index e77e22d..d8a5c0d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -112,6 +112,7 @@ Route::get('/peminjaman', [AdminPeminjamanController::class, 'index'])->name('peminjaman.index'); Route::get('/peminjaman/tambah', [AdminPeminjamanController::class, 'create'])->name('peminjaman.create'); Route::post('/peminjaman', [AdminPeminjamanController::class, 'store'])->name('peminjaman.store'); + Route::post('/peminjaman/kembali', [AdminPeminjamanController::class, 'kembalikan'])->name('peminjaman.kembali'); Route::get('/denda', [AdminPeminjamanController::class, 'dendaIndex'])->name('denda.index'); Route::post('/denda/sanksi', [AdminPeminjamanController::class, 'berikanSanksi'])->name('denda.sanksi');