Update project and prepare for JTI Polije upload

This commit is contained in:
wardhatul1765 2026-06-17 13:42:00 +07:00
parent 5f30be4811
commit 17c93a4252
22 changed files with 1103 additions and 408 deletions

26
.htaccess Normal file
View File

@ -0,0 +1,26 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /E31230887/
# 1. Handle Storage (Internal Rewrite)
# Mapping /storage/... ke /public/storage/... secara internal
RewriteRule ^storage/(.*)$ public/storage/$1 [L]
# 2. Handle Other Static Assets (img, build, images, favicon, robots.txt)
# Menghindari redirect R=302 agar URL tetap bersih tanpa /public/
RewriteCond %{REQUEST_URI} !^/E31230887/public/
RewriteRule ^(img|build|images|favicon\.ico|robots\.txt)(.*)$ public/$1$2 [L]
# 3. Handle Laravel Front Controller
# Jika file fisik tidak ada, lempar ke index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
</IfModule>
Options -Indexes
<FilesMatch "^\.env">
Order allow,deny
Deny from all
</FilesMatch>

View File

@ -117,41 +117,41 @@ private function tentukanKoordinatRak($nomor_panggil)
return match (true) {
$kode_utama >= 0 && $kode_utama <= 99 => match (true) {
$kode_utama <= 19 => ['x' => 13.00, 'y' => 22.00], // Rak 01
$kode_utama <= 50 => ['x' => 18.00, 'y' => 22.00], // Rak 02
default => ['x' => 25.00, 'y' => 22.00], // Rak 03-05
$kode_utama <= 19 => ['x' => 12.00, 'y' => 13.00], // Rak 01
$kode_utama <= 50 => ['x' => 17.00, 'y' => 13.00], // Rak 02
default => ['x' => 22.00, 'y' => 13.00], // Rak 03-05
},
$kode_utama >= 100 && $kode_utama <= 199 => match (true) {
$kode_utama <= 150 => ['x' => 43.00, 'y' => 20.00], // Rak 06-10
default => ['x' => 57.00, 'y' => 20.00], // Rak 11-14
$kode_utama <= 150 => ['x' => 35.00, 'y' => 13.00], // Rak 06-10
default => ['x' => 35.00, 'y' => 17.00], // Rak 11-14
},
$kode_utama >= 200 && $kode_utama <= 299 => match (true) {
$kode_utama == 297 => ['x' => 25.00, 'y' => 38.00], // Rak 25-32 (Islam)
default => ['x' => 13.00, 'y' => 38.00], // Rak 15-24
$kode_utama == 297 => ['x' => 14.00, 'y' => 38.00], // Rak 25-32 (Islam)
default => ['x' => 14.00, 'y' => 30.00], // Rak 15-24
},
$kode_utama >= 300 && $kode_utama <= 399 => match (true) {
$kode_utama <= 330 => ['x' => 43.00, 'y' => 30.00], // Rak 33-36
$kode_utama <= 360 => ['x' => 50.00, 'y' => 30.00], // Rak 37-40
default => ['x' => 57.00, 'y' => 30.00], // Rak 41-44
$kode_utama <= 330 => ['x' => 38.00, 'y' => 23.00], // Rak 33-36
$kode_utama <= 360 => ['x' => 43.00, 'y' => 23.00], // Rak 37-40
default => ['x' => 48.00, 'y' => 23.00], // Rak 41-44
},
$kode_utama >= 400 && $kode_utama <= 499 => ['x' => 18.00, 'y' => 62.00], // Rak 45
$kode_utama >= 500 && $kode_utama <= 599 => ['x' => 50.00, 'y' => 74.00], // Rak 46-48
$kode_utama >= 400 && $kode_utama <= 499 => ['x' => 14.00, 'y' => 58.00], // Rak 45
$kode_utama >= 500 && $kode_utama <= 599 => ['x' => 38.00, 'y' => 72.00], // Rak 46-48
$kode_utama >= 600 && $kode_utama <= 699 => match (true) {
$kode_utama <= 610 => ['x' => 35.00, 'y' => 85.00], // Rak 49-53
$kode_utama <= 630 => ['x' => 45.00, 'y' => 85.00], // Rak 54-58
$kode_utama <= 650 => ['x' => 55.00, 'y' => 85.00], // Rak 59-63
default => ['x' => 65.00, 'y' => 85.00], // Rak 64-68
$kode_utama <= 610 => ['x' => 28.00, 'y' => 83.00], // Rak 49-53
$kode_utama <= 630 => ['x' => 36.00, 'y' => 83.00], // Rak 54-58
$kode_utama <= 650 => ['x' => 44.00, 'y' => 83.00], // Rak 59-63
default => ['x' => 52.00, 'y' => 83.00], // Rak 64-68
},
$kode_utama >= 700 && $kode_utama <= 799 => match (true) {
$kode_utama <= 739 => ['x' => 77.00, 'y' => 22.00], // Rak 71
$kode_utama <= 769 => ['x' => 82.00, 'y' => 22.00], // Rak 72
$kode_utama <= 789 => ['x' => 87.00, 'y' => 22.00], // Rak 73
default => ['x' => 82.00, 'y' => 22.00], // Rak 74
$kode_utama <= 739 => ['x' => 82.00, 'y' => 12.00], // Rak 71
$kode_utama <= 769 => ['x' => 86.00, 'y' => 12.00], // Rak 72
$kode_utama <= 789 => ['x' => 82.00, 'y' => 16.00], // Rak 73
default => ['x' => 86.00, 'y' => 16.00], // Rak 74
},
$kode_utama >= 800 && $kode_utama <= 899 => ['x' => 82.00, 'y' => 32.00], // Rak 77-79
$kode_utama >= 800 && $kode_utama <= 899 => ['x' => 82.00, 'y' => 25.00], // Rak 77-79
$kode_utama >= 900 && $kode_utama <= 999 => match (true) {
$kode_utama <= 919 => ['x' => 77.00, 'y' => 42.00], // Rak 69-70
default => ['x' => 87.00, 'y' => 42.00], // Rak 80-84
$kode_utama <= 919 => ['x' => 82.00, 'y' => 30.00], // Rak 69-70
default => ['x' => 82.00, 'y' => 35.00], // Rak 80-84
},
default => ['x' => null, 'y' => null],
};

View File

@ -58,84 +58,49 @@ public function store(Request $request)
$validated = $request->validate([
'id_anggota' => 'required|exists:anggotas,id',
'id_buku' => 'required|exists:buku,id_buku',
'id_buku_2' => 'nullable|exists:buku,id_buku|different:id_buku',
'tanggal_pinjam' => 'required|date_format:Y-m-d|after_or_equal:2000-01-01|before_or_equal:2100-12-31',
'tanggal_kembali' => 'required|date_format:Y-m-d|after_or_equal:tanggal_pinjam|before_or_equal:2100-12-31',
]);
$validated['status_peminjaman'] = 'Dipinjam';
$status = 'Dipinjam';
// 2. Kurangi Stok Buku
$buku = Buku::findOrFail($validated['id_buku']);
$buku->decrement('eksemplar');
// 2. Kurangi Stok Buku Pertama & Simpan
$buku1 = Buku::findOrFail($validated['id_buku']);
$buku1->decrement('eksemplar');
// 3. Simpan ke Database
$peminjaman = Peminjaman::create($validated);
$peminjaman->load(['buku', 'anggota']);
$peminjaman1 = Peminjaman::create([
'id_anggota' => $validated['id_anggota'],
'id_buku' => $validated['id_buku'],
'tanggal_pinjam' => $validated['tanggal_pinjam'],
'tanggal_kembali' => $validated['tanggal_kembali'],
'status_peminjaman' => $status,
]);
$peminjaman1->load(['buku', 'anggota']);
$waSuccess = false;
$waSuccess = $this->kirimWaPeminjaman($peminjaman1, false);
// 4. Proses Kirim WA (Format Struk Teks Resmi)
try {
$targetNum = $peminjaman->anggota->no_hp ?? '';
$fonnteToken = 'vpzqxF2ZGgTGz9F5UbUS'; // Token Fonnte
// 3. Proses Buku Kedua (Opsional)
if (!empty($validated['id_buku_2'])) {
$buku2 = Buku::findOrFail($validated['id_buku_2']);
$buku2->decrement('eksemplar');
// Standarisasi Format Nomor ke awalan 62
if (!empty($targetNum)) {
$targetNum = preg_replace('/^0/', '62', trim($targetNum));
$peminjaman2 = Peminjaman::create([
'id_anggota' => $validated['id_anggota'],
'id_buku' => $validated['id_buku_2'],
'tanggal_pinjam' => $validated['tanggal_pinjam'],
'tanggal_kembali' => $validated['tanggal_kembali'],
'status_peminjaman' => $status,
]);
$peminjaman2->load(['buku', 'anggota']);
$wa2Success = $this->kirimWaPeminjaman($peminjaman2, false);
if ($wa2Success) {
$waSuccess = true;
}
if (!empty($targetNum) && !empty($fonnteToken)) {
// Merangkai Teks Menyerupai Struk Kertas
$pesanStruk = "🏢 *PERPUSTAKAAN DAERAH JEMBER*\n";
$pesanStruk .= "Jl. Mastrip No. 1, Kabupaten Jember\n";
$pesanStruk .= "===============================\n\n";
$pesanStruk .= "📄 *BUKTI PEMINJAMAN BUKU*\n";
$pesanStruk .= "No. Transaksi : PMJ-{$peminjaman->id_peminjaman}\n";
$pesanStruk .= "Tanggal Cetak : " . \Carbon\Carbon::now()->format('d-m-Y H:i') . "\n\n";
$pesanStruk .= "*DATA PEMINJAM*\n";
$pesanStruk .= "Nama : {$peminjaman->anggota->nama}\n";
$pesanStruk .= "No. HP : {$peminjaman->anggota->no_hp}\n\n";
$pesanStruk .= "*DETAIL BUKU*\n";
$pesanStruk .= "Judul : {$peminjaman->buku->judul}\n";
$pesanStruk .= "Kode Pengembalian : {$peminjaman->buku->bibid}\n";
$pesanStruk .= "Pinjam : " . \Carbon\Carbon::parse($peminjaman->tanggal_pinjam)->format('d F Y') . "\n";
$pesanStruk .= "Kembali: *" . \Carbon\Carbon::parse($peminjaman->tanggal_kembali)->format('d F Y') . "*\n\n";
$pesanStruk .= "===============================\n";
$pesanStruk .= "⚠️ *Catatan:*\n";
$pesanStruk .= "Tunjukkan Kode Pengembalian ke Admin saat pengembalian buku.\n";
$pesanStruk .= "Harap kembalikan buku tepat waktu.\n";
$pesanStruk .= "Denda keterlambatan: Rp 1.000/hari.\n\n";
$pesanStruk .= "Terima kasih atas kunjungan Anda!\n";
$pesanStruk .= "_Sistem Sarakata - TA 2026_";
// Eksekusi Pengiriman via Http Laravel
$response = \Illuminate\Support\Facades\Http::withoutVerifying()->timeout(15)->withHeaders([
'Authorization' => $fonnteToken,
])->post('https://api.fonnte.com/send', [
'target' => $targetNum,
'message' => $pesanStruk,
]);
if ($response->successful() && ($response->json('status') == true)) {
$waSuccess = true;
} else {
\Illuminate\Support\Facades\Log::warning("Fonnte Log: Gagal Terkirim", [
'target' => $targetNum,
'http_status' => $response->status(),
'body' => $response->body()
]);
}
} else {
\Illuminate\Support\Facades\Log::warning('Fonnte Log: Token / No HP kosong', [
'target' => $targetNum ?? 'kosong',
'token_set' => !empty($fonnteToken),
]);
}
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error("Error WA Pengiriman: " . $e->getMessage());
}
// 5. Notifikasi Kembali ke Layar Admin
// 4. Notifikasi Kembali ke Layar Admin
$msg = 'Transaksi peminjaman berhasil dicatat.';
if ($waSuccess) {
$msg .= ' Struk WA berhasil terkirim kepada Anggota.';
@ -310,6 +275,16 @@ public function resendWa($id)
{
$peminjaman = Peminjaman::with(['buku', 'anggota'])->findOrFail($id);
$success = $this->kirimWaPeminjaman($peminjaman, true);
if ($success) {
return back()->with('success', 'Struk WhatsApp berhasil dikirim ulang ke nomor ' . $peminjaman->anggota->no_hp);
} else {
return back()->with('error', 'Gagal mengirim ulang WA. Silakan cek log Fonnte.');
}
}
private function kirimWaPeminjaman($peminjaman, $isSalinan = false)
{
try {
$targetNum = $peminjaman->anggota->no_hp ?? '';
$fonnteToken = env('FONNTE_TOKEN', 'vpzqxF2ZGgTGz9F5UbUS');
@ -319,10 +294,12 @@ public function resendWa($id)
}
if (!empty($targetNum) && !empty($fonnteToken)) {
$tipeStruk = $isSalinan ? "SALINAN BUKTI PEMINJAMAN" : "BUKTI PEMINJAMAN BUKU";
$pesanStruk = "🏢 *PERPUSTAKAAN DAERAH JEMBER*\n";
$pesanStruk .= "Jl. Mastrip No. 1, Kabupaten Jember\n";
$pesanStruk .= "===============================\n\n";
$pesanStruk .= "📄 *SALINAN BUKTI PEMINJAMAN*\n";
$pesanStruk .= "📄 *{$tipeStruk}*\n";
$pesanStruk .= "No. Transaksi : PMJ-{$peminjaman->id_peminjaman}\n";
$pesanStruk .= "Tanggal Cetak : " . \Carbon\Carbon::now()->format('d-m-Y H:i') . "\n\n";
$pesanStruk .= "*DATA PEMINJAM*\n";
@ -330,12 +307,12 @@ public function resendWa($id)
$pesanStruk .= "No. HP : {$peminjaman->anggota->no_hp}\n\n";
$pesanStruk .= "*DETAIL BUKU*\n";
$pesanStruk .= "Judul : {$peminjaman->buku->judul}\n";
$pesanStruk .= "Kode Panggil : {$peminjaman->buku->bibid}\n";
$pesanStruk .= "Kode Pengembalian : {$peminjaman->buku->bibid}\n";
$pesanStruk .= "Pinjam : " . \Carbon\Carbon::parse($peminjaman->tanggal_pinjam)->format('d F Y') . "\n";
$pesanStruk .= "Kembali: *" . \Carbon\Carbon::parse($peminjaman->tanggal_kembali)->format('d F Y') . "*\n\n";
$pesanStruk .= "===============================\n";
$pesanStruk .= "⚠️ *Catatan:*\n";
$pesanStruk .= "Tunjukkan Kode Panggil ke Admin saat pengembalian buku.\n";
$pesanStruk .= "Tunjukkan Kode Pengembalian ke Admin saat pengembalian buku.\n";
$pesanStruk .= "Harap kembalikan buku tepat waktu.\n";
$pesanStruk .= "Denda keterlambatan: Rp 1.000/hari.\n\n";
$pesanStruk .= "Terima kasih atas kunjungan Anda!\n";
@ -349,17 +326,18 @@ public function resendWa($id)
]);
if ($response->successful() && ($response->json('status') == true)) {
return back()->with('success', 'Struk WhatsApp berhasil dikirim ulang ke nomor ' . $targetNum);
return true;
} else {
\Illuminate\Support\Facades\Log::warning("Fonnte Log: Gagal Terkirim Ulang", ['body' => $response->body()]);
return back()->with('error', 'Gagal mengirim ulang WA. API Fonnte menolak koneksi (Status: '.$response->status().').');
\Illuminate\Support\Facades\Log::warning("Fonnte Log: Gagal Terkirim", [
'target' => $targetNum,
'http_status' => $response->status(),
'body' => $response->body()
]);
}
} else {
return back()->with('error', 'Nomor HP anggota kosong atau Token Fonnte belum dikonfigurasi.');
}
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error("Error WA Pengiriman Ulang: " . $e->getMessage());
return back()->with('error', 'Terjadi kesalahan sistem saat menghubungi server WhatsApp.');
\Illuminate\Support\Facades\Log::error("Error WA Pengiriman: " . $e->getMessage());
}
return false;
}
}

