416 lines
16 KiB
PHP
416 lines
16 KiB
PHP
@extends('layouts.dashboard')
|
|
|
|
@section('content')
|
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
|
|
|
<style>
|
|
/* 1. Menghilangkan Scrollbar Secara Visual */
|
|
html, body {
|
|
scrollbar-width: none;
|
|
-ms-overflow-style: none;
|
|
}
|
|
::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
:root {
|
|
--perpusnas-blue: #004d91;
|
|
--perpusnas-dark: #002d5a;
|
|
--slate-50: #f8fafc;
|
|
--slate-100: #f1f5f9;
|
|
--slate-200: #e2e8f0;
|
|
--slate-600: #475569;
|
|
--slate-900: #0f172a;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Plus Jakarta Sans', sans-serif;
|
|
background-color: #f4f7fa;
|
|
color: var(--slate-900);
|
|
}
|
|
|
|
.container-custom {
|
|
padding: 2rem 3rem;
|
|
}
|
|
|
|
/* Welcome Card - Perpusnas Style */
|
|
.welcome-card {
|
|
background: linear-gradient(135deg, var(--perpusnas-blue) 0%, var(--perpusnas-dark) 100%);
|
|
color: white;
|
|
border-radius: 16px;
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
border: none;
|
|
box-shadow: 0 10px 25px rgba(0, 77, 145, 0.2);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.welcome-card::after {
|
|
content: "";
|
|
position: absolute;
|
|
right: -30px;
|
|
top: -30px;
|
|
width: 150px;
|
|
height: 150px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.hero-title {
|
|
font-weight: 800;
|
|
color: var(--slate-900);
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
/* Note Box - Custom Style */
|
|
.note-box {
|
|
background: rgba(255, 251, 235, 0.8);
|
|
backdrop-filter: blur(4px);
|
|
border-left: 4px solid #f59e0b;
|
|
color: #92400e;
|
|
padding: 1.25rem 1.5rem;
|
|
border-radius: 12px;
|
|
font-size: 0.85rem;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 15px;
|
|
margin-bottom: 2rem;
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.03);
|
|
}
|
|
|
|
.note-box i {
|
|
font-size: 1.2rem;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* Table Styling */
|
|
.custom-table-card {
|
|
background: white;
|
|
border: 1px solid var(--slate-200);
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.table thead th {
|
|
background-color: #f8fafc;
|
|
padding: 1rem;
|
|
font-size: 0.7rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
color: var(--slate-600);
|
|
border-bottom: 2px solid var(--slate-100);
|
|
}
|
|
|
|
.table tbody td {
|
|
padding: 1.25rem 1rem;
|
|
border-bottom: 1px solid #f8fafc;
|
|
}
|
|
|
|
.status-pill {
|
|
padding: 6px 12px;
|
|
border-radius: 8px;
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.btn-feedback-trigger {
|
|
font-size: 0.65rem;
|
|
font-weight: 800;
|
|
color: var(--perpusnas-blue);
|
|
text-decoration: none;
|
|
margin-top: 6px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 8px;
|
|
border: 1px solid var(--slate-200);
|
|
border-radius: 6px;
|
|
background: white;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-feedback-trigger:hover {
|
|
background: var(--perpusnas-blue);
|
|
color: white;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn-action {
|
|
border-radius: 8px;
|
|
padding: 8px 16px;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-perpanjang {
|
|
background-color: #f59e0b;
|
|
color: white;
|
|
border: none;
|
|
}
|
|
|
|
.badge-count {
|
|
background: rgba(255,255,255,0.2);
|
|
backdrop-filter: blur(5px);
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 10px;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.modal-content-glass {
|
|
background: rgba(255, 255, 255, 0.98);
|
|
backdrop-filter: blur(15px);
|
|
border: none;
|
|
border-radius: 24px;
|
|
}
|
|
</style>
|
|
|
|
<div class="container-custom">
|
|
{{-- Header Card --}}
|
|
<div class="welcome-card d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<h2 class="fw-800 mb-2">Daftar Transaksi Pustaka Nawasena</h2>
|
|
<p class="mb-0 opacity-75">Kelola peminjaman buku Anda dengan mudah dan efisien.</p>
|
|
</div>
|
|
<div class="text-end d-none d-lg-block">
|
|
<div class="badge-count">
|
|
<i class="bi bi-journal-text me-2"></i><strong>{{ $peminjaman->count() }}</strong> Transaksi
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Note Box: Ketentuan Penting --}}
|
|
<div class="note-box">
|
|
<i class="bi bi-exclamation-circle-fill text-warning"></i>
|
|
<div>
|
|
<span class="fw-800 d-block mb-1">Ketentuan Peminjaman:</span>
|
|
<ul class="mb-0 ps-3">
|
|
<li>Anggota <strong>wajib membawa Surat Pengantar</strong> saat meminjam buku di lokasi.</li>
|
|
<li>Harap mengembalikan buku <strong>tepat waktu</strong> sesuai tenggat yang ditentukan untuk menghindari denda atau pemblokiran akun.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row align-items-center mb-4">
|
|
<div class="col-md-8">
|
|
<h3 class="hero-title mb-1">Peminjaman Saya</h3>
|
|
<p class="text-muted small mb-0">Pastikan status peminjaman Anda terpantau secara berkala.</p>
|
|
</div>
|
|
</div>
|
|
|
|
@if(session('success'))
|
|
<div class="alert alert-success border-0 shadow-sm rounded-3 mb-4">
|
|
<i class="bi bi-check-circle-fill me-2"></i>{{ session('success') }}
|
|
</div>
|
|
@endif
|
|
|
|
<div class="custom-table-card">
|
|
<div class="table-responsive">
|
|
<table class="table align-middle mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center" width="60">No</th>
|
|
<th>Koleksi Buku</th>
|
|
<th class="text-center">Tenggat Waktu</th>
|
|
<th class="text-center">Status</th>
|
|
<th class="text-center" width="200">Kontrol</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($peminjaman as $item)
|
|
@php
|
|
$today = \Carbon\Carbon::now();
|
|
$due = $item->tanggal_kembali ? \Carbon\Carbon::parse($item->tanggal_kembali) : null;
|
|
$isLate = $due && $today->gt($due) && ($item->status === 'dipinjam' || $item->status === 'perpanjangan_menunggu');
|
|
|
|
$showFeedback = (($item->status === 'dikembalikan' || $item->status === 'kembali') && !empty($item->admin_feedback));
|
|
@endphp
|
|
<tr>
|
|
<td class="text-center fw-bold text-muted">{{ $loop->iteration }}</td>
|
|
<td>
|
|
<div class="fw-bold text-dark">{{ $item->koleksi->title ?? '-' }}</div>
|
|
<div class="text-muted small" style="font-size: 0.75rem;">
|
|
<i class="bi bi-upc-scan me-1"></i>{{ $item->koleksi->biblio_id ?? '-' }}
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
<div class="small fw-700">Tenggat:</div>
|
|
<div class="{{ $isLate ? 'text-danger fw-800' : 'text-muted' }} small">
|
|
{{ $item->tanggal_kembali ? \Carbon\Carbon::parse($item->tanggal_kembali)->format('d M Y') : '-' }}
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
@php
|
|
$statusStyles = [
|
|
'menunggu' => ['bg' => '#fef3c7', 'text' => '#92400e', 'icon' => 'bi-hourglass-split'],
|
|
'dipinjam' => ['bg' => '#eff6ff', 'text' => '#1e40af', 'icon' => 'bi-book-half'],
|
|
'kembali' => ['bg' => '#f0fdf4', 'text' => '#15803d', 'icon' => 'bi-check-all'],
|
|
'dikembalikan' => ['bg' => '#f0fdf4', 'text' => '#15803d', 'icon' => 'bi-check-all'],
|
|
'ditolak' => ['bg' => '#fef2f2', 'text' => '#991b1b', 'icon' => 'bi-x-circle'],
|
|
'perpanjangan_menunggu' => ['bg' => '#f5f3ff', 'text' => '#6d28d9', 'icon' => 'bi-arrow-repeat'],
|
|
];
|
|
$st = $isLate ? ['bg' => '#fff1f2', 'text' => '#be123c', 'icon' => 'bi-exclamation-octagon'] : ($statusStyles[$item->status] ?? ['bg' => '#f1f5f9', 'text' => '#475569', 'icon' => 'bi-dash']);
|
|
@endphp
|
|
|
|
<div class="d-flex flex-column align-items-center">
|
|
<span class="status-pill" style="background-color: {{ $st['bg'] }}; color: {{ $st['text'] }}">
|
|
<i class="bi {{ $st['icon'] }}"></i>
|
|
{{ $isLate ? 'Terlambat' : ucfirst(str_replace('_', ' ', $item->status)) }}
|
|
</span>
|
|
|
|
@if($showFeedback)
|
|
<a href="javascript:void(0)"
|
|
class="btn-feedback-trigger"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#feedbackModal{{ $item->id }}">
|
|
<i class="bi bi-chat-left-text-fill"></i> Feedback Admin
|
|
</a>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
@if($item->status === 'dipinjam')
|
|
@if(!($item->perpanjangan_diajukan))
|
|
@if($isLate)
|
|
<span class="badge bg-danger rounded-pill px-3 py-2" style="font-size: 0.65rem;">Sistem Terkunci</span>
|
|
@else
|
|
<a href="{{ route('user.peminjaman.perpanjang.form', $item->id) }}" class="btn btn-action btn-perpanjang text-white w-100">
|
|
Perpanjang
|
|
</a>
|
|
@endif
|
|
@else
|
|
<span class="badge bg-light text-primary border px-3 py-2">Perpanjang Disetujui</span>
|
|
@endif
|
|
@elseif($item->status === 'kembali' || $item->status === 'dikembalikan')
|
|
<a href="{{ route('user.rating.form', $item->id) }}" class="btn btn-action btn-success w-100">
|
|
Beri Rating
|
|
</a>
|
|
@else
|
|
<span class="text-muted small">-</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
|
|
{{-- Modal Feedback --}}
|
|
@if($showFeedback)
|
|
<div class="modal fade" id="feedbackModal{{ $item->id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content modal-content-glass shadow-lg">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-800"><i class="bi bi-chat-square-quote me-2 text-primary"></i>Catatan Pengembalian</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body py-4 text-center">
|
|
<div class="p-4 rounded-4 bg-light border-0 mb-3">
|
|
<p class="text-dark mb-0 fw-600" style="line-height: 1.6; font-style: italic;">
|
|
"{{ $item->admin_feedback }}"
|
|
</p>
|
|
</div>
|
|
<p class="text-muted small mb-0">Catatan ini dikirim oleh admin Bakorwil III Malang saat verifikasi.</p>
|
|
</div>
|
|
<div class="modal-footer border-0 pt-0">
|
|
<button type="button" class="btn btn-primary w-100 btn-action py-2" data-bs-dismiss="modal" style="background: var(--perpusnas-blue); border: none;">Tutup</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="text-center py-5 text-muted">
|
|
<i class="bi bi-inbox fs-1 d-block mb-2 opacity-25"></i>
|
|
Belum ada riwayat peminjaman buku.
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- SCRIPT DEBUGGING MQTT -->
|
|
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Ambil ID Anggota dari session Laravel
|
|
const currentUserId = "{{ Auth::user()->anggota->id ?? '' }}";
|
|
|
|
console.log("=== 🔍 NAVASENA MQTT DEBUGGER ===");
|
|
console.log("ID Anggota Terdeteksi:", currentUserId);
|
|
|
|
if (!currentUserId) {
|
|
alert("⚠️ Debug: ID Anggota tidak terbaca oleh Blade. Pastikan session login aktif!");
|
|
return;
|
|
}
|
|
|
|
// Ambil IP Host tempat Laragon/Mosquitto berjalan secara dinamis
|
|
const brokerHost = window.location.hostname; // Otomatis mendeteksi jika diakses via IP/Domain lokal
|
|
const brokerUrl = `ws://${brokerHost}:9001`;
|
|
const topicTarget = `peminjaman/user/${currentUserId}`;
|
|
|
|
console.log(`🔌 Mencoba koneksi ke Broker: ${brokerUrl}`);
|
|
console.log(`📌 Target Topik Subs: ${topicTarget}`);
|
|
|
|
const client = mqtt.connect(brokerUrl, {
|
|
clientId: 'nawasena_web_' + Math.random().toString(16).substr(2, 8),
|
|
clean: true,
|
|
connectTimeout: 5000
|
|
});
|
|
|
|
// Event saat berhasil terhubung ke broker via WebSockets
|
|
client.on('connect', function () {
|
|
console.log("✅ BERHASIL TERHUBUNG ke Mosquitto via WebSockets!");
|
|
|
|
client.subscribe(topicTarget, function (err) {
|
|
if (err) {
|
|
console.error("❌ Gagal Subscribe ke topik:", err);
|
|
} else {
|
|
console.log(`📡 Berhasil Subscribe! Menunggu sinyal dari hardware di topik: [${topicTarget}]`);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Event saat menerima pesan MQTT
|
|
client.on('message', function (topic, message) {
|
|
console.log(`📥 Sinyal Masuk di topik [${topic}]:`, message.toString());
|
|
|
|
if (topic === topicTarget) {
|
|
try {
|
|
const payload = JSON.parse(message.toString());
|
|
console.log("📦 Data JSON berhasil di-parse:", payload);
|
|
|
|
// Memunculkan alert bawaan browser agar kelihatan meski toast error
|
|
alert(`🔔 Real-time Update: Sinyal ${payload.status} diterima! Halaman akan memuat ulang...`);
|
|
|
|
// Eksekusi Refresh Halaman
|
|
window.location.reload();
|
|
|
|
} catch (e) {
|
|
console.error("❌ Payload bukan JSON yang valid. Mencoba refresh paksa...");
|
|
alert("🔔 Sinyal masuk non-JSON. Memuat ulang halaman...");
|
|
window.location.reload();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Event jika koneksi gagal/error
|
|
client.on('error', function (err) {
|
|
console.error("🔴 MQTT Connection Error:", err);
|
|
});
|
|
|
|
client.on('close', function() {
|
|
console.warn("⚠️ Koneksi MQTT ke Broker terputus (Closed).");
|
|
});
|
|
});
|
|
</script>
|
|
@endsection
|
|
|