perbaiki tampilan

This commit is contained in:
wardhatul1765 2026-05-17 16:33:43 +07:00
parent a656f599a4
commit bf0996430a
9 changed files with 211 additions and 47 deletions

View File

@ -34,6 +34,10 @@ public function store(Request $request)
$validated['cover'] = $request->file('cover')->store('covers', 'public'); $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; $validated['konten_digital'] = 0;
Buku::create($validated); Buku::create($validated);
@ -88,6 +92,10 @@ public function update(Request $request, $id)
$validated['cover'] = $request->file('cover')->store('covers', 'public'); $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); $buku->update($validated);
return redirect()->route('admin.buku.index')->with('success', 'Aset buku berhasil diperbarui.'); 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.'); 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' => 13.00, 'y' => 22.00], // Rak 01
$kode_utama <= 50 => ['x' => 18.00, 'y' => 22.00], // Rak 02
default => ['x' => 25.00, 'y' => 22.00], // Rak 03-05
},
$kode_utama >= 100 && $kode_utama <= 199 => match (true) {
$kode_utama <= 150 => ['x' => 43.00, 'y' => 20.00], // Rak 06-10
default => ['x' => 57.00, 'y' => 20.00], // Rak 11-14
},
$kode_utama >= 200 && $kode_utama <= 299 => match (true) {
$kode_utama == 297 => ['x' => 25.00, 'y' => 38.00], // Rak 25-32 (Islam)
default => ['x' => 13.00, 'y' => 38.00], // Rak 15-24
},
$kode_utama >= 300 && $kode_utama <= 399 => match (true) {
$kode_utama <= 330 => ['x' => 43.00, 'y' => 30.00], // Rak 33-36
$kode_utama <= 360 => ['x' => 50.00, 'y' => 30.00], // Rak 37-40
default => ['x' => 57.00, 'y' => 30.00], // Rak 41-44
},
$kode_utama >= 400 && $kode_utama <= 499 => ['x' => 18.00, 'y' => 62.00], // Rak 45
$kode_utama >= 500 && $kode_utama <= 599 => ['x' => 50.00, 'y' => 74.00], // Rak 46-48
$kode_utama >= 600 && $kode_utama <= 699 => match (true) {
$kode_utama <= 610 => ['x' => 35.00, 'y' => 85.00], // Rak 49-53
$kode_utama <= 630 => ['x' => 45.00, 'y' => 85.00], // Rak 54-58
$kode_utama <= 650 => ['x' => 55.00, 'y' => 85.00], // Rak 59-63
default => ['x' => 65.00, 'y' => 85.00], // Rak 64-68
},
$kode_utama >= 700 && $kode_utama <= 799 => match (true) {
$kode_utama <= 739 => ['x' => 77.00, 'y' => 22.00], // Rak 71
$kode_utama <= 769 => ['x' => 82.00, 'y' => 22.00], // Rak 72
$kode_utama <= 789 => ['x' => 87.00, 'y' => 22.00], // Rak 73
default => ['x' => 82.00, 'y' => 22.00], // Rak 74
},
$kode_utama >= 800 && $kode_utama <= 899 => ['x' => 82.00, 'y' => 32.00], // Rak 77-79
$kode_utama >= 900 && $kode_utama <= 999 => match (true) {
$kode_utama <= 919 => ['x' => 77.00, 'y' => 42.00], // Rak 69-70
default => ['x' => 87.00, 'y' => 42.00], // Rak 80-84
},
default => ['x' => null, 'y' => null],
};
}
} }

View File

@ -33,14 +33,14 @@ public function index(Request $request)
$peminjaman = $query->paginate(15)->appends(['search' => $search]); $peminjaman = $query->paginate(15)->appends(['search' => $search]);
$buku = Buku::orderBy('judul', 'asc')->get(); $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')); return view('admin.peminjaman.index', compact('peminjaman', 'buku', 'anggota', 'search'));
} }
public function create() public function create()
{ {
$buku = Buku::where('eksemplar', '>', 0)->orderBy('judul', 'asc')->get(); $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')); return view('admin.peminjaman.create', compact('buku', 'anggota'));
} }
@ -48,7 +48,7 @@ public function edit($id)
{ {
$peminjaman = Peminjaman::findOrFail($id); $peminjaman = Peminjaman::findOrFail($id);
$buku = Buku::all(); $buku = Buku::all();
$anggota = Anggota::orderBy('nama', 'asc')->get(); $anggota = Anggota::latest()->get();
return view('admin.peminjaman.edit', compact('peminjaman', 'buku', 'anggota')); return view('admin.peminjaman.edit', compact('peminjaman', 'buku', 'anggota'));
} }

View File