View File

@ -8,23 +8,85 @@
class LaporanController extends Controller
{
public function kehadiran()
public function kehadiran(Request $request)
{
$bukuTamu = BukuTamu::with('user')
->orderBy('tanggal_kunjungan', 'desc')
->get();
$query = BukuTamu::with('user')->orderBy('tanggal_kunjungan', 'desc');
// Filter Bulan & Tahun
if ($request->filled('bulan')) {
$query->whereMonth('tanggal_kunjungan', $request->input('bulan'));
}
if ($request->filled('tahun')) {
$query->whereYear('tanggal_kunjungan', $request->input('tahun'));
}
$bukuTamu = $query->get();
// Slice data berdasarkan rentang baris (Limit Cetak)
if ($request->filled('limit_start') || $request->filled('limit_end')) {
$start = $request->filled('limit_start') ? (int)$request->input('limit_start') : 1;
$offset = max(0, $start - 1);
if ($request->filled('limit_end')) {
$end = (int)$request->input('limit_end');
$length = max(0, $end - $offset);
$bukuTamu = $bukuTamu->slice($offset, $length);
} else {
$bukuTamu = $bukuTamu->slice($offset);
}
}
return view('laporan.kehadiran', compact('bukuTamu'));
}
public function peminjaman(Request $request)
{
$query = Peminjaman::with(['anggota', 'user', 'buku'])->orderBy('tanggal_pinjam', 'desc');
$query = Peminjaman::with(['anggota', 'user', 'buku.kategori'])->orderBy('tanggal_pinjam', 'desc');
// Optional filtering by month/year if needed (can be added later)
// 1. Filter Bulan & Tahun
if ($request->filled('bulan')) {
$query->whereMonth('tanggal_pinjam', $request->input('bulan'));
}
if ($request->filled('tahun')) {
$query->whereYear('tanggal_pinjam', $request->input('tahun'));
}
// 2. Filter Kategori Buku (Berdasarkan DDC / Nomor Panggil)
if ($request->filled('id_kategori')) {
$classDigit = $request->input('id_kategori'); // '0', '1', ..., '9'
$query->whereHas('buku', function ($q) use ($classDigit) {
$q->where('nomor_panggil', 'like', "{$classDigit}%");
});
}
$peminjaman = $query->get();
return view('laporan.peminjaman', compact('peminjaman'));
// Slice data berdasarkan rentang baris (Limit Cetak)
if ($request->filled('limit_start') || $request->filled('limit_end')) {
$start = $request->filled('limit_start') ? (int)$request->input('limit_start') : 1;
$offset = max(0, $start - 1);
if ($request->filled('limit_end')) {
$end = (int)$request->input('limit_end');
$length = max(0, $end - $offset);
$peminjaman = $peminjaman->slice($offset, $length);
} else {
$peminjaman = $peminjaman->slice($offset);
}
}
// Kategori berdasarkan DDC & Lokasi Rak sesuai permintaan user
$categories = [
'0' => '000-099 : Karya Umum (Rak 01-05)',
'1' => '100-199 : Filsafat & Psikologi (Rak 06-14)',
'2' => '200-299 : Agama (Rak 15-32)',
'3' => '300-399 : Ilmu Sosial (Rak 33-44)',
'4' => '400-499 : Bahasa (Rak 45)',
'5' => '500-599 : Ilmu Murni / Sains (Rak 46-48)',
'6' => '600-699 : Ilmu Terapan (Rak 49-68)',
'7' => '700-799 : Kesenian & Olahraga (Rak 71-74)',
'8' => '800-899 : Sastra (Rak 77-79)',
'9' => '900-999 : Geografi & Sejarah (Rak 69-70, Rak 80-84)'
];
return view('laporan.peminjaman', compact('peminjaman', 'categories'));
}
}

View File

@ -7,20 +7,19 @@
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Paginator::useTailwind();
}
// Paksa URL Utama & HTTPS di Production untuk menjamin asset() & route() konsisten
if (config('app.env') !== 'local') {
\Illuminate\Support\Facades\URL::forceRootUrl(config('app.url'));
\Illuminate\Support\Facades\URL::forceScheme('https');
}
}
}

31
buat_link.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/**
* Script untuk membuat Symlink (Storage Link) di Shared Hosting
* Jalankan file ini sekali saja melalui browser: https://ta.myhost.id/E31230887/buat_link.php
*/
$target = __DIR__ . '/storage/app/public';
$link = __DIR__ . '/public/storage';
echo "<h2>🔧 Memperbaiki Jembatan Storage (Symlink)</h2>";
echo "<p>Target: <code>$target</code></p>";
echo "<p>Link: <code>$link</code></p>";
if (file_exists($link)) {
echo "<p>⚠️ Menghapus link/folder storage lama...</p>";
if (is_link($link)) {
unlink($link);
} else {
// Jika ternyata folder biasa (bukan link), kita amankan dengan ganti nama
rename($link, $link . '_backup_' . time());
}
}
if (symlink($target, $link)) {
echo "<h3 style='color: green;'>✅ Jembatan Storage Berhasil Dibuat!</h3>";
echo "<p>Sekarang gambar buku Anda seharusnya sudah muncul di website.</p>";
echo "<a href='./'>Kembali ke Website</a>";
} else {
echo "<h3 style='color: red;'>❌ Gagal membuat jembatan symlink.</h3>";
echo "<p>Beberapa hosting membatasi fungsi symlink(). Jika ini terjadi, beri tahu asisten AI Anda untuk menggunakan metode .htaccess redirect.</p>";
}

8
check_link.php Normal file
View File

@ -0,0 +1,8 @@
<?php
$target = __DIR__ . '/public/storage';
echo "Path: $target\n";
echo "Is Link: " . (is_link($target) ? 'YES' : 'NO') . "\n";
echo "Is Dir: " . (is_dir($target) ? 'YES' : 'NO') . "\n";
if (is_link($target)) {
echo "Readlink: " . readlink($target) . "\n";
}

View File

@ -1,42 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::create('bukus', function (Blueprint $table) {
$table->id();
$table->string('bibid')->nullable();
$table->string('judul');
$table->string('edisi')->nullable();
$table->string('pengarang');
$table->string('penerbit');
$table->year('tahun_terbit')->nullable();
$table->text('deskripsi_fisik')->nullable();
$table->string('nomor_panggil')->nullable();
$table->string('konten_digital')->nullable();
$table->integer('eksemplar')->default(1);
$table->unsignedBigInteger('id_kategori')->nullable();
$table->string('cover')->nullable();
$table->float('lokasi_x')->nullable();
$table->float('lokasi_y')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('bukus');
}
};

13
debug.php Normal file
View File

@ -0,0 +1,13 @@
<?php
// File debug sementara — HAPUS setelah selesai!
echo "<pre>";
echo "DOCUMENT_ROOT: " . $_SERVER['DOCUMENT_ROOT'] . "\n";
echo "REQUEST_URI: " . $_SERVER['REQUEST_URI'] . "\n";
echo "SCRIPT_FILENAME: " . $_SERVER['SCRIPT_FILENAME'] . "\n";
echo "__DIR__: " . __DIR__ . "\n";
echo "\n--- Cek keberadaan file ---\n";
echo "img/denah.webp via __DIR__: " . (file_exists(__DIR__ . '/img/denah.webp') ? '✅ ADA' : '❌ TIDAK ADA') . "\n";
echo "public/img/denah.webp via __DIR__: " . (file_exists(__DIR__ . '/public/img/denah.webp') ? '✅ ADA' : '❌ TIDAK ADA') . "\n";
echo "DOCUMENT_ROOT/img/denah.webp: " . (file_exists($_SERVER['DOCUMENT_ROOT'] . '/img/denah.webp') ? '✅ ADA' : '❌ TIDAK ADA') . "\n";
echo "DOCUMENT_ROOT/E31230887/img/denah.webp: " . (file_exists($_SERVER['DOCUMENT_ROOT'] . '/E31230887/img/denah.webp') ? '✅ ADA' : '❌ TIDAK ADA') . "\n";
echo "</pre>";

4
index.php Normal file
View File

@ -0,0 +1,4 @@
<?php
// Bootstrap Laravel dari folder public/
require __DIR__ . '/public/index.php';

View File

