408 lines
21 KiB
PHP
408 lines
21 KiB
PHP
@extends('layouts.koleksi')
|
|
|
|
@section('title', 'Manajemen Koleksi')
|
|
|
|
@section('content')
|
|
@php
|
|
use Picqer\Barcode\BarcodeGeneratorPNG;
|
|
@endphp
|
|
|
|
<!-- TOOLBAR PENCARIAN -->
|
|
<div class="card border-0 shadow-sm mb-4 search-toolbar">
|
|
<div class="card-body p-3">
|
|
<form method="GET" action="{{ route('admin.koleksi.index') }}" class="row g-3 align-items-center">
|
|
<div class="col-md-5">
|
|
<div class="search-box">
|
|
<i class="fas fa-search search-icon"></i>
|
|
<input type="text" name="search" class="form-control form-control-lg border-0 bg-light"
|
|
placeholder="Cari judul, klasifikasi, atau kode eksemplar..." value="{{ request('search') }}">
|
|
</div>
|
|
</div>
|
|
<div class="col-auto d-flex gap-2">
|
|
<button class="btn btn-primary-custom px-4 h-100" type="submit">
|
|
<i class="fas fa-filter me-2"></i>Cari
|
|
</button>
|
|
|
|
@if(request('search'))
|
|
<a href="{{ route('admin.koleksi.index') }}" class="btn btn-outline-danger px-4 h-100 d-inline-flex align-items-center justify-content-center fw-semibold text-nowrap rounded-3" title="Batal Cari">
|
|
<i class="fas fa-undo me-2"></i>Batal
|
|
</a>
|
|
@endif
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NOTIFIKASI SUKSES / GAGAL -->
|
|
@if (session('success'))
|
|
<div class="alert alert-custom-success alert-dismissible fade show mb-4 border-0 shadow-sm" role="alert">
|
|
<div class="d-flex align-items-center">
|
|
<div class="alert-icon-circle me-3"><i class="fas fa-check"></i></div>
|
|
<div>
|
|
<strong class="d-block">Berhasil!</strong>
|
|
<span class="small">{{ session('success') }}</span>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
@endif
|
|
|
|
@if (session('error'))
|
|
<div class="alert alert-danger alert-dismissible fade show mb-4 border-0 shadow-sm" role="alert">
|
|
<div class="d-flex align-items-center">
|
|
<div class="alert-icon-circle bg-danger me-3"><i class="fas fa-exclamation-triangle text-white"></i></div>
|
|
<div>
|
|
<strong class="d-block">Gagal!</strong>
|
|
<span class="small">{{ session('error') }}</span>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- FORM UTAMA DENGAN TABEL SEJAJAR -->
|
|
<form id="bulkActionForm" method="POST">
|
|
@csrf
|
|
<div class="card border-0 shadow-sm overflow-hidden main-table-card">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center" width="60">NO</th>
|
|
<th>DETAIL KOLEKSI</th>
|
|
<th class="text-center" width="140">KLASIFIKASI</th>
|
|
<th class="text-center" width="160">BARCODE & KODE</th>
|
|
<th class="text-center" width="160">AKTIVITAS</th>
|
|
<th class="text-center" width="150">PILIH CETAK/HAPUS</th>
|
|
<th class="text-center pe-4" width="120">OPSI</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@php $no = $results->firstItem(); @endphp
|
|
@forelse($results as $koleksi)
|
|
<tr>
|
|
<td class="text-center fw-bold text-muted small">{{ $no++ }}</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
@if($koleksi->image && Storage::disk('public')->exists($koleksi->image))
|
|
<div class="img-wrapper me-3">
|
|
<img src="{{ Storage::url($koleksi->image) }}" class="book-cover shadow-sm">
|
|
</div>
|
|
@else
|
|
<div class="book-placeholder me-3 shadow-sm">
|
|
<i class="fas fa-book text-secondary"></i>
|
|
</div>
|
|
@endif
|
|
<div class="info-content">
|
|
<div class="fw-bold text-dark text-truncate mb-0" style="max-width: 250px;" title="{{ $koleksi->title }}">
|
|
{{ $koleksi->title ?? 'Judul Tidak Tersedia' }}
|
|
</div>
|
|
<span class="text-muted small fw-medium">ISBN: {{ $koleksi->isbn_issn ?? 'N/A' }}</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
<span class="badge-custom-primary">{{ $koleksi->classification ?? '-' }}</span>
|
|
</td>
|
|
<td class="text-center">
|
|
<div class="barcode-wrapper mb-1">
|
|
@if($koleksi->kode_eksemplar)
|
|
@php
|
|
$barcodeGen = new Picqer\Barcode\BarcodeGeneratorPNG();
|
|
$barcodeImg = base64_encode($barcodeGen->getBarcode($koleksi->kode_eksemplar, $barcodeGen::TYPE_CODE_128));
|
|
@endphp
|
|
<img src="data:image/png;base64,{{ $barcodeImg }}" class="barcode-img">
|
|
@else
|
|
<span class="text-muted smaller">N/A</span>
|
|
@endif
|
|
</div>
|
|
<code class="text-primary-dark small">{{ $koleksi->kode_eksemplar }}</code>
|
|
</td>
|
|
<td class="text-center">
|
|
<div class="activity-log">
|
|
<div class="small text-muted"><i class="far fa-calendar-alt me-1"></i> {{ $koleksi->input_date ? \Carbon\Carbon::parse($koleksi->input_date)->format('d/m/Y') : '-' }}</div>
|
|
<div class="small fw-bold text-primary-custom mt-1"><i class="fas fa-sync-alt me-1"></i> {{ $koleksi->last_update ? \Carbon\Carbon::parse($koleksi->last_update)->diffForHumans() : '-' }}</div>
|
|
</div>
|
|
</td>
|
|
|
|
<!-- KOLOM CHECKBOX (DIJAMIN LURUS TEPAT DI TENGAH) -->
|
|
<td class="text-center">
|
|
@if($koleksi->kode_eksemplar)
|
|
<div class="d-flex justify-content-center align-items-center" style="min-height: 40px;">
|
|
<input type="checkbox" name="kode_eksemplar[]" value="{{ $koleksi->kode_eksemplar }}" class="form-check-input row-checkbox custom-checkbox m-0">
|
|
</div>
|
|
@else
|
|
<div class="d-flex justify-content-center align-items-center" style="min-height: 40px;">
|
|
<span class="text-muted small">-</span>
|
|
</div>
|
|
@endif
|
|
</td>
|
|
|
|
<!-- KOLOM OPSI EDIT (SEJAJAR DAN TIDAK AKAN TURUN KE BAWAH) -->
|
|
<td class="text-center pe-4">
|
|
<div class="d-flex justify-content-center align-items-center" style="min-height: 40px;">
|
|
<a href="{{ route('admin.koleksi.edit', ['kode_eksemplar' => $koleksi->kode_eksemplar]) }}"
|
|
class="btn btn-action-edit text-nowrap m-0" title="Ubah Data">
|
|
<i class="fas fa-pen-nib me-1"></i><span>Edit Koleksi</span>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="7" class="text-center py-5">
|
|
<div class="empty-state py-4">
|
|
<i class="fas fa-folder-open fa-3x text-light mb-3"></i>
|
|
<h5 class="text-muted">Data tidak ditemukan</h5>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- PAGINASI -->
|
|
<div class="card-footer bg-white border-0 py-4">
|
|
<div class="d-flex justify-content-center">
|
|
{{ $results->links('pagination::bootstrap-5') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FLOATING BAR UNTUK MULTIPLE ACTION -->
|
|
<div id="floatingActionBar" class="floating-action-bar d-none">
|
|
<div class="d-flex align-items-center justify-content-between px-4 py-3 bg-white shadow-lg rounded-4 border">
|
|
<div>
|
|
<i class="fas fa-info-circle text-primary me-2"></i>
|
|
<span class="fw-bold"><span id="selectedCount">0</span> terpilih</span>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" id="selectAllPage" class="btn btn-outline-primary rounded-3 btn-sm fw-semibold">Pilih Semua di Halaman Ini</button>
|
|
<button type="button" id="clearSelection" class="btn btn-light rounded-3 btn-sm fw-semibold">Batal</button>
|
|
|
|
<button type="button" id="submitBulkDeleteBtn" class="btn btn-action-delete rounded-3 btn-sm px-4 fw-semibold" disabled>
|
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
|
</button>
|
|
|
|
<button type="button" id="submitBulkPrintBtn" class="btn btn-primary-custom rounded-3 btn-sm px-4" disabled>
|
|
<i class="fas fa-print me-2"></i>Cetak Label Multiple
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<style>
|
|
/* Desain Dasar */
|
|
html, body { -ms-overflow-style: none; scrollbar-width: none; }
|
|
:root {
|
|
--primary: #4361ee;
|
|
--primary-light: #eef2ff;
|
|
--primary-dark: #3730a3;
|
|
--success-bg: #ecfdf5;
|
|
--success-text: #065f46;
|
|
--bg-light: #f8fafc;
|
|
--text-main: #1e293b;
|
|
--text-muted: #64748b;
|
|
--btn-edit-bg: #e0e7ff;
|
|
--btn-edit-text: #4338ca;
|
|
--btn-delete-bg: #fee2e2;
|
|
--btn-delete-text: #b91c1c;
|
|
}
|
|
body { background-color: var(--bg-light); font-family: 'Inter', sans-serif; color: var(--text-main); }
|
|
|
|
/* Desain Header Tabel Presisi */
|
|
table thead th {
|
|
background: #f8fafc;
|
|
color: var(--text-muted);
|
|
font-size: 0.70rem !important;
|
|
font-weight: 800 !important;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
padding: 1rem 0.5rem !important;
|
|
border-bottom: 2px solid #e2e8f0 !important;
|
|
vertical-align: middle !important;
|
|
}
|
|
|
|
.search-toolbar { border-radius: 16px; }
|
|
.search-box { position: relative; }
|
|
.search-icon { position: absolute; left: 1.25rem; top: 50%; transform: translateY(-50%); color: var(--text-muted); z-index: 5; }
|
|
.search-box .form-control { padding-left: 3.2rem; border-radius: 12px; font-size: 0.95rem; }
|
|
|
|
.btn-primary-custom { background: var(--primary); color: white; border-radius: 12px; font-weight: 600; border: none; }
|
|
.btn-primary-custom:disabled { background: #cbd5e1; color: #94a3b8; cursor: not-allowed; }
|
|
|
|
/* Ukuran Custom Checkbox */
|
|
.custom-checkbox { width: 1.25rem; height: 1.25rem; border-radius: 6px !important; cursor: pointer; }
|
|
.main-table-card { border-radius: 16px; }
|
|
table tbody tr:hover { background-color: #f1f5f9; }
|
|
|
|
.book-cover { width: 48px; height: 68px; border-radius: 8px; object-fit: cover; }
|
|
.book-placeholder { width: 48px; height: 68px; border-radius: 8px; background: #f1f5f9; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; }
|
|
.badge-custom-primary { background: var(--primary-light); color: var(--primary); padding: 6px 14px; border-radius: 8px; font-size: 0.85rem; font-weight: 700; display: inline-block; }
|
|
.barcode-img { width: 110px; height: 35px; border-radius: 4px; filter: contrast(1.1); mix-blend-mode: multiply; }
|
|
|
|
/* Tombol Aksi */
|
|
.btn-action-edit, .btn-action-delete { padding: 0.5rem 1rem; font-size: 0.825rem; font-weight: 600; border-radius: 8px !important; border: none; transition: 0.2s ease; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; }
|
|
.btn-action-edit { background-color: var(--btn-edit-bg); color: var(--btn-edit-text); }
|
|
.btn-action-delete { background-color: var(--btn-delete-bg); color: var(--btn-delete-text); }
|
|
.btn-action-delete:disabled { background-color: #cbd5e1; color: #94a3b8; cursor: not-allowed; }
|
|
|
|
/* Floating Action Bar */
|
|
.floating-action-bar { position: fixed; bottom: 25px; left: 50%; transform: translateX(-50%); width: 90%; max-width: 850px; z-index: 1050; }
|
|
.pagination .page-link { border: none; color: var(--text-muted); border-radius: 10px; font-weight: 600; padding: 10px 16px; }
|
|
.pagination .page-item.active .page-link { background: var(--primary); color: white; }
|
|
</style>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const checkboxes = document.querySelectorAll('.row-checkbox');
|
|
const floatingBar = document.getElementById('floatingActionBar');
|
|
const selectedCountSpan = document.getElementById('selectedCount');
|
|
const selectAllPageBtn = document.getElementById('selectAllPage');
|
|
const clearSelectionBtn = document.getElementById('clearSelection');
|
|
|
|
const submitBulkPrintBtn = document.getElementById('submitBulkPrintBtn');
|
|
const submitBulkDeleteBtn = document.getElementById('submitBulkDeleteBtn');
|
|
const actionForm = document.getElementById('bulkActionForm');
|
|
|
|
let isStandBy = false;
|
|
|
|
function getStored() { return JSON.parse(localStorage.getItem('selected_koleksi') || '[]'); }
|
|
function setStored(arr) { localStorage.setItem('selected_koleksi', JSON.stringify(arr)); }
|
|
|
|
let selected = getStored();
|
|
if (selected.length > 0) {
|
|
isStandBy = true;
|
|
}
|
|
|
|
checkboxes.forEach(cb => {
|
|
if (selected.includes(cb.value)) cb.checked = true;
|
|
|
|
cb.addEventListener('change', function() {
|
|
let s = getStored();
|
|
if(this.checked) {
|
|
if(!s.includes(this.value)) s.push(this.value);
|
|
isStandBy = true;
|
|
} else {
|
|
s = s.filter(id => id !== this.value);
|
|
}
|
|
setStored(s);
|
|
updateFloatingBar();
|
|
});
|
|
});
|
|
|
|
function injectHiddenInputs(formElement, storedItems) {
|
|
document.querySelectorAll('.hidden-persist').forEach(el => el.remove());
|
|
storedItems.forEach(val => {
|
|
let input = document.createElement('input');
|
|
input.type = 'hidden';
|
|
input.name = 'kode_eksemplar[]';
|
|
input.value = val;
|
|
input.className = 'hidden-persist';
|
|
formElement.appendChild(input);
|
|
});
|
|
}
|
|
|
|
submitBulkPrintBtn.addEventListener('click', function() {
|
|
const s = getStored();
|
|
if(s.length === 0) { alert('Pilih minimal 1 item.'); return; }
|
|
if(s.length > 20) { alert('Maksimal 20 item terpilih untuk cetak.'); return; }
|
|
|
|
actionForm.action = "{{ route('admin.koleksi.barcode_multiple') }}";
|
|
actionForm.target = "_blank";
|
|
|
|
const methodInput = actionForm.querySelector('input[name="_method"]');
|
|
if(methodInput) methodInput.remove();
|
|
|
|
injectHiddenInputs(actionForm, s);
|
|
actionForm.submit();
|
|
});
|
|
|
|
submitBulkDeleteBtn.addEventListener('click', function() {
|
|
const s = getStored();
|
|
if(s.length === 0) { alert('Pilih minimal 1 item untuk dihapus.'); return; }
|
|
|
|
if (confirm(`Apakah Anda yakin ingin menghapus permanen ${s.length} data koleksi terpilih?`)) {
|
|
actionForm.action = "{{ route('admin.koleksi.destroy_multiple') }}";
|
|
actionForm.target = "_self";
|
|
|
|
let methodInput = actionForm.querySelector('input[name="_method"]');
|
|
if(!methodInput) {
|
|
methodInput = document.createElement('input');
|
|
methodInput.type = 'hidden';
|
|
methodInput.name = '_method';
|
|
methodInput.value = 'DELETE';
|
|
actionForm.appendChild(methodInput);
|
|
}
|
|
|
|
injectHiddenInputs(actionForm, s);
|
|
localStorage.removeItem('selected_koleksi');
|
|
actionForm.submit();
|
|
}
|
|
});
|
|
|
|
function isAllPageChecked() {
|
|
if (checkboxes.length === 0) return false;
|
|
let s = getStored();
|
|
return Array.from(checkboxes).every(cb => s.includes(cb.value));
|
|
}
|
|
|
|
function updateFloatingBar() {
|
|
const checkedCount = getStored().length;
|
|
selectedCountSpan.textContent = checkedCount;
|
|
|
|
submitBulkPrintBtn.disabled = (checkedCount === 0);
|
|
submitBulkDeleteBtn.disabled = (checkedCount === 0);
|
|
|
|
if (isStandBy || checkedCount > 0) {
|
|
floatingBar.classList.remove('d-none');
|
|
} else {
|
|
floatingBar.classList.add('d-none');
|
|
}
|
|
|
|
if (isAllPageChecked()) {
|
|
selectAllPageBtn.textContent = 'Hapus Pilihan';
|
|
selectAllPageBtn.classList.replace('btn-outline-primary', 'btn-outline-danger');
|
|
} else {
|
|
selectAllPageBtn.textContent = 'Pilih Semua di Halaman Ini';
|
|
selectAllPageBtn.classList.replace('btn-outline-danger', 'btn-outline-primary');
|
|
}
|
|
}
|
|
|
|
updateFloatingBar();
|
|
|
|
clearSelectionBtn.addEventListener('click', function () {
|
|
localStorage.removeItem('selected_koleksi');
|
|
checkboxes.forEach(cb => cb.checked = false);
|
|
isStandBy = false;
|
|
updateFloatingBar();
|
|
});
|
|
|
|
selectAllPageBtn.addEventListener('click', function () {
|
|
let s = getStored();
|
|
|
|
if (isAllPageChecked()) {
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = false;
|
|
s = s.filter(id => id !== checkbox.value);
|
|
});
|
|
isStandBy = true;
|
|
setStored(s);
|
|
updateFloatingBar();
|
|
} else {
|
|
for (let checkbox of checkboxes) {
|
|
if (!s.includes(checkbox.value)) {
|
|
checkbox.checked = true;
|
|
s.push(checkbox.value);
|
|
}
|
|
}
|
|
isStandBy = true;
|
|
setStored(s);
|
|
updateFloatingBar();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
@endsection |