@ -31,13 +31,13 @@ public function index(\Illuminate\Http\Request $request)
$peminjaman = $query->paginate(15)->appends(['search' => $search]); $peminjaman = $query->paginate(15)->appends(['search' => $search]);
$buku = Buku::where('eksemplar', '>', 0)->get(); $buku = Buku::where('eksemplar', '>', 0)->get();
$anggota = Anggota::all(); $anggota = Anggota::latest()->get();
return view('admin.peminjaman.index', compact('peminjaman', 'buku', 'anggota', 'search')); return view('admin.peminjaman.index', compact('peminjaman', 'buku', 'anggota', 'search'));
} }
public function create() public function create()
{ {
$anggota = Anggota::all(); $anggota = Anggota::latest()->get();
$bukus = Buku::all(); $bukus = Buku::all();
return view('admin.peminjaman.create', compact('anggota', 'bukus')); return view('admin.peminjaman.create', compact('anggota', 'bukus'));
} }

View File

@ -51,21 +51,12 @@ public function index(Request $request)
$hasil = []; $hasil = [];
foreach ($semuaBuku as $b) { foreach ($semuaBuku as $b) {
// Hitung nilai Cosine Similarity untuk masing-masing atribut berdasarkan kata kunci yang sudah dibersihkan // Hitung nilai kemiripan dengan algoritma Dynamic Token-wise WTS
$judulSimilarity = $this->calculateSimilarity($processedSearch, $b->judul); $totalScore = $this->calculateDynamicWTS($processedSearch, $b);
$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);
// 3. Penetapan Batas Relevansi (Threshold) // 3. Penetapan Batas Relevansi (Threshold)
// Hanya tampilkan yang memiliki Total Similarity Score >= 0.3 (30%) // Hanya tampilkan yang memiliki Total Similarity Score >= 0.2 (20%)
if ($totalScore >= 0.3) { if ($totalScore >= 0.2) {
$b->similarity_score = $totalScore; $b->similarity_score = $totalScore;
$hasil[] = $b; $hasil[] = $b;
} }
@ -90,44 +81,77 @@ public function index(Request $request)
} }
/** /**
* Helper untuk menghitung nilai perbandingan kemiripan teks. * [ALGORITMA DYNAMIC TOKEN-WISE WTS]
* Menggunakan pendekatan Cosine Similarity yang dimodifikasi (Query Coverage) * Dibuat khusus untuk memecahkan masalah "Length Penalty" pada Global Search Bar.
* agar panjang judul buku tidak menurunkan skor secara drastis *
* pada pencarian kata kunci pendek. * 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))); $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 // 2. Ekstraksi Token Atribut Buku
$vecA = array_count_values(array_filter($queryWords)); $judulWords = explode(' ', strtolower(preg_replace('/[^\p{L}\p{N}\s]/u', '', $buku->judul)));
$vecB = array_count_values(array_filter($textWords)); $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; // 3. Konfigurasi Bobot WTS
$normA = 0; $weights = [
$normB = 0; 'judul' => 0.6,
'pengarang' => 0.3,
'tambahan' => 0.1
];
foreach ($terms as $term) { $totalScore = 0;
$valA = $vecA[$term] ?? 0;
$valB = $vecB[$term] ?? 0;
$dotProduct += ($valA * $valB); // 4. Evaluasi Token-wise (Local Max Pooling)
$normA += pow($valA, 2); foreach ($queryWords as $qWord) {
$normB += pow($valB, 2); // 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; // 5. Normalisasi
// Membagi total skor dengan jumlah token query agar term-frequency ternormalisasi,
// Standard Cosine Similarity: $dotProduct / (sqrt($normA) * sqrt($normB)) // namun tetap secara matematis mempertahankan keunggulan bobot absolut antar atribut.
// Kelemahannya: Jika judul buku sangat panjang (normB besar), skor akan anjlok (misal < 0.2) return $totalScore / count($queryWords);
// padahal kata pencariannya cocok 100%.
// Modifikasi menjadi Query Coverage (Pembagi dominan adalah panjang query).
return $dotProduct / $normA;
} }
public function show($id) public function show($id)

8
get_buku_52.php Normal file
View File

@ -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());

12
get_coordinates.php Normal file
View File

@ -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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 MiB

After

Width:  |  Height:  |  Size: 6.8 MiB

View File

@ -283,7 +283,7 @@ class="text-blue-600 font-bold text-sm">{{ $buku->kategori->nama_kategori ?? $lo
<div class="map-container shadow-sm border-2 border-slate-300 select-none font-sans min-h-[300px]" <div class="map-container shadow-sm border-2 border-slate-300 select-none font-sans min-h-[300px]"
id="mapContainer"> id="mapContainer">
<!-- Background Image Map --> <!-- Background Image Map -->
<img src="{{ asset('img/img 2.png') }}" alt="Denah Perpustakaan" id="mapImage"> <img src="{{ asset('img/img 2.png') }}?v={{ time() }}" alt="Denah Perpustakaan" id="mapImage">
<!-- Pin Container (JavaScript akan merender pin di sini) --> <!-- Pin Container (JavaScript akan merender pin di sini) -->
<div id="pinContainer"></div> <div id="pinContainer"></div>

64
update_lokasi.php Normal file
View File

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