MIF_E31222629/resources/views/rekomendasi/index.blade.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>