@ -1,6 +1,31 @@
@extends('layouts.app')
@section('content')
<!-- CDNs for icons, TomSelect and sweetalert -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script>
<style>
/* TomSelect Custom Styling matching dashboard */
.ts-control {
border: 1px solid #d1d5db !important;
background-color: #ffffff !important;
border-radius: 0.5rem !important;
padding: 0.75rem 1rem !important;
font-size: 0.875rem !important;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important;
}
.ts-control.focus {
border-color: #3b82f6 !important;
box-shadow: 0 0 0 1px #3b82f6 !important;
}
.ts-control input {
font-size: 0.875rem !important;
}
</style>
<div class="container mx-auto px-4 py-8 max-w-2xl">
<div class="bg-white shadow-lg rounded-xl overflow-hidden border border-gray-100">
<div class="p-6 bg-blue-50 border-b border-blue-100">
@ -34,18 +59,29 @@ class="w-full border-gray-300 rounded-lg shadow-sm focus:border-blue-500 focus:r
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Pilih Aset Buku (Hanya yang
tersedia)</label>
<select name="id_buku"
class="w-full border-gray-300 rounded-lg shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">-- Pilih Buku --</option>
@foreach ($buku as $b)
<option value="{{ $b->id_buku }}" {{ old('id_buku') == $b->id_buku ? 'selected' : '' }}>
{{ $b->bibid }} - {{ $b->judul }} (Stok: {{ $b->eksemplar }})
</option>
@endforeach
</select>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Pilih Buku Utama (Pindai Barcode / Cari Judul)</label>
<select name="id_buku" id="select-buku-peminjaman" required>
<option value="">-- Pilih Buku Pertama --</option>
@foreach ($buku as $b)
<option value="{{ $b->id_buku }}" data-bibid="{{ $b->bibid }}" {{ old('id_buku') == $b->id_buku ? 'selected' : '' }}>
{{ $b->bibid }} - {{ $b->judul }} (Stok: {{ $b->eksemplar }})
</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Pilih Buku Kedua (Opsional)</label>
<select name="id_buku_2" id="select-buku-peminjaman-2">
<option value="">-- Pilih Buku Kedua (Kosongkan jika hanya 1) --</option>
@foreach ($buku as $b)
<option value="{{ $b->id_buku }}" data-bibid="{{ $b->bibid }}" {{ old('id_buku_2') == $b->id_buku ? 'selected' : '' }}>
{{ $b->bibid }} - {{ $b->judul }} (Stok: {{ $b->eksemplar }})
</option>
@endforeach
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@ -83,5 +119,131 @@ class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg s
btn.innerHTML = 'Memproses...';
btn.classList.add('opacity-50', 'cursor-not-allowed');
});
let scanBuffer = "";
let lastKeyTime = Date.now();
// Global barcode scan detection
document.addEventListener('keypress', function(e) {
const target = e.target;
if (target.tagName === 'INPUT' && target.type !== 'submit' && target.type !== 'button' && target.type !== 'checkbox' && target.type !== 'radio') {
if (!target.classList.contains('ts-control') && !target.closest('.ts-wrapper')) {
return;
}
}
if (target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {
return;
}
const currentTime = Date.now();
if (currentTime - lastKeyTime > 100) {
scanBuffer = "";
}
lastKeyTime = currentTime;
if (e.key === 'Enter') {
if (scanBuffer.length >= 2) {
e.preventDefault();
const cleanCode = scanBuffer.trim();
handleGlobalScan(cleanCode);
scanBuffer = "";
}
} else {
if (e.key.length === 1) {
scanBuffer += e.key;
}
}
});
function handleGlobalScan(bibid) {
const select1 = document.getElementById('select-buku-peminjaman');
const select2 = document.getElementById('select-buku-peminjaman-2');
if (!select1) return;
function findVal(selectEl) {
const options = selectEl.options;
for (let i = 0; i < options.length; i++) {
if (options[i].getAttribute('data-bibid') === bibid) {
return { value: options[i].value, text: options[i].text };
}
}
return null;
}
const item = findVal(select1);
if (!item) {
Swal.fire({
icon: 'error',
title: 'Buku Tidak Ditemukan',
text: 'Kode buku "' + bibid + '" tidak terdaftar atau tidak tersedia.',
timer: 2000,
showConfirmButton: false,
toast: true,
position: 'top-end'
});
return;
}
let targetSelect = null;
if (select1.tomselect && !select1.tomselect.getValue()) {
targetSelect = select1;
} else if (select2 && select2.tomselect && !select2.tomselect.getValue()) {
targetSelect = select2;
} else {
targetSelect = select1;
}
if (targetSelect && targetSelect.tomselect) {
const otherSelect = (targetSelect === select1) ? select2 : select1;
if (otherSelect && otherSelect.tomselect && otherSelect.tomselect.getValue() === item.value) {
Swal.fire({
icon: 'warning',
title: 'Buku Sudah Dipilih',
text: 'Buku ini sudah dipilih pada slot lainnya.',
timer: 2000,
showConfirmButton: false,
toast: true,
position: 'top-end'
});
return;
}
targetSelect.tomselect.setValue(item.value);
Swal.fire({
icon: 'success',
title: 'Buku Berhasil Dipilih',
text: item.text,
timer: 1500,
showConfirmButton: false,
toast: true,
position: 'top-end'
});
}
}
document.addEventListener('DOMContentLoaded', function() {
// Initialize TomSelect for books
const ts1 = new TomSelect('#select-buku-peminjaman', { maxOptions: null });
const ts2 = new TomSelect('#select-buku-peminjaman-2', { maxOptions: null });
// Focus on first book dropdown
setTimeout(() => {
ts1.focus();
}, 100);
// Prevent Enter key from submitting form on text/select inputs
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && e.target.tagName !== 'BUTTON' && e.target.type !== 'submit') {
e.preventDefault();
}
});
});
});
</script>
@endsection

View File

@ -16,6 +16,16 @@
tanggal_pinjam: '{{ old('tanggal_pinjam') ?? '' }}',
tanggal_kembali: '{{ old('tanggal_kembali') ?? '' }}'
},
init() {
this.$watch('isModalPeminjamanOpen', value => {
if (value) {
setTimeout(() => {
const ts1 = document.getElementById('select-buku-peminjaman');
if (ts1 && ts1.tomselect) ts1.tomselect.focus();
}, 200);
}
});
},
openEditModal(id, anggotaId, buku, pinjam, kembali) {
this.editData = { id: id, id_anggota: anggotaId, id_buku: buku, tanggal_pinjam: pinjam, tanggal_kembali: kembali };
this.isModalEditOpen = true;
@ -252,19 +262,34 @@ class="mt-1 block w-full border-gray-200 focus:border-indigo-500 focus:ring-indi
</select>
</div>
<div>
<x-input-label value="Pilih Buku (Hanya yang tersedia)" />
<select name="id_buku" x-init="new TomSelect($el, { maxOptions: null })"
class="mt-1 block w-full border-gray-200 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50 p-3"
required>
<option value="">-- Pilih Buku --</option>
@foreach ($buku as $b)
<option value="{{ $b->id_buku }}"
{{ old('id_buku') == $b->id_buku ? 'selected' : '' }}>
{{ $b->bibid }} - {{ $b->judul }} (Stok: {{ $b->eksemplar }})
</option>
@endforeach
</select>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<x-input-label value="Pilih Buku Utama (Pindai Barcode / Cari Judul)" />
<select name="id_buku" id="select-buku-peminjaman" x-init="new TomSelect($el, { maxOptions: null })"
class="mt-1 block w-full border-gray-200 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50 p-3"
required>
<option value="">-- Pilih Buku Pertama --</option>
@foreach ($buku as $b)
<option value="{{ $b->id_buku }}" data-bibid="{{ $b->bibid }}"
{{ old('id_buku') == $b->id_buku ? 'selected' : '' }}>
{{ $b->bibid }} - {{ $b->judul }} (Stok: {{ $b->eksemplar }})
</option>
@endforeach
</select>
</div>
<div>
<x-input-label value="Pilih Buku Kedua (Opsional)" />
<select name="id_buku_2" id="select-buku-peminjaman-2" x-init="new TomSelect($el, { maxOptions: null })"
class="mt-1 block w-full border-gray-200 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50 p-3">
<option value="">-- Pilih Buku Kedua (Kosongkan jika hanya 1) --</option>
@foreach ($buku as $b)
<option value="{{ $b->id_buku }}" data-bibid="{{ $b->bibid }}"
{{ old('id_buku_2') == $b->id_buku ? 'selected' : '' }}>
{{ $b->bibid }} - {{ $b->judul }} (Stok: {{ $b->eksemplar }})
</option>
@endforeach
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@ -376,7 +401,7 @@ class="bg-green-50 text-green-700 p-3 rounded-lg text-center font-bold text-xs m
</div>
</template>
<form :action="'/admin/peminjaman/' + kembaliData.id + '/kembali'" method="POST">
<form :action="'{{ url('admin/peminjaman') }}/' + kembaliData.id + '/kembali'" method="POST">
@csrf
@method('PUT')
<div class="flex items-center justify-end gap-2 pt-3 border-t border-gray-100">
@ -424,7 +449,7 @@ class="w-10 h-10 rounded-full bg-gray-50 hover:bg-red-50 text-gray-400 hover:tex
</button>
</div>
<form :action="'/admin/peminjaman/' + editData.id" method="POST" class="space-y-6">
<form :action="'{{ url('admin/peminjaman') }}/' + editData.id" method="POST" class="space-y-6">
@csrf
@method('PUT')
<input type="hidden" name="peminjaman_id_edit" :value="editData.id">
@ -517,7 +542,7 @@ class="text-gray-400 hover:text-gray-600 transition-colors">
buku akan otomatis dikembalikan jika status masih dipinjam.</p>
<div class="flex flex-col gap-3">
<form :action="'/admin/peminjaman/' + deleteId" method="POST" class="w-full m-0">
<form :action="'{{ url('admin/peminjaman') }}/' + deleteId" method="POST" class="w-full m-0">
@csrf
@method('DELETE')
<button type="submit"
@ -536,3 +561,123 @@ class="w-full py-4 bg-gray-50 hover:bg-gray-100 text-gray-600 rounded-2xl font-b
</div>
</div>
@endsection
@push('scripts')
<script>
let scanBuffer = "";
let lastKeyTime = Date.now();
// Global barcode scan detection
document.addEventListener('keypress', function(e) {
const target = e.target;
if (target.tagName === 'INPUT' && target.type !== 'submit' && target.type !== 'button' && target.type !== 'checkbox' && target.type !== 'radio') {
if (!target.classList.contains('ts-control') && !target.closest('.ts-wrapper')) {
return;
}
}
if (target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {
return;
}
const currentTime = Date.now();
if (currentTime - lastKeyTime > 100) {
scanBuffer = "";
}
lastKeyTime = currentTime;
if (e.key === 'Enter') {
if (scanBuffer.length >= 2) {
e.preventDefault();
const cleanCode = scanBuffer.trim();
handleGlobalScan(cleanCode);
scanBuffer = "";
}
} else {
if (e.key.length === 1) {
scanBuffer += e.key;
}
}
});
function handleGlobalScan(bibid) {
const select1 = document.getElementById('select-buku-peminjaman');
const select2 = document.getElementById('select-buku-peminjaman-2');
if (!select1) return;
function findVal(selectEl) {
const options = selectEl.options;
for (let i = 0; i < options.length; i++) {
if (options[i].getAttribute('data-bibid') === bibid) {
return { value: options[i].value, text: options[i].text };
}
}
return null;
}
const item = findVal(select1);
if (!item) {
Swal.fire({
icon: 'error',
title: 'Buku Tidak Ditemukan',
text: 'Kode buku "' + bibid + '" tidak terdaftar atau tidak tersedia.',
timer: 2000,
showConfirmButton: false,
toast: true,
position: 'top-end'
});
return;
}
let targetSelect = null;
if (select1.tomselect && !select1.tomselect.getValue()) {
targetSelect = select1;
} else if (select2 && select2.tomselect && !select2.tomselect.getValue()) {
targetSelect = select2;
} else {
targetSelect = select1;
}
if (targetSelect && targetSelect.tomselect) {
const otherSelect = (targetSelect === select1) ? select2 : select1;
if (otherSelect && otherSelect.tomselect && otherSelect.tomselect.getValue() === item.value) {
Swal.fire({
icon: 'warning',
title: 'Buku Sudah Dipilih',
text: 'Buku ini sudah dipilih pada slot lainnya.',
timer: 2000,
showConfirmButton: false,
toast: true,
position: 'top-end'
});
return;
}
targetSelect.tomselect.setValue(item.value);
Swal.fire({
icon: 'success',
title: 'Buku Berhasil Dipilih',
text: item.text,
timer: 1500,
showConfirmButton: false,
toast: true,
position: 'top-end'
});
}
}
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && e.target.tagName !== 'BUTTON' && e.target.type !== 'submit') {
e.preventDefault();
}
});
});
});
</script>
@endpush

View File

@ -1,84 +1,101 @@
@extends('layouts.admin')
@section('content')
<div class="container mx-auto px-4 py-8 max-w-3xl">
<div class="mb-6">
<a href="{{ route('admin.peminjaman.index') }}" class="text-blue-600 hover:text-blue-800 font-medium">&larr; Kembali ke Daftar Sirkulasi</a>
</div>
<div class="bg-white shadow-lg rounded-xl overflow-hidden border border-gray-100 mb-8">
<div class="p-8 bg-gray-900 text-center">
<h1 class="text-2xl font-extrabold text-white mb-2">Pindai Barcode Buku</h1>
<p class="text-gray-400 text-sm mb-6">Arahkan scanner ke label BIBID pada buku yang dikembalikan.</p>
<form action="{{ route('admin.peminjaman.proses_scan') }}" method="POST" class="max-w-md mx-auto relative">
@csrf
<input type="text" name="bibid" autofocus autocomplete="off" placeholder="Scan Barcode di sini..." class="w-full pl-12 pr-4 py-4 text-center text-xl font-mono border-2 border-gray-700 bg-gray-800 text-white rounded-lg shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:bg-gray-900 transition-colors">
<svg class="w-6 h-6 text-gray-500 absolute left-4 top-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm14 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"></path></svg>
</form>
<div class="container mx-auto px-4 py-8 max-w-3xl">
<div class="mb-6">
<a href="{{ route('admin.pengembalian.index') }}" class="text-blue-600 hover:text-blue-800 font-medium">&larr;
Kembali ke Daftar Pengembalian</a>
</div>
</div>
@if(session('error'))
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded shadow-sm" role="alert">
<p class="font-bold">Pemindaian Gagal</p>
<p>{{ session('error') }}</p>
</div>
@endif
<div class="bg-white shadow-lg rounded-xl overflow-hidden border border-gray-100 mb-8">
<div class="p-8 bg-gray-900 text-center">
<h1 class="text-2xl font-extrabold text-white mb-2">Pindai Barcode Buku</h1>
<p class="text-gray-400 text-sm mb-6">Arahkan scanner ke label BIBID pada buku yang dikembalikan.</p>
@if(isset($peminjaman))
<div class="bg-white shadow-xl rounded-xl overflow-hidden border-2 border-green-500">
<div class="p-6 bg-green-50 border-b border-green-100 flex justify-between items-center">
<h2 class="text-xl font-bold text-green-900">Data Transaksi Ditemukan</h2>
<span class="bg-green-200 text-green-800 px-3 py-1 rounded-full text-xs font-bold uppercase">#{{ $peminjaman->id_peminjaman }}</span>
</div>
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="space-y-4">
<div>
<span class="block text-xs font-bold text-gray-500 uppercase">Peminjam</span>
<span class="block text-lg font-bold text-gray-900">{{ $peminjaman->anggota?->nama ?? ($peminjaman->user?->name ?? 'Anonim') }}</span>
</div>
<div>
<span class="block text-xs font-bold text-gray-500 uppercase">Aset Buku</span>
<span class="block text-base font-bold text-gray-900">{{ $buku->judul }}</span>
<span class="block text-sm text-gray-600 font-mono">{{ $buku->bibid }}</span>
</div>
<div>
<span class="block text-xs font-bold text-gray-500 uppercase">Tenggat Waktu</span>
<span class="block text-sm font-medium {{ $denda > 0 ? 'text-red-600' : 'text-gray-900' }}">
{{ \Carbon\Carbon::parse($peminjaman->tanggal_kembali)->format('d F Y') }}
</span>
</div>
</div>
<div class="flex flex-col space-y-4">
<div class="bg-blue-900 rounded-xl p-5 text-center text-white shadow-inner flex-1 flex flex-col justify-center border border-blue-800">
<span class="block text-xs font-bold text-blue-300 uppercase tracking-widest mb-2">Panduan Pengembalian Rak Fisik</span>
<span class="block text-4xl font-black mb-1 drop-shadow-md">{{ $lokasi['rak'] }}</span>
<span class="block text-sm text-blue-100 font-medium bg-blue-950/50 py-1 rounded-full w-max mx-auto px-4 mt-2 border border-blue-700/50">{{ $lokasi['area'] }}</span>
</div>
@if($denda > 0)
<div class="bg-red-50 border-2 border-red-200 rounded-xl p-4 text-center">
<span class="block text-xs font-bold text-red-600 uppercase mb-1">Denda Keterlambatan</span>
<span class="block text-2xl font-black text-red-700">Rp {{ number_format($denda, 0, ',', '.') }}</span>
</div>
@endif
<form action="{{ route('admin.peminjaman.proses_scan') }}" method="POST" class="max-w-md mx-auto relative">
@csrf
<input type="text" name="bibid" autofocus autocomplete="off" placeholder="Scan Barcode di sini..."
class="w-full pl-12 pr-4 py-4 text-center text-xl font-mono border-2 border-gray-700 bg-gray-800 text-white rounded-lg shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:bg-gray-900 transition-colors">
<svg class="w-6 h-6 text-gray-500 absolute left-4 top-4" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm14 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z">
</path>
</svg>
</form>
</div>
</div>
<div class="p-6 border-t border-gray-100 bg-gray-50 flex justify-end">
<form action="{{ route('admin.peminjaman.kembalikan', $peminjaman->id_peminjaman) }}" method="POST" onsubmit="return confirm('Selesaikan transaksi dan pulihkan stok buku?');">
@csrf
@method('PUT')
<button type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold py-4 px-8 rounded-lg shadow-md transition duration-300 w-full md:w-auto text-lg flex items-center gap-2">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
Konfirmasi Pengembalian
</button>
</form>
</div>
@if(session('error'))
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded shadow-sm" role="alert">
<p class="font-bold">Pemindaian Gagal</p>
<p>{{ session('error') }}</p>
</div>
@endif
@if(isset($peminjaman))
<div class="bg-white shadow-xl rounded-xl overflow-hidden border-2 border-green-500">
<div class="p-6 bg-green-50 border-b border-green-100 flex justify-between items-center">
<h2 class="text-xl font-bold text-green-900">Data Transaksi Ditemukan</h2>
<span
class="bg-green-200 text-green-800 px-3 py-1 rounded-full text-xs font-bold uppercase">#{{ $peminjaman->id_peminjaman }}</span>
</div>
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="space-y-4">
<div>
<span class="block text-xs font-bold text-gray-500 uppercase">Peminjam</span>
<span
class="block text-lg font-bold text-gray-900">{{ $peminjaman->anggota?->nama ?? ($peminjaman->user?->name ?? 'Anonim') }}</span>
</div>
<div>
<span class="block text-xs font-bold text-gray-500 uppercase">Aset Buku</span>
<span class="block text-base font-bold text-gray-900">{{ $buku->judul }}</span>
<span class="block text-sm text-gray-600 font-mono">{{ $buku->bibid }}</span>
</div>
<div>
<span class="block text-xs font-bold text-gray-500 uppercase">Tenggat Waktu</span>
<span class="block text-sm font-medium {{ $denda > 0 ? 'text-red-600' : 'text-gray-900' }}">
{{ \Carbon\Carbon::parse($peminjaman->tanggal_kembali)->format('d F Y') }}
</span>
</div>
</div>
<div class="flex flex-col space-y-4">
<div
class="bg-blue-900 rounded-xl p-5 text-center text-white shadow-inner flex-1 flex flex-col justify-center border border-blue-800">
<span class="block text-xs font-bold text-blue-300 uppercase tracking-widest mb-2">Panduan
Pengembalian Rak Fisik</span>
<span class="block text-4xl font-black mb-1 drop-shadow-md">{{ $lokasi['rak'] }}</span>
<span
class="block text-sm text-blue-100 font-medium bg-blue-950/50 py-1 rounded-full w-max mx-auto px-4 mt-2 border border-blue-700/50">{{ $lokasi['area'] }}</span>
</div>
@if($denda > 0)
<div class="bg-red-50 border-2 border-red-200 rounded-xl p-4 text-center">
<span class="block text-xs font-bold text-red-600 uppercase mb-1">Denda Keterlambatan</span>
<span class="block text-2xl font-black text-red-700">Rp
{{ number_format($denda, 0, ',', '.') }}</span>
</div>
@endif
</div>
</div>
<div class="p-6 border-t border-gray-100 bg-gray-50 flex justify-end">
<form action="{{ route('admin.peminjaman.kembalikan', $peminjaman->id_peminjaman) }}" method="POST"
onsubmit="return confirm('Selesaikan transaksi dan pulihkan stok buku?');">
@csrf
@method('PUT')
<button type="submit"
class="bg-green-600 hover:bg-green-700 text-white font-bold py-4 px-8 rounded-lg shadow-md transition duration-300 w-full md:w-auto text-lg flex items-center gap-2">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
Konfirmasi Pengembalian
</button>
</form>
</div>
</div>
@endif
</div>
@endif
</div>
@endsection
@endsection

View File

@ -5,14 +5,78 @@
@section('content')
<x-page-header title="Laporan Kehadiran" />
<x-card>
<div class="mb-4 flex flex-col md:flex-row justify-between items-center gap-4">
<p class="text-gray-500 text-sm">Berikut adalah daftar rekapitulasi kehadiran pengunjung dan anggota perpustakaan.</p>
<!-- Filter Form (Sembunyikan saat cetak) -->
<form method="GET" action="{{ route('admin.laporan.kehadiran') }}" class="mb-6 bg-gray-50 p-5 rounded-2xl border border-gray-200/60 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4 items-end print:hidden">
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Bulan</label>
<select name="bulan" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
<option value="">-- Semua Bulan --</option>
@foreach([
'1' => 'Januari', '2' => 'Februari', '3' => 'Maret', '4' => 'April',
'5' => 'Mei', '6' => 'Juni', '7' => 'Juli', '8' => 'Agustus',
'9' => 'September', '10' => 'Oktober', '11' => 'November', '12' => 'Desember'
] as $num => $name)
<option value="{{ $num }}" {{ request('bulan') == $num ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Tahun</label>
<select name="tahun" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
<option value="">-- Semua Tahun --</option>
@for($y = date('Y') + 1; $y >= date('Y') - 4; $y--)
<option value="{{ $y }}" {{ request('tahun', date('Y')) == $y ? 'selected' : '' }}>{{ $y }}</option>
@endfor
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Dari Baris</label>
<input type="number" name="limit_start" value="{{ request('limit_start') }}" min="1" placeholder="Contoh: 1" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Sampai Baris</label>
<input type="number" name="limit_end" value="{{ request('limit_end') }}" min="1" placeholder="Contoh: 20" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
</div>
<div class="flex gap-2">
<button type="submit" class="flex-grow bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-xl shadow-md transition duration-300 text-sm flex items-center justify-center gap-2">
<i class="fas fa-filter"></i> Filter
</button>
@if(request()->anyFilled(['bulan', 'tahun', 'limit_start', 'limit_end']))
<a href="{{ route('admin.laporan.kehadiran') }}" class="bg-gray-100 hover:bg-red-50 border border-gray-200 text-gray-500 hover:text-red-500 p-3 rounded-xl shadow-sm transition duration-300 flex items-center justify-center" title="Reset Filter">
<i class="fas fa-times"></i>
</a>
@endif
</div>
</form>
<div class="mb-4 flex flex-col md:flex-row justify-between items-center gap-4 print:hidden">
<p class="text-gray-500 text-sm">Berikut adalah daftar rekapitulasi kehadiran pengunjung yang disaring.</p>
<button onclick="window.print()" class="bg-gray-800 hover:bg-gray-900 text-white px-4 py-2 rounded-lg text-sm font-bold shadow-sm transition-all flex items-center gap-2">
<i class="fas fa-print"></i> Cetak PDF / Print
</button>
</div>
<div class="overflow-x-auto print:overflow-visible">
<!-- Print Header (Hanya terlihat saat cetak/print) -->
<div class="hidden print:block mb-6 border-b-2 border-gray-800 pb-4 text-center">
<h1 class="text-2xl font-black uppercase tracking-wide text-gray-900">Laporan Rekapitulasi Kehadiran</h1>
<h2 class="text-sm font-bold text-gray-700 mt-1">PERPUSTAKAAN DAERAH JEMBER</h2>
<p class="text-xs text-gray-500 mt-0.5">Jl. Mastrip No. 1, Kabupaten Jember</p>
<div class="text-[11px] text-gray-600 mt-3 flex flex-wrap justify-center gap-x-4 gap-y-1 font-medium border-t border-gray-100 pt-3">
@if(request('bulan') || request('tahun'))
<span>Periode:
{{ request('bulan') ? Carbon\Carbon::create()->month((int) request('bulan'))->translatedFormat('F') : 'Semua Bulan' }}
{{ request('tahun') ?? '' }}
</span>
@endif
<span>Dicetak Pada: {{ \Carbon\Carbon::now()->translatedFormat('d F Y H:i') }}</span>
</div>
</div>
<x-table>
<x-slot name="head">
<x-th>No</x-th>
@ -24,7 +88,7 @@
@forelse($bukuTamu as $item)
<tr class="hover:bg-gray-50 transition-colors border-b border-gray-100 last:border-0">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $loop->iteration }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ (request('limit_start') ? (int)request('limit_start') : 1) + $loop->index }}</td>
<td class="px-6 py-4">
@if($item->user)
{{-- Member path: data dari users --}}
@ -59,22 +123,68 @@
<style>
@media print {
body * {
visibility: hidden;
}
.sidebar, header, nav, footer, button, .mb-4 {
/* Hide layout elements not needed for print */
header, aside, .sidebar, nav, footer, button, .print\:hidden, form, .mb-4 {
display: none !important;
}
.print\:overflow-visible, .print\:overflow-visible * {
visibility: visible !important;
/* Reset body, main, and containers to allow natural multi-page flow */
html, body {
height: auto !important;
min-height: auto !important;
overflow: visible !important;
background-color: #fff !important;
color: #000 !important;
}
.print\:overflow-visible {
position: absolute;
left: 0;
top: 0;
width: 100%;
margin: 0 !important;
/* Force the relative layout container to overflow naturally */
div.flex.flex-1.overflow-hidden.relative {
display: block !important;
height: auto !important;
min-height: auto !important;
overflow: visible !important;
position: static !important;
}
main {
padding: 0 !important;
margin: 0 !important;
height: auto !important;
min-height: auto !important;
overflow: visible !important;
position: static !important;
display: block !important;
width: 100% !important;
}
/* Reset card and table parent wrappers */
.bg-white, .shadow-xl, .rounded-3xl, .p-6, .p-8 {
background: transparent !important;
box-shadow: none !important;
border: none !important;
padding: 0 !important;
margin: 0 !important;
height: auto !important;
min-height: auto !important;
overflow: visible !important;
}
/* Adjust table styles for print */
table {
width: 100% !important;
border-collapse: collapse !important;
}
th, td {
padding: 8px 12px !important;
border: 1px solid #ddd !important;
font-size: 11px !important;
color: #000 !important;
}
/* Avoid page breaks inside table rows */
tr {
page-break-inside: avoid !important;
}
}
</style>

View File

@ -5,14 +5,91 @@
@section('content')
<x-page-header title="Laporan Peminjaman" />
<x-card>
<div class="mb-4 flex flex-col md:flex-row justify-between items-center gap-4">
<p class="text-gray-500 text-sm">Berikut adalah seluruh rekap data peminjaman buku perpustakaan.</p>
<!-- Filter Form (Sembunyikan saat cetak) -->
<form method="GET" action="{{ route('admin.laporan.peminjaman') }}" class="mb-6 bg-gray-50 p-5 rounded-2xl border border-gray-200/60 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4 items-end print:hidden">
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Bulan</label>
<select name="bulan" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
<option value="">-- Semua Bulan --</option>
@foreach([
'1' => 'Januari', '2' => 'Februari', '3' => 'Maret', '4' => 'April',
'5' => 'Mei', '6' => 'Juni', '7' => 'Juli', '8' => 'Agustus',
'9' => 'September', '10' => 'Oktober', '11' => 'November', '12' => 'Desember'
] as $num => $name)
<option value="{{ $num }}" {{ request('bulan') == $num ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Tahun</label>
<select name="tahun" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
<option value="">-- Semua Tahun --</option>
@for($y = date('Y') + 1; $y >= date('Y') - 4; $y--)
<option value="{{ $y }}" {{ request('tahun', date('Y')) == $y ? 'selected' : '' }}>{{ $y }}</option>
@endfor
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Kategori & Lokasi Buku</label>
<select name="id_kategori" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
<option value="">-- Semua Kategori --</option>
@foreach($categories as $key => $name)
<option value="{{ $key }}" {{ request('id_kategori') === (string)$key ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Dari Baris</label>
<input type="number" name="limit_start" value="{{ request('limit_start') }}" min="1" placeholder="Contoh: 1" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Sampai Baris</label>
<input type="number" name="limit_end" value="{{ request('limit_end') }}" min="1" placeholder="Contoh: 20" class="w-full bg-white border border-gray-200 text-gray-800 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block p-3 shadow-sm outline-none">
</div>
<div class="flex gap-2">
<button type="submit" class="flex-grow bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-xl shadow-md transition duration-300 text-sm flex items-center justify-center gap-2">
<i class="fas fa-filter"></i> Filter
</button>
@if(request()->anyFilled(['bulan', 'tahun', 'id_kategori', 'limit_start', 'limit_end']))
<a href="{{ route('admin.laporan.peminjaman') }}" class="bg-gray-100 hover:bg-red-50 border border-gray-200 text-gray-500 hover:text-red-500 p-3 rounded-xl shadow-sm transition duration-300 flex items-center justify-center" title="Reset Filter">
<i class="fas fa-times"></i>
</a>
@endif
</div>
</form>
<div class="mb-4 flex flex-col md:flex-row justify-between items-center gap-4 print:hidden">
<p class="text-gray-500 text-sm">Berikut adalah rekap data peminjaman buku perpustakaan yang disaring.</p>
<button onclick="window.print()" class="bg-gray-800 hover:bg-gray-900 text-white px-4 py-2 rounded-lg text-sm font-bold shadow-sm transition-all flex items-center gap-2">
<i class="fas fa-print"></i> Cetak Laporan
</button>
</div>
<div class="overflow-x-auto print:overflow-visible">
<!-- Print Header (Hanya terlihat saat cetak/print) -->
<div class="hidden print:block mb-6 border-b-2 border-gray-800 pb-4 text-center">
<h1 class="text-2xl font-black uppercase tracking-wide text-gray-900">Laporan Rekapitulasi Peminjaman</h1>
<h2 class="text-sm font-bold text-gray-700 mt-1">PERPUSTAKAAN DAERAH JEMBER</h2>
<p class="text-xs text-gray-500 mt-0.5">Jl. Mastrip No. 1, Kabupaten Jember</p>
<div class="text-[11px] text-gray-600 mt-3 flex flex-wrap justify-center gap-x-4 gap-y-1 font-medium border-t border-gray-100 pt-3">
@if(request('bulan') || request('tahun'))
<span>Periode:
{{ request('bulan') ? Carbon\Carbon::create()->month((int) request('bulan'))->translatedFormat('F') : 'Semua Bulan' }}
{{ request('tahun') ?? '' }}
</span>
@endif
@if(request('id_kategori') !== null && isset($categories[request('id_kategori')]))
<span>Kategori: {{ $categories[request('id_kategori')] }}</span>
@endif
<span>Dicetak Pada: {{ \Carbon\Carbon::now()->translatedFormat('d F Y H:i') }}</span>
</div>
</div>
<x-table>
<x-slot name="head">
<x-th>No</x-th>
@ -27,7 +104,7 @@
@forelse($peminjaman as $index => $item)
<tr class="hover:bg-gray-50 transition-colors border-b border-gray-100 last:border-0">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $index + 1 }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ (request('limit_start') ? (int)request('limit_start') : 1) + $loop->index }}</td>
<td class="px-6 py-4">
@if($item->anggota)
<div class="text-sm font-bold text-gray-900">{{ $item->anggota->nama }}</div>
@ -80,22 +157,68 @@
<style>
@media print {
body * {
visibility: hidden;
}
.sidebar, header, nav, footer, button, .mb-4 {
/* Hide layout elements not needed for print */
header, aside, .sidebar, nav, footer, button, .print\:hidden, form, .mb-4 {
display: none !important;
}
.print\:overflow-visible, .print\:overflow-visible * {
visibility: visible !important;
/* Reset body, main, and containers to allow natural multi-page flow */
html, body {
height: auto !important;
min-height: auto !important;
overflow: visible !important;
background-color: #fff !important;
color: #000 !important;
}
.print\:overflow-visible {
position: absolute;
left: 0;
top: 0;
width: 100%;
margin: 0 !important;
/* Force the relative layout container to overflow naturally */
div.flex.flex-1.overflow-hidden.relative {
display: block !important;
height: auto !important;
min-height: auto !important;
overflow: visible !important;
position: static !important;
}
main {
padding: 0 !important;
margin: 0 !important;
height: auto !important;
min-height: auto !important;
overflow: visible !important;
position: static !important;
display: block !important;
width: 100% !important;
}
/* Reset card and table parent wrappers */
.bg-white, .shadow-xl, .rounded-3xl, .p-6, .p-8 {
background: transparent !important;
box-shadow: none !important;
border: none !important;
padding: 0 !important;
margin: 0 !important;
height: auto !important;
min-height: auto !important;
overflow: visible !important;
}
/* Adjust table styles for print */
table {
width: 100% !important;
border-collapse: collapse !important;
}
th, td {
padding: 8px 12px !important;
border: 1px solid #ddd !important;
font-size: 11px !important;
color: #000 !important;
}
/* Avoid page breaks inside table rows */
tr {
page-break-inside: avoid !important;
}
}
</style>

View File

@ -1,11 +1,13 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'Admin Dashboard') - SARAKATA</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script>
@ -16,15 +18,20 @@
extend: {
fontFamily: { sans: ['Inter', 'sans-serif'] },
colors: {
primary: { 50:'#eef2ff',100:'#e0e7ff',200:'#c7d2fe',300:'#a5b4fc',400:'#818cf8',500:'#6366f1',600:'#4f46e5',700:'#4338ca',800:'#3730a3',900:'#312e81' },
primary: { 50: '#eef2ff', 100: '#e0e7ff', 200: '#c7d2fe', 300: '#a5b4fc', 400: '#818cf8', 500: '#6366f1', 600: '#4f46e5', 700: '#4338ca', 800: '#3730a3', 900: '#312e81' },
}
}
}
}
</script>
<style>
.gradient-text { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #2563eb 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
.gradient-text {
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #2563eb 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* TomSelect Custom Styling */
.ts-control {
border: 1px solid #e5e7eb !important;
@ -34,71 +41,85 @@
font-size: 0.875rem !important;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important;
}
.ts-control.focus {
border-color: #6366f1 !important;
box-shadow: 0 0 0 1px #6366f1 !important;
}
.ts-control input {
font-size: 0.875rem !important;
}
</style>
</head>
<body class="bg-gray-50 font-sans antialiased flex flex-col h-screen overflow-hidden" x-data="{ sidebarOpen: false }">
{{-- TOP HEADER --}}
<header class="bg-gradient-to-r from-primary-600 via-primary-700 to-primary-800 text-white h-16 flex items-center justify-between px-6 shadow-lg z-20">
<header
class="bg-gradient-to-r from-primary-600 via-primary-700 to-primary-800 text-white h-16 flex items-center justify-between px-6 shadow-lg z-20">
<div class="flex items-center gap-3">
<!-- Mobile Hamburger -->
<button @click="sidebarOpen = true" class="md:hidden w-9 h-9 bg-white/15 hover:bg-white/25 rounded-xl flex items-center justify-center backdrop-blur-sm transition focus:outline-none">
<button @click="sidebarOpen = true"
class="md:hidden w-9 h-9 bg-white/15 hover:bg-white/25 rounded-xl flex items-center justify-center backdrop-blur-sm transition focus:outline-none">
<i class="fas fa-bars text-white"></i>
</button>
<div class="hidden md:flex w-9 h-9 bg-white/15 rounded-xl items-center justify-center backdrop-blur-sm">
<i class="fas fa-book-open text-lg"></i>
</div>
<h1 class="text-base font-bold tracking-wide">SARAKATA <span class="font-normal text-primary-200 hidden sm:inline"> Sistem Informasi Perpustakaan</span></h1>
<h1 class="text-base font-bold tracking-wide">SARAKATA <span
class="font-normal text-primary-200 hidden sm:inline"> Sistem Informasi Perpustakaan</span></h1>
</div>
<div class="flex items-center gap-4">
<form method="POST" action="{{ route('logout') }}">
@csrf
<button class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/10 hover:bg-white/20 transition text-sm font-medium">
<button
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/10 hover:bg-white/20 transition text-sm font-medium">
<i class="fas fa-sign-out-alt text-xs"></i> Logout
</button>
</form>
<div class="w-9 h-9 rounded-xl overflow-hidden border-2 border-white/30">
<img src="https://ui-avatars.com/api/?name={{ auth()->user()->name ?? 'Admin' }}&background=6366f1&color=fff" class="w-full h-full object-cover">
<img src="https://ui-avatars.com/api/?name={{ auth()->user()->name ?? 'Admin' }}&background=6366f1&color=fff"
class="w-full h-full object-cover">
</div>
</div>
</header>
<div class="flex flex-1 overflow-hidden relative">
<!-- Overlay Khusus Mobile -->
<div x-show="sidebarOpen" x-transition.opacity class="fixed inset-0 bg-gray-900/60 z-40 md:hidden backdrop-blur-sm" @click="sidebarOpen = false" style="display: none;"></div>
<div x-show="sidebarOpen" x-transition.opacity
class="fixed inset-0 bg-gray-900/60 z-40 md:hidden backdrop-blur-sm" @click="sidebarOpen = false"
style="display: none;"></div>
{{-- SIDEBAR --}}
<aside :class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'" class="w-64 bg-white shadow-2xl flex flex-col overflow-y-auto z-50 border-r border-gray-100 absolute inset-y-0 left-0 transform md:relative md:translate-x-0 transition-transform duration-300 ease-in-out h-full">
<aside :class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'"
class="w-64 bg-white shadow-2xl flex flex-col overflow-y-auto z-50 border-r border-gray-100 absolute inset-y-0 left-0 transform md:relative md:translate-x-0 transition-transform duration-300 ease-in-out h-full">
{{-- Profile --}}
<div class="flex flex-col items-center py-8 border-b border-gray-100 px-4">
<div class="w-20 h-20 rounded-2xl overflow-hidden border-4 border-primary-100 shadow-lg shadow-primary-100">
<img src="https://ui-avatars.com/api/?name={{ auth()->user()->name ?? 'Admin' }}&background=6366f1&color=fff&size=128" class="w-full h-full object-cover">
<div
class="w-20 h-20 rounded-2xl overflow-hidden border-4 border-primary-100 shadow-lg shadow-primary-100">
<img src="https://ui-avatars.com/api/?name={{ auth()->user()->name ?? 'Admin' }}&background=6366f1&color=fff&size=128"
class="w-full h-full object-cover">
</div>
<h2 class="mt-4 font-bold text-gray-800 text-lg">{{ auth()->user()->name ?? 'Admin' }}</h2>
<span class="px-3 py-1 mt-1.5 text-[10px] font-bold text-primary-700 bg-primary-50 rounded-full uppercase tracking-wider">Administrator</span>
<span
class="px-3 py-1 mt-1.5 text-[10px] font-bold text-primary-700 bg-primary-50 rounded-full uppercase tracking-wider">Administrator</span>
</div>
{{-- Navigation --}}
<nav class="flex-1 py-6 space-y-1 px-3">
<a href="{{ route('admin.dashboard') }}"
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1
{{ request()->routeIs('admin.dashboard') ? 'bg-gradient-to-r from-primary-500 to-primary-600 text-white shadow-lg shadow-primary-200' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
<i class="fas fa-th-large w-5 text-center"></i>
<span class="font-semibold text-sm">Dashboard</span>
</a>
<a href="{{ route('admin.buku.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1
{{ request()->routeIs('admin.buku.*') ? 'bg-gradient-to-r from-primary-500 to-primary-600 text-white shadow-lg shadow-primary-200' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
<i class="fas fa-book w-5 text-center"></i>
<span class="font-semibold text-sm">Data Buku</span>
@ -107,30 +128,35 @@ class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300
{{-- Anggota Dropdown --}}
<div x-data="{ open: {{ request()->routeIs('admin.anggota.*') ? 'true' : 'false' }} }">
<button @click="open = !open"
class="w-full flex items-center justify-between px-4 py-3 rounded-xl transition-all duration-300 mb-1
class="w-full flex items-center justify-between px-4 py-3 rounded-xl transition-all duration-300 mb-1
{{ request()->routeIs('admin.anggota.*') ? 'bg-primary-50 text-primary-600' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
<div class="flex items-center gap-3">
<i class="fas fa-users w-5 text-center"></i>
<span class="font-semibold text-sm">Data Anggota</span>
</div>
<i :class="open ? 'rotate-180' : ''" class="fas fa-chevron-down text-[10px] transition-transform duration-200"></i>
<i :class="open ? 'rotate-180' : ''"
class="fas fa-chevron-down text-[10px] transition-transform duration-200"></i>
</button>
<div x-show="open" x-transition class="pl-12 pr-3 py-1 space-y-1">
<a href="{{ route('admin.anggota.tamu') }}" class="block px-3 py-2 text-xs font-medium text-gray-400 hover:text-primary-600 hover:bg-primary-50 rounded-lg transition">Buku Tamu</a>
<a href="{{ route('admin.anggota.member.index') }}" class="block px-3 py-2 text-xs font-medium text-gray-400 hover:text-primary-600 hover:bg-primary-50 rounded-lg transition">Data Member</a>
<a href="{{ route('admin.anggota.tamu') }}"
class="block px-3 py-2 text-xs font-medium text-gray-400 hover:text-primary-600 hover:bg-primary-50 rounded-lg transition">Buku
Tamu</a>
<a href="{{ route('admin.anggota.member.index') }}"
class="block px-3 py-2 text-xs font-medium text-gray-400 hover:text-primary-600 hover:bg-primary-50 rounded-lg transition">Data
Member</a>
</div>
</div>
<a href="{{ route('admin.peminjaman.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1
{{ request()->routeIs('admin.peminjaman.*') ? 'bg-gradient-to-r from-primary-500 to-primary-600 text-white shadow-lg shadow-primary-200' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1
{{ request()->routeIs('admin.peminjaman.*') && !request()->routeIs('admin.peminjaman.scan') ? 'bg-gradient-to-r from-primary-500 to-primary-600 text-white shadow-lg shadow-primary-200' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
<i class="fas fa-file-export w-5 text-center"></i>
<span class="font-semibold text-sm">Data Peminjaman</span>
</a>
<a href="{{ route('admin.pengembalian.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1
{{ request()->routeIs('admin.pengembalian.*') ? 'bg-gradient-to-r from-primary-500 to-primary-600 text-white shadow-lg shadow-primary-200' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1
{{ request()->routeIs('admin.pengembalian.*') || request()->routeIs('admin.peminjaman.scan') ? 'bg-gradient-to-r from-primary-500 to-primary-600 text-white shadow-lg shadow-primary-200' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
<i class="fas fa-file-import w-5 text-center"></i>
<span class="font-semibold text-sm">Data Pengembalian</span>
</a>
@ -138,23 +164,28 @@ class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300
{{-- Laporan Dropdown --}}
<div x-data="{ open: {{ request()->routeIs('admin.laporan.*') ? 'true' : 'false' }} }">
<button @click="open = !open"
class="w-full flex items-center justify-between px-4 py-3 rounded-xl transition-all duration-300 mb-1
class="w-full flex items-center justify-between px-4 py-3 rounded-xl transition-all duration-300 mb-1
{{ request()->routeIs('admin.laporan.*') ? 'bg-primary-50 text-primary-600' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
<div class="flex items-center gap-3">
<i class="fas fa-chart-bar w-5 text-center"></i>
<span class="font-semibold text-sm">Laporan</span>
</div>
<i :class="open ? 'rotate-180' : ''" class="fas fa-chevron-down text-[10px] transition-transform duration-200"></i>
<i :class="open ? 'rotate-180' : ''"
class="fas fa-chevron-down text-[10px] transition-transform duration-200"></i>
</button>
<div x-show="open" x-transition class="pl-12 pr-3 py-1 space-y-1">
<a href="{{ route('admin.laporan.kehadiran') }}" class="block px-3 py-2 text-xs font-medium text-gray-400 hover:text-primary-600 hover:bg-primary-50 rounded-lg transition">Lap. Kehadiran</a>
<a href="{{ route('admin.laporan.peminjaman') }}" class="block px-3 py-2 text-xs font-medium text-gray-400 hover:text-primary-600 hover:bg-primary-50 rounded-lg transition">Lap. Peminjaman</a>
<a href="{{ route('admin.laporan.kehadiran') }}"
class="block px-3 py-2 text-xs font-medium text-gray-400 hover:text-primary-600 hover:bg-primary-50 rounded-lg transition">Lap.
Kehadiran</a>
<a href="{{ route('admin.laporan.peminjaman') }}"
class="block px-3 py-2 text-xs font-medium text-gray-400 hover:text-primary-600 hover:bg-primary-50 rounded-lg transition">Lap.
Peminjaman</a>
</div>
</div>
{{-- Akun Admin Menu --}}
<a href="{{ route('admin.akun.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1 mt-4 border-t border-gray-100 pt-5
class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 mb-1 mt-4 border-t border-gray-100 pt-5
{{ request()->routeIs('admin.akun.*') ? 'bg-gradient-to-r from-primary-500 to-primary-600 text-white shadow-lg shadow-primary-200' : 'text-gray-500 hover:bg-primary-50 hover:text-primary-600' }}">
<i class="fas fa-user-shield w-5 text-center"></i>
<span class="font-semibold text-sm">Kelola Admin</span>
@ -175,16 +206,16 @@ class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300
document.addEventListener('DOMContentLoaded', function () {
// Intercept all forms that have a confirm() in their onsubmit attribute
const deleteForms = document.querySelectorAll('form[onsubmit*="return confirm"]');
deleteForms.forEach(form => {
// Extract the message from the confirm('Message') call
const originalOnsubmit = form.getAttribute('onsubmit');
const messageMatch = originalOnsubmit.match(/confirm\(['"](.*?)['"]\)/);
const message = messageMatch ? messageMatch[1] : 'Apakah Anda yakin ingin menghapus data ini?';
// Remove the default onsubmit to prevent browser confirm
form.removeAttribute('onsubmit');
// Add the custom SweetAlert event listener
form.addEventListener('submit', function (e) {
e.preventDefault();
@ -217,4 +248,5 @@ class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300
</script>
@stack('scripts')
</body>
</html>

View File

@ -6,6 +6,7 @@
<title>@yield('title', 'Sarakata — Perpustakaan Digital')</title>
<meta name="description" content="Sarakata - Sistem Informasi Perpustakaan Digital untuk Generasi Modern">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{{ asset('favicon.ico') }}" type="image/x-icon">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap"
rel="stylesheet">
@ -127,7 +128,8 @@ class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mt-1 hidden
</a>
<!-- Hamburger Button for Mobile -->
<button id="mobile-menu-btn" class="md:hidden text-gray-600 hover:text-primary-600 focus:outline-none p-2 rounded-lg bg-gray-50">
<button id="mobile-menu-btn"
class="md:hidden text-gray-600 hover:text-primary-600 focus:outline-none p-2 rounded-lg bg-gray-50">
<i class="fas fa-bars text-xl"></i>
</button>
@ -171,29 +173,39 @@ class="px-5 py-2.5 bg-gradient-to-r from-green-600 to-green-700 text-white round
</div>
<!-- Mobile Menu -->
<div id="mobile-menu" class="hidden md:hidden bg-white/95 backdrop-blur-xl border-t border-gray-100 shadow-xl absolute w-full left-0 top-full flex-col gap-4 px-6 py-6 transition-all">
<a href="{{ route('home') }}" class="block font-bold text-gray-700 hover:text-primary-600 py-2 border-b border-gray-50">Beranda</a>
<a href="{{ route('katalog.index') }}" class="block font-bold text-gray-700 hover:text-primary-600 py-2 border-b border-gray-50">Katalog Buku</a>
<a href="{{ route('home') }}#fitur" class="block font-bold text-gray-700 hover:text-primary-600 py-2 border-b border-gray-50">Fitur</a>
<a href="{{ route('home') }}#rekomendasi" class="block font-bold text-gray-700 hover:text-primary-600 py-2 border-b border-gray-50">Koleksi</a>
<div id="mobile-menu"
class="hidden md:hidden bg-white/95 backdrop-blur-xl border-t border-gray-100 shadow-xl absolute w-full left-0 top-full flex-col gap-4 px-6 py-6 transition-all">
<a href="{{ route('home') }}"
class="block font-bold text-gray-700 hover:text-primary-600 py-2 border-b border-gray-50">Beranda</a>
<a href="{{ route('katalog.index') }}"
class="block font-bold text-gray-700 hover:text-primary-600 py-2 border-b border-gray-50">Katalog
Buku</a>
<a href="{{ route('home') }}#fitur"
class="block font-bold text-gray-700 hover:text-primary-600 py-2 border-b border-gray-50">Fitur</a>
<a href="{{ route('home') }}#rekomendasi"
class="block font-bold text-gray-700 hover:text-primary-600 py-2 border-b border-gray-50">Koleksi</a>
<div class="flex flex-col gap-3 mt-4">
@guest
<button onclick="openGuestModal()" class="w-full px-5 py-3 bg-primary-50 text-primary-600 rounded-xl font-bold hover:bg-primary-100 transition-colors text-center flex items-center justify-center">
<button onclick="openGuestModal()"
class="w-full px-5 py-3 bg-primary-50 text-primary-600 rounded-xl font-bold hover:bg-primary-100 transition-colors text-center flex items-center justify-center">
<i class="fas fa-book-reader mr-1.5"></i> Buku Tamu
</button>
<a href="{{ route('login') }}" class="w-full px-5 py-3 bg-gradient-to-r from-primary-600 to-primary-700 text-white rounded-xl font-bold shadow-lg shadow-primary-200 text-center">
<a href="{{ route('login') }}"
class="w-full px-5 py-3 bg-gradient-to-r from-primary-600 to-primary-700 text-white rounded-xl font-bold shadow-lg shadow-primary-200 text-center">
<i class="fas fa-sign-in-alt mr-1.5"></i> Login
</a>
@endguest
@auth
@if (auth()->user()->role === 'admin')
<a href="{{ route('admin.dashboard') }}" class="w-full px-5 py-3 bg-gradient-to-r from-primary-600 to-primary-700 text-white rounded-xl font-bold shadow-lg text-center">
<a href="{{ route('admin.dashboard') }}"
class="w-full px-5 py-3 bg-gradient-to-r from-primary-600 to-primary-700 text-white rounded-xl font-bold shadow-lg text-center">
<i class="fas fa-tachometer-alt mr-1.5"></i> Dashboard Admin
</a>
@else
<a href="{{ route('user.dashboard') }}" class="w-full px-5 py-3 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-xl font-bold shadow-lg text-center">
<a href="{{ route('user.dashboard') }}"
class="w-full px-5 py-3 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-xl font-bold shadow-lg text-center">
<i class="fas fa-user mr-1.5"></i> Dashboard Anggota
</a>
@endif
@ -235,8 +247,7 @@ class="w-10 h-10 bg-gradient-to-br from-primary-500 to-primary-700 rounded-xl fl
Buku</a></li>
<li><a href="{{ route('home') }}#fitur" class="hover:text-white transition-colors">Fitur</a>
</li>
<li><a href="{{ route('home') }}#rekomendasi"
class="hover:text-white transition-colors">Koleksi
<li><a href="{{ route('home') }}#rekomendasi" class="hover:text-white transition-colors">Koleksi
Buku</a></li>
<li><a href="{{ route('buku_tamu.index') }}" class="hover:text-white transition-colors">Buku
Tamu</a></li>
@ -276,38 +287,47 @@ class="fab fa-twitter text-sm"></i></a>
</footer>
<!-- Global Guest Modal -->
<div id="guestModal" class="fixed inset-0 bg-gray-900/60 backdrop-blur-sm hidden z-[100] flex items-center justify-center p-4 transition-opacity duration-300">
<div id="guestModal"
class="fixed inset-0 bg-gray-900/60 backdrop-blur-sm hidden z-[100] flex items-center justify-center p-4 transition-opacity duration-300">
<div class="bg-white rounded-[2rem] shadow-2xl max-w-md w-full overflow-hidden transform transition-all">
<div class="p-8 text-center relative">
<!-- Close Button -->
<button onclick="closeGuestModal()" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors w-8 h-8 rounded-full hover:bg-gray-100 flex items-center justify-center">
<button onclick="closeGuestModal()"
class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors w-8 h-8 rounded-full hover:bg-gray-100 flex items-center justify-center">
<i class="fas fa-times"></i>
</button>
<!-- Icon Box -->
<div class="w-16 h-16 bg-gradient-to-br from-primary-500 to-primary-600 text-white rounded-2xl flex items-center justify-center mx-auto mb-5 text-2xl shadow-lg shadow-primary-200">
<div
class="w-16 h-16 bg-gradient-to-br from-primary-500 to-primary-600 text-white rounded-2xl flex items-center justify-center mx-auto mb-5 text-2xl shadow-lg shadow-primary-200">
<i class="fas fa-user-check"></i>
</div>
<h3 class="text-2xl font-black text-gray-900 tracking-tight">Selamat Datang!</h3>
<p class="text-gray-500 text-sm mt-2 leading-relaxed">Apakah Anda sudah terdaftar sebagai anggota Sarakata?</p>
<p class="text-gray-500 text-sm mt-2 leading-relaxed">Apakah Anda sudah terdaftar sebagai anggota
Sarakata?</p>
<div class="grid grid-cols-1 gap-3 mt-8">
<!-- Member Option -->
<a href="{{ route('buku_tamu.index') }}" class="flex items-center gap-4 p-4 border-2 border-primary-100 rounded-2xl hover:border-primary-300 hover:bg-primary-50/50 transition-all group cursor-pointer shadow-sm hover:shadow-md">
<div class="w-12 h-12 bg-primary-100 rounded-xl flex items-center justify-center text-primary-600 group-hover:bg-gradient-to-br group-hover:from-primary-500 group-hover:to-primary-600 group-hover:text-white transition-all shadow-sm group-hover:shadow-primary-300">
<a href="{{ route('buku_tamu.index') }}"
class="flex items-center gap-4 p-4 border-2 border-primary-100 rounded-2xl hover:border-primary-300 hover:bg-primary-50/50 transition-all group cursor-pointer shadow-sm hover:shadow-md">
<div
class="w-12 h-12 bg-primary-100 rounded-xl flex items-center justify-center text-primary-600 group-hover:bg-gradient-to-br group-hover:from-primary-500 group-hover:to-primary-600 group-hover:text-white transition-all shadow-sm group-hover:shadow-primary-300">
<i class="fas fa-id-card"></i>
</div>
<div class="text-left flex-1 border-gray-100">
<div class="font-bold text-gray-900 text-sm">Saya Anggota</div>
<div class="text-xs text-gray-400">Punya No. Anggota</div>
</div>
<i class="fas fa-chevron-right text-primary-300 group-hover:text-primary-500 transition-colors"></i>
<i
class="fas fa-chevron-right text-primary-300 group-hover:text-primary-500 transition-colors"></i>
</a>
<!-- Visitor Option -->
<a href="{{ route('buku_tamu.index', ['tipe' => 'tamu']) }}" class="flex items-center gap-4 p-4 border-2 border-gray-100 rounded-2xl hover:border-gray-300 hover:bg-gray-50 transition-all group cursor-pointer shadow-sm hover:shadow-md">
<div class="w-12 h-12 bg-gray-100 rounded-xl flex items-center justify-center text-gray-500 group-hover:bg-gray-800 group-hover:text-white transition-all shadow-sm">
<a href="{{ route('buku_tamu.index', ['tipe' => 'tamu']) }}"
class="flex items-center gap-4 p-4 border-2 border-gray-100 rounded-2xl hover:border-gray-300 hover:bg-gray-50 transition-all group cursor-pointer shadow-sm hover:shadow-md">
<div
class="w-12 h-12 bg-gray-100 rounded-xl flex items-center justify-center text-gray-500 group-hover:bg-gray-800 group-hover:text-white transition-all shadow-sm">
<i class="fas fa-user-friends"></i>
</div>
<div class="text-left flex-1 border-gray-100">
@ -323,7 +343,7 @@ class="fab fa-twitter text-sm"></i></a>
@stack('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
const btn = document.getElementById('mobile-menu-btn');
const menu = document.getElementById('mobile-menu');
const icon = btn.querySelector('i');
@ -331,7 +351,7 @@ class="fab fa-twitter text-sm"></i></a>
btn.addEventListener('click', () => {
menu.classList.toggle('hidden');
menu.classList.toggle('flex');
// Toggle icon
if (menu.classList.contains('flex')) {
icon.classList.remove('fa-bars');
@ -355,4 +375,4 @@ function closeGuestModal() {
</script>
</body>
</html>
</html>

View File

@ -17,8 +17,10 @@ class="absolute bottom-0 left-0 -ml-20 -mb-20 w-72 h-72 rounded-full bg-indigo-3
<div class="container mx-auto px-4 relative z-10">
<div class="text-center max-w-3xl mx-auto">
<h1 class="text-3xl sm:text-4xl md:text-5xl font-extrabold text-white mb-4 sm:mb-6 tracking-tight leading-tight">
Eksplorasi <span class="text-transparent bg-clip-text bg-gradient-to-r from-blue-200 to-cyan-200">Katalog
<h1
class="text-3xl sm:text-4xl md:text-5xl font-extrabold text-white mb-4 sm:mb-6 tracking-tight leading-tight">
Eksplorasi <span
class="text-transparent bg-clip-text bg-gradient-to-r from-blue-200 to-cyan-200">Katalog
Perpustakaan</span>
</h1>
<p class="text-blue-100 text-sm sm:text-lg md:text-xl mb-8 sm:mb-10 font-light px-2 sm:px-0">
@ -27,7 +29,8 @@ class="absolute bottom-0 left-0 -ml-20 -mb-20 w-72 h-72 rounded-full bg-indigo-3
</p>
<!-- Search Bar -->
<form action="{{ route('katalog.index') }}" method="GET" class="relative group max-w-2xl mx-auto px-2 sm:px-0">
<form action="{{ route('katalog.index') }}" method="GET"
class="relative group max-w-2xl mx-auto px-2 sm:px-0">
<div class="absolute inset-y-0 left-2 sm:left-0 pl-4 flex items-center pointer-events-none">
<svg class="h-5 w-5 sm:h-6 sm:w-6 text-gray-400 group-focus-within:text-blue-500 transition-colors"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -35,8 +38,7 @@ class="absolute bottom-0 left-0 -ml-20 -mb-20 w-72 h-72 rounded-full bg-indigo-3
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input type="text" name="search" value="{{ $search }}"
placeholder="Cari judul buku, pengarang..."
<input type="text" name="search" value="{{ $search }}" placeholder="Cari judul buku, pengarang..."
class="block w-full pl-10 pr-24 sm:pl-12 sm:pr-32 py-3.5 sm:py-4 bg-white/95 backdrop-blur-sm border-0 rounded-xl sm:rounded-2xl shadow-xl focus:ring-4 focus:ring-blue-500/30 text-gray-800 text-sm sm:text-lg transition-all placeholder-gray-400">
<button type="submit"
class="absolute right-3.5 sm:right-2 top-1.5 bottom-1.5 sm:top-2 sm:bottom-2 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white font-semibold px-5 sm:px-6 lg:px-8 rounded-lg sm:rounded-xl shadow-md transition-all transform hover:scale-[1.02] active:scale-[0.98] text-sm sm:text-base">
@ -65,19 +67,14 @@ class="absolute right-3.5 sm:right-2 top-1.5 bottom-1.5 sm:top-2 sm:bottom-2 bg-
class="group bg-white rounded-2xl shadow-sm hover:shadow-2xl border border-gray-100 overflow-hidden transition-all duration-300 transform {{ $habis ? 'grayscale opacity-75 cursor-not-allowed' : 'hover:-translate-y-1' }} flex flex-col h-full">
<!-- Book Cover -->
<div class="relative w-full pt-[140%] overflow-hidden bg-gray-50 flex-shrink-0">
@if ($item->cover)
<img src="{{ asset('storage/' . $item->cover) }}" alt="{{ $item->judul }}"
@php
$coverPath = $item->cover ?? $item->sampul;
$finalPath = $coverPath ? (Str::contains($coverPath, '/') ? $coverPath : 'covers/' . $coverPath) : null;
@endphp
@if ($finalPath)
<img src="{{ asset('storage/' . $finalPath) }}" alt="{{ $item->judul }}"
class="absolute inset-0 w-full h-full object-cover {{ !$habis ? 'group-hover:scale-110' : '' }} transition-transform duration-700 ease-in-out"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<div
class="hidden absolute inset-0 flex flex-col items-center justify-center text-gray-300 bg-gray-100 {{ !$habis ? 'group-hover:bg-gray-200' : '' }} transition-colors duration-300">
<svg class="w-12 h-12 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253">
</path>
</svg>
<span class="text-xs font-medium">Cover Tidak Tersedia</span>
</div>
onerror="this.src='https://ui-avatars.com/api/?name={{ urlencode($item->judul) }}&background=F3F4F6&color=6366F1&size=512&bold=true';">
@else
<div
class="absolute inset-0 flex flex-col items-center justify-center text-gray-300 bg-gray-100 {{ !$habis ? 'group-hover:bg-gray-200' : '' }} transition-colors duration-300">
@ -92,8 +89,7 @@ class="absolute inset-0 flex flex-col items-center justify-center text-gray-300
@if ($habis)
<!-- Overlay Dipinjam Semua -->
<div
class="absolute inset-0 bg-red-900/30 backdrop-blur-[1px] flex items-center justify-center z-10">
<div class="absolute inset-0 bg-red-900/30 backdrop-blur-[1px] flex items-center justify-center z-10">
<div
class="bg-red-600/90 text-white font-black text-sm md:text-base px-6 py-2 border-y-2 border-white transform -rotate-12 shadow-2xl tracking-widest uppercase pointer-events-none">
TIDAK TERSEDIA
@ -229,4 +225,4 @@ class="inline-flex items-center gap-1.5 bg-gradient-to-r from-blue-50 to-indigo-
</div>
@endif
</div>
@endsection
@endsection

View File

@ -202,9 +202,14 @@ class="absolute right-1 top-1 bottom-1 w-10 flex items-center justify-center bg-
<div class="md:col-span-12 lg:col-span-3">
<div
class="bg-white rounded-3xl shadow-[0_10px_40px_-10px_rgba(0,0,0,0.08)] border border-gray-50 p-5 flex flex-col items-center text-center overflow-hidden transition-transform duration-500 hover:-translate-y-2">
@if ($buku->cover)
<img src="{{ asset('storage/' . $buku->cover) }}" alt="{{ $buku->judul }}"
class="w-full aspect-[3/4] object-cover rounded-2xl mb-6 shadow-md border border-gray-100">
@php
$coverPath = $buku->cover ?? $buku->sampul;
$finalPath = $coverPath ? (Str::contains($coverPath, '/') ? $coverPath : 'covers/' . $coverPath) : null;
@endphp
@if ($finalPath)
<img src="{{ asset('storage/' . $finalPath) }}" alt="{{ $buku->judul }}"
class="w-full aspect-[3/4] object-cover rounded-2xl mb-6 shadow-md border border-gray-100"
onerror="this.src='https://ui-avatars.com/api/?name={{ urlencode($buku->judul) }}&background=F3F4F6&color=6366F1&size=512&bold=true';">
@else
<div
class="w-full aspect-[3/4] bg-gradient-to-br from-indigo-50 to-blue-50 rounded-2xl flex items-center justify-center mb-6 overflow-hidden relative shadow-inner border border-blue-100/50">

View File

@ -6,8 +6,7 @@
<div
class="flex flex-col md:flex-row justify-between items-start md:items-center mb-10 gap-4 text-center md:text-left">
<div class="w-full md:w-auto">
<div
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-100 rounded-full mb-4 mx-auto md:mx-0">
<div class="inline-flex items-center gap-2 px-4 py-2 bg-blue-100 rounded-full mb-4 mx-auto md:mx-0">
<i class="fas fa-fire text-red-500 text-xs"></i>
<span class="text-xs font-semibold text-blue-800 tracking-wide">Paling Laris</span>
</div>
@ -39,15 +38,18 @@ class="bg-blue-600/90 backdrop-blur-md text-white text-[10px] sm:text-xs font-bo
<!-- Book Cover -->
<div class="relative w-full pt-[140%] overflow-hidden bg-gray-50 flex-shrink-0">
@if ($item->cover ?? $item->sampul)
<img src="{{ asset('storage/' . ($item->cover ?? $item->sampul)) }}"
alt="{{ $item->judul }}"
class="absolute inset-0 w-full h-full object-cover {{ !$habis ? 'group-hover:scale-110' : '' }} transition-transform duration-700 ease-in-out">
@php
$coverPath = $item->cover ?? $item->sampul;
$finalPath = $coverPath ? (Str::contains($coverPath, '/') ? $coverPath : 'covers/' . $coverPath) : null;
@endphp
@if ($finalPath)
<img src="{{ asset('storage/' . $finalPath) }}" alt="{{ $item->judul }}"
class="absolute inset-0 w-full h-full object-cover {{ !$habis ? 'group-hover:scale-110' : '' }} transition-transform duration-700 ease-in-out"
onerror="this.src='https://ui-avatars.com/api/?name={{ urlencode($item->judul) }}&background=F3F4F6&color=6366F1&size=512&bold=true';">
@else
<div
class="absolute inset-0 flex flex-col items-center justify-center text-gray-300 bg-gray-100 {{ !$habis ? 'group-hover:bg-gray-200' : '' }} transition-colors duration-300">
<svg class="w-12 h-12 mb-2" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<svg class="w-12 h-12 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253">
</path>
@ -113,4 +115,4 @@ class="block w-full text-center bg-gray-50 hover:bg-blue-600 text-gray-700 hover
</div>
@endif
</div>
</section>
</section>

View File

@ -42,15 +42,19 @@ class="bg-gradient-to-r from-yellow-400 to-orange-500 text-white text-[9px] sm:t
<!-- Book Cover -->
<div class="relative w-full pt-[140%] overflow-hidden bg-gray-50 flex-shrink-0">
@if ($item->cover ?? $item->sampul)
<img src="{{ asset('storage/' . ($item->cover ?? $item->sampul)) }}"
alt="{{ $item->judul }}"
class="absolute inset-0 w-full h-full object-cover {{ !$habis ? 'group-hover:scale-110' : '' }} transition-transform duration-700 ease-in-out">
@php
$coverPath = $item->cover ?? $item->sampul;
// Cek apakah path sudah mengandung folder, jika tidak default ke 'covers/'
$finalPath = $coverPath ? (Str::contains($coverPath, '/') ? $coverPath : 'covers/' . $coverPath) : null;
@endphp
@if ($finalPath)
<img src="{{ asset('storage/' . $finalPath) }}" alt="{{ $item->judul }}"
class="absolute inset-0 w-full h-full object-cover {{ !$habis ? 'group-hover:scale-110' : '' }} transition-transform duration-700 ease-in-out"
onerror="this.src='https://ui-avatars.com/api/?name={{ urlencode($item->judul) }}&background=EBF4FF&color=7F9CF5&size=512&bold=true';">
@else
<div
class="absolute inset-0 flex flex-col items-center justify-center text-gray-300 bg-gray-100 {{ !$habis ? 'group-hover:bg-gray-200' : '' }} transition-colors duration-300">
<svg class="w-12 h-12 mb-2" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<svg class="w-12 h-12 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253">
</path>
@ -124,4 +128,4 @@ class="block w-full text-center bg-gray-50 hover:bg-blue-600 text-gray-700 hover
</div>
@endif
</div>
</section>
</section>

View File

@ -14,41 +14,41 @@
$koordinat = match (true) {
$kode_utama >= 0 && $kode_utama <= 99 => match (true) {
$kode_utama <= 19 => ['x' => 13.00, 'y' => 22.00], // Rak 01
$kode_utama <= 50 => ['x' => 18.00, 'y' => 22.00], // Rak 02
default => ['x' => 25.00, 'y' => 22.00], // Rak 03-05
$kode_utama <= 19 => ['x' => 12.00, 'y' => 13.00], // Rak 01
$kode_utama <= 50 => ['x' => 17.00, 'y' => 13.00], // Rak 02
default => ['x' => 22.00, 'y' => 13.00], // Rak 03-05
},
$kode_utama >= 100 && $kode_utama <= 199 => match (true) {
$kode_utama <= 150 => ['x' => 43.00, 'y' => 20.00], // Rak 06-10
default => ['x' => 57.00, 'y' => 20.00], // Rak 11-14
$kode_utama <= 150 => ['x' => 35.00, 'y' => 13.00], // Rak 06-10
default => ['x' => 35.00, 'y' => 17.00], // Rak 11-14
},
$kode_utama >= 200 && $kode_utama <= 299 => match (true) {
$kode_utama == 297 => ['x' => 25.00, 'y' => 38.00], // Rak 25-32 (Islam)
default => ['x' => 13.00, 'y' => 38.00], // Rak 15-24
$kode_utama == 297 => ['x' => 14.00, 'y' => 38.00], // Rak 25-32 (Islam)
default => ['x' => 14.00, 'y' => 30.00], // Rak 15-24
},
$kode_utama >= 300 && $kode_utama <= 399 => match (true) {
$kode_utama <= 330 => ['x' => 43.00, 'y' => 30.00], // Rak 33-36
$kode_utama <= 360 => ['x' => 50.00, 'y' => 30.00], // Rak 37-40
default => ['x' => 57.00, 'y' => 30.00], // Rak 41-44
$kode_utama <= 330 => ['x' => 38.00, 'y' => 23.00], // Rak 33-36
$kode_utama <= 360 => ['x' => 43.00, 'y' => 23.00], // Rak 37-40
default => ['x' => 48.00, 'y' => 23.00], // Rak 41-44
},
$kode_utama >= 400 && $kode_utama <= 499 => ['x' => 18.00, 'y' => 62.00], // Rak 45
$kode_utama >= 500 && $kode_utama <= 599 => ['x' => 50.00, 'y' => 74.00], // Rak 46-48
$kode_utama >= 400 && $kode_utama <= 499 => ['x' => 14.00, 'y' => 58.00], // Rak 45
$kode_utama >= 500 && $kode_utama <= 599 => ['x' => 38.00, 'y' => 72.00], // Rak 46-48
$kode_utama >= 600 && $kode_utama <= 699 => match (true) {
$kode_utama <= 610 => ['x' => 35.00, 'y' => 85.00], // Rak 49-53
$kode_utama <= 630 => ['x' => 45.00, 'y' => 85.00], // Rak 54-58
$kode_utama <= 650 => ['x' => 55.00, 'y' => 85.00], // Rak 59-63
default => ['x' => 65.00, 'y' => 85.00], // Rak 64-68
$kode_utama <= 610 => ['x' => 28.00, 'y' => 83.00], // Rak 49-53
$kode_utama <= 630 => ['x' => 36.00, 'y' => 83.00], // Rak 54-58
$kode_utama <= 650 => ['x' => 44.00, 'y' => 83.00], // Rak 59-63
default => ['x' => 52.00, 'y' => 83.00], // Rak 64-68
},
$kode_utama >= 700 && $kode_utama <= 799 => match (true) {
$kode_utama <= 739 => ['x' => 77.00, 'y' => 22.00], // Rak 71
$kode_utama <= 769 => ['x' => 82.00, 'y' => 22.00], // Rak 72
$kode_utama <= 789 => ['x' => 87.00, 'y' => 22.00], // Rak 73
default => ['x' => 82.00, 'y' => 22.00], // Rak 74
$kode_utama <= 739 => ['x' => 82.00, 'y' => 12.00], // Rak 71
$kode_utama <= 769 => ['x' => 86.00, 'y' => 12.00], // Rak 72
$kode_utama <= 789 => ['x' => 82.00, 'y' => 16.00], // Rak 73
default => ['x' => 86.00, 'y' => 16.00], // Rak 74
},
$kode_utama >= 800 && $kode_utama <= 899 => ['x' => 82.00, 'y' => 32.00], // Rak 77-79
$kode_utama >= 800 && $kode_utama <= 899 => ['x' => 82.00, 'y' => 25.00], // Rak 77-79
$kode_utama >= 900 && $kode_utama <= 999 => match (true) {
$kode_utama <= 919 => ['x' => 77.00, 'y' => 42.00], // Rak 69-70
default => ['x' => 87.00, 'y' => 42.00], // Rak 80-84
$kode_utama <= 919 => ['x' => 82.00, 'y' => 30.00], // Rak 69-70
default => ['x' => 82.00, 'y' => 35.00], // Rak 80-84
},
default => ['x' => null, 'y' => null],
};