368 lines
14 KiB
PHP
368 lines
14 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="id">
|
|
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>Filter Rekomendasi Mobil</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link href="https://fonts.googleapis.com/css2?family=Ancizar+Serif&family=Lora&display=swap" rel="stylesheet" />
|
|
<style>
|
|
.font-ancizar {
|
|
font-family: 'Ancizar Serif', serif;
|
|
}
|
|
|
|
.font-lora {
|
|
font-family: 'Lora', serif;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
appearance: none;
|
|
background: url('https://www.svgrepo.com/show/405445/car-front.svg');
|
|
background-size: contain;
|
|
background-repeat: no-repeat;
|
|
width: 40px;
|
|
height: 30px;
|
|
cursor: pointer;
|
|
border: none;
|
|
}
|
|
|
|
input[type="range"]::-moz-range-thumb {
|
|
background: url('https://www.svgrepo.com/show/405445/car-front.svg');
|
|
background-size: contain;
|
|
background-repeat: no-repeat;
|
|
width: 40px;
|
|
height: 30px;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
select {
|
|
border: none;
|
|
border-bottom: 2px solid #1e3a8a;
|
|
border-radius: 0;
|
|
padding: 0.25rem 0.5rem;
|
|
font-size: 1rem;
|
|
color: #1e3a8a;
|
|
background-color: transparent;
|
|
transition: border-color 0.3s ease;
|
|
}
|
|
|
|
select:focus {
|
|
outline: none;
|
|
border-bottom-color: #162e6b;
|
|
background-color: #f0f4ff;
|
|
}
|
|
|
|
label>input[type="checkbox"]+span {
|
|
color: #1e3a8a;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body
|
|
class="min-h-screen bg-gradient-to-b from-[#e0e7ff] to-[#1e3a8a] flex items-center justify-center px-4 py-10 font-sans text-[#1e3a8a]">
|
|
|
|
<!-- Tombol kembali ke landing page -->
|
|
<div class="absolute top-6 left-6 z-50">
|
|
<a href="{{ route('landing') }}"
|
|
class="group relative flex items-center justify-center w-10 h-10 rounded-full bg-[#1e3a8a] text-white hover:bg-[#3344aa] transition"
|
|
aria-label="Kembali ke landing page">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
|
stroke-width="2" stroke="currentColor" class="w-5 h-5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
<span
|
|
class="absolute left-full ml-3 px-3 py-1 rounded bg-gray-800 text-white text-xs opacity-0 group-hover:opacity-100 transition duration-200 whitespace-nowrap">
|
|
Kembali ke landing page
|
|
</span>
|
|
</a>
|
|
</div>
|
|
|
|
<form action="{{ route('rekomendasi.hasil') }}" method="GET"
|
|
class="flex flex-col lg:flex-row w-full max-w-7xl rounded-xl overflow-hidden shadow-2xl bg-white">
|
|
@csrf
|
|
|
|
<!-- KIRI -->
|
|
<div
|
|
class="w-full lg:w-1/3 bg-[#e0e7ff] text-[#1e3a8a] p-6 flex flex-col justify-start items-center space-y-4 min-h-[480px]">
|
|
<h1 class="text-4xl font-ancizar text-center w-full">Filter Rekomendasi Mobil</h1>
|
|
<img src="assets/images/rekomendasi.svg" alt="Ilustrasi Mobil" class="w-72 drop-shadow-xl mx-auto" />
|
|
<p class="font-lora text-center text-[#1e3a8a]/90 max-w-[280px] mx-auto">
|
|
Temukan mobil terbaik sesuai kebutuhan dan preferensimu. Gunakan filter dan sesuaikan bobot prioritas!
|
|
</p>
|
|
</div>
|
|
|
|
<!-- TENGAH -->
|
|
<div class="w-full lg:w-1/3 bg-[#f9fafb] p-6 space-y-8">
|
|
@if ($errors->any())
|
|
<div class="bg-red-100 text-red-700 p-4 rounded mb-6 border border-red-300">
|
|
<ul class="text-sm">
|
|
@foreach ($errors->all() as $error)
|
|
<li>{{ $error }}</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
|
|
@foreach ($subKriteria as $namaKriteria => $items)
|
|
<div>
|
|
<label class="block text-lg font-semibold mb-2 text-[#1e3a8a]">{{ $namaKriteria }}</label>
|
|
@php
|
|
$isCheckbox = $namaKriteria === 'Kelengkapan Fasilitas';
|
|
$selectId = 'kriteria_' . Str::slug($namaKriteria, '_');
|
|
@endphp
|
|
|
|
@if ($isCheckbox)
|
|
<div class="space-y-3">
|
|
@foreach ($items as $item)
|
|
<label class="flex items-center space-x-3 cursor-pointer">
|
|
<input type="checkbox" name="sub_kriteria[]" value="{{ $item->id }}"
|
|
class="form-checkbox text-[#1e3a8a] focus:ring-[#1e3a8a]" />
|
|
<span class="text-[#1e3a8a]">{{ $item->nama_subkriteria }}</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
@else
|
|
<select name="sub_kriteria[]" id="{{ $selectId }}" class="w-full"
|
|
data-kriteria="{{ $namaKriteria }}">
|
|
<option value="">Pilih {{ $namaKriteria }}</option>
|
|
@foreach ($items as $item)
|
|
<option value="{{ $item->id }}">{{ $item->nama_subkriteria }}</option>
|
|
@endforeach
|
|
</select>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<!-- KANAN -->
|
|
<div class="w-full lg:w-1/3 bg-[#e0e7ff] text-[#1e3a8a] p-6 space-y-6 min-h-[480px]">
|
|
<h3 class="text-xl font-semibold text-center mb-6">Sesuaikan Bobot Kriteria</h3>
|
|
<p class="text-sm text-center text-[#1e3a8a]/80 mb-6 px-2">
|
|
Atur bobot masing-masing kriteria untuk menentukan prioritasmu. Semakin tinggi bobot, semakin penting kriteria tersebut dalam rekomendasi.
|
|
</p>
|
|
|
|
@foreach ($kriteria as $k)
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 items-center">
|
|
<label for="slider-{{ $k->id }}" class="text-sm md:text-base font-medium text-[#1e3a8a]">{{ $k->nama_kriteria }}</label>
|
|
<div class="flex items-center gap-3 w-full">
|
|
<input type="range" min="0" max="1" step="0.01"
|
|
name="bobot[{{ $k->id }}]" value="0" id="slider-{{ $k->id }}"
|
|
class="bobot-slider flex-grow accent-blue-700 bg-white/30 rounded-lg" />
|
|
<span id="value-{{ $k->id }}" class="text-sm font-semibold w-10 text-center">0.00</span>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
|
|
<div class="text-center pt-4">
|
|
<button id="submit-btn" type="submit"
|
|
class="bg-blue-900 hover:bg-blue-800 text-white font-semibold py-2 px-6 rounded-xl transition duration-300 shadow-md hover:shadow-lg disabled:bg-gray-400 disabled:cursor-not-allowed"
|
|
disabled>
|
|
Cari Rekomendasi
|
|
</button>
|
|
<p id="bobot-error" class="text-red-600 text-sm mt-2 hidden">
|
|
Total bobot harus sama dengan 1.00
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Script JavaScript -->
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const sliders = document.querySelectorAll('.bobot-slider');
|
|
const submitBtn = document.getElementById('submit-btn');
|
|
const errorMsg = document.getElementById('bobot-error');
|
|
let values = {};
|
|
let isUpdating = false;
|
|
|
|
const initialValue = 1 / sliders.length;
|
|
sliders.forEach(slider => {
|
|
const id = slider.id.split('-')[1];
|
|
values[id] = initialValue;
|
|
slider.value = initialValue.toFixed(2);
|
|
document.getElementById(`value-${id}`).textContent = initialValue.toFixed(2);
|
|
});
|
|
|
|
function redistributeValues(changedId, newValue) {
|
|
if (isUpdating) return;
|
|
isUpdating = true;
|
|
|
|
newValue = Math.max(0, Math.min(1, parseFloat(newValue)));
|
|
const oldValue = values[changedId];
|
|
values[changedId] = newValue;
|
|
|
|
const otherIds = Object.keys(values).filter(id => id !== changedId);
|
|
let otherTotal = 0;
|
|
otherIds.forEach(id => otherTotal += values[id]);
|
|
|
|
const targetOtherTotal = 1 - newValue;
|
|
const scaleFactor = otherTotal === 0 ? 0 : targetOtherTotal / otherTotal;
|
|
|
|
const tempValues = {};
|
|
let tempTotal = 0;
|
|
|
|
otherIds.forEach(id => {
|
|
tempValues[id] = Math.max(0, Math.min(1, values[id] * scaleFactor));
|
|
if (isNaN(tempValues[id])) tempValues[id] = 0;
|
|
tempTotal += tempValues[id];
|
|
});
|
|
|
|
const remaining = targetOtherTotal - tempTotal;
|
|
if (Math.abs(remaining) > 0.001) {
|
|
const adjustableIds = otherIds.filter(id => {
|
|
if (remaining > 0) return tempValues[id] < 1;
|
|
else return tempValues[id] > 0;
|
|
});
|
|
|
|
if (adjustableIds.length > 0) {
|
|
const perSlider = remaining / adjustableIds.length;
|
|
adjustableIds.forEach(id => {
|
|
tempValues[id] = Math.max(0, Math.min(1, tempValues[id] + perSlider));
|
|
});
|
|
}
|
|
}
|
|
|
|
// Terapkan nilai baru
|
|
otherIds.forEach(id => {
|
|
values[id] = tempValues[id];
|
|
});
|
|
|
|
// Koreksi total akhir
|
|
let finalTotal = Object.values(values).reduce((a, b) => a + b, 0);
|
|
const correction = 1 - finalTotal;
|
|
|
|
for (let id of otherIds) {
|
|
const newVal = values[id] + correction;
|
|
if (newVal >= 0 && newVal <= 1) {
|
|
values[id] = newVal;
|
|
break;
|
|
}
|
|
}
|
|
|
|
updateDisplay();
|
|
isUpdating = false;
|
|
}
|
|
|
|
function updateDisplay() {
|
|
Object.keys(values).forEach(id => {
|
|
const slider = document.getElementById(`slider-${id}`);
|
|
const display = document.getElementById(`value-${id}`);
|
|
if (slider && display) {
|
|
slider.value = values[id].toFixed(2);
|
|
display.textContent = values[id].toFixed(2);
|
|
}
|
|
});
|
|
|
|
// Tombol submit aktif karena total otomatis 1
|
|
submitBtn.disabled = false;
|
|
errorMsg.classList.add('hidden');
|
|
}
|
|
|
|
sliders.forEach(slider => {
|
|
slider.addEventListener('input', (e) => {
|
|
const id = e.target.id.split('-')[1];
|
|
redistributeValues(id, e.target.value);
|
|
});
|
|
});
|
|
|
|
updateDisplay();
|
|
// Periksa total dan update tombol submit
|
|
let total = Object.values(values).reduce((a, b) => a + b, 0);
|
|
total = parseFloat(total.toFixed(2));
|
|
|
|
// Koreksi terakhir jika dibulatkan menyebabkan lewat/kurang
|
|
if (total !== 1) {
|
|
const delta = 1 - total;
|
|
const firstId = Object.keys(values)[0];
|
|
values[firstId] = parseFloat((values[firstId] + delta).toFixed(2));
|
|
}
|
|
|
|
// Update lagi setelah koreksi
|
|
Object.keys(values).forEach(id => {
|
|
const slider = document.getElementById(`slider-${id}`);
|
|
const display = document.getElementById(`value-${id}`);
|
|
|
|
if (slider && display) {
|
|
slider.value = values[id].toFixed(2);
|
|
display.textContent = values[id].toFixed(2);
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const form = document.querySelector('form');
|
|
const submitBtn = document.getElementById('submit-btn');
|
|
|
|
form.addEventListener('submit', (e) => {
|
|
const selectFields = form.querySelectorAll('select[name="sub_kriteria[]"]');
|
|
const checkboxes = form.querySelectorAll('input[type="checkbox"][name="sub_kriteria[]"]');
|
|
|
|
let allDropdownFilled = true;
|
|
selectFields.forEach(select => {
|
|
if (select.value.trim() === "") {
|
|
allDropdownFilled = false;
|
|
}
|
|
});
|
|
|
|
let anyCheckboxChecked = false;
|
|
checkboxes.forEach(checkbox => {
|
|
if (checkbox.checked) {
|
|
anyCheckboxChecked = true;
|
|
}
|
|
});
|
|
|
|
if (!allDropdownFilled || !anyCheckboxChecked) {
|
|
e.preventDefault(); // Mencegah pengiriman form
|
|
|
|
let msg = "Semua kriteria wajib diisi:\n";
|
|
if (!allDropdownFilled) msg += "- Pilih semua dropdown\n";
|
|
if (!anyCheckboxChecked) msg += "- Minimal 1 checkbox fasilitas dicentang";
|
|
|
|
alert(msg);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const kapasitasSelect = document.querySelector('select[data-kriteria="Kapasitas Kursi"]');
|
|
|
|
kapasitasSelect.addEventListener('change', function () {
|
|
const selectedKapasitas = this.value;
|
|
|
|
if (!selectedKapasitas) return;
|
|
|
|
fetch(`/rekomendasi/subkriteria-filtered?kapasitas_sub_id=${selectedKapasitas}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Update dropdown lain
|
|
document.querySelectorAll('select[data-kriteria]').forEach(select => {
|
|
const kriteria = select.dataset.kriteria;
|
|
if (kriteria === 'Kapasitas Kursi') return; // Skip kapasitas kursi
|
|
|
|
const options = data[kriteria] || {};
|
|
select.innerHTML = `<option value="">Pilih ${kriteria}</option>`;
|
|
|
|
for (const [id, name] of Object.entries(options)) {
|
|
const option = document.createElement('option');
|
|
option.value = id;
|
|
option.textContent = name;
|
|
select.appendChild(option);
|
|
}
|
|
});
|
|
})
|
|
.catch(err => console.error(err));
|
|
});
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html> |