Compare commits
10 Commits
18529760d7
...
17c93a4252
| Author | SHA1 | Date |
|---|---|---|
|
|
17c93a4252 | |
|
|
5f30be4811 | |
|
|
1071e4e428 | |
|
|
bf0996430a | |
|
|
a656f599a4 | |
|
|
077e2746f6 | |
|
|
570b134f7c | |
|
|
70a420ae47 | |
|
|
ca98d0a665 | |
|
|
c2a61ea5a0 |
|
|
@ -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>
|
||||
|
|
@ -20,20 +20,24 @@ public function store(Request $request)
|
|||
'bibid' => 'required|string|max:30|unique:buku,bibid',
|
||||
'judul' => 'required|string',
|
||||
'pengarang' => 'required|string|max:100',
|
||||
'penerbit' => 'nullable|string',
|
||||
'tahun_terbit' => 'nullable|digits:4',
|
||||
'edisi' => 'nullable|string|max:50',
|
||||
'deskripsi_fisik' => 'nullable|string|max:100',
|
||||
'nomor_panggil' => 'required|string|max:50',
|
||||
'penerbit' => 'required|string',
|
||||
'tahun_terbit' => 'required|digits:4',
|
||||
'edisi' => 'required|digits:4',
|
||||
'deskripsi_fisik' => 'required|string|max:100',
|
||||
'nomor_panggil' => 'required|string|max:50|unique:buku,nomor_panggil',
|
||||
'eksemplar' => 'required|integer|min:1',
|
||||
'id_kategori' => 'required|exists:kategori,id_kategori',
|
||||
'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048'
|
||||
'cover' => 'image|mimes:jpeg,png,jpg,webp|max:2048',
|
||||
]);
|
||||
|
||||
if ($request->hasFile('cover')) {
|
||||
$validated['cover'] = $request->file('cover')->store('covers', 'public');
|
||||
}
|
||||
|
||||
$koordinat = $this->tentukanKoordinatRak($validated['nomor_panggil']);
|
||||
$validated['lokasi_x'] = $koordinat['x'];
|
||||
$validated['lokasi_y'] = $koordinat['y'];
|
||||
|
||||
$validated['konten_digital'] = 0;
|
||||
Buku::create($validated);
|
||||
|
||||
|
|
@ -71,14 +75,14 @@ public function update(Request $request, $id)
|
|||
'bibid' => 'required|string|max:30|unique:buku,bibid,' . $id . ',id_buku',
|
||||
'judul' => 'required|string',
|
||||
'pengarang' => 'required|string|max:100',
|
||||
'penerbit' => 'nullable|string',
|
||||
'tahun_terbit' => 'nullable|digits:4',
|
||||
'edisi' => 'nullable|string|max:50',
|
||||
'deskripsi_fisik' => 'nullable|string|max:100',
|
||||
'nomor_panggil' => 'required|string|max:50',
|
||||
'penerbit' => 'required|string',
|
||||
'tahun_terbit' => 'required|digits:4',
|
||||
'edisi' => 'required|digits:4',
|
||||
'deskripsi_fisik' => 'required|string|max:100',
|
||||
'nomor_panggil' => 'required|string|max:50|unique:buku,nomor_panggil,' . $id . ',id_buku',
|
||||
'eksemplar' => 'required|integer|min:1',
|
||||
'id_kategori' => 'required|exists:kategori,id_kategori',
|
||||
'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048'
|
||||
'cover' => 'image|mimes:jpeg,png,jpg,webp|max:2048'
|
||||
]);
|
||||
|
||||
if ($request->hasFile('cover')) {
|
||||
|
|
@ -88,6 +92,10 @@ public function update(Request $request, $id)
|
|||
$validated['cover'] = $request->file('cover')->store('covers', 'public');
|
||||
}
|
||||
|
||||
$koordinat = $this->tentukanKoordinatRak($validated['nomor_panggil']);
|
||||
$validated['lokasi_x'] = $koordinat['x'];
|
||||
$validated['lokasi_y'] = $koordinat['y'];
|
||||
|
||||
$buku->update($validated);
|
||||
|
||||
return redirect()->route('admin.buku.index')->with('success', 'Aset buku berhasil diperbarui.');
|
||||
|
|
@ -100,4 +108,52 @@ public function destroy($id)
|
|||
|
||||
return redirect()->route('admin.buku.index')->with('success', 'Aset buku berhasil dihapus dari sistem.');
|
||||
}
|
||||
|
||||
private function tentukanKoordinatRak($nomor_panggil)
|
||||
{
|
||||
if (empty($nomor_panggil)) return ['x' => null, 'y' => null];
|
||||
|
||||
$kode_utama = (int) substr(trim(preg_replace('/[^0-9]/', '', $nomor_panggil)), 0, 3);
|
||||
|
||||
return match (true) {
|
||||
$kode_utama >= 0 && $kode_utama <= 99 => match (true) {
|
||||
$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' => 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' => 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' => 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' => 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' => 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' => 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' => 25.00], // Rak 77-79
|
||||
$kode_utama >= 900 && $kode_utama <= 999 => match (true) {
|
||||
$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],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@
|
|||
use App\Models\Buku;
|
||||
use App\Models\Anggota;
|
||||
use Illuminate\Http\Request;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AdminPeminjamanController extends Controller
|
||||
{
|
||||
|
|
@ -35,14 +33,14 @@ public function index(Request $request)
|
|||
$peminjaman = $query->paginate(15)->appends(['search' => $search]);
|
||||
|
||||
$buku = Buku::orderBy('judul', 'asc')->get();
|
||||
$anggota = Anggota::orderBy('nama', 'asc')->get();
|
||||
$anggota = Anggota::latest()->get();
|
||||
return view('admin.peminjaman.index', compact('peminjaman', 'buku', 'anggota', 'search'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$buku = Buku::where('eksemplar', '>', 0)->orderBy('judul', 'asc')->get();
|
||||
$anggota = Anggota::orderBy('nama', 'asc')->get();
|
||||
$anggota = Anggota::latest()->get();
|
||||
return view('admin.peminjaman.create', compact('buku', 'anggota'));
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +48,7 @@ public function edit($id)
|
|||
{
|
||||
$peminjaman = Peminjaman::findOrFail($id);
|
||||
$buku = Buku::all();
|
||||
$anggota = Anggota::orderBy('nama', 'asc')->get();
|
||||
$anggota = Anggota::latest()->get();
|
||||
return view('admin.peminjaman.edit', compact('peminjaman', 'buku', 'anggota'));
|
||||
}
|
||||
|
||||
|
|
@ -60,84 +58,49 @@ public function store(Request $request)
|
|||
$validated = $request->validate([
|
||||
'id_anggota' => 'required|exists:anggotas,id',
|
||||
'id_buku' => 'required|exists:buku,id_buku',
|
||||
'tanggal_pinjam' => 'required|date',
|
||||
'tanggal_kembali' => 'required|date|after_or_equal:tanggal_pinjam',
|
||||
'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.';
|
||||
|
|
@ -197,42 +160,42 @@ private function prediksiLokasiRakUntukAdmin($nomor_panggil)
|
|||
|
||||
return match (true) {
|
||||
$kode_utama >= 0 && $kode_utama <= 99 => match (true) {
|
||||
$kode_utama <= 19 => ['rak' => 'Rak 01', 'area' => 'Karya Umum'],
|
||||
$kode_utama <= 50 => ['rak' => 'Rak 02', 'area' => 'Karya Umum'],
|
||||
default => ['rak' => 'Rak 03-05', 'area' => 'Karya Umum Lainnya'],
|
||||
},
|
||||
$kode_utama <= 19 => ['rak' => 'Rak 01', 'area' => 'Karya Umum'],
|
||||
$kode_utama <= 50 => ['rak' => 'Rak 02', 'area' => 'Karya Umum'],
|
||||
default => ['rak' => 'Rak 03-05', 'area' => 'Karya Umum Lainnya'],
|
||||
},
|
||||
$kode_utama >= 100 && $kode_utama <= 199 => match (true) {
|
||||
$kode_utama <= 150 => ['rak' => 'Rak 06-10', 'area' => 'Filsafat'],
|
||||
default => ['rak' => 'Rak 11-14', 'area' => 'Psikologi'],
|
||||
},
|
||||
$kode_utama <= 150 => ['rak' => 'Rak 06-10', 'area' => 'Filsafat'],
|
||||
default => ['rak' => 'Rak 11-14', 'area' => 'Psikologi'],
|
||||
},
|
||||
$kode_utama >= 200 && $kode_utama <= 299 => match (true) {
|
||||
$kode_utama == 297 => ['rak' => 'Rak 25-32', 'area' => 'Agama Islam'],
|
||||
default => ['rak' => 'Rak 15-24', 'area' => 'Agama Umum'],
|
||||
},
|
||||
$kode_utama == 297 => ['rak' => 'Rak 25-32', 'area' => 'Agama Islam'],
|
||||
default => ['rak' => 'Rak 15-24', 'area' => 'Agama Umum'],
|
||||
},
|
||||
$kode_utama >= 300 && $kode_utama <= 399 => match (true) {
|
||||
$kode_utama <= 330 => ['rak' => 'Rak 33-36', 'area' => 'Sosiologi & Politik'],
|
||||
$kode_utama <= 360 => ['rak' => 'Rak 37-40', 'area' => 'Ekonomi & Hukum'],
|
||||
default => ['rak' => 'Rak 41-44', 'area' => 'Pendidikan & Adat'],
|
||||
},
|
||||
$kode_utama <= 330 => ['rak' => 'Rak 33-36', 'area' => 'Sosiologi & Politik'],
|
||||
$kode_utama <= 360 => ['rak' => 'Rak 37-40', 'area' => 'Ekonomi & Hukum'],
|
||||
default => ['rak' => 'Rak 41-44', 'area' => 'Pendidikan & Adat'],
|
||||
},
|
||||
$kode_utama >= 400 && $kode_utama <= 499 => ['rak' => 'Rak 45', 'area' => 'Bahasa'],
|
||||
$kode_utama >= 500 && $kode_utama <= 599 => ['rak' => 'Rak 46-48', 'area' => 'Ilmu Murni'],
|
||||
$kode_utama >= 600 && $kode_utama <= 699 => match (true) {
|
||||
$kode_utama <= 610 => ['rak' => 'Rak 49-53', 'area' => 'Kedokteran'],
|
||||
$kode_utama <= 630 => ['rak' => 'Rak 54-58', 'area' => 'Teknik'],
|
||||
$kode_utama <= 650 => ['rak' => 'Rak 59-63', 'area' => 'Pertanian'],
|
||||
default => ['rak' => 'Rak 64-68', 'area' => 'Manajemen Bisnis'],
|
||||
},
|
||||
$kode_utama <= 610 => ['rak' => 'Rak 49-53', 'area' => 'Kedokteran'],
|
||||
$kode_utama <= 630 => ['rak' => 'Rak 54-58', 'area' => 'Teknik'],
|
||||
$kode_utama <= 650 => ['rak' => 'Rak 59-63', 'area' => 'Pertanian'],
|
||||
default => ['rak' => 'Rak 64-68', 'area' => 'Manajemen Bisnis'],
|
||||
},
|
||||
$kode_utama >= 700 && $kode_utama <= 799 => match (true) {
|
||||
$kode_utama <= 739 => ['rak' => 'Rak 71', 'area' => 'Kesenian'],
|
||||
$kode_utama <= 769 => ['rak' => 'Rak 72', 'area' => 'Seni Rupa'],
|
||||
$kode_utama <= 789 => ['rak' => 'Rak 73', 'area' => 'Fotografi/Musik'],
|
||||
default => ['rak' => 'Rak 74', 'area' => 'Olahraga'],
|
||||
},
|
||||
$kode_utama <= 739 => ['rak' => 'Rak 71', 'area' => 'Kesenian'],
|
||||
$kode_utama <= 769 => ['rak' => 'Rak 72', 'area' => 'Seni Rupa'],
|
||||
$kode_utama <= 789 => ['rak' => 'Rak 73', 'area' => 'Fotografi/Musik'],
|
||||
default => ['rak' => 'Rak 74', 'area' => 'Olahraga'],
|
||||
},
|
||||
$kode_utama >= 800 && $kode_utama <= 899 => ['rak' => 'Rak 77-79', 'area' => 'Sastra'],
|
||||
$kode_utama >= 900 && $kode_utama <= 999 => match (true) {
|
||||
$kode_utama <= 919 => ['rak' => 'Rak 69, 70', 'area' => 'Geografi'],
|
||||
default => ['rak' => 'Rak 80-84', 'area' => 'Sejarah Umum'],
|
||||
},
|
||||
$kode_utama <= 919 => ['rak' => 'Rak 69, 70', 'area' => 'Geografi'],
|
||||
default => ['rak' => 'Rak 80-84', 'area' => 'Sejarah Umum'],
|
||||
},
|
||||
default => ['rak' => 'Rak 75-76', 'area' => 'Koleksi Terbaru'],
|
||||
};
|
||||
}
|
||||
|
|
@ -242,12 +205,12 @@ public function update(Request $request, $id)
|
|||
$validated = $request->validate([
|
||||
'id_anggota' => 'required|exists:anggotas,id',
|
||||
'id_buku' => 'required|exists:buku,id_buku',
|
||||
'tanggal_pinjam' => 'required|date',
|
||||
'tanggal_kembali' => 'required|date|after_or_equal:tanggal_pinjam',
|
||||
'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',
|
||||
]);
|
||||
|
||||
$peminjaman = Peminjaman::findOrFail($id);
|
||||
|
||||
|
||||
// If the book changed, adjust stock
|
||||
if ($peminjaman->id_buku != $validated['id_buku']) {
|
||||
if ($peminjaman->status_peminjaman == 'Dipinjam') {
|
||||
|
|
@ -267,7 +230,7 @@ public function update(Request $request, $id)
|
|||
public function destroy($id)
|
||||
{
|
||||
$peminjaman = Peminjaman::findOrFail($id);
|
||||
|
||||
|
||||
if ($peminjaman->status_peminjaman == 'Dipinjam' && $peminjaman->buku) {
|
||||
$peminjaman->buku->increment('eksemplar');
|
||||
}
|
||||
|
|
@ -280,7 +243,7 @@ public function destroy($id)
|
|||
public function kembalikan($id)
|
||||
{
|
||||
$peminjaman = Peminjaman::findOrFail($id);
|
||||
|
||||
|
||||
$tglTenggat = \Carbon\Carbon::parse($peminjaman->tanggal_kembali)->startOfDay();
|
||||
$tglSekarang = \Carbon\Carbon::now()->startOfDay();
|
||||
|
||||
|
|
@ -307,4 +270,74 @@ public function kembalikan($id)
|
|||
|
||||
return redirect()->back()->with('success', $pesan);
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
if (!empty($targetNum)) {
|
||||
$targetNum = preg_replace('/^0/', '62', trim($targetNum));
|
||||
}
|
||||
|
||||
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 .= "📄 *{$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";
|
||||
$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_";
|
||||
|
||||
$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)) {
|
||||
return true;
|
||||
} else {
|
||||
\Illuminate\Support\Facades\Log::warning("Fonnte Log: Gagal Terkirim", [
|
||||
'target' => $targetNum,
|
||||
'http_status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
]);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error("Error WA Pengiriman: " . $e->getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ public function store(Request $request)
|
|||
'nama' => 'required|string|max:255',
|
||||
'jenis_anggota' => 'required|in:Mahasiswa,Siswa,Dosen,Umum',
|
||||
'no_identitas' => 'required|unique:anggotas,no_identitas',
|
||||
'no_ktp' => 'required|numeric|digits_between:10,16',
|
||||
'prodi' => 'nullable|string|max:255',
|
||||
'no_hp' => 'required|string|max:20',
|
||||
'no_ktp' => 'required|numeric|digits_between:1,16|unique:anggotas,no_ktp',
|
||||
'prodi' => 'required_unless:jenis_anggota,Umum|nullable|string|max:255',
|
||||
'no_hp' => 'required|numeric|digits_between:10,13',
|
||||
'alamat' => 'required|string',
|
||||
'nama_wali' => 'required|string|max:255',
|
||||
'no_hp_wali' => 'required|string|max:20',
|
||||
'no_hp_wali' => 'required|numeric|digits_between:10,13',
|
||||
'hubungan_wali' => 'required|in:Orang Tua,Saudara,Dosen Wali,Lainnya',
|
||||
]);
|
||||
|
||||
|
|
@ -60,12 +60,12 @@ public function update(Request $request, Anggota $member)
|
|||
'nama' => 'required|string|max:255',
|
||||
'jenis_anggota' => 'required|in:Mahasiswa,Siswa,Dosen,Umum',
|
||||
'no_identitas' => 'required|unique:anggotas,no_identitas,' . $member->id,
|
||||
'no_ktp' => 'required|numeric|digits_between:10,16',
|
||||
'prodi' => 'nullable|string|max:255',
|
||||
'no_hp' => 'required|string|max:20',
|
||||
'no_ktp' => 'required|numeric|digits_between:1,16|unique:anggotas,no_ktp,' . $member->id,
|
||||
'prodi' => 'required_unless:jenis_anggota,Umum|nullable|string|max:255',
|
||||
'no_hp' => 'required|numeric|digits_between:10,13',
|
||||
'alamat' => 'required|string',
|
||||
'nama_wali' => 'required|string|max:255',
|
||||
'no_hp_wali' => 'required|string|max:20',
|
||||
'no_hp_wali' => 'required|numeric|digits_between:10,13',
|
||||
'hubungan_wali' => 'required|in:Orang Tua,Saudara,Dosen Wali,Lainnya',
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public function index()
|
|||
*/
|
||||
public function showAdminLoginForm()
|
||||
{
|
||||
return view('auth.login'); // login admin
|
||||
return view('Auth.login'); // login admin
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -53,7 +53,7 @@ public function adminLogin(Request $request)
|
|||
*/
|
||||
public function showUserLoginForm()
|
||||
{
|
||||
return view('auth.login-user'); // login pengunjung
|
||||
return view('Auth.login-user'); // login pengunjung
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ public function store(Request $request)
|
|||
'keperluan' => 'required',
|
||||
]);
|
||||
|
||||
$anggota = Anggota::where('no_identitas', $request->no_anggota)->first();
|
||||
$anggota = Anggota::where('no_ktp', $request->no_anggota)->orWhere('no_identitas', $request->no_anggota)->first();
|
||||
|
||||
if (!$anggota) {
|
||||
return back()->withErrors(['no_anggota' => 'Nomor Anggota tidak ditemukan dalam sistem kami.'])->withInput();
|
||||
return back()->withErrors(['no_anggota' => 'Nomor KTP / NIK tidak ditemukan dalam sistem kami.'])->withInput();
|
||||
}
|
||||
|
||||
// Cari user terkait untuk id_user
|
||||
|
|
@ -47,10 +47,10 @@ public function store(Request $request)
|
|||
// Jalur tamu: isi manual
|
||||
$request->validate([
|
||||
'nama_tamu' => 'required|string|max:255',
|
||||
'email' => 'nullable|email|max:255',
|
||||
'no_hp' => 'nullable|string|max:20',
|
||||
'email' => 'required|email|max:255',
|
||||
'no_hp' => 'required|digits_between:10,13',
|
||||
'asal_instansi' => 'required|string|max:255',
|
||||
'status' => 'nullable|string|max:255',
|
||||
'status' => 'required|string|max:255',
|
||||
'keperluan' => 'required',
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class PeminjamanController extends Controller
|
||||
{
|
||||
|
|
@ -32,13 +31,13 @@ public function index(\Illuminate\Http\Request $request)
|
|||
|
||||
$peminjaman = $query->paginate(15)->appends(['search' => $search]);
|
||||
$buku = Buku::where('eksemplar', '>', 0)->get();
|
||||
$anggota = Anggota::all();
|
||||
$anggota = Anggota::latest()->get();
|
||||
return view('admin.peminjaman.index', compact('peminjaman', 'buku', 'anggota', 'search'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$anggota = Anggota::all();
|
||||
$anggota = Anggota::latest()->get();
|
||||
$bukus = Buku::all();
|
||||
return view('admin.peminjaman.create', compact('anggota', 'bukus'));
|
||||
}
|
||||
|
|
@ -110,9 +109,9 @@ public function store(Request $request)
|
|||
$response = Http::withoutVerifying()->timeout(15)->withHeaders([
|
||||
'Authorization' => $fonnteToken,
|
||||
])->attach('file', file_get_contents($filePath), $fileName)->post('https://api.fonnte.com/send', [
|
||||
'target' => $targetNum,
|
||||
'message' => "Halo Kak *{$peminjaman->anggota->nama}*! 📚✨\n\nTerima kasih telah meminjam buku di *Perpustakaan Daerah Jember*.\n\nBersama pesan ini, kami lampirkan file e-Struk (PDF) untuk peminjaman buku:\n📖 Judul: _{$peminjaman->buku->judul}_\n📅 Batas Kembali: *{$peminjaman->tanggal_kembali}*\n\nMohon simpan dokumen PDF ini sebagai bukti transaksi yang sah. Harap kembalikan buku tepat waktu untuk menghindari denda keterlambatan.\n\nSelamat menikmati waktu membaca Anda!\n\nSalam Literasi,\n*Sistem Sarakata Jember*"
|
||||
]);
|
||||
'target' => $targetNum,
|
||||
'message' => "Halo Kak *{$peminjaman->anggota->nama}*! 📚✨\n\nTerima kasih telah meminjam buku di *Perpustakaan Daerah Jember*.\n\nBersama pesan ini, kami lampirkan file e-Struk (PDF) untuk peminjaman buku:\n📖 Judul: _{$peminjaman->buku->judul}_\n📅 Batas Kembali: *{$peminjaman->tanggal_kembali}*\n\nMohon simpan dokumen PDF ini sebagai bukti transaksi yang sah. Harap kembalikan buku tepat waktu untuk menghindari denda keterlambatan.\n\nSelamat menikmati waktu membaca Anda!\n\nSalam Literasi,\n*Sistem Sarakata Jember*"
|
||||
]);
|
||||
|
||||
// Cek respon resmi fonnte API
|
||||
if ($response->successful() && ($response->json('status') == true)) {
|
||||
|
|
@ -128,7 +127,6 @@ public function store(Request $request)
|
|||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Bila error (misal: memori PDF kurang, library dompdf crash, timeout dari Fonnte)
|
||||
// Error ditangkap agar sistem tidak langsung menampilkan layar error 500 ke User.
|
||||
|
|
@ -184,4 +182,4 @@ public function indexPengembalian()
|
|||
|
||||
return view('admin.peminjaman.pengembalian', compact('pengembalian'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,21 +51,12 @@ public function index(Request $request)
|
|||
$hasil = [];
|
||||
|
||||
foreach ($semuaBuku as $b) {
|
||||
// Hitung nilai Cosine Similarity untuk masing-masing atribut berdasarkan kata kunci yang sudah dibersihkan
|
||||
$judulSimilarity = $this->calculateSimilarity($processedSearch, $b->judul);
|
||||
$pengarangSimilarity = $this->calculateSimilarity($processedSearch, $b->pengarang);
|
||||
|
||||
// Gabungkan penerbit, deskripsi, dan kategori sebagai atribut teks tambahan
|
||||
$teksTambahan = trim(($b->penerbit ?? '') . ' ' . ($b->deskripsi ?? '') . ' ' . ($b->kategori->nama_kategori ?? ''));
|
||||
$tambahanSimilarity = $this->calculateSimilarity($processedSearch, $teksTambahan);
|
||||
|
||||
// Penyesuaian Bobot WTS (Weighting)
|
||||
// Judul (70%), Pengarang (20%), Penerbit/Deskripsi/Kategori (10%)
|
||||
$totalScore = ($judulSimilarity * 0.7) + ($pengarangSimilarity * 0.2) + ($tambahanSimilarity * 0.1);
|
||||
// Hitung nilai kemiripan dengan algoritma Dynamic Token-wise WTS
|
||||
$totalScore = $this->calculateDynamicWTS($processedSearch, $b);
|
||||
|
||||
// 3. Penetapan Batas Relevansi (Threshold)
|
||||
// Hanya tampilkan yang memiliki Total Similarity Score >= 0.3 (30%)
|
||||
if ($totalScore >= 0.3) {
|
||||
// Hanya tampilkan yang memiliki Total Similarity Score >= 0.2 (20%)
|
||||
if ($totalScore >= 0.2) {
|
||||
$b->similarity_score = $totalScore;
|
||||
$hasil[] = $b;
|
||||
}
|
||||
|
|
@ -90,44 +81,77 @@ public function index(Request $request)
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper untuk menghitung nilai perbandingan kemiripan teks.
|
||||
* Menggunakan pendekatan Cosine Similarity yang dimodifikasi (Query Coverage)
|
||||
* agar panjang judul buku tidak menurunkan skor secara drastis
|
||||
* pada pencarian kata kunci pendek.
|
||||
* [ALGORITMA DYNAMIC TOKEN-WISE WTS]
|
||||
* Dibuat khusus untuk memecahkan masalah "Length Penalty" pada Global Search Bar.
|
||||
*
|
||||
* KONTEKS MASALAH (Untuk Sidang TA):
|
||||
* Algoritma Cosine Similarity standar membandingkan string secara utuh.
|
||||
* Saat user menggabungkan "Judul + Pengarang" (contoh: "Aku Anak Mandiri Watiek"),
|
||||
* panjang vektor query membesar. Saat dicocokkan HANYA ke kolom Judul ("Aku Anak Mandiri"),
|
||||
* kata "Watiek" dianggap sebagai noise/sampah, sehingga skor kemiripan Judul anjlok.
|
||||
* Akibatnya total WTS turun dan buku yang relevan malah tenggelam.
|
||||
*
|
||||
* SOLUSI (Tokenisasi Dinamis Lintas Atribut):
|
||||
* 1. Query dipecah menjadi token (kata per kata).
|
||||
* 2. Setiap token dievaluasi secara independen ke semua atribut buku (Judul, Pengarang, Kategori).
|
||||
* 3. Token berhak memilih skor kemiripan TERTINGGI dari atribut manapun (Local Max Pooling).
|
||||
* -> Contoh: Kata "Watiek" tidak akan menghukum skor Judul, melainkan diidentifikasi
|
||||
* sebagai milik "Pengarang" dan menyumbang skor 0.3.
|
||||
* 4. Agregasi: Skor semua token dijumlahkan, lalu dibagi jumlah token query.
|
||||
*
|
||||
* Hasilnya: "Aku Anak Mandiri Watiek" akan secara dinamis menyatukan bobot Judul (0.6)
|
||||
* dan Pengarang (0.3), BUKAN saling memberikan penalti!
|
||||
*
|
||||
* @param string $query String pencarian dari user
|
||||
* @param \App\Models\Buku $buku Objek buku dari database
|
||||
* @return float Skor relevansi akhir
|
||||
*/
|
||||
private function calculateSimilarity($query, $text)
|
||||
private function calculateDynamicWTS($query, $buku)
|
||||
{
|
||||
if (empty(trim($text))) return 0;
|
||||
|
||||
// 1. Ekstraksi dan Pembersihan Token Query
|
||||
$queryWords = explode(' ', strtolower(preg_replace('/[^\p{L}\p{N}\s]/u', '', $query)));
|
||||
$textWords = explode(' ', strtolower(preg_replace('/[^\p{L}\p{N}\s]/u', '', $text)));
|
||||
$queryWords = array_filter($queryWords);
|
||||
|
||||
if (empty($queryWords)) return 0;
|
||||
|
||||
// Frekuensi kata
|
||||
$vecA = array_count_values(array_filter($queryWords));
|
||||
$vecB = array_count_values(array_filter($textWords));
|
||||
// 2. Ekstraksi Token Atribut Buku
|
||||
$judulWords = explode(' ', strtolower(preg_replace('/[^\p{L}\p{N}\s]/u', '', $buku->judul)));
|
||||
$pengarangWords = explode(' ', strtolower(preg_replace('/[^\p{L}\p{N}\s]/u', '', $buku->pengarang)));
|
||||
|
||||
$teksTambahan = trim(($buku->penerbit ?? '') . ' ' . ($buku->deskripsi ?? '') . ' ' . ($buku->kategori->nama_kategori ?? ''));
|
||||
$tambahanWords = explode(' ', strtolower(preg_replace('/[^\p{L}\p{N}\s]/u', '', $teksTambahan)));
|
||||
|
||||
$terms = array_unique(array_merge(array_keys($vecA), array_keys($vecB)));
|
||||
// Konversi ke array asosiatif (Hash Map) untuk efisiensi pencarian O(1)
|
||||
$judulMap = array_flip(array_filter($judulWords));
|
||||
$pengarangMap = array_flip(array_filter($pengarangWords));
|
||||
$tambahanMap = array_flip(array_filter($tambahanWords));
|
||||
|
||||
$dotProduct = 0;
|
||||
$normA = 0;
|
||||
$normB = 0;
|
||||
// 3. Konfigurasi Bobot WTS
|
||||
$weights = [
|
||||
'judul' => 0.6,
|
||||
'pengarang' => 0.3,
|
||||
'tambahan' => 0.1
|
||||
];
|
||||
|
||||
foreach ($terms as $term) {
|
||||
$valA = $vecA[$term] ?? 0;
|
||||
$valB = $vecB[$term] ?? 0;
|
||||
$totalScore = 0;
|
||||
|
||||
$dotProduct += ($valA * $valB);
|
||||
$normA += pow($valA, 2);
|
||||
$normB += pow($valB, 2);
|
||||
// 4. Evaluasi Token-wise (Local Max Pooling)
|
||||
foreach ($queryWords as $qWord) {
|
||||
// Berikan bobot penuh jika kata ditemukan di atribut bersangkutan
|
||||
$scoreJudul = isset($judulMap[$qWord]) ? $weights['judul'] : 0;
|
||||
$scorePengarang = isset($pengarangMap[$qWord]) ? $weights['pengarang'] : 0;
|
||||
$scoreTambahan = isset($tambahanMap[$qWord]) ? $weights['tambahan'] : 0;
|
||||
|
||||
// KUNCI SOLUSI: Kata ini akan menyumbang skor dari atribut yang paling relevan untuknya
|
||||
$bestScoreForToken = max($scoreJudul, $scorePengarang, $scoreTambahan);
|
||||
|
||||
$totalScore += $bestScoreForToken;
|
||||
}
|
||||
|
||||
if ($normA == 0 || $normB == 0) return 0;
|
||||
|
||||
// Standard Cosine Similarity: $dotProduct / (sqrt($normA) * sqrt($normB))
|
||||
// Kelemahannya: Jika judul buku sangat panjang (normB besar), skor akan anjlok (misal < 0.2)
|
||||
// padahal kata pencariannya cocok 100%.
|
||||
// Modifikasi menjadi Query Coverage (Pembagi dominan adalah panjang query).
|
||||
return $dotProduct / $normA;
|
||||
// 5. Normalisasi
|
||||
// Membagi total skor dengan jumlah token query agar term-frequency ternormalisasi,
|
||||
// namun tetap secara matematis mempertahankan keunggulan bobot absolut antar atribut.
|
||||
return $totalScore / count($queryWords);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class BukuTamu extends Model
|
|||
use HasFactory;
|
||||
|
||||
protected $table = 'buku_tamu';
|
||||
protected $primaryKey = 'id_tamu';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
|
|
|
|||
|
|
@ -2,24 +2,24 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>";
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
try {
|
||||
DB::statement('ALTER TABLE peminjaman DROP FOREIGN KEY peminjaman_ibfk_2');
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
try {
|
||||
DB::statement('ALTER TABLE peminjaman DROP FOREIGN KEY fk_peminjaman_buku');
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
try {
|
||||
Schema::table('peminjaman', function (Blueprint $table) {
|
||||
$table->dropForeign(['id_buku']);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
DB::statement('ALTER TABLE peminjaman MODIFY id_buku INT UNSIGNED NOT NULL');
|
||||
|
||||
Schema::table('peminjaman', function (Blueprint $table) {
|
||||
$table->foreign('id_buku')
|
||||
->references('id_buku')
|
||||
->on('buku')
|
||||
->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
try {
|
||||
Schema::table('peminjaman', function (Blueprint $table) {
|
||||
$table->dropForeign(['id_buku']);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
DB::statement('ALTER TABLE peminjaman MODIFY id_buku BIGINT UNSIGNED NOT NULL');
|
||||
|
||||
Schema::table('peminjaman', function (Blueprint $table) {
|
||||
$table->foreign('id_buku')
|
||||
->references('id')
|
||||
->on('bukus')
|
||||
->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?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(): void
|
||||
{
|
||||
\Illuminate\Support\Facades\DB::statement('ALTER TABLE buku_tamu MODIFY id_tamu INT UNSIGNED NOT NULL AUTO_INCREMENT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
\Illuminate\Support\Facades\DB::statement('ALTER TABLE buku_tamu MODIFY id_tamu INT UNSIGNED NOT NULL');
|
||||
}
|
||||
};
|
||||
|
|
@ -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>";
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
|
||||
$buku = App\Models\Buku::find(52);
|
||||
echo json_encode($buku->toArray());
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
|
||||
$res = [];
|
||||
foreach(['0','1','2','3','4','5','6','7','8','9'] as $d) {
|
||||
$b = App\Models\Buku::where('nomor_panggil', 'like', $d.'%')->whereNotNull('lokasi_x')->first();
|
||||
$res[$d.'00'] = $b ? ['x' => $b->lokasi_x, 'y' => $b->lokasi_y] : null;
|
||||
}
|
||||
echo json_encode($res);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
// Bootstrap Laravel dari folder public/
|
||||
require __DIR__ . '/public/index.php';
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 240 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 MiB After Width: | Height: | Size: 6.8 MiB |
|
|
@ -3,7 +3,7 @@
|
|||
@section('title', 'Manajemen Admin')
|
||||
|
||||
@section('content')
|
||||
<div x-data="{ isModalOpen: false }" x-cloak>
|
||||
<div x-data="{ isModalOpen: {{ $errors->any() ? 'true' : 'false' }} }" x-cloak>
|
||||
<x-page-header title="Manajemen Akun Admin">
|
||||
<x-slot name="actions">
|
||||
<button @click="isModalOpen = true" class="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white px-5 py-2.5 rounded-lg border border-transparent shadow-[0_4px_10px_rgba(37,99,235,0.2)] hover:shadow-[0_6px_15px_rgba(37,99,235,0.3)] transition-all font-semibold flex items-center gap-2 transform hover:translate-y-[-1px]">
|
||||
|
|
@ -15,16 +15,6 @@
|
|||
<x-alert type="success" :message="session('success')" />
|
||||
<x-alert type="error" :message="session('error')" />
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-r-xl text-sm shadow-sm">
|
||||
<p class="font-bold mb-1"><i class="fas fa-exclamation-circle mr-1"></i> Validasi Gagal</p>
|
||||
<ul class="list-disc ml-5 space-y-1">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<x-card>
|
||||
<x-table>
|
||||
|
|
@ -95,27 +85,38 @@ class="relative z-[60] inline-block px-4 pt-5 pb-4 overflow-hidden text-left ali
|
|||
</button>
|
||||
</div>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-r-xl text-sm shadow-sm">
|
||||
<p class="font-bold mb-1"><i class="fas fa-exclamation-circle mr-1"></i> Validasi Gagal</p>
|
||||
<ul class="list-disc ml-5 space-y-1">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('admin.akun.store') }}" method="POST" class="space-y-5">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">Nama Lengkap</label>
|
||||
<input type="text" name="name" value="{{ old('name') }}" placeholder="Masukkan nama..." class="w-full rounded-xl border-gray-300 focus:border-blue-500 focus:ring-blue-500 shadow-sm bg-gray-50/50" required>
|
||||
<x-input-label for="name" value="Nama Lengkap" class="mb-1" />
|
||||
<x-text-input id="name" type="text" name="name" :value="old('name')" placeholder="Masukkan nama..." required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">Email</label>
|
||||
<input type="email" name="email" value="{{ old('email') }}" placeholder="admin@domain.com" class="w-full rounded-xl border-gray-300 focus:border-blue-500 focus:ring-blue-500 shadow-sm bg-gray-50/50" required>
|
||||
<x-input-label for="email" value="Email" class="mb-1" />
|
||||
<x-text-input id="email" type="email" name="email" :value="old('email')" placeholder="admin@domain.com" required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">Password</label>
|
||||
<input type="password" name="password" placeholder="Minimal 8 karakter" class="w-full rounded-xl border-gray-300 focus:border-blue-500 focus:ring-blue-500 shadow-sm bg-gray-50/50" required>
|
||||
<x-input-label for="password" value="Password" class="mb-1" />
|
||||
<x-text-input id="password" type="password" name="password" placeholder="Minimal 8 karakter" required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">Konfirmasi Password</label>
|
||||
<input type="password" name="password_confirmation" placeholder="Ulangi password" class="w-full rounded-xl border-gray-300 focus:border-blue-500 focus:ring-blue-500 shadow-sm bg-gray-50/50" required>
|
||||
<x-input-label for="password_confirmation" value="Konfirmasi Password" class="mb-1" />
|
||||
<x-text-input id="password_confirmation" type="password" name="password_confirmation" placeholder="Ulangi password" required />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 pt-4 border-t border-gray-100 mt-6">
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<x-input-label for="id_kategori" value="Kategori" />
|
||||
<select id="id_kategori" name="id_kategori" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm">
|
||||
<select id="id_kategori" name="id_kategori" class="border border-gray-200 bg-gray-50 text-gray-900 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block w-full p-3 shadow-sm hover:bg-white transition-all duration-200 outline-none">
|
||||
<option value="">Pilih Kategori</option>
|
||||
@foreach($kategori as $kat)
|
||||
<option value="{{ $kat->id_kategori }}" {{ old('id_kategori') == $kat->id_kategori ? 'selected' : '' }}>
|
||||
|
|
@ -65,26 +65,26 @@
|
|||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<x-input-label for="penerbit" value="Penerbit" />
|
||||
<x-text-input id="penerbit" name="penerbit" type="text" class="mt-1 block w-full" :value="old('penerbit')" />
|
||||
<x-text-input id="penerbit" name="penerbit" type="text" class="mt-1 block w-full" :value="old('penerbit')" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="tahun_terbit" value="Tahun Terbit" />
|
||||
<x-text-input id="tahun_terbit" name="tahun_terbit" type="number" class="mt-1 block w-full" :value="old('tahun_terbit')" />
|
||||
<x-text-input id="tahun_terbit" name="tahun_terbit" type="number" class="mt-1 block w-full" :value="old('tahun_terbit')" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="edisi" value="Edisi" />
|
||||
<x-text-input id="edisi" name="edisi" type="text" class="mt-1 block w-full" :value="old('edisi')" />
|
||||
<x-text-input id="edisi" name="edisi" type="number" class="mt-1 block w-full" :value="old('edisi')" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<x-input-label for="deskripsi_fisik" value="Deskripsi Fisik" />
|
||||
<x-text-input id="deskripsi_fisik" name="deskripsi_fisik" type="text" class="mt-1 block w-full" :value="old('deskripsi_fisik')" />
|
||||
<x-text-input id="deskripsi_fisik" name="deskripsi_fisik" type="text" class="mt-1 block w-full" :value="old('deskripsi_fisik')" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="eksemplar" value="Jumlah Eksemplar" />
|
||||
<x-text-input id="eksemplar" name="eksemplar" type="number" class="mt-1 block w-full" :value="old('eksemplar')" />
|
||||
<x-text-input id="eksemplar" name="eksemplar" type="number" class="mt-1 block w-full" :value="old('eksemplar')" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class="p-6 space-y-6">
|
|||
<div>
|
||||
<x-input-label for="id_kategori" value="Kategori" />
|
||||
<select id="id_kategori" name="id_kategori"
|
||||
class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm">
|
||||
class="border border-gray-200 bg-gray-50 text-gray-900 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block w-full p-3 shadow-sm hover:bg-white transition-all duration-200 outline-none">
|
||||
<option value="">Pilih Kategori</option>
|
||||
@foreach ($kategori as $kat)
|
||||
<option value="{{ $kat->id_kategori }}"
|
||||
|
|
@ -66,17 +66,17 @@ class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indi
|
|||
<div>
|
||||
<x-input-label for="penerbit" value="Penerbit" />
|
||||
<x-text-input id="penerbit" name="penerbit" type="text" class="mt-1 block w-full"
|
||||
:value="old('penerbit', $buku->penerbit)" />
|
||||
:value="old('penerbit', $buku->penerbit)" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="tahun_terbit" value="Tahun Terbit" />
|
||||
<x-text-input id="tahun_terbit" name="tahun_terbit" type="number" class="mt-1 block w-full"
|
||||
:value="old('tahun_terbit', $buku->tahun_terbit)" />
|
||||
:value="old('tahun_terbit', $buku->tahun_terbit)" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="edisi" value="Edisi" />
|
||||
<x-text-input id="edisi" name="edisi" type="text" class="mt-1 block w-full"
|
||||
:value="old('edisi', $buku->edisi)" />
|
||||
<x-text-input id="edisi" name="edisi" type="number" class="mt-1 block w-full"
|
||||
:value="old('edisi', $buku->edisi)" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -84,12 +84,12 @@ class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indi
|
|||
<div>
|
||||
<x-input-label for="deskripsi_fisik" value="Deskripsi Fisik" />
|
||||
<x-text-input id="deskripsi_fisik" name="deskripsi_fisik" type="text" class="mt-1 block w-full"
|
||||
:value="old('deskripsi_fisik', $buku->deskripsi_fisik)" />
|
||||
:value="old('deskripsi_fisik', $buku->deskripsi_fisik)" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="eksemplar" value="Jumlah Eksemplar" />
|
||||
<x-text-input id="eksemplar" name="eksemplar" type="number" class="mt-1 block w-full"
|
||||
:value="old('eksemplar', $buku->eksemplar)" />
|
||||
:value="old('eksemplar', $buku->eksemplar)" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class="mt-1 block w-full bg-gray-50/50" :value="old('bibid')" required />
|
|||
<div>
|
||||
<x-input-label for="id_kategori" value="Kategori" />
|
||||
<select id="id_kategori" name="id_kategori"
|
||||
class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50"
|
||||
class="border border-gray-200 bg-gray-50 text-gray-900 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block w-full p-3 shadow-sm hover:bg-white transition-all duration-200 outline-none"
|
||||
required>
|
||||
<option value="">Pilih Kategori</option>
|
||||
@foreach ($kategori as $kat)
|
||||
|
|
@ -169,17 +169,17 @@ class="mt-1 block w-full bg-gray-50/50" :value="old('nomor_panggil')" required /
|
|||
<div>
|
||||
<x-input-label for="penerbit" value="Penerbit" />
|
||||
<x-text-input id="penerbit" name="penerbit" type="text"
|
||||
class="mt-1 block w-full bg-gray-50/50" :value="old('penerbit')" />
|
||||
class="mt-1 block w-full bg-gray-50/50" :value="old('penerbit')" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="tahun_terbit" value="Tahun Terbit" />
|
||||
<x-text-input id="tahun_terbit" name="tahun_terbit" type="number"
|
||||
class="mt-1 block w-full bg-gray-50/50" :value="old('tahun_terbit')" />
|
||||
class="mt-1 block w-full bg-gray-50/50" :value="old('tahun_terbit')" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="edisi" value="Edisi" />
|
||||
<x-text-input id="edisi" name="edisi" type="text"
|
||||
class="mt-1 block w-full bg-gray-50/50" :value="old('edisi')" />
|
||||
<x-text-input id="edisi" name="edisi" type="number"
|
||||
class="mt-1 block w-full bg-gray-50/50" :value="old('edisi')" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ class="mt-1 block w-full bg-gray-50/50" :value="old('edisi')" />
|
|||
<div>
|
||||
<x-input-label for="deskripsi_fisik" value="Deskripsi Fisik" />
|
||||
<x-text-input id="deskripsi_fisik" name="deskripsi_fisik" type="text"
|
||||
class="mt-1 block w-full bg-gray-50/50" :value="old('deskripsi_fisik')" />
|
||||
class="mt-1 block w-full bg-gray-50/50" :value="old('deskripsi_fisik')" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="eksemplar" value="Jumlah Eksemplar" />
|
||||
|
|
|
|||
|
|
@ -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,30 +59,43 @@ 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 }}" {{ old('id_buku') == $b->id ? '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">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Tanggal Pinjam</label>
|
||||
<input type="date" name="tanggal_pinjam" value="{{ old('tanggal_pinjam', date('Y-m-d')) }}"
|
||||
min="2000-01-01" max="2100-12-31"
|
||||
class="w-full border-gray-300 rounded-lg shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Tanggal Kembali (Tenggat)</label>
|
||||
<input type="date" name="tanggal_kembali"
|
||||
value="{{ old('tanggal_kembali', date('Y-m-d', strtotime('+7 days'))) }}"
|
||||
min="2000-01-01" max="2100-12-31"
|
||||
class="w-full border-gray-300 rounded-lg shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -81,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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -153,6 +163,14 @@ class="text-green-600 font-bold px-3 py-1.5 text-xs bg-green-50 rounded border b
|
|||
<a href="{{ route('admin.peminjaman.struk', $item->id_peminjaman) }}" target="_blank"
|
||||
class="bg-gray-800 text-white hover:bg-gray-900 px-3 py-1.5 rounded text-xs font-bold transition flex items-center">Cetak
|
||||
Struk</a>
|
||||
<form action="{{ route('admin.peminjaman.resend_wa', $item->id_peminjaman) }}" method="POST" class="m-0 p-0 flex items-center">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="bg-green-500 text-white hover:bg-green-600 px-2 py-1.5 rounded text-xs font-bold transition flex items-center"
|
||||
title="Kirim Struk ke WhatsApp">
|
||||
<i class="fab fa-whatsapp"></i>
|
||||
</button>
|
||||
</form>
|
||||
@if ($item->status_peminjaman == 'Dipinjam')
|
||||
<button type="button"
|
||||
@click="openEditModal('{{ $item->id_peminjaman }}', '{{ $item->id_anggota ?? $item->id_user }}', '{{ $item->id_buku }}', '{{ \Carbon\Carbon::parse($item->tanggal_pinjam)->format('Y-m-d') }}', '{{ \Carbon\Carbon::parse($item->tanggal_kembali)->format('Y-m-d') }}')"
|
||||
|
|
@ -232,7 +250,7 @@ class="w-10 h-10 rounded-full bg-gray-50 hover:bg-red-50 text-gray-400 hover:tex
|
|||
<div>
|
||||
<x-input-label value="Pilih Anggota / Member" />
|
||||
<select name="id_anggota" x-init="new TomSelect($el, { maxOptions: null })"
|
||||
class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50"
|
||||
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 Member --</option>
|
||||
@foreach ($anggota as $a)
|
||||
|
|
@ -244,18 +262,34 @@ class="mt-1 block w-full border-gray-300 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-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50"
|
||||
required>
|
||||
<option value="">-- Pilih Buku --</option>
|
||||
@foreach ($buku as $b)
|
||||
<option value="{{ $b->id }}" {{ old('id_buku') == $b->id ? '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">
|
||||
|
|
@ -263,12 +297,14 @@ class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indi
|
|||
<x-input-label value="Tanggal Pinjam" />
|
||||
<x-text-input type="date" name="tanggal_pinjam"
|
||||
value="{{ old('tanggal_pinjam', date('Y-m-d')) }}"
|
||||
min="2000-01-01" max="2100-12-31"
|
||||
class="mt-1 block w-full bg-gray-50/50" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label value="Tanggal Kembali (Tenggat)" />
|
||||
<x-text-input type="date" name="tanggal_kembali"
|
||||
value="{{ old('tanggal_kembali', date('Y-m-d', strtotime('+7 days'))) }}"
|
||||
min="2000-01-01" max="2100-12-31"
|
||||
class="mt-1 block w-full bg-gray-50/50" required />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -365,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">
|
||||
|
|
@ -413,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">
|
||||
|
|
@ -422,7 +458,7 @@ class="w-10 h-10 rounded-full bg-gray-50 hover:bg-red-50 text-gray-400 hover:tex
|
|||
<x-input-label value="Pilih Anggota / Member" />
|
||||
<select name="id_anggota" x-model="editData.id_anggota" x-init="let tsA = null;
|
||||
$watch('isModalEditOpen', value => { if (value && !tsA && $el.offsetHeight) setTimeout(() => { try { tsA = new TomSelect($el, { maxOptions: null }); } catch (e) {} }, 100) })"
|
||||
class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50"
|
||||
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 Member --</option>
|
||||
@foreach ($anggota as $a)
|
||||
|
|
@ -436,11 +472,11 @@ class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indi
|
|||
<x-input-label value="Pilih Buku" />
|
||||
<select name="id_buku" x-model="editData.id_buku" x-init="let tsB = null;
|
||||
$watch('isModalEditOpen', value => { if (value && !tsB && $el.offsetHeight) setTimeout(() => { try { tsB = new TomSelect($el, { maxOptions: null }); } catch (e) {} }, 100) })"
|
||||
class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50"
|
||||
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 }}">{{ $b->bibid }} - {{ $b->judul }}
|
||||
<option value="{{ $b->id_buku }}">{{ $b->bibid }} - {{ $b->judul }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
|
@ -450,11 +486,13 @@ class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indi
|
|||
<div>
|
||||
<x-input-label value="Tanggal Pinjam" />
|
||||
<x-text-input type="date" name="tanggal_pinjam" x-model="editData.tanggal_pinjam"
|
||||
min="2000-01-01" max="2100-12-31"
|
||||
class="mt-1 block w-full bg-gray-50/50" required />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label value="Tanggal Kembali (Tenggat)" />
|
||||
<x-text-input type="date" name="tanggal_kembali" x-model="editData.tanggal_kembali"
|
||||
min="2000-01-01" max="2100-12-31"
|
||||
class="mt-1 block w-full bg-gray-50/50" required />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -504,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"
|
||||
|
|
@ -523,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
|
||||
|
|
|
|||
|
|
@ -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">← 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">←
|
||||
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
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<div>
|
||||
<x-input-label for="jenis_anggota" value="Jenis Anggota" />
|
||||
<select id="jenis_anggota" name="jenis_anggota" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm" required onchange="toggleProdi()">
|
||||
<select id="jenis_anggota" name="jenis_anggota" x-data x-init="new TomSelect($el, { maxOptions: null })" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50" required onchange="toggleProdi()">
|
||||
<option value="" disabled {{ old('jenis_anggota') ? '' : 'selected' }}>-- Pilih Jenis --</option>
|
||||
<option value="Mahasiswa" {{ old('jenis_anggota') == 'Mahasiswa' ? 'selected' : '' }}>Mahasiswa</option>
|
||||
<option value="Siswa" {{ old('jenis_anggota') == 'Siswa' ? 'selected' : '' }}>Siswa</option>
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
|
||||
<div class="md:col-span-2">
|
||||
<x-input-label for="alamat" value="Alamat Lengkap" />
|
||||
<textarea id="alamat" name="alamat" rows="3" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm" placeholder="Masukkan alamat lengkap" required>{{ old('alamat') }}</textarea>
|
||||
<textarea id="alamat" name="alamat" rows="3" class="mt-1 block w-full border border-gray-200 bg-gray-50 text-gray-900 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 p-3 shadow-sm hover:bg-white transition-all duration-200 outline-none" placeholder="Masukkan alamat lengkap" required>{{ old('alamat') }}</textarea>
|
||||
<x-input-error class="mt-2" :messages="$errors->get('alamat')" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
|
||||
<div>
|
||||
<x-input-label for="hubungan_wali" value="Hubungan" />
|
||||
<select id="hubungan_wali" name="hubungan_wali" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm" required>
|
||||
<select id="hubungan_wali" name="hubungan_wali" x-data x-init="new TomSelect($el, { maxOptions: null })" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50" required>
|
||||
<option value="" disabled {{ old('hubungan_wali') ? '' : 'selected' }}>-- Pilih Hubungan --</option>
|
||||
<option value="Orang Tua" {{ old('hubungan_wali') == 'Orang Tua' ? 'selected' : '' }}>Orang Tua</option>
|
||||
<option value="Saudara" {{ old('hubungan_wali') == 'Saudara' ? 'selected' : '' }}>Saudara</option>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
<div>
|
||||
<x-input-label for="jenis_anggota" value="Jenis Anggota" />
|
||||
<select id="jenis_anggota" name="jenis_anggota" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm" required onchange="toggleProdi()">
|
||||
<select id="jenis_anggota" name="jenis_anggota" x-data x-init="new TomSelect($el, { maxOptions: null })" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50" required onchange="toggleProdi()">
|
||||
<option value="" disabled>-- Pilih Jenis --</option>
|
||||
@foreach(['Mahasiswa', 'Siswa', 'Dosen', 'Umum'] as $jenis)
|
||||
<option value="{{ $jenis }}" {{ old('jenis_anggota', $anggota->jenis_anggota) == $jenis ? 'selected' : '' }}>{{ $jenis }}</option>
|
||||
|
|
@ -64,13 +64,13 @@
|
|||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<x-input-label for="no_hp" value="No. HP" />
|
||||
<x-text-input id="no_hp" name="no_hp" type="text" class="mt-1 block w-full" :value="old('no_hp', $anggota->no_hp)" required />
|
||||
<x-text-input id="no_hp" name="no_hp" type="number" class="mt-1 block w-full" :value="old('no_hp', $anggota->no_hp)" required />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('no_hp')" />
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<x-input-label for="alamat" value="Alamat Lengkap" />
|
||||
<textarea id="alamat" name="alamat" rows="3" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm" required>{{ old('alamat', $anggota->alamat) }}</textarea>
|
||||
<textarea id="alamat" name="alamat" rows="3" class="mt-1 block w-full border border-gray-200 bg-gray-50 text-gray-900 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 p-3 shadow-sm hover:bg-white transition-all duration-200 outline-none" required>{{ old('alamat', $anggota->alamat) }}</textarea>
|
||||
<x-input-error class="mt-2" :messages="$errors->get('alamat')" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -92,13 +92,13 @@
|
|||
|
||||
<div>
|
||||
<x-input-label for="no_hp_wali" value="No. HP Wali" />
|
||||
<x-text-input id="no_hp_wali" name="no_hp_wali" type="text" class="mt-1 block w-full" :value="old('no_hp_wali', $anggota->no_hp_wali)" required />
|
||||
<x-text-input id="no_hp_wali" name="no_hp_wali" type="number" class="mt-1 block w-full" :value="old('no_hp_wali', $anggota->no_hp_wali)" required />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('no_hp_wali')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="hubungan_wali" value="Hubungan" />
|
||||
<select id="hubungan_wali" name="hubungan_wali" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm" required>
|
||||
<select id="hubungan_wali" name="hubungan_wali" x-data x-init="new TomSelect($el, { maxOptions: null })" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50" required>
|
||||
<option value="" disabled>-- Pilih Hubungan --</option>
|
||||
@foreach(['Orang Tua', 'Saudara', 'Dosen Wali', 'Lainnya'] as $hub)
|
||||
<option value="{{ $hub }}" {{ old('hubungan_wali', $anggota->hubungan_wali) == $hub ? 'selected' : '' }}>{{ $hub }}</option>
|
||||
|
|
@ -131,8 +131,10 @@ function toggleProdi() {
|
|||
if (jenis === 'Umum') {
|
||||
prodiField.style.display = 'none';
|
||||
document.getElementById('prodi').value = '';
|
||||
document.getElementById('prodi').removeAttribute('required');
|
||||
} else {
|
||||
prodiField.style.display = 'block';
|
||||
document.getElementById('prodi').setAttribute('required', 'required');
|
||||
}
|
||||
|
||||
// Logika Dynamic Form untuk Identitas Siswa / Pelajar
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<div x-data="{ isModalAnggotaOpen: {{ ($errors->any() || request('add') == 'true') ? 'true' : 'false' }} }">
|
||||
<x-page-header title="Data Anggota">
|
||||
<x-slot name="actions">
|
||||
<button @click="isModalAnggotaOpen = true" class="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700 text-white px-5 py-2.5 rounded-xl shadow-lg shadow-emerald-500/30 transition-all font-semibold flex items-center gap-2 transform hover:scale-105">
|
||||
<button @click="isModalAnggotaOpen = true" class="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white px-5 py-2.5 rounded-xl shadow-lg shadow-blue-500/30 transition-all font-semibold flex items-center gap-2 transform hover:scale-105">
|
||||
<i class="fas fa-user-plus"></i> Tambah Anggota
|
||||
</button>
|
||||
</x-slot>
|
||||
|
|
@ -164,7 +164,7 @@ class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transi
|
|||
|
||||
<div>
|
||||
<x-input-label for="jenis_anggota" value="Jenis Anggota" />
|
||||
<select id="jenis_anggota" name="jenis_anggota" x-model="jenisAnggota" class="mt-1 block w-full rounded-xl border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm bg-gray-50/50" required>
|
||||
<select id="jenis_anggota" name="jenis_anggota" x-model="jenisAnggota" x-init="new TomSelect($el, { maxOptions: null })" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50" required>
|
||||
<option value="" disabled>-- Pilih Jenis --</option>
|
||||
<option value="Mahasiswa">Mahasiswa</option>
|
||||
<option value="Siswa">Siswa</option>
|
||||
|
|
@ -185,7 +185,7 @@ class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transi
|
|||
|
||||
<div x-show="jenisAnggota !== 'Umum'" x-transition>
|
||||
<x-input-label for="prodi" value="Program Studi / Jurusan" />
|
||||
<x-text-input id="prodi" name="prodi" type="text" class="mt-1 block w-full bg-gray-50/50" :value="old('prodi')" placeholder="Contoh: Teknik Informatika" />
|
||||
<x-text-input id="prodi" name="prodi" type="text" class="mt-1 block w-full bg-gray-50/50" :value="old('prodi')" placeholder="Contoh: Teknik Informatika" x-bind:required="jenisAnggota !== 'Umum'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -199,7 +199,7 @@ class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transi
|
|||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<x-input-label for="no_hp" value="No. HP" />
|
||||
<x-text-input id="no_hp" name="no_hp" type="text" class="mt-1 block w-full bg-gray-50/50" :value="old('no_hp')" placeholder="08xxxxxxxxx" required />
|
||||
<x-text-input id="no_hp" name="no_hp" type="number" class="mt-1 block w-full bg-gray-50/50" :value="old('no_hp')" placeholder="08xxxxxxxxx" required />
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
|
|
@ -224,12 +224,12 @@ class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transi
|
|||
|
||||
<div>
|
||||
<x-input-label for="no_hp_wali" value="No. HP Wali" />
|
||||
<x-text-input id="no_hp_wali" name="no_hp_wali" type="text" class="mt-1 block w-full bg-gray-50/50" :value="old('no_hp_wali')" placeholder="08xxxxxxxxx" required />
|
||||
<x-text-input id="no_hp_wali" name="no_hp_wali" type="number" class="mt-1 block w-full bg-gray-50/50" :value="old('no_hp_wali')" placeholder="08xxxxxxxxx" required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="hubungan_wali" value="Hubungan" />
|
||||
<select id="hubungan_wali" name="hubungan_wali" class="mt-1 block w-full rounded-xl border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm bg-gray-50/50" required>
|
||||
<select id="hubungan_wali" name="hubungan_wali" x-init="new TomSelect($el, { maxOptions: null })" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-xl shadow-sm bg-gray-50/50" required>
|
||||
<option value="" disabled {{ old('hubungan_wali') ? '' : 'selected' }}>-- Pilih Hubungan --</option>
|
||||
<option value="Orang Tua" {{ old('hubungan_wali') == 'Orang Tua' ? 'selected' : '' }}>Orang Tua</option>
|
||||
<option value="Saudara" {{ old('hubungan_wali') == 'Saudara' ? 'selected' : '' }}>Saudara</option>
|
||||
|
|
@ -244,7 +244,7 @@ class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transi
|
|||
<button type="button" @click="isModalAnggotaOpen = false" class="px-5 py-2.5 text-gray-600 bg-gray-100 hover:bg-gray-200 hover:text-gray-900 rounded-xl font-semibold transition-colors">
|
||||
Batal
|
||||
</button>
|
||||
<button type="submit" class="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700 text-white px-6 py-2.5 rounded-xl font-bold shadow-lg shadow-emerald-500/30 transition-all transform hover:scale-105">
|
||||
<button type="submit" class="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white px-6 py-2.5 rounded-xl font-bold shadow-lg shadow-blue-500/30 transition-all transform hover:scale-105">
|
||||
<i class="fas fa-save mr-2"></i> Simpan Anggota
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,31 @@
|
|||
@keyframes blob { 0% { transform: translate(0,0) scale(1); } 33% { transform: translate(30px,-50px) scale(1.1); } 66% { transform: translate(-20px,20px) scale(0.9); } 100% { transform: translate(0,0) scale(1); } }
|
||||
.animate-blob { animation: blob 7s infinite; }
|
||||
.animation-delay-2000 { animation-delay: 2s; }
|
||||
|
||||
/* Hide spin buttons for number input */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -79,8 +104,12 @@
|
|||
{{-- RIGHT PANEL (Form) --}}
|
||||
<div class="w-full md:w-7/12 bg-gradient-to-br from-primary-900 via-primary-800 to-primary-900 relative flex flex-col p-8 md:p-12">
|
||||
|
||||
<div class="mb-6 pb-4">
|
||||
<h2 class="text-2xl font-bold text-white tracking-wide mb-2">Buku Tamu</h2>
|
||||
@php $activeTipe = old('tipe', request('tipe', 'member')); @endphp
|
||||
|
||||
<div class="mb-5 pb-3">
|
||||
<h2 id="form-title" class="text-2xl font-bold text-white tracking-wide mb-1">
|
||||
{{ $activeTipe == 'member' ? 'Buku Tamu Anggota' : 'Buku Tamu Pengunjung Umum' }}
|
||||
</h2>
|
||||
<p class="text-primary-300/60 text-sm">Isi data kunjungan Anda hari ini.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -96,65 +125,73 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
|
||||
{{-- Toggle --}}
|
||||
<div class="flex gap-3 mb-6">
|
||||
<label class="toggle-option flex-1 flex items-center gap-3 p-4 rounded-xl border border-white/10 active" id="toggle-member" onclick="switchMode('member')">
|
||||
<input type="radio" name="mode_toggle" value="member" checked class="hidden">
|
||||
<label class="toggle-option flex-1 flex items-center gap-3 p-4 rounded-xl border border-white/10 {{ $activeTipe == 'member' ? 'active' : '' }}" id="toggle-member" onclick="switchMode('member')">
|
||||
<input type="radio" name="mode_toggle" value="member" {{ $activeTipe == 'member' ? 'checked' : '' }} class="hidden">
|
||||
<div class="w-5 h-5 rounded-full border-2 border-white/40 flex items-center justify-center" id="radio-member">
|
||||
@if($activeTipe == 'member')
|
||||
<div class="w-3 h-3 rounded-full bg-primary-400"></div>
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-white text-sm font-semibold">Anggota</p>
|
||||
<p class="text-primary-300/50 text-[10px]">Sudah punya No. Anggota</p>
|
||||
<p class="text-primary-300/50 text-[10px]">Punya NIK / No. Anggota</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="toggle-option flex-1 flex items-center gap-3 p-4 rounded-xl border border-white/10" id="toggle-tamu" onclick="switchMode('tamu')">
|
||||
<input type="radio" name="mode_toggle" value="tamu" class="hidden">
|
||||
<div class="w-5 h-5 rounded-full border-2 border-white/40 flex items-center justify-center" id="radio-tamu"></div>
|
||||
<label class="toggle-option flex-1 flex items-center gap-3 p-4 rounded-xl border border-white/10 {{ $activeTipe == 'tamu' ? 'active' : '' }}" id="toggle-tamu" onclick="switchMode('tamu')">
|
||||
<input type="radio" name="mode_toggle" value="tamu" {{ $activeTipe == 'tamu' ? 'checked' : '' }} class="hidden">
|
||||
<div class="w-5 h-5 rounded-full border-2 border-white/40 flex items-center justify-center" id="radio-tamu">
|
||||
@if($activeTipe == 'tamu')
|
||||
<div class="w-3 h-3 rounded-full bg-primary-400"></div>
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-white text-sm font-semibold">Pengunjung</p>
|
||||
<p class="text-primary-300/50 text-[10px]">Belum punya No. Anggota</p>
|
||||
<p class="text-primary-300/50 text-[10px]">Belum punya NIK / Anggota</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('buku_tamu.store') }}" class="space-y-6" id="buku-tamu-form">
|
||||
@csrf
|
||||
<input type="hidden" name="tipe" id="tipe-input" value="member">
|
||||
<input type="hidden" name="tipe" id="tipe-input" value="{{ $activeTipe }}">
|
||||
|
||||
<div id="form-member" class="space-y-5">
|
||||
<div id="form-member" class="space-y-5" style="{{ $activeTipe == 'member' ? '' : 'display: none;' }}">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-primary-200/70 mb-2 ml-1 uppercase tracking-wider">No. Anggota</label>
|
||||
<input type="text" name="no_anggota" placeholder="Masukkan Nomor Anggota"
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('no_anggota') }}">
|
||||
<label class="block text-xs font-semibold text-primary-200/70 mb-2 ml-1 uppercase tracking-wider">NIK / No. KTP / No. Anggota</label>
|
||||
<input type="text" name="no_anggota" placeholder="Masukkan 16 digit NIK atau Nomor Anggota"
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('no_anggota') }}" {{ $activeTipe == 'member' ? 'required' : '' }}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="form-tamu" class="space-y-5" style="display: none;">
|
||||
<div id="form-tamu" class="space-y-5" style="{{ $activeTipe == 'tamu' ? '' : 'display: none;' }}">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-primary-200/70 mb-2 ml-1 uppercase tracking-wider">Nama Lengkap</label>
|
||||
<input type="text" name="nama_tamu" placeholder="Isi nama lengkap anda"
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('nama_tamu') }}">
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('nama_tamu') }}" {{ $activeTipe == 'tamu' ? 'required' : '' }}>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-primary-200/70 mb-2 ml-1 uppercase tracking-wider">Email</label>
|
||||
<input type="email" name="email" placeholder="Isi email anda"
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('email') }}">
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('email') }}" {{ $activeTipe == 'tamu' ? 'required' : '' }}>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-primary-200/70 mb-2 ml-1 uppercase tracking-wider">No HP</label>
|
||||
<input type="text" name="no_hp" placeholder="+62"
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('no_hp') }}">
|
||||
<input type="number" name="no_hp" placeholder="08..."
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('no_hp') }}" {{ $activeTipe == 'tamu' ? 'required' : '' }} oninput="if(this.value.length > 13) this.value = this.value.slice(0, 13);">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-primary-200/70 mb-2 ml-1 uppercase tracking-wider">Asal Instansi/ Sekolah / Kampus</label>
|
||||
<input type="text" name="asal_instansi" placeholder="Politeknik Negeri Jember"
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('asal_instansi') }}">
|
||||
class="w-full glass-input rounded-xl px-5 py-4 text-sm" value="{{ old('asal_instansi') }}" {{ $activeTipe == 'tamu' ? 'required' : '' }}>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-primary-200/70 mb-2 ml-1 uppercase tracking-wider">Status</label>
|
||||
<div class="relative">
|
||||
<select name="status" class="w-full glass-input rounded-xl px-5 py-4 text-sm appearance-none cursor-pointer">
|
||||
<select name="status" class="w-full glass-input rounded-xl px-5 py-4 text-sm appearance-none cursor-pointer" {{ $activeTipe == 'tamu' ? 'required' : '' }}>
|
||||
<option value="" disabled selected>Pilih Status</option>
|
||||
<option value="Siswa" {{ old('status') == 'Siswa' ? 'selected' : '' }}>Siswa</option>
|
||||
<option value="Mahasiswa" {{ old('status') == 'Mahasiswa' ? 'selected' : '' }}>Mahasiswa</option>
|
||||
|
|
@ -204,14 +241,7 @@ class="group w-full py-4 bg-gradient-to-r from-primary-400 to-primary-500 hover:
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-center pt-2">
|
||||
<p class="text-sm text-primary-200/40">
|
||||
Belum punya akun anggota?
|
||||
<a href="{{ route('register') }}" class="text-white font-semibold hover:text-primary-300 transition underline decoration-primary-400/30 underline-offset-4">
|
||||
Daftar Sekarang
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -226,6 +256,10 @@ function switchMode(mode) {
|
|||
const radioMember = document.getElementById('radio-member');
|
||||
const radioTamu = document.getElementById('radio-tamu');
|
||||
const tipeInput = document.getElementById('tipe-input');
|
||||
const formTitle = document.getElementById('form-title');
|
||||
|
||||
const reqMember = formMember.querySelectorAll('input, select');
|
||||
const reqTamu = formTamu.querySelectorAll('input, select');
|
||||
|
||||
if (mode === 'member') {
|
||||
formMember.style.display = 'block';
|
||||
|
|
@ -235,6 +269,10 @@ function switchMode(mode) {
|
|||
radioMember.innerHTML = '<div class="w-3 h-3 rounded-full bg-primary-400"></div>';
|
||||
radioTamu.innerHTML = '';
|
||||
tipeInput.value = 'member';
|
||||
if(formTitle) formTitle.innerText = 'Buku Tamu Anggota';
|
||||
|
||||
reqMember.forEach(el => el.setAttribute('required', 'required'));
|
||||
reqTamu.forEach(el => el.removeAttribute('required'));
|
||||
} else {
|
||||
formMember.style.display = 'none';
|
||||
formTamu.style.display = 'block';
|
||||
|
|
@ -243,6 +281,10 @@ function switchMode(mode) {
|
|||
radioMember.innerHTML = '';
|
||||
radioTamu.innerHTML = '<div class="w-3 h-3 rounded-full bg-primary-400"></div>';
|
||||
tipeInput.value = 'tamu';
|
||||
if(formTitle) formTitle.innerText = 'Buku Tamu Pengunjung Umum';
|
||||
|
||||
reqMember.forEach(el => el.removeAttribute('required'));
|
||||
reqTamu.forEach(el => el.setAttribute('required', 'required'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div {{ $attributes->merge(['class' => 'bg-white overflow-hidden shadow-sm sm:rounded-lg']) }}>
|
||||
<div class="p-6 text-gray-900">
|
||||
<div {{ $attributes->merge(['class' => 'bg-white overflow-hidden shadow-[0_8px_30px_rgb(0,0,0,0.04)] sm:rounded-2xl border border-gray-100']) }}>
|
||||
<div class="p-8 text-gray-800">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex justify-center items-center px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 border border-transparent rounded-xl font-bold text-xs text-white uppercase tracking-widest shadow-md shadow-blue-500/30 active:scale-[0.98] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-all duration-200']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
@props(['disabled' => false])
|
||||
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}>
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border border-gray-200 bg-gray-50 text-gray-900 text-sm rounded-xl focus:ring-blue-500 focus:border-blue-500 block w-full p-3 shadow-sm hover:bg-white transition-all duration-200 outline-none']) }}>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,64 +18,108 @@
|
|||
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;
|
||||
background-color: #f9fafb !important;
|
||||
border-radius: 0.75rem !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: #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">
|
||||
|
||||
<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">
|
||||
<div class="w-9 h-9 bg-white/15 rounded-xl flex items-center justify-center backdrop-blur-sm">
|
||||
<!-- 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">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
{{-- SIDEBAR --}}
|
||||
<aside class="w-64 bg-white shadow-xl flex flex-col overflow-y-auto z-10 border-r border-gray-100">
|
||||
<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>
|
||||
|
|
@ -82,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>
|
||||
|
|
@ -113,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>
|
||||
|
|
@ -144,5 +200,53 @@ class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300
|
|||
</main>
|
||||
</div>
|
||||
|
||||
{{-- SweetAlert2 for Global Delete Confirmation --}}
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script>
|
||||
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();
|
||||
Swal.fire({
|
||||
title: 'Konfirmasi Hapus',
|
||||
text: message,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#ef4444',
|
||||
cancelButtonColor: '#6b7280',
|
||||
confirmButtonText: '<i class="fas fa-trash-alt mr-2"></i> Ya, Hapus',
|
||||
cancelButtonText: 'Batal',
|
||||
reverseButtons: true,
|
||||
customClass: {
|
||||
popup: 'rounded-3xl shadow-2xl',
|
||||
title: 'text-2xl font-bold text-gray-800',
|
||||
htmlContainer: 'text-gray-500',
|
||||
confirmButton: 'rounded-xl px-6 py-2.5 font-bold shadow-lg shadow-red-500/30 transition-all ml-3',
|
||||
cancelButton: 'rounded-xl px-6 py-2.5 font-bold bg-gray-100 text-gray-600 hover:bg-gray-200 transition-all'
|
||||
},
|
||||
buttonsStyling: false
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@stack('scripts')
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -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">
|
||||
|
|
@ -107,11 +108,11 @@
|
|||
@stack('styles')
|
||||
</head>
|
||||
|
||||
<body class="bg-white text-gray-800 font-sans antialiased">
|
||||
<body class="bg-white text-gray-800 font-sans antialiased overflow-x-hidden w-full">
|
||||
|
||||
{{-- NAVBAR --}}
|
||||
<nav class="glass-nav fixed w-full top-0 z-50 border-b border-gray-100/50">
|
||||
<div class="max-w-7xl mx-auto px-6 py-4 flex justify-between items-center">
|
||||
<div class="max-w-7xl mx-auto px-6 py-4 flex justify-between items-center relative">
|
||||
<a href="{{ route('home') }}" class="flex items-center gap-3 group">
|
||||
<div
|
||||
class="w-10 h-10 bg-gradient-to-br from-primary-500 to-primary-700 rounded-xl flex items-center justify-center shadow-lg shadow-primary-200 group-hover:shadow-primary-300 transition-shadow">
|
||||
|
|
@ -126,20 +127,27 @@ class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mt-1 hidden
|
|||
</div>
|
||||
</a>
|
||||
|
||||
<div class="flex items-center gap-8 text-sm font-medium">
|
||||
<!-- 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">
|
||||
<i class="fas fa-bars text-xl"></i>
|
||||
</button>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center gap-8 text-sm font-medium">
|
||||
<a href="{{ route('home') }}"
|
||||
class="relative transition-colors {{ request()->routeIs('home') ? 'text-primary-600 font-semibold' : 'text-gray-600 hover:text-primary-600' }} after:absolute after:bottom-[-4px] after:left-0 {{ request()->routeIs('home') ? 'after:w-full' : 'after:w-0 hover:after:w-full' }} after:h-0.5 after:bg-primary-500 after:transition-all">Beranda</a>
|
||||
<a href="{{ route('katalog.index') }}"
|
||||
class="relative transition-colors {{ request()->routeIs('katalog.*') ? 'text-primary-600 font-semibold' : 'text-gray-600 hover:text-primary-600' }} after:absolute after:bottom-[-4px] after:left-0 {{ request()->routeIs('katalog.*') ? 'after:w-full' : 'after:w-0 hover:after:w-full' }} after:h-0.5 after:bg-primary-500 after:transition-all">Katalog
|
||||
Buku</a>
|
||||
<a href="{{ route('home') }}#fitur"
|
||||
class="relative transition-colors {{ request()->routeIs('home') && request()->getQueryString() == '' ? 'text-primary-600 font-semibold' : 'text-gray-600 hover:text-primary-600' }} after:absolute after:bottom-[-4px] after:left-0 after:w-0 after:h-0.5 after:bg-primary-500 after:transition-all hover:after:w-full">Fitur</a>
|
||||
class="relative transition-colors text-gray-600 hover:text-primary-600 after:absolute after:bottom-[-4px] after:left-0 after:w-0 after:h-0.5 after:bg-primary-500 after:transition-all hover:after:w-full">Fitur</a>
|
||||
<a href="{{ route('home') }}#rekomendasi"
|
||||
class="relative transition-colors {{ request()->routeIs('home') && request()->getQueryString() == '' ? 'text-primary-600 font-semibold' : 'text-gray-600 hover:text-primary-600' }} after:absolute after:bottom-[-4px] after:left-0 after:w-0 after:h-0.5 after:bg-primary-500 after:transition-all hover:after:w-full">Koleksi</a>
|
||||
class="relative transition-colors text-gray-600 hover:text-primary-600 after:absolute after:bottom-[-4px] after:left-0 after:w-0 after:h-0.5 after:bg-primary-500 after:transition-all hover:after:w-full">Koleksi</a>
|
||||
|
||||
@guest
|
||||
<button onclick="openGuestModal()"
|
||||
class="px-5 py-2.5 bg-primary-50 text-primary-600 rounded-xl font-semibold hover:bg-primary-100 transition-colors cursor-pointer">
|
||||
class="px-5 py-2.5 bg-primary-50 text-primary-600 rounded-xl font-semibold hover:bg-primary-100 transition-colors cursor-pointer flex items-center justify-center">
|
||||
<i class="fas fa-book-reader mr-1.5"></i> Buku Tamu
|
||||
</button>
|
||||
<a href="{{ route('login') }}"
|
||||
|
|
@ -163,6 +171,47 @@ class="px-5 py-2.5 bg-gradient-to-r from-green-600 to-green-700 text-white round
|
|||
@endauth
|
||||
</div>
|
||||
</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 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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<i class="fas fa-user mr-1.5"></i> Dashboard Anggota
|
||||
</a>
|
||||
@endif
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{{-- CONTENT --}}
|
||||
|
|
@ -198,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>
|
||||
|
|
@ -238,7 +286,93 @@ class="fab fa-twitter text-sm"></i></a>
|
|||
</div>
|
||||
</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 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">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<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">
|
||||
<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>
|
||||
</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">
|
||||
<i class="fas fa-user-friends"></i>
|
||||
</div>
|
||||
<div class="text-left flex-1 border-gray-100">
|
||||
<div class="font-bold text-gray-900 text-sm">Pengunjung Umum</div>
|
||||
<div class="text-xs text-gray-400">Belum punya member</div>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right text-gray-300 group-hover:text-gray-600 transition-colors"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stack('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const btn = document.getElementById('mobile-menu-btn');
|
||||
const menu = document.getElementById('mobile-menu');
|
||||
const icon = btn.querySelector('i');
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
menu.classList.toggle('hidden');
|
||||
menu.classList.toggle('flex');
|
||||
|
||||
// Toggle icon
|
||||
if (menu.classList.contains('flex')) {
|
||||
icon.classList.remove('fa-bars');
|
||||
icon.classList.add('fa-times');
|
||||
} else {
|
||||
icon.classList.remove('fa-times');
|
||||
icon.classList.add('fa-bars');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Global Modal Scripts
|
||||
function openGuestModal() {
|
||||
document.getElementById('guestModal').classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeGuestModal() {
|
||||
document.getElementById('guestModal').classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
|
||||
<span class="page-link" aria-hidden="true">‹</span>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">‹</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- Pagination Elements --}}
|
||||
@foreach ($elements as $element)
|
||||
{{-- "Three Dots" Separator --}}
|
||||
@if (is_string($element))
|
||||
<li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
|
||||
@endif
|
||||
|
||||
{{-- Array Of Links --}}
|
||||
@if (is_array($element))
|
||||
@foreach ($element as $page => $url)
|
||||
@if ($page == $paginator->currentPage())
|
||||
<li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
|
||||
@else
|
||||
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">›</a>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
|
||||
<span class="page-link" aria-hidden="true">›</span>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</nav>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav class="d-flex justify-items-center justify-content-between">
|
||||
<div class="d-flex justify-content-between flex-fill d-sm-none">
|
||||
<ul class="pagination">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="page-item disabled" aria-disabled="true">
|
||||
<span class="page-link">@lang('pagination.previous')</span>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item disabled" aria-disabled="true">
|
||||
<span class="page-link">@lang('pagination.next')</span>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="d-none flex-sm-fill d-sm-flex align-items-sm-center justify-content-sm-between">
|
||||
<div>
|
||||
<p class="small text-muted">
|
||||
{!! __('Showing') !!}
|
||||
<span class="fw-semibold">{{ $paginator->firstItem() }}</span>
|
||||
{!! __('to') !!}
|
||||
<span class="fw-semibold">{{ $paginator->lastItem() }}</span>
|
||||
{!! __('of') !!}
|
||||
<span class="fw-semibold">{{ $paginator->total() }}</span>
|
||||
{!! __('results') !!}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul class="pagination">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
|
||||
<span class="page-link" aria-hidden="true">‹</span>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">‹</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- Pagination Elements --}}
|
||||
@foreach ($elements as $element)
|
||||
{{-- "Three Dots" Separator --}}
|
||||
@if (is_string($element))
|
||||
<li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
|
||||
@endif
|
||||
|
||||
{{-- Array Of Links --}}
|
||||
@if (is_array($element))
|
||||
@foreach ($element as $page => $url)
|
||||
@if ($page == $paginator->currentPage())
|
||||
<li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
|
||||
@else
|
||||
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">›</a>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
|
||||
<span class="page-link" aria-hidden="true">›</span>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
|
||||
<span aria-hidden="true">‹</span>
|
||||
</li>
|
||||
@else
|
||||
<li>
|
||||
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">‹</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- Pagination Elements --}}
|
||||
@foreach ($elements as $element)
|
||||
{{-- "Three Dots" Separator --}}
|
||||
@if (is_string($element))
|
||||
<li class="disabled" aria-disabled="true"><span>{{ $element }}</span></li>
|
||||
@endif
|
||||
|
||||
{{-- Array Of Links --}}
|
||||
@if (is_array($element))
|
||||
@foreach ($element as $page => $url)
|
||||
@if ($page == $paginator->currentPage())
|
||||
<li class="active" aria-current="page"><span>{{ $page }}</span></li>
|
||||
@else
|
||||
<li><a href="{{ $url }}">{{ $page }}</a></li>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<li>
|
||||
<a href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">›</a>
|
||||
</li>
|
||||
@else
|
||||
<li class="disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
|
||||
<span aria-hidden="true">›</span>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</nav>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
@if ($paginator->hasPages())
|
||||
<div class="ui pagination menu" role="navigation">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<a class="icon item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')"> <i class="left chevron icon"></i> </a>
|
||||
@else
|
||||
<a class="icon item" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')"> <i class="left chevron icon"></i> </a>
|
||||
@endif
|
||||
|
||||
{{-- Pagination Elements --}}
|
||||
@foreach ($elements as $element)
|
||||
{{-- "Three Dots" Separator --}}
|
||||
@if (is_string($element))
|
||||
<a class="icon item disabled" aria-disabled="true">{{ $element }}</a>
|
||||
@endif
|
||||
|
||||
{{-- Array Of Links --}}
|
||||
@if (is_array($element))
|
||||
@foreach ($element as $page => $url)
|
||||
@if ($page == $paginator->currentPage())
|
||||
<a class="item active" href="{{ $url }}" aria-current="page">{{ $page }}</a>
|
||||
@else
|
||||
<a class="item" href="{{ $url }}">{{ $page }}</a>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<a class="icon item" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')"> <i class="right chevron icon"></i> </a>
|
||||
@else
|
||||
<a class="icon item disabled" aria-disabled="true" aria-label="@lang('pagination.next')"> <i class="right chevron icon"></i> </a>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="page-item disabled" aria-disabled="true">
|
||||
<span class="page-link">@lang('pagination.previous')</span>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item disabled" aria-disabled="true">
|
||||
<span class="page-link">@lang('pagination.next')</span>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</nav>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav role="navigation" aria-label="{!! __('Pagination Navigation') !!}">
|
||||
<ul class="pagination">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="page-item disabled" aria-disabled="true">
|
||||
<span class="page-link">{!! __('pagination.previous') !!}</span>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">
|
||||
{!! __('pagination.previous') !!}
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">{!! __('pagination.next') !!}</a>
|
||||
</li>
|
||||
@else
|
||||
<li class="page-item disabled" aria-disabled="true">
|
||||
<span class="page-link">{!! __('pagination.next') !!}</span>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</nav>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="disabled" aria-disabled="true"><span>@lang('pagination.previous')</span></li>
|
||||
@else
|
||||
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a></li>
|
||||
@endif
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a></li>
|
||||
@else
|
||||
<li class="disabled" aria-disabled="true"><span>@lang('pagination.next')</span></li>
|
||||
@endif
|
||||
</ul>
|
||||
</nav>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav role="navigation" aria-label="{!! __('Pagination Navigation') !!}" class="flex justify-between">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600">
|
||||
{!! __('pagination.previous') !!}
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:focus:border-blue-700 dark:active:bg-gray-700 dark:active:text-gray-300">
|
||||
{!! __('pagination.previous') !!}
|
||||
</a>
|
||||
@endif
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<a href="{{ $paginator->nextPageUrl() }}" rel="next" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:focus:border-blue-700 dark:active:bg-gray-700 dark:active:text-gray-300">
|
||||
{!! __('pagination.next') !!}
|
||||
</a>
|
||||
@else
|
||||
<span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600">
|
||||
{!! __('pagination.next') !!}
|
||||
</span>
|
||||
@endif
|
||||
</nav>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" class="flex flex-col sm:flex-row items-center justify-between gap-4 mt-6">
|
||||
{{-- Info text --}}
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 leading-5">
|
||||
Menampilkan
|
||||
@if ($paginator->firstItem())
|
||||
<span class="font-semibold text-gray-700">{{ $paginator->firstItem() }}</span>
|
||||
sampai
|
||||
<span class="font-semibold text-gray-700">{{ $paginator->lastItem() }}</span>
|
||||
@else
|
||||
{{ $paginator->count() }}
|
||||
@endif
|
||||
dari
|
||||
<span class="font-semibold text-gray-700">{{ $paginator->total() }}</span>
|
||||
data
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Pagination Buttons --}}
|
||||
<div class="flex items-center gap-1.5">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<span class="inline-flex items-center justify-center w-9 h-9 rounded-lg text-gray-300 cursor-not-allowed" aria-disabled="true">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ $paginator->previousPageUrl() }}" rel="prev"
|
||||
class="inline-flex items-center justify-center w-9 h-9 rounded-lg text-gray-500 bg-white border border-gray-200 shadow-sm hover:bg-indigo-50 hover:text-indigo-600 hover:border-indigo-200 transition-all duration-200"
|
||||
aria-label="{{ __('pagination.previous') }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/>
|
||||
</svg>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
{{-- Pagination Elements --}}
|
||||
@foreach ($elements as $element)
|
||||
{{-- "Three Dots" Separator --}}
|
||||
@if (is_string($element))
|
||||
<span class="inline-flex items-center justify-center w-9 h-9 text-sm text-gray-400 select-none" aria-disabled="true">
|
||||
{{ $element }}
|
||||
</span>
|
||||
@endif
|
||||
|
||||
{{-- Array Of Links --}}
|
||||
@if (is_array($element))
|
||||
@foreach ($element as $page => $url)
|
||||
@if ($page == $paginator->currentPage())
|
||||
<span aria-current="page"
|
||||
class="inline-flex items-center justify-center w-9 h-9 rounded-lg text-sm font-bold text-white bg-gradient-to-br from-indigo-500 to-blue-600 shadow-md shadow-indigo-200 cursor-default transition-all duration-200">
|
||||
{{ $page }}
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ $url }}"
|
||||
class="inline-flex items-center justify-center w-9 h-9 rounded-lg text-sm font-medium text-gray-600 bg-white border border-gray-200 shadow-sm hover:bg-indigo-50 hover:text-indigo-600 hover:border-indigo-200 hover:shadow-md transition-all duration-200"
|
||||
aria-label="{{ __('Go to page :page', ['page' => $page]) }}">
|
||||
{{ $page }}
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<a href="{{ $paginator->nextPageUrl() }}" rel="next"
|
||||
class="inline-flex items-center justify-center w-9 h-9 rounded-lg text-gray-500 bg-white border border-gray-200 shadow-sm hover:bg-indigo-50 hover:text-indigo-600 hover:border-indigo-200 transition-all duration-200"
|
||||
aria-label="{{ __('pagination.next') }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"/>
|
||||
</svg>
|
||||
</a>
|
||||
@else
|
||||
<span class="inline-flex items-center justify-center w-9 h-9 rounded-lg text-gray-300 cursor-not-allowed" aria-disabled="true">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</nav>
|
||||
@endif
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
@section('content')
|
||||
<!-- Hero Section -->
|
||||
<div
|
||||
class="relative bg-gradient-to-br from-blue-900 via-indigo-800 to-blue-900 py-16 sm:py-24 overflow-hidden shadow-2xl mb-12">
|
||||
class="relative bg-gradient-to-br from-blue-900 via-indigo-800 to-blue-900 py-12 sm:py-24 overflow-hidden shadow-2xl mb-8 sm:mb-12">
|
||||
<!-- Decorative background elements -->
|
||||
<div
|
||||
class="absolute inset-0 opacity-10 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')] mix-blend-overlay">
|
||||
|
|
@ -17,29 +17,31 @@ 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-4xl sm:text-5xl font-extrabold text-white mb-6 tracking-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-lg sm:text-xl mb-10 font-light">
|
||||
<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">
|
||||
Temukan ribuan koleksi buku, jurnal, dan referensi akademik untuk menginspirasi perjalanan literasi
|
||||
Anda.
|
||||
</p>
|
||||
|
||||
<!-- Search Bar -->
|
||||
<form action="{{ route('katalog.index') }}" method="GET" class="relative group max-w-2xl mx-auto">
|
||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||
<svg class="h-6 w-6 text-gray-400 group-focus-within:text-blue-500 transition-colors"
|
||||
<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">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
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, atau penerbit..."
|
||||
class="block w-full pl-12 pr-32 py-4 bg-white/95 backdrop-blur-sm border-0 rounded-2xl shadow-xl focus:ring-4 focus:ring-blue-500/30 text-gray-800 text-lg transition-all placeholder-gray-400">
|
||||
<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-2 top-2 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-6 lg:px-8 rounded-xl shadow-md transition-all transform hover:scale-[1.02] active:scale-[0.98]">
|
||||
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">
|
||||
Cari
|
||||
</button>
|
||||
</form>
|
||||
|
|
@ -65,19 +67,14 @@ class="absolute right-2 top-2 bottom-2 bg-gradient-to-r from-blue-600 to-indigo-
|
|||
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
|
||||
|
|
@ -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">
|
||||
|
|
@ -279,21 +284,32 @@ class="text-blue-600 font-bold text-sm">{{ $buku->kategori->nama_kategori ?? $lo
|
|||
|
||||
<div class="w-full lg:w-96 flex-shrink-0 flex flex-col gap-6 mx-auto relative">
|
||||
|
||||
<!-- MAP CONTAINER -->
|
||||
<div class="map-container shadow-sm border-2 border-slate-300 select-none font-sans min-h-[300px]"
|
||||
id="mapContainer">
|
||||
<!-- MAP CONTAINER RESPONSIVE -->
|
||||
<div class="relative inline-block w-full max-w-4xl shadow-sm border-2 border-slate-300 select-none font-sans overflow-hidden rounded-lg">
|
||||
<!-- Background Image Map -->
|
||||
<img src="{{ asset('img/img 2.png') }}" alt="Denah Perpustakaan" id="mapImage">
|
||||
<img src="{{ asset('img/denah.webp') }}" loading="lazy" alt="Denah Perpustakaan" class="w-full h-auto block">
|
||||
|
||||
<!-- Pin Container (JavaScript akan merender pin di sini) -->
|
||||
<div id="pinContainer"></div>
|
||||
@if ($buku->lokasi_x && $buku->lokasi_y)
|
||||
<!-- Pin Marker (Render via Blade) -->
|
||||
<div class="absolute -translate-x-1/2 -translate-y-full flex flex-col items-center"
|
||||
style="top: {{ $buku->lokasi_y }}%; left: {{ $buku->lokasi_x }}%; margin-top: 4px;">
|
||||
|
||||
<!-- Tooltip / Label -->
|
||||
<div class="bg-blue-700 text-white text-[10px] sm:text-xs font-bold px-2 py-1 rounded shadow-md mb-1 whitespace-nowrap">
|
||||
{{ $lokasi['rak'] }} — {{ $lokasi['area'] }}
|
||||
</div>
|
||||
|
||||
@if (!$buku->lokasi_x || !$buku->lokasi_y)
|
||||
<!-- Ikon Pin Merah -->
|
||||
<div class="relative flex flex-col items-center animate-bounce">
|
||||
<i class="fas fa-map-marker-alt text-red-600 text-3xl sm:text-4xl drop-shadow-md"></i>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<!-- Overlay Not Found -->
|
||||
<div class="map-overlay-notfound">
|
||||
<i class="fas fa-exclamation-triangle text-red-500 text-3xl mb-2 animate-pulse"></i>
|
||||
<h4 class="font-bold text-red-600 text-sm sm:text-base">Lokasi Rak Belum Dipetakan</h4>
|
||||
<p class="text-xs text-gray-500 mt-1">Silakan hubungi petugas perpustakaan untuk bantuan.</p>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center bg-white/70 backdrop-blur-sm z-10 text-center p-4">
|
||||
<i class="fas fa-exclamation-triangle text-red-500 text-3xl sm:text-4xl mb-2 animate-pulse"></i>
|
||||
<h4 class="font-bold text-red-600 text-sm sm:text-lg">Lokasi Rak Belum Dipetakan</h4>
|
||||
<p class="text-xs sm:text-sm text-gray-700 mt-1">Silakan hubungi petugas perpustakaan untuk bantuan.</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -419,69 +435,5 @@ class="text-sm font-semibold text-blue-600 hover:text-blue-800 flex items-center
|
|||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
@php
|
||||
$pinDataJson = [
|
||||
'x' => $buku->lokasi_x,
|
||||
'y' => $buku->lokasi_y,
|
||||
'label' => $lokasi['rak'] . ' — ' . $lokasi['area'],
|
||||
];
|
||||
@endphp
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Data koordinat dari Laravel
|
||||
const pinData = @json($pinDataJson);
|
||||
|
||||
const container = document.getElementById('pinContainer');
|
||||
if (!container || !pinData.x || !pinData.y) return;
|
||||
|
||||
/**
|
||||
* Membuat elemen pin pointer dan menambahkannya ke container.
|
||||
* @param {number} x - Posisi X dalam persentase (0-100)
|
||||
* @param {number} y - Posisi Y dalam persentase (0-100)
|
||||
* @param {string} label - Label teks untuk pin
|
||||
* @param {boolean} isActive - Apakah pin ini aktif (bounce + label visible)
|
||||
*/
|
||||
function renderPin(x, y, label, isActive) {
|
||||
// Wrapper pin
|
||||
const pin = document.createElement('div');
|
||||
pin.className = 'map-pin' + (isActive ? ' active' : '');
|
||||
pin.style.left = x + '%';
|
||||
pin.style.top = y + '%';
|
||||
|
||||
// Ikon pin (circle + tail)
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'map-pin__icon';
|
||||
|
||||
const circle = document.createElement('div');
|
||||
circle.className = 'map-pin__circle';
|
||||
circle.innerHTML = '<i class="fas fa-map-marker-alt"></i>';
|
||||
|
||||
const tail = document.createElement('div');
|
||||
tail.className = 'map-pin__tail';
|
||||
|
||||
icon.appendChild(circle);
|
||||
icon.appendChild(tail);
|
||||
pin.appendChild(icon);
|
||||
|
||||
// Pulse effect
|
||||
const pulse = document.createElement('div');
|
||||
pulse.className = 'map-pin__pulse';
|
||||
pin.appendChild(pulse);
|
||||
|
||||
// Label tooltip
|
||||
if (label) {
|
||||
const labelEl = document.createElement('span');
|
||||
labelEl.className = 'map-pin__label';
|
||||
labelEl.textContent = label;
|
||||
pin.appendChild(labelEl);
|
||||
}
|
||||
|
||||
container.appendChild(pin);
|
||||
return pin;
|
||||
}
|
||||
|
||||
// Render pin utama buku ini
|
||||
renderPin(pinData.x, pinData.y, pinData.label, true);
|
||||
});
|
||||
</script>
|
||||
<!-- Skrip lama pembuat pin JS sudah dihapus dan digantikan full Tailwind CSS di HTML -->
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -11,61 +11,4 @@
|
|||
@include('welcome.rekomendasi')
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<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">
|
||||
<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>
|
||||
</a>
|
||||
|
||||
<!-- Visitor Option -->
|
||||
<a href="{{ route('buku_tamu.index') }}" 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">
|
||||
<div class="font-bold text-gray-900 text-sm">Pengunjung Umum</div>
|
||||
<div class="text-xs text-gray-400">Belum punya member</div>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right text-gray-300 group-hover:text-gray-600 transition-colors"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function openGuestModal() {
|
||||
document.getElementById('guestModal').classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeGuestModal() {
|
||||
document.getElementById('guestModal').classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
||||
|
|
@ -5,58 +5,128 @@
|
|||
<div class="absolute bottom-10 right-10 w-96 h-96 bg-purple-200/20 blob blur-3xl"></div>
|
||||
<div class="absolute top-40 right-1/3 w-48 h-48 bg-blue-200/20 rounded-full blur-2xl"></div>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-6 py-24 md:py-32 grid md:grid-cols-2 gap-16 items-center relative z-10">
|
||||
<div
|
||||
class="max-w-7xl mx-auto px-4 sm:px-6 py-14 sm:py-20 md:py-26
|
||||
grid md:grid-cols-2 gap-10 md:gap-16 items-center relative z-10">
|
||||
|
||||
<div class="fade-up">
|
||||
{{-- Badge --}}
|
||||
<div class="inline-flex items-center gap-2 px-4 py-2 bg-white/80 rounded-full shadow-sm border border-primary-100 mb-8">
|
||||
<div
|
||||
class="inline-flex items-center gap-2 px-3 py-2 bg-white/80
|
||||
rounded-full shadow-sm border border-primary-100 mb-6">
|
||||
<span class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></span>
|
||||
<span class="text-xs font-semibold text-primary-700 tracking-wide">Sistem Informasi Perpustakaan</span>
|
||||
|
||||
<span class="text-[11px] sm:text-xs font-semibold text-primary-700 tracking-wide">
|
||||
Sistem Informasi Perpustakaan
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h1 class="text-4xl md:text-5xl lg:text-6xl font-black leading-[1.1] text-gray-900 tracking-tight">
|
||||
<h1
|
||||
class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl
|
||||
font-black leading-tight text-gray-900 tracking-tight">
|
||||
Sistem Informasi Perpustakaan
|
||||
<span class="gradient-text block mt-2">Daerah Jember</span>
|
||||
|
||||
<span class="gradient-text block mt-1 sm:mt-2">
|
||||
Daerah Jember
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p class="mt-6 text-lg text-gray-500 max-w-lg leading-relaxed">
|
||||
Akses ribuan koleksi buku, jurnal, dan referensi akademik dalam satu platform terintegrasi — kapan saja, di mana saja.
|
||||
<p
|
||||
class="mt-5 text-sm sm:text-base md:text-lg text-gray-500
|
||||
max-w-lg leading-relaxed">
|
||||
Akses ribuan koleksi buku, jurnal, dan referensi akademik
|
||||
dalam satu platform terintegrasi — kapan saja, di mana saja.
|
||||
</p>
|
||||
|
||||
<div class="mt-10 flex flex-col sm:flex-row gap-4 flex-wrap">
|
||||
<a href="{{ route('katalog.index') }}" class="px-8 py-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-2xl font-bold text-sm shadow-xl shadow-blue-200 hover:shadow-blue-300 hover:from-blue-700 hover:to-blue-800 transition-all inline-flex items-center gap-2 justify-center">
|
||||
<i class="fas fa-search"></i> Cari Katalog Buku
|
||||
{{-- BUTTONS --}}
|
||||
<div class="mt-8 flex flex-col gap-3 sm:flex-row sm:flex-wrap">
|
||||
|
||||
<a href="{{ route('katalog.index') }}"
|
||||
class="w-full sm:w-auto px-6 py-3.5
|
||||
bg-gradient-to-r from-blue-600 to-blue-700
|
||||
text-white rounded-xl sm:rounded-2xl
|
||||
font-bold text-sm shadow-xl shadow-blue-200
|
||||
hover:from-blue-700 hover:to-blue-800
|
||||
transition-all inline-flex items-center
|
||||
gap-2 justify-center">
|
||||
|
||||
<i class="fas fa-search"></i>
|
||||
Cari Katalog Buku
|
||||
</a>
|
||||
<a href="{{ route('buku_tamu.index') }}" class="px-8 py-4 bg-gradient-to-r from-primary-600 to-primary-700 text-white rounded-2xl font-bold text-sm shadow-xl shadow-primary-200 hover:shadow-primary-300 hover:from-primary-700 hover:to-primary-800 transition-all inline-flex items-center gap-2 justify-center">
|
||||
<i class="fas fa-book-reader"></i> Mulai Kunjungan
|
||||
|
||||
<a href="{{ route('buku_tamu.index') }}"
|
||||
class="w-full sm:w-auto px-6 py-3.5
|
||||
bg-gradient-to-r from-primary-600 to-primary-700
|
||||
text-white rounded-xl sm:rounded-2xl
|
||||
font-bold text-sm shadow-xl shadow-primary-200
|
||||
hover:from-primary-700 hover:to-primary-800
|
||||
transition-all inline-flex items-center
|
||||
gap-2 justify-center">
|
||||
|
||||
<i class="fas fa-book-reader"></i>
|
||||
Mulai Kunjungan
|
||||
</a>
|
||||
<a href="#fitur" class="px-8 py-4 bg-white text-gray-700 rounded-2xl font-bold text-sm shadow-sm border border-gray-200 hover:border-primary-200 hover:text-primary-600 transition-all inline-flex items-center gap-2 justify-center">
|
||||
<i class="fas fa-info-circle"></i> Pelajari Lebih Lanjut
|
||||
|
||||
<a href="#fitur"
|
||||
class="w-full sm:w-auto px-6 py-3.5
|
||||
bg-white text-gray-700 rounded-xl sm:rounded-2xl
|
||||
font-bold text-sm shadow-sm border border-gray-200
|
||||
hover:border-primary-200 hover:text-primary-600
|
||||
transition-all inline-flex items-center
|
||||
gap-2 justify-center">
|
||||
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Pelajari Lebih Lanjut
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- Stats --}}
|
||||
<div class="mt-14 flex gap-10">
|
||||
<div>
|
||||
<div class="text-3xl font-black text-gray-900">1000+</div>
|
||||
<div class="text-xs text-gray-400 font-medium mt-1">Koleksi Buku</div>
|
||||
{{-- STATS --}}
|
||||
<div class="mt-10 grid grid-cols-3 gap-3 sm:gap-6">
|
||||
|
||||
<div class="bg-white/50 rounded-xl p-3 sm:p-4 text-center">
|
||||
<div class="text-xl sm:text-3xl font-black text-gray-900">
|
||||
1000+
|
||||
</div>
|
||||
|
||||
<div class="text-[10px] sm:text-xs text-gray-400 font-medium mt-1">
|
||||
Koleksi Buku
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-l border-gray-200 pl-10">
|
||||
<div class="text-3xl font-black text-gray-900">500+</div>
|
||||
<div class="text-xs text-gray-400 font-medium mt-1">Anggota Aktif</div>
|
||||
|
||||
<div class="bg-white/50 rounded-xl p-3 sm:p-4 text-center">
|
||||
<div class="text-xl sm:text-3xl font-black text-gray-900">
|
||||
500+
|
||||
</div>
|
||||
|
||||
<div class="text-[10px] sm:text-xs text-gray-400 font-medium mt-1">
|
||||
Anggota Aktif
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-l border-gray-200 pl-10">
|
||||
<div class="text-3xl font-black text-gray-900">24/7</div>
|
||||
<div class="text-xs text-gray-400 font-medium mt-1">Akses Digital</div>
|
||||
|
||||
<div class="bg-white/50 rounded-xl p-3 sm:p-4 text-center">
|
||||
<div class="text-xl sm:text-3xl font-black text-gray-900">
|
||||
24/7
|
||||
</div>
|
||||
|
||||
<div class="text-[10px] sm:text-xs text-gray-400 font-medium mt-1">
|
||||
Akses Digital
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- IMAGE --}}
|
||||
<div class="hidden md:flex justify-center relative items-center">
|
||||
<div class="absolute inset-0 bg-gradient-to-tr from-primary-400/10 to-purple-400/10 rounded-[3rem] blur-2xl"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-tr
|
||||
from-primary-400/10 to-purple-400/10
|
||||
rounded-[3rem] blur-2xl">
|
||||
</div>
|
||||
|
||||
<img src="{{ asset('img/buku sampul awal.png') }}"
|
||||
class="w-[600px] object-contain drop-shadow-2xl relative z-10 rounded-3xl"
|
||||
alt="Animasi Perpustakaan Digital">
|
||||
class="w-[500px] lg:w-[600px] object-contain
|
||||
drop-shadow-2xl relative z-10 rounded-3xl"
|
||||
alt="Animasi Perpustakaan Digital">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -129,4 +129,5 @@
|
|||
Route::get('/admin/buku/{id}/edit', [AdminBukuController::class, 'edit'])->name('admin.buku.edit');
|
||||
Route::put('/admin/buku/{id}', [AdminBukuController::class, 'update'])->name('admin.buku.update');
|
||||
Route::delete('/admin/buku/{id}', [AdminBukuController::class, 'destroy'])->name('admin.buku.destroy');
|
||||
Route::get('/admin/peminjaman/{id}/struk', [AdminPeminjamanController::class, 'cetakStruk'])->name('admin.peminjaman.struk');
|
||||
Route::get('/admin/peminjaman/{id}/struk', [AdminPeminjamanController::class, 'cetakStruk'])->name('admin.peminjaman.struk');
|
||||
Route::post('/admin/peminjaman/{id}/resend-wa', [AdminPeminjamanController::class, 'resendWa'])->name('admin.peminjaman.resend_wa');
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
|
||||
$bukus = App\Models\Buku::all();
|
||||
$count = 0;
|
||||
|
||||
foreach ($bukus as $buku) {
|
||||
if (empty($buku->nomor_panggil)) continue;
|
||||
|
||||
$kode_utama = (int) substr(trim(preg_replace('/[^0-9]/', '', $buku->nomor_panggil)), 0, 3);
|
||||
|
||||
$koordinat = match (true) {
|
||||
$kode_utama >= 0 && $kode_utama <= 99 => match (true) {
|
||||
$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' => 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' => 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' => 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' => 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' => 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' => 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' => 25.00], // Rak 77-79
|
||||
$kode_utama >= 900 && $kode_utama <= 999 => match (true) {
|
||||
$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],
|
||||
};
|
||||
|
||||
if ($koordinat['x'] !== null) {
|
||||
$buku->lokasi_x = $koordinat['x'];
|
||||
$buku->lokasi_y = $koordinat['y'];
|
||||
$buku->save();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
echo "Berhasil update $count buku presisi.";
|
||||
Loading…
Reference in New Issue