perbaiki tampilan
This commit is contained in:
parent
a656f599a4
commit
bf0996430a
|
|
@ -34,6 +34,10 @@ public function store(Request $request)
|
|||
$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);
|
||||
|
||||
|
|
@ -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' => 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],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,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'));
|
||||
}
|
||||
|
||||
|
|
@ -48,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'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,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'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
// Frekuensi kata
|
||||
$vecA = array_count_values(array_filter($queryWords));
|
||||
$vecB = array_count_values(array_filter($textWords));
|
||||
if (empty($queryWords)) return 0;
|
||||
|
||||
$terms = array_unique(array_merge(array_keys($vecA), array_keys($vecB)));
|
||||
// 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)));
|
||||
|
||||
$dotProduct = 0;
|
||||
$normA = 0;
|
||||
$normB = 0;
|
||||
$teksTambahan = trim(($buku->penerbit ?? '') . ' ' . ($buku->deskripsi ?? '') . ' ' . ($buku->kategori->nama_kategori ?? ''));
|
||||
$tambahanWords = explode(' ', strtolower(preg_replace('/[^\p{L}\p{N}\s]/u', '', $teksTambahan)));
|
||||
|
||||
foreach ($terms as $term) {
|
||||
$valA = $vecA[$term] ?? 0;
|
||||
$valB = $vecB[$term] ?? 0;
|
||||
// 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 += ($valA * $valB);
|
||||
$normA += pow($valA, 2);
|
||||
$normB += pow($valB, 2);
|
||||
// 3. Konfigurasi Bobot WTS
|
||||
$weights = [
|
||||
'judul' => 0.6,
|
||||
'pengarang' => 0.3,
|
||||
'tambahan' => 0.1
|
||||
];
|
||||
|
||||
$totalScore = 0;
|
||||
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 MiB After Width: | Height: | Size: 6.8 MiB |
|
|
@ -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]"
|
||||
id="mapContainer">
|
||||
<!-- 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) -->
|
||||
<div id="pinContainer"></div>
|
||||
|
|
|
|||
|
|
@ -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.";
|
||||
Loading…
Reference in New Issue