811 lines
36 KiB
PHP
811 lines
36 KiB
PHP
@extends('layouts.admin')
|
|
|
|
@section('title', 'Metode SAW')
|
|
|
|
@section('content')
|
|
<div class="container-fluid px-4">
|
|
<style>
|
|
.breadcrumb {
|
|
background: transparent;
|
|
border-radius: 8px;
|
|
padding: 0;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.breadcrumb-item a {
|
|
color: #667eea;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.breadcrumb-item.active {
|
|
color: #764ba2;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.header-saw {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border-radius: 12px;
|
|
padding: 2rem;
|
|
color: white;
|
|
margin-bottom: 2rem;
|
|
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.15);
|
|
}
|
|
|
|
.header-saw h2 {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.header-saw p {
|
|
opacity: 0.95;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.section-header {
|
|
color: #667eea;
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
padding-bottom: 1rem;
|
|
border-bottom: 2px solid #667eea;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.form-card {
|
|
border: none;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.08);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.btn-submit {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border: none;
|
|
color: white;
|
|
font-weight: 600;
|
|
padding: 0.75rem 1.75rem;
|
|
border-radius: 8px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-submit:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
|
|
color: white;
|
|
}
|
|
</style>
|
|
|
|
<!-- Breadcrumb -->
|
|
<nav aria-label="breadcrumb" class="mb-4">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{{ route('dashboard') }}">Dashboard</a></li>
|
|
<li class="breadcrumb-item active">Metode SAW</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<!-- Header -->
|
|
<div class="header-saw mb-4">
|
|
<h2 class="mb-0">🧮 Metode SAW</h2>
|
|
<p class="mb-0 opacity-95">Sistem Pendukung Keputusan untuk rekomendasi terbaik</p>
|
|
</div>
|
|
|
|
<!-- Alert -->
|
|
@if(session('error'))
|
|
<div class="alert alert-danger alert-dismissible fade show shadow-sm mb-4" role="alert">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
<span>{{ session('error') }}</span>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Info Alert -->
|
|
<div class="alert alert-info border-0 shadow-sm mb-4" style="background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); border-left: 4px solid #667eea;">
|
|
<div class="d-flex">
|
|
<div class="me-3">
|
|
<i class="bi bi-info-circle fs-5" style="color: #667eea;"></i>
|
|
</div>
|
|
<div>
|
|
<h6 class="fw-bold mb-2" style="color: #667eea;">Tentang Metode SAW</h6>
|
|
<p class="mb-0">
|
|
Metode SAW digunakan untuk menentukan ranking alternatif terbaik berdasarkan kriteria yang telah ditentukan.
|
|
Sistem akan menghitung nilai normalisasi dan bobot untuk setiap kriteria, kemudian menghasilkan peringkat rekomendasi.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<!-- Choose Type Card -->
|
|
<div class="card form-card mb-4">
|
|
<div class="card-body p-4">
|
|
<h5 class="section-header mb-0">
|
|
<i class="bi bi-list-ul me-2" style="color: #667eea;"></i>Pilih Jenis Pencarian
|
|
</h5>
|
|
|
|
<form action="{{ route('saw.proses') }}" method="POST" id="sawForm">
|
|
@csrf
|
|
|
|
<!-- Hidden inputs untuk koordinat user (HANYA UNTUK LAUNDRY) -->
|
|
<input type="hidden" name="user_lat" id="user_lat">
|
|
<input type="hidden" name="user_lng" id="user_lng">
|
|
|
|
<div class="row g-2 g-md-3 mb-3 mb-md-4">
|
|
<!-- Pilih Kontrakan -->
|
|
<div class="col-12 col-md-6">
|
|
<input type="radio" class="btn-check" name="tipe" id="tipe_kontrakan" value="kontrakan" checked>
|
|
<label class="btn btn-outline-warning w-100 py-3 py-md-4" for="tipe_kontrakan" style="border-color: #667eea; color: #667eea;">
|
|
<i class="bi bi-building fs-2 fs-md-1 d-block mb-2 mb-md-3"></i>
|
|
<h5 class="mb-1 mb-md-2 fs-6 fs-md-5">Kontrakan</h5>
|
|
<small class="text-muted">Cari kontrakan deket kampus</small>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Pilih Laundry -->
|
|
<div class="col-12 col-md-6">
|
|
<input type="radio" class="btn-check" name="tipe" id="tipe_laundry" value="laundry">
|
|
<label class="btn btn-outline-warning w-100 py-3 py-md-4" for="tipe_laundry" style="border-color: #667eea; color: #667eea;">
|
|
<i class="bi bi-basket3 fs-2 fs-md-1 d-block mb-2 mb-md-3"></i>
|
|
<h5 class="mb-1 mb-md-2 fs-6 fs-md-5">Laundry</h5>
|
|
<small class="text-muted">Cari laundry deket lokasi Anda</small>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- INFO JARAK KONTRAKAN -->
|
|
<div class="alert border-0 mb-3 mb-md-4" id="infoKontrakan" style="background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); border-left: 4px solid #667eea !important;">
|
|
<div class="d-flex align-items-start">
|
|
<i class="bi bi-info-circle-fill fs-5 me-2 mt-1" style="color: #667eea;"></i>
|
|
<div>
|
|
<strong class="small" style="color: #667eea;">Jarak Kontrakan</strong>
|
|
<p class="mb-0 small mt-1">
|
|
Jarak dihitung otomatis dari <strong>Kampus Polije</strong>.
|
|
Sistem akan merekomendasikan kontrakan terdekat dari kampus.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DETEKSI LOKASI UNTUK LAUNDRY (OPSIONAL) -->
|
|
<div class="card form-card mb-4" id="deteksiLokasiCard" style="display: none;">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex align-items-start align-items-md-center mb-3">
|
|
<i class="bi bi-geo-alt-fill fs-4 fs-md-3 me-2 me-md-3 mt-1 mt-md-0" style="color: #667eea;"></i>
|
|
<div class="flex-grow-1">
|
|
<h6 class="fw-bold mb-1 fs-6">Referensi Jarak Laundry</h6>
|
|
<small class="text-muted d-block" style="font-size: 0.8rem;">
|
|
Pilih titik referensi untuk menghitung jarak ke laundry
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pilihan Referensi Jarak -->
|
|
<div class="row g-2 mb-3">
|
|
<div class="col-6">
|
|
<input type="radio" class="btn-check" name="referensi_jarak" id="referensi_kampus" value="kampus" checked>
|
|
<label class="btn btn-outline-warning w-100 py-2" for="referensi_kampus" style="border-color: #667eea; color: #667eea;">
|
|
<i class="bi bi-building d-block fs-4 mb-1"></i>
|
|
<strong class="small d-block">Dari Kampus</strong>
|
|
<small class="text-muted" style="font-size: 0.7rem;">Default</small>
|
|
</label>
|
|
</div>
|
|
<div class="col-6">
|
|
<input type="radio" class="btn-check" name="referensi_jarak" id="referensi_user" value="user">
|
|
<label class="btn btn-outline-warning w-100 py-2" for="referensi_user" style="border-color: #667eea; color: #667eea;">
|
|
<i class="bi bi-person-pin d-block fs-4 mb-1"></i>
|
|
<strong class="small d-block">Dari Lokasi Saya</strong>
|
|
<small class="text-muted" style="font-size: 0.7rem;">GPS</small>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info Default (Dari Kampus) -->
|
|
<div id="infoDefaultKampus" class="alert alert-info border-0 mb-0 p-2 small">
|
|
<i class="bi bi-info-circle me-2"></i>
|
|
Jarak laundry akan dihitung dari <strong>Kampus Polije</strong>.
|
|
</div>
|
|
|
|
<!-- Tombol Deteksi Lokasi (muncul ketika pilih "Dari Lokasi Saya") -->
|
|
<div id="deteksiLokasiWrapper" style="display: none;">
|
|
<button type="button" id="detectLocationBtn" class="btn btn-success w-100 mb-2">
|
|
<i class="bi bi-crosshair me-2"></i>Deteksi Lokasi Saya Sekarang
|
|
</button>
|
|
|
|
<div id="locationStatus" class="mt-2" style="display: none;">
|
|
<div class="alert alert-success mb-0 p-2 small">
|
|
<i class="bi bi-check-circle-fill me-2"></i>
|
|
<strong>Lokasi terdeteksi!</strong>
|
|
<div class="mt-1" style="font-size: 0.75rem;" id="locationCoords"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="locationWarning" class="alert alert-warning mb-0 p-2 small">
|
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
<strong>Klik tombol di atas</strong> untuk mendeteksi lokasi Anda.
|
|
Jika tidak diklik, jarak akan dihitung dari kampus.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pilihan Jenis Layanan (khusus Laundry) -->
|
|
<div class="card form-card mb-4" id="jenisLayananCard" style="display: none;">
|
|
<div class="card-body p-4">
|
|
<h6 class="section-header mb-0">
|
|
<i class="bi bi-speedometer2 me-2" style="color: #667eea;"></i>Pilih Jenis Layanan
|
|
</h6>
|
|
<div class="row g-2 g-md-3">
|
|
<div class="col-12 col-md-6">
|
|
<input type="radio" class="btn-check" name="jenis_layanan" id="layanan_harian" value="harian">
|
|
<label class="btn btn-outline-warning w-100 py-2 py-md-3" for="layanan_harian" style="border-color: #667eea; color: #667eea;">
|
|
<i class="bi bi-clock fs-5 fs-md-4 d-block mb-2"></i>
|
|
<strong class="small">Harian</strong>
|
|
<small class="d-block text-muted" style="font-size: 0.75rem;">Selesai harian</small>
|
|
</label>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<input type="radio" class="btn-check" name="jenis_layanan" id="layanan_jam" value="jam">
|
|
<label class="btn btn-outline-warning w-100 py-2 py-md-3" for="layanan_jam" style="border-color: #667eea; color: #667eea;">
|
|
<i class="bi bi-lightning-charge fs-5 fs-md-4 d-block mb-2"></i>
|
|
<strong class="small">Jam</strong>
|
|
<small class="d-block text-muted" style="font-size: 0.75rem;">Selesai per jam</small>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kriteria Info -->
|
|
<div class="card form-card mb-4">
|
|
<div class="card-body p-4">
|
|
<h6 class="section-header mb-0">
|
|
<i class="bi bi-star me-2" style="color: #667eea;"></i>Kriteria yang Digunakan
|
|
</h6>
|
|
<div class="table-responsive">
|
|
<table class="table table-sm mb-0 small" id="kriteriaTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Master Kriteria SAW</th>
|
|
<th class="text-center">Bobot</th>
|
|
<th class="text-center">Tipe</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@if(isset($kriteria) && $kriteria->count() > 0)
|
|
@foreach($kriteria as $k)
|
|
<tr data-tipe-bisnis="{{ $k->tipe_bisnis ?? 'kontrakan' }}" data-bobot="{{ $k->bobot ?? 0 }}">
|
|
<td>{{ $k->nama_kriteria ?? 'N/A' }}</td>
|
|
<td class="text-center">
|
|
<span class="badge" style="background-color: #667eea;">{{ $k->bobot ?? 0 }}</span>
|
|
</td>
|
|
<td class="text-center">
|
|
@if(isset($k->tipe) && strtolower($k->tipe) == 'benefit')
|
|
<span class="badge bg-success">
|
|
<i class="bi bi-arrow-up-circle me-1"></i>Benefit
|
|
</span>
|
|
@else
|
|
<span class="badge bg-danger">
|
|
<i class="bi bi-arrow-down-circle me-1"></i>Cost
|
|
</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
@else
|
|
<tr>
|
|
<td colspan="3" class="text-center text-muted">Tidak ada kriteria yang tersedia</td>
|
|
</tr>
|
|
@endif
|
|
</tbody>
|
|
<tfoot class="fw-bold">
|
|
<tr>
|
|
<td>Total Bobot</td>
|
|
<td class="text-center">
|
|
<span class="badge" id="totalBobotDisplay" style="background-color: #667eea;">
|
|
{{ isset($kriteria) ? $kriteria->where('tipe_bisnis', 'kontrakan')->sum('bobot') : 0 }}
|
|
</span>
|
|
</td>
|
|
<td></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-lg" id="btnProsesSAW" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-weight: 600;">
|
|
<span id="btnText">
|
|
<i class="bi bi-calculator me-2"></i>Proses Perhitungan SAW
|
|
</span>
|
|
<span id="btnLoading" style="display: none;">
|
|
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
|
Memproses data...
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Loading Overlay -->
|
|
<div id="loadingOverlay" style="display: none;">
|
|
<div class="loading-content">
|
|
<div class="spinner-grow text-primary mb-3" style="width: 3rem; height: 3rem;" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<h5 class="text-white mb-2">Memproses Perhitungan SAW</h5>
|
|
<p class="text-white-50 small">Mohon tunggu sebentar...</p>
|
|
<div class="progress mt-3" style="height: 4px; width: 250px; max-width: 90vw;">
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 100%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info Cards -->
|
|
<div class="row g-2 g-md-3">
|
|
<div class="col-12 col-md-6">
|
|
<div class="card border-0 h-100" style="background: rgba(102, 126, 234, 0.1);">
|
|
<div class="card-body p-3">
|
|
<h6 class="fw-bold mb-2 fs-6" style="color: #667eea;">
|
|
<i class="bi bi-check-circle me-2"></i>Kelebihan SAW
|
|
</h6>
|
|
<ul class="small mb-0" style="font-size: 0.85rem;">
|
|
<li>Mudah dipahami</li>
|
|
<li>Perhitungan sederhana</li>
|
|
<li>Hasil objektif</li>
|
|
<li>Jarak dihitung otomatis dari GPS</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<div class="card border-0 h-100" style="background: rgba(102, 126, 234, 0.1);">
|
|
<div class="card-body p-3">
|
|
<h6 class="fw-bold mb-2 fs-6" style="color: #667eea;">
|
|
<i class="bi bi-lightbulb me-2"></i>Cara Kerja
|
|
</h6>
|
|
<ol class="small mb-0" style="font-size: 0.85rem;">
|
|
<li><strong>Kontrakan:</strong> Jarak dari Kampus Polije</li>
|
|
<li><strong>Laundry:</strong> Jarak dari Kampus <em>atau</em> Lokasi Anda</li>
|
|
<li>Normalisasi nilai kriteria</li>
|
|
<li>Kalikan dengan bobot</li>
|
|
<li>Urutkan berdasarkan nilai tertinggi</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast Container -->
|
|
<div id="toastContainer" style="position: fixed; top: 10px; right: 10px; z-index: 10000; max-width: 90vw;"></div>
|
|
<style>
|
|
/* Purple Theme for Radio Buttons */
|
|
.btn-check:checked + .btn-outline-warning {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
|
border-color: #667eea !important;
|
|
color: white !important;
|
|
}
|
|
|
|
.btn-check:checked + .btn-outline-primary {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
|
border-color: #667eea !important;
|
|
color: white !important;
|
|
}
|
|
|
|
.btn-check:checked + .btn-outline-success {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
|
border-color: #667eea !important;
|
|
color: white !important;
|
|
}
|
|
|
|
.btn-check:checked + .btn-outline-danger {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
|
border-color: #667eea !important;
|
|
color: white !important;
|
|
}
|
|
|
|
.btn-outline-warning:hover,
|
|
.btn-outline-primary:hover,
|
|
.btn-outline-success:hover,
|
|
.btn-outline-danger:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.2);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
#kriteriaTable tbody tr {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.bg-gradient {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.bg-gradient::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50%;
|
|
right: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
|
animation: pulse 3s ease-in-out infinite;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
|
|
.bg-gradient .btn,
|
|
.bg-gradient .card-body > * {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); opacity: 0.5; }
|
|
50% { transform: scale(1.1); opacity: 0.8; }
|
|
}
|
|
|
|
/* Loading Overlay */
|
|
#loadingOverlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
z-index: 9999;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
|
|
.loading-content {
|
|
text-align: center;
|
|
animation: fadeInUp 0.5s ease;
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
@keyframes fadeInUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(30px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Toast Notification */
|
|
.custom-toast {
|
|
min-width: 250px;
|
|
max-width: 350px;
|
|
margin-bottom: 10px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
animation: slideInRight 0.3s ease;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
@keyframes slideInRight {
|
|
from {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.custom-toast.hiding {
|
|
animation: slideOutRight 0.3s ease;
|
|
}
|
|
|
|
@keyframes slideOutRight {
|
|
from {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
/* Mobile optimizations */
|
|
@media (max-width: 767px) {
|
|
.btn-lg {
|
|
padding: 0.75rem 1rem;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.badge {
|
|
font-size: 0.75rem;
|
|
padding: 0.25em 0.5em;
|
|
}
|
|
|
|
#toastContainer {
|
|
right: 5px;
|
|
top: 5px;
|
|
}
|
|
|
|
.custom-toast {
|
|
min-width: auto;
|
|
width: calc(100vw - 20px);
|
|
max-width: calc(100vw - 20px);
|
|
}
|
|
}
|
|
</style>
|
|
<script>
|
|
// Toast Notification Function
|
|
function showToast(type, message) {
|
|
const container = document.getElementById('toastContainer');
|
|
|
|
const toastId = 'toast_' + Date.now();
|
|
const iconMap = {
|
|
success: 'check-circle-fill',
|
|
error: 'exclamation-triangle-fill',
|
|
warning: 'exclamation-circle-fill',
|
|
info: 'info-circle-fill'
|
|
};
|
|
|
|
const bgMap = {
|
|
success: 'bg-success',
|
|
error: 'bg-danger',
|
|
warning: 'bg-warning',
|
|
info: 'bg-info'
|
|
};
|
|
|
|
const toast = document.createElement('div');
|
|
toast.id = toastId;
|
|
toast.className = `custom-toast alert ${bgMap[type]} alert-dismissible fade show text-white`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.innerHTML = `
|
|
<div class="d-flex align-items-center">
|
|
<i class="bi bi-${iconMap[type]} me-2"></i>
|
|
<div class="flex-grow-1 small">${message}</div>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(toast);
|
|
|
|
// Auto remove after 4 seconds
|
|
setTimeout(() => {
|
|
toast.classList.add('hiding');
|
|
setTimeout(() => {
|
|
if (toast.parentNode) {
|
|
toast.parentNode.removeChild(toast);
|
|
}
|
|
}, 300);
|
|
}, 4000);
|
|
}
|
|
|
|
// Main Script
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
console.log('Script SAW loaded!');
|
|
|
|
const radioKontrakan = document.getElementById('tipe_kontrakan');
|
|
const radioLaundry = document.getElementById('tipe_laundry');
|
|
const kriteriaTable = document.getElementById('kriteriaTable');
|
|
const jenisLayananCard = document.getElementById('jenisLayananCard');
|
|
const referensiJarakCard = document.getElementById('referensiJarakCard');
|
|
const deteksiLokasiCard = document.getElementById('deteksiLokasiCard');
|
|
const infoKontrakan = document.getElementById('infoKontrakan');
|
|
const sawForm = document.getElementById('sawForm');
|
|
const detectLocationBtn = document.getElementById('detectLocationBtn');
|
|
const locationStatus = document.getElementById('locationStatus');
|
|
const locationCoords = document.getElementById('locationCoords');
|
|
const userLatInput = document.getElementById('user_lat');
|
|
const userLngInput = document.getElementById('user_lng');
|
|
const radioReferensiUser = document.getElementById('referensi_user');
|
|
const radioReferensiKampus = document.getElementById('referensi_kampus');
|
|
const deteksiLokasiWrapper = document.getElementById('deteksiLokasiWrapper');
|
|
const infoDefaultKampus = document.getElementById('infoDefaultKampus');
|
|
const locationWarning = document.getElementById('locationWarning');
|
|
|
|
let locationDetected = false;
|
|
|
|
// ========== TOGGLE REFERENSI JARAK (KAMPUS/USER) ==========
|
|
function toggleReferensiJarak() {
|
|
if (radioReferensiUser && radioReferensiUser.checked) {
|
|
// User memilih "Dari Lokasi Saya"
|
|
if (deteksiLokasiWrapper) deteksiLokasiWrapper.style.display = 'block';
|
|
if (infoDefaultKampus) infoDefaultKampus.style.display = 'none';
|
|
if (locationWarning) locationWarning.style.display = locationDetected ? 'none' : 'block';
|
|
} else {
|
|
// Default: "Dari Kampus"
|
|
if (deteksiLokasiWrapper) deteksiLokasiWrapper.style.display = 'none';
|
|
if (infoDefaultKampus) infoDefaultKampus.style.display = 'block';
|
|
// Reset user location ketika pilih kampus
|
|
if (userLatInput) userLatInput.value = '';
|
|
if (userLngInput) userLngInput.value = '';
|
|
}
|
|
}
|
|
|
|
// ========== TOGGLE DETEKSI LOKASI BERDASARKAN TIPE ==========
|
|
function toggleDeteksiLokasi() {
|
|
if (radioLaundry.checked) {
|
|
// LAUNDRY: Tampilkan jenis layanan dan pilihan referensi jarak
|
|
jenisLayananCard.style.display = 'block';
|
|
deteksiLokasiCard.style.display = 'block';
|
|
infoKontrakan.style.display = 'none';
|
|
toggleReferensiJarak();
|
|
} else {
|
|
// KONTRAKAN: Sembunyikan jenis layanan dan deteksi lokasi
|
|
jenisLayananCard.style.display = 'none';
|
|
deteksiLokasiCard.style.display = 'none';
|
|
infoKontrakan.style.display = 'block';
|
|
// Reset user location untuk kontrakan
|
|
if (userLatInput) userLatInput.value = '';
|
|
if (userLngInput) userLngInput.value = '';
|
|
}
|
|
}
|
|
|
|
// ========== EVENT LISTENER UNTUK PILIHAN REFERENSI JARAK ==========
|
|
if (radioReferensiUser) {
|
|
radioReferensiUser.addEventListener('change', toggleReferensiJarak);
|
|
}
|
|
if (radioReferensiKampus) {
|
|
radioReferensiKampus.addEventListener('change', toggleReferensiJarak);
|
|
}
|
|
|
|
// ========== DETEKSI LOKASI (HANYA UNTUK LAUNDRY) ==========
|
|
if (detectLocationBtn) {
|
|
detectLocationBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
console.log('Tombol deteksi lokasi diklik!');
|
|
|
|
const button = this;
|
|
const originalHTML = button.innerHTML;
|
|
|
|
if (!navigator.geolocation) {
|
|
showToast('error', 'Browser Anda tidak mendukung Geolocation.');
|
|
return;
|
|
}
|
|
|
|
button.disabled = true;
|
|
button.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Mendeteksi lokasi...';
|
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
function(position) {
|
|
console.log('Lokasi berhasil terdeteksi!', position);
|
|
|
|
const lat = position.coords.latitude;
|
|
const lng = position.coords.longitude;
|
|
|
|
userLatInput.value = lat;
|
|
userLngInput.value = lng;
|
|
locationDetected = true;
|
|
|
|
console.log('Lat:', lat, 'Lng:', lng);
|
|
|
|
locationCoords.innerHTML = `
|
|
<strong>Latitude:</strong> ${lat.toFixed(6)}<br>
|
|
<strong>Longitude:</strong> ${lng.toFixed(6)}
|
|
`;
|
|
locationStatus.style.display = 'block';
|
|
if (locationWarning) locationWarning.style.display = 'none';
|
|
|
|
button.disabled = false;
|
|
button.innerHTML = '<i class="bi bi-check-circle-fill me-2"></i>Lokasi Terdeteksi!';
|
|
button.classList.remove('btn-success');
|
|
button.classList.add('btn-outline-success');
|
|
|
|
showToast('success', '📍 Lokasi Anda berhasil terdeteksi! Jarak laundry akan dihitung dari posisi Anda.');
|
|
},
|
|
function(error) {
|
|
console.error('Error geolocation:', error);
|
|
|
|
button.disabled = false;
|
|
button.innerHTML = originalHTML;
|
|
|
|
let errorMsg = '';
|
|
switch(error.code) {
|
|
case error.PERMISSION_DENIED:
|
|
errorMsg = 'Izinkan akses lokasi di browser Anda.';
|
|
break;
|
|
case error.POSITION_UNAVAILABLE:
|
|
errorMsg = 'Informasi lokasi tidak tersedia.';
|
|
break;
|
|
case error.TIMEOUT:
|
|
errorMsg = 'Waktu permintaan habis. Coba lagi.';
|
|
break;
|
|
default:
|
|
errorMsg = 'Terjadi kesalahan saat mendeteksi lokasi.';
|
|
}
|
|
showToast('error', errorMsg);
|
|
},
|
|
{
|
|
enableHighAccuracy: true,
|
|
timeout: 10000,
|
|
maximumAge: 0
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
// ========== FILTER KRITERIA ==========
|
|
function filterKriteria() {
|
|
if (!kriteriaTable) return;
|
|
|
|
const selectedTipe = radioKontrakan.checked ? 'kontrakan' : 'laundry';
|
|
|
|
const rows = kriteriaTable.querySelectorAll('tbody tr');
|
|
let totalBobot = 0;
|
|
|
|
rows.forEach(row => {
|
|
const tipeBisnis = row.getAttribute('data-tipe-bisnis');
|
|
const bobot = parseFloat(row.getAttribute('data-bobot')) || 0;
|
|
|
|
if (tipeBisnis === selectedTipe) {
|
|
row.style.display = '';
|
|
totalBobot += bobot;
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
const totalBobotDisplay = document.getElementById('totalBobotDisplay');
|
|
if (totalBobotDisplay) {
|
|
totalBobotDisplay.textContent = totalBobot.toFixed(2);
|
|
}
|
|
}
|
|
|
|
// ========== VALIDASI FORM ==========
|
|
if (sawForm) {
|
|
sawForm.addEventListener('submit', function(e) {
|
|
// Validasi jenis layanan untuk laundry
|
|
if (radioLaundry.checked) {
|
|
const jenisLayananChecked = document.querySelector('input[name="jenis_layanan"]:checked');
|
|
if (!jenisLayananChecked) {
|
|
e.preventDefault();
|
|
showToast('warning', '⚠️ Pilih jenis layanan terlebih dahulu!');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Tampilkan loading
|
|
const btnProsesSAW = document.getElementById('btnProsesSAW');
|
|
const btnText = document.getElementById('btnText');
|
|
const btnLoading = document.getElementById('btnLoading');
|
|
const loadingOverlay = document.getElementById('loadingOverlay');
|
|
|
|
btnProsesSAW.disabled = true;
|
|
btnText.style.display = 'none';
|
|
btnLoading.style.display = 'inline-block';
|
|
loadingOverlay.style.display = 'flex';
|
|
});
|
|
}
|
|
|
|
// Event listeners
|
|
if (radioKontrakan) {
|
|
radioKontrakan.addEventListener('change', function() {
|
|
toggleDeteksiLokasi();
|
|
filterKriteria();
|
|
});
|
|
}
|
|
|
|
if (radioLaundry) {
|
|
radioLaundry.addEventListener('change', function() {
|
|
toggleDeteksiLokasi();
|
|
filterKriteria();
|
|
});
|
|
}
|
|
|
|
// Initialize
|
|
toggleDeteksiLokasi();
|
|
filterKriteria();
|
|
});
|
|
</script>
|
|
@endsection
|