TKK_E32230908/resources/views/admin/peminjaman.blade.php

420 lines
19 KiB
PHP

@extends('layouts.anggota')
@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>
/* Menghilangkan scrollbar untuk IE, Edge, dan Firefox */
html, body {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
:root {
--primary: #4f46e5;
--danger: #ef4444;
--slate-200: #e2e8f0;
--slate-900: #0f172a;
--success: #22c55e;
--indigo: #6366f1;
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background-color: #f8fafc;
color: var(--slate-900);
}
.hero-title {
font-weight: 800;
letter-spacing: -0.02em;
color: var(--slate-900);
}
.table-card {
background: white;
border: 1px solid var(--slate-200);
border-radius: 20px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.table thead th {
background-color: #f1f5f9;
padding: 1rem;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
color: #475569;
}
.user-name { font-weight: 700; display: block; }
.clickable-meta {
font-size: 0.75rem;
text-decoration: none;
transition: color 0.2s;
display: inline-flex;
align-items: center;
gap: 4px;
}
.user-email { color: var(--primary); font-weight: 500; }
.user-email:hover { color: #3730a3; text-decoration: underline; }
.contact-link { color: #64748b; font-size: 0.8rem; }
.contact-link:hover { color: var(--slate-900); }
.wa-link:hover { color: #25d366 !important; }
.status-badge {
padding: 6px 12px;
border-radius: 8px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
}
.btn-nav {
padding: 6px 14px;
border-radius: 8px;
font-size: 0.8rem;
font-weight: 600;
transition: all 0.2s;
border: none;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn-setujui { background: var(--slate-900); color: white; }
.btn-setujui:hover { background: var(--primary); transform: translateY(-1px); }
.btn-perpanjang { background: var(--indigo); color: white; }
.btn-perpanjang:hover { background: #4338ca; transform: translateY(-1px); }
.btn-tolak { background: white; color: var(--danger); border: 1px solid var(--danger); }
.btn-tolak:hover { background: var(--danger); color: white; transform: translateY(-1px); }
.btn-print { background: var(--success); color: white; }
.btn-print:hover { background: #16a34a; transform: translateY(-1px); }
</style>
<div class="container-fluid py-4">
<div class="mb-4 px-2 d-flex justify-content-between align-items-end">
<div>
<h2 class="hero-title">Daftar Transaksi Peminjaman</h2>
<p class="text-muted small mb-0">Kelola permohonan peminjaman dan perpanjangan buku secara efisien.</p>
</div>
</div>
@if(session('success'))
<div class="alert alert-success border-0 shadow-sm rounded-4 mb-4 d-flex align-items-center gap-2">
<i class="bi bi-check-circle-fill"></i> {{ session('success') }}
</div>
@endif
<div class="table-card">
<div class="table-responsive">
<table class="table align-middle mb-0">
<thead>
<tr>
<th class="text-center">No</th>
<th>Anggota & Akun</th>
<th>Kontak & Lokasi</th>
<th>Koleksi Buku</th>
<th class="text-center">Timeline</th>
<th class="text-center">Status</th>
<th class="text-center">Navigasi</th>
</tr>
</thead>
<tbody>
@forelse($transactions as $t)
<tr>
<td class="text-center fw-bold text-muted">{{ $loop->iteration }}</td>
<td>
<span class="user-name">{{ $t->anggota->nama ?? '-' }}</span>
<a href="mailto:{{ $t->anggota->user->email ?? '' }}" class="clickable-meta user-email">
<i class="bi bi-envelope"></i> {{ $t->anggota->user->email ?? '-' }}
</a>
<div class="text-muted small">NIP/NIM: {{ $t->anggota->nip_nim ?? '-' }}</div>
</td>
<td>
@php
$phone = $t->anggota->no_hp ?? '';
$cleanPhone = preg_replace('/[^0-9]/', '', $phone);
if(str_starts_with($cleanPhone, '0')) {
$cleanPhone = '62' . substr($cleanPhone, 1);
}
@endphp
<div class="d-flex flex-column gap-1">
<a href="https://wa.me/{{ $cleanPhone }}" target="_blank" class="clickable-meta contact-link wa-link text-decoration-none">
<i class="bi bi-whatsapp text-success"></i> {{ $phone ?: '-' }}
</a>
<div class="address-info small text-muted">
<i class="bi bi-geo-alt text-danger"></i> {{ Str::limit($t->anggota->alamat ?? '-', 25) }}
</div>
</div>
</td>
<td>
<div class="fw-bold" style="max-width: 200px; white-space: normal;">{{ $t->koleksi->title ?? '-' }}</div>
@if($t->surat_pengantar)
<a href="{{ route('admin.peminjaman.download', $t->id) }}" class="text-decoration-none small text-primary fw-600" target="_blank">
<i class="bi bi-file-earmark-pdf"></i> Lihat Dokumen
</a>
@endif
</td>
<td class="text-center">
<div class="small">P: <strong>{{ $t->tanggal_pinjam ? \Carbon\Carbon::parse($t->tanggal_pinjam)->format('d/m/y') : '-' }}</strong></div>
<div class="small text-danger">K: <strong>{{ $t->tanggal_kembali ? \Carbon\Carbon::parse($t->tanggal_kembali)->format('d/m/y') : '-' }}</strong></div>
</td>
<td class="text-center">
@php
$badges = [
'menunggu' => 'bg-warning text-dark',
'perpanjangan_menunggu' => 'bg-info text-white',
'disetujui' => 'bg-info text-white',
'dipinjam' => 'bg-primary text-white',
'dikembalikan' => 'bg-success text-white',
'ditolak' => 'bg-danger text-white'
];
$statusLabel = ($t->status == 'perpanjangan_menunggu') ? 'Butuh Perpanjang' : ucfirst($t->status);
@endphp
<span class="status-badge {{ $badges[$t->status] ?? 'bg-secondary' }}">
{{ $statusLabel }}
</span>
</td>
<td class="text-center">
<div class="d-flex justify-content-center gap-2">
{{-- 1. PERSSETUJUAN PINJAM BARU --}}
@if($t->status == 'menunggu')
<button class="btn-nav btn-setujui shadow-sm"
data-bs-toggle="modal" data-bs-target="#approveModal"
data-id="{{ $t->id }}"
data-title="Setujui Peminjaman"
data-action="/admin/peminjaman/{{ $t->id }}/setujui"
data-pickup="{{ date('Y-m-d') }}"
data-pickuptime="{{ date('H:i') }}"
data-return="{{ \Carbon\Carbon::now()->addDays(7)->format('Y-m-d') }}">
Setujui
</button>
<form action="{{ route('admin.peminjaman.tolak', $t->id) }}" method="POST">
@csrf
<button type="submit" class="btn-nav btn-tolak" onclick="return confirm('Tolak permohonan pinjam ini?')">Tolak</button>
</form>
{{-- 2. PERSETUJUAN PERPANJANGAN --}}
@elseif($t->status == 'perpanjangan_menunggu')
<button class="btn-nav btn-perpanjang shadow-sm"
data-bs-toggle="modal" data-bs-target="#approveModal"
data-id="{{ $t->id }}"
data-title="Konfirmasi Perpanjangan"
data-action="/admin/peminjaman/{{ $t->id }}/setujui-perpanjangan"
data-pickup="{{ \Carbon\Carbon::parse($t->pickup_date)->format('Y-m-d') }}"
data-pickuptime="{{ \Carbon\Carbon::parse($t->pickup_date)->format('H:i') }}"
data-return="{{ \Carbon\Carbon::parse($t->tanggal_kembali)->addDays(7)->format('Y-m-d') }}">
<i class="bi bi-arrow-repeat"></i> Terima
</button>
<form action="{{ route('admin.peminjaman.tolak-perpanjangan', $t->id) }}" method="POST">
@csrf
<button type="submit" class="btn-nav btn-tolak" onclick="return confirm('Tolak permohonan perpanjangan ini?')">Tolak</button>
</form>
{{-- 3. STATUS LAIN --}}
@elseif(in_array($t->status, ['disetujui', 'dipinjam']))
@if($t->status == 'disetujui')
<form action="{{ route('admin.peminjaman.ambil', $t->id) }}" method="POST">
@csrf
<button type="submit" class="btn-nav btn-setujui bg-dark">Ambil</button>
</form>
@endif
<button onclick="printNota('{{ $t->id }}')" class="btn-nav btn-print shadow-sm">
<i class="bi bi-printer"></i> Nota
</button>
@else
<span class="text-muted small">-</span>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center py-5 text-muted">Belum ada data transaksi.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
{{-- MODAL DINAMIS --}}
<div class="modal fade" id="approveModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form id="approveForm" method="POST">
@csrf
<div class="modal-content border-0 shadow-lg rounded-4">
<div class="modal-header border-0">
<h5 class="fw-800 mb-0" id="modalTitle">Lengkapi Data Pinjam</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div class="row g-3">
<div class="col-6">
<label class="form-label small fw-bold">Tanggal Ambil</label>
<input type="date" class="form-control" name="pickup_date" id="pickup_date" required>
</div>
<div class="col-6">
<label class="form-label small fw-bold">Waktu</label>
<input type="time" class="form-control" name="pickup_time" id="pickup_time" required>
</div>
<div class="col-12">
<label class="form-label small fw-bold">Batas Pengembalian Baru</label>
<input type="date" class="form-control border-primary" name="tanggal_kembali" id="tanggal_kembali" required>
</div>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-link text-muted fw-600 text-decoration-none" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-setujui px-4 shadow">Konfirmasi</button>
</div>
</div>
</form>
</div>
</div>
<iframe id="printFrame" style="display:none;"></iframe>
<script>
document.addEventListener('DOMContentLoaded', function () {
var approveModal = document.getElementById('approveModal');
approveModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
// Data pendukung dari atribut tombol
var actionUrl = button.getAttribute('data-action');
var modalTitle = button.getAttribute('data-title');
// Update Form dan Judul secara dinamis
document.getElementById('approveForm').action = actionUrl;
document.getElementById('modalTitle').innerText = modalTitle;
// Reset dan isi input
document.getElementById('pickup_date').value = button.getAttribute('data-pickup');
document.getElementById('pickup_time').value = button.getAttribute('data-pickuptime');
document.getElementById('tanggal_kembali').value = button.getAttribute('data-return');
});
});
function printNota(id) {
const printFrame = document.getElementById('printFrame');
printFrame.src = '/admin/peminjaman/' + id + '/cetak-nota';
printFrame.onload = function() {
printFrame.contentWindow.focus();
printFrame.contentWindow.print();
};
}
</script>
<!-- MQTT Library CDN via WebSockets -->
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// === 1. LOGIKA HANDLING MODAL BOOTSTRAP (EKSISTING) ===
var approveModal = document.getElementById('approveModal');
if (approveModal) {
approveModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var actionUrl = button.getAttribute('data-action');
var modalTitle = button.getAttribute('data-title');
document.getElementById('approveForm').action = actionUrl;
document.getElementById('modalTitle').innerText = modalTitle;
document.getElementById('pickup_date').value = button.getAttribute('data-pickup');
document.getElementById('pickup_time').value = button.getAttribute('data-pickuptime');
document.getElementById('tanggal_kembali').value = button.getAttribute('data-return');
});
}
// === 2. SINKRONISASI MQTT DASHBOARD ADMIN (REAL-TIME UPDATE) ===
console.log("=== 🔍 NAVASENA ADMIN MQTT MONITOR ===");
// Mengambil IP/Domain host tempat Laragon/Mosquitto berjalan secara dinamis
const brokerHost = window.location.hostname;
const brokerUrl = `ws://${brokerHost}:9001`;
// Menggunakan Wildcard (+) agar Admin bisa memonitor transaksi dari SELURUH User/Anggota
const topicTarget = 'peminjaman/user/+';
console.log(`🔌 Admin mencoba koneksi ke Broker: ${brokerUrl}`);
console.log(`📌 Admin memonitor seluruh user via topik: ${topicTarget}`);
const client = mqtt.connect(brokerUrl, {
clientId: 'nawasena_admin_monitor_' + 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("✅ ADMIN BERHASIL TERHUBUNG ke Mosquitto via WebSockets!");
client.subscribe(topicTarget, function (err) {
if (err) {
console.error("❌ Admin Gagal Subscribe ke topik wildcard:", err);
} else {
console.log(`📡 Admin Berhasil Subscribe! Memantau sinyal peminjaman/pengembalian seluruh anggota...`);
}
});
});
// Event saat menerima pesan MQTT dari user mana pun
client.on('message', function (topic, message) {
console.log(`📥 Sinyal Transaksi Masuk di topik [${topic}]:`, message.toString());
// Validasi struktur topik (Memastikan berasal dari rumpun peminjaman/user/)
if (topic.startsWith('peminjaman/user/')) {
// Ekstrak ID Anggota dari topik jika sewaktu-waktu Anda butuh debug (contoh: peminjaman/user/5 -> id = 5)
const scannedAnggotaId = topic.split('/')[2];
try {
const payload = JSON.parse(message.toString());
console.log(`📦 Data JSON Terurai untuk Anggota ID [${scannedAnggotaId}]:`, payload);
// Notifikasi Alert untuk Admin sebelum refresh (bisa dimatikan jika mengganggu produksi)
alert(`🔔 Real-time Info: Transaksi Anggota ID ${scannedAnggotaId} (${payload.status || 'Update'}) terdeteksi! Memperbarui data tabel admin...`);
// Eksekusi Reload Tabel Admin secara Instan
window.location.reload();
} catch (e) {
console.error("❌ Payload dari Hardware bukan JSON yang valid. Mencoba refresh paksa...");
alert(`🔔 Sinyal transaksi masuk (Non-JSON) dari Anggota ID ${scannedAnggotaId}. Memuat ulang halaman admin...`);
window.location.reload();
}
}
});
// Event jika koneksi gagal/error
client.on('error', function (err) {
console.error("🔴 Admin MQTT Connection Error:", err);
});
client.on('close', function() {
console.warn("⚠️ Koneksi MQTT Admin ke Broker terputus.");
});
});
function printNota(id) {
const printFrame = document.getElementById('printFrame');
printFrame.src = '/admin/peminjaman/' + id + '/cetak-nota';
printFrame.onload = function() {
printFrame.contentWindow.focus();
printFrame.contentWindow.print();
};
}
</script>
@endsection