415 lines
22 KiB
PHP
415 lines
22 KiB
PHP
@extends('layout.app')
|
|
|
|
@section('content')
|
|
@php
|
|
$saatyLabels = [
|
|
1 => 'Sama Penting (1)',
|
|
2 => 'Mendekati Sedikit Lebih Penting (2)',
|
|
3 => 'Sedikit Lebih Penting (3)',
|
|
4 => 'Mendekati Lebih Penting (4)',
|
|
5 => 'Lebih Penting (5)',
|
|
6 => 'Mendekati Sangat Penting (6)',
|
|
7 => 'Sangat Penting (7)',
|
|
8 => 'Mendekati Mutlak Penting (8)',
|
|
9 => 'Mutlak Lebih Penting (9)',
|
|
];
|
|
|
|
$bOptions = [
|
|
['val' => 1/2, 'lbl' => '1/2', 'name' => 'Mendekati Sedikit Lebih Penting (2)'],
|
|
['val' => 1/3, 'lbl' => '1/3', 'name' => 'Sedikit Lebih Penting (3)'],
|
|
['val' => 1/4, 'lbl' => '1/4', 'name' => 'Mendekati Lebih Penting (4)'],
|
|
['val' => 1/5, 'lbl' => '1/5', 'name' => 'Lebih Penting (5)'],
|
|
['val' => 1/6, 'lbl' => '1/6', 'name' => 'Mendekati Sangat Penting (6)'],
|
|
['val' => 1/7, 'lbl' => '1/7', 'name' => 'Sangat Penting (7)'],
|
|
['val' => 1/8, 'lbl' => '1/8', 'name' => 'Mendekati Mutlak Penting (8)'],
|
|
['val' => 1/9, 'lbl' => '1/9', 'name' => 'Mutlak Lebih Penting (9)'],
|
|
];
|
|
@endphp
|
|
|
|
<div class="page-header">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item">Home</li>
|
|
<li class="breadcrumb-item active">Perbandingan Kriteria (F-AHP Multi-Pakar)</li>
|
|
</ol>
|
|
</div>
|
|
|
|
{{-- Notifikasi --}}
|
|
@if (session('success'))
|
|
<script>
|
|
setTimeout(() => {
|
|
Swal.fire('Berhasil!', "{{ session('success') }}", 'success')
|
|
}, 100);
|
|
</script>
|
|
@endif
|
|
@if (session('error'))
|
|
<script>
|
|
setTimeout(() => {
|
|
Swal.fire('Gagal!', "{{ session('error') }}", 'error')
|
|
}, 100);
|
|
</script>
|
|
@endif
|
|
|
|
<div class="row gutters">
|
|
<div class="col-xl-8 col-lg-8 col-md-12">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<i class="icon-shield mr-2"></i>
|
|
<h5 class="mb-0">Kuesioner Perbandingan Pakar</h5>
|
|
</div>
|
|
<div>
|
|
<button type="button" class="btn btn-warning btn-sm font-weight-bold shadow-sm mr-2"
|
|
onclick="unlockInput()">
|
|
<i class="icon-lock-open"></i> Buka Akses Input
|
|
</button>
|
|
<button type="button" class="btn btn-light btn-sm font-weight-bold shadow-sm" id="btnAddPakar">
|
|
<i class="icon-plus"></i> Pakar Baru
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="lockOverlayMsg" class="alert alert-info py-2 small mb-3">
|
|
<i class="icon-info"></i> <strong>Akses Terkunci:</strong> Klik tombol <b>Buka Akses Input</b> untuk
|
|
mengisi data.
|
|
</div>
|
|
|
|
<ul class="nav nav-pills mb-4 bg-light p-2 rounded shadow-sm" id="pakarTab" role="tablist">
|
|
@foreach ($existingPakar as $index => $p)
|
|
<li class="nav-item">
|
|
<a class="nav-link {{ $index == 0 ? 'active' : '' }} font-weight-bold"
|
|
id="pakar{{ $p }}-tab" data-toggle="pill" href="#pakar{{ $p }}"
|
|
role="tab">Pakar {{ $p }}</a>
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
|
|
<div class="tab-content" id="pakarTabContent">
|
|
@foreach ($existingPakar as $index => $pakar)
|
|
<div class="tab-pane fade {{ $index == 0 ? 'show active' : '' }}" id="pakar{{ $pakar }}"
|
|
role="tabpanel">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h6 class="font-weight-bold mb-0 text-success">Form Input Pakar {{ $pakar }}</h6>
|
|
@if ($pakar > 1)
|
|
<form action="{{ route('perbandingan.hapus_pakar') }}" method="POST"
|
|
id="deleteForm{{ $pakar }}">
|
|
@csrf
|
|
<input type="hidden" name="pakar_dihapus" value="{{ $pakar }}">
|
|
<button type="button" class="btn btn-outline-danger btn-sm"
|
|
onclick="confirmDeletePakar({{ $pakar }})">
|
|
<i class="icon-trash"></i> Hapus Pakar {{ $pakar }}
|
|
</button>
|
|
</form>
|
|
@endif
|
|
</div>
|
|
|
|
<form action="{{ route('perbandingan.hitung') }}" method="POST" class="form-perbandingan"
|
|
id="formPakar{{ $pakar }}">
|
|
@csrf
|
|
<input type="hidden" name="pakar_ke" value="{{ $pakar }}">
|
|
<input type="hidden" name="pin_rahasia" class="final-pin-input">
|
|
|
|
<div class="table-responsive border rounded"
|
|
style="max-height: 450px; overflow-y: auto;">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="table-light sticky-top shadow-sm">
|
|
<tr>
|
|
<th class="py-3 px-4">Kriteria Kiri</th>
|
|
<th width="350" class="text-center">Skala</th>
|
|
<th class="py-3 px-4 text-right">Kriteria Kanan</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($pairs as $pair)
|
|
@php
|
|
$savedValue =
|
|
$allData
|
|
->where('pakar_ke', $pakar)
|
|
->where('kriteria_kiri_id', $pair['kiri']->id)
|
|
->where('kriteria_kanan_id', $pair['kanan']->id)
|
|
->first()->nilai_skala ?? 1;
|
|
@endphp
|
|
<tr>
|
|
<td class="py-3 px-4 small">
|
|
<input type="hidden" name="kiri_id[]"
|
|
value="{{ $pair['kiri']->id }}">
|
|
<span
|
|
class="badge badge-pill badge-outline-success mr-1">{{ $pair['kiri']->kode }}</span>
|
|
<b>{{ $pair['kiri']->nama }}</b>
|
|
</td>
|
|
<td>
|
|
<select name="nilai_skala[]"
|
|
class="form-control form-control-sm compare-input shadow-xs"
|
|
disabled>
|
|
<option value="1"
|
|
{{ $savedValue == 1 ? 'selected' : '' }}>1 - Sama
|
|
Penting</option>
|
|
<optgroup label="Kiri Lebih Penting">
|
|
@foreach ($saatyLabels as $val => $label)
|
|
@if ($val > 1)
|
|
<option value="{{ $val }}"
|
|
{{ $savedValue == $val ? 'selected' : '' }}>
|
|
{{ $label }}</option>
|
|
@endif
|
|
@endforeach
|
|
</optgroup>
|
|
<optgroup label="Kanan Lebih Penting">
|
|
@foreach ($bOptions as $opt)
|
|
<option value="{{ $opt['val'] }}"
|
|
{{ abs($savedValue - $opt['val']) < 0.01 ? 'selected' : '' }}>
|
|
{{ $opt['lbl'] }} - {{ $opt['name'] }}
|
|
</option>
|
|
@endforeach
|
|
</optgroup>
|
|
</select>
|
|
</td>
|
|
<td class="py-3 px-4 text-right small">
|
|
<input type="hidden" name="kanan_id[]"
|
|
value="{{ $pair['kanan']->id }}">
|
|
<b>{{ $pair['kanan']->nama }}</b>
|
|
<span
|
|
class="badge badge-pill badge-outline-success ml-1">{{ $pair['kanan']->kode }}</span>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="text-center mt-4">
|
|
<button type="button"
|
|
class="btn btn-success btn-simpan px-5 shadow-sm font-weight-bold"
|
|
onclick="submitWithConfirm('formPakar{{ $pakar }}')" disabled>
|
|
<i class="icon-save"></i> Simpan Data Pakar {{ $pakar }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- HASIL BOBOT GLOBAL --}}
|
|
<div class="col-xl-4 col-lg-4 col-md-12">
|
|
<div class="card shadow-sm border-0">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0"><i class="icon-pie-chart"></i> Hasil Bobot Global</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
@php
|
|
$kriterias_ordered = $kriterias->sortByDesc('bobot_global');
|
|
$lastUpdate = $kriterias->max('updated_at')
|
|
? \Carbon\Carbon::parse($kriterias->max('updated_at'))->timezone('Asia/Jakarta')
|
|
: null;
|
|
@endphp
|
|
|
|
@if ($kriterias->sum('bobot_global') > 0)
|
|
<div class="p-3 mb-4 rounded border text-center"
|
|
style="background-color: #f0f7ff; border-color: #cfe2ff !important;">
|
|
<span class="d-block text-muted mb-2 small font-weight-bold uppercase">Update Terakhir</span>
|
|
<div class="h6 mb-1 font-weight-bold">
|
|
{{ $lastUpdate ? $lastUpdate->translatedFormat('d F Y') : '-' }}</div>
|
|
<div class="badge badge-primary px-3 py-1">{{ $lastUpdate ? $lastUpdate->format('H:i') : '-' }}
|
|
WIB</div>
|
|
</div>
|
|
|
|
@foreach ($kriterias_ordered as $k)
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small font-weight-bold text-truncate"
|
|
style="max-width: 65%;">{{ $k->kode }} - {{ $k->nama }}</span>
|
|
<div class="text-right d-flex align-items-center">
|
|
{{-- Menampilkan 4 angka desimal sesuai standar kalkulasi SPK --}}
|
|
<span class="text-dark font-weight-bold mr-2"
|
|
style="font-size: 0.85rem;">{{ number_format($k->bobot_global, 4) }}</span>
|
|
<span class="badge badge-primary"
|
|
style="font-size: 0.65rem; padding: 2px 5px;">{{ number_format($k->bobot_global * 100, 2) }}%</span>
|
|
</div>
|
|
</div>
|
|
<div class="progress" style="height: 5px;">
|
|
<div class="progress-bar bg-info" role="progressbar"
|
|
style="width: {{ $k->bobot_global * 100 }}%"></div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
|
|
{{-- TOMBOL DETAIL PERHITUNGAN --}}
|
|
<hr>
|
|
<div class="text-center">
|
|
<a href="{{ route('perbandingan.detail') }}"
|
|
class="btn btn-outline-info btn-block btn-sm font-weight-bold">
|
|
<i class="icon-zoom-in"></i> Lihat Detail Perhitungan
|
|
</a>
|
|
</div>
|
|
@else
|
|
<div class="text-center py-5 text-muted">Kosong</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
<script>
|
|
let globalPin = "";
|
|
const validPin = "{{ $pinSistem }}";
|
|
|
|
async function unlockInput() {
|
|
const { value: pin } = await Swal.fire({
|
|
title: 'Verifikasi PIN',
|
|
input: 'password',
|
|
inputLabel: 'Masukkan PIN Pakar untuk Membuka Akses',
|
|
inputPlaceholder: 'PIN...',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#28a745',
|
|
});
|
|
|
|
if (pin) {
|
|
if (pin === validPin) {
|
|
globalPin = pin;
|
|
document.querySelectorAll('.compare-input').forEach(el => el.disabled = false);
|
|
document.querySelectorAll('.btn-simpan').forEach(el => el.disabled = false);
|
|
document.querySelectorAll('.final-pin-input').forEach(el => el.value = pin);
|
|
|
|
const msgBox = document.getElementById('lockOverlayMsg');
|
|
msgBox.className = "alert alert-success py-2 small mb-3";
|
|
msgBox.innerHTML =
|
|
'<i class="icon-unlock"></i> <strong>Akses Terbuka:</strong> PIN terverifikasi. Silakan isi data.';
|
|
Swal.fire('Akses Dibuka!', 'PIN Benar. Silakan isi data perbandingan.', 'success');
|
|
} else {
|
|
Swal.fire('Akses Ditolak!', 'PIN yang Anda masukkan SALAH!', 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function submitWithConfirm(formId) {
|
|
if (!globalPin) {
|
|
Swal.fire('Terkunci!', 'Buka akses input dengan PIN terlebih dahulu.', 'error');
|
|
return;
|
|
}
|
|
|
|
const { value: confirmPin } = await Swal.fire({
|
|
title: 'Konfirmasi PIN',
|
|
text: "Masukkan PIN kembali untuk menyimpan data.",
|
|
input: 'password',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#28a745',
|
|
confirmButtonText: 'Verifikasi & Simpan'
|
|
});
|
|
|
|
if (confirmPin === globalPin) {
|
|
Swal.fire({
|
|
title: 'Memproses...',
|
|
text: 'Harap tunggu.',
|
|
allowOutsideClick: false,
|
|
didOpen: () => {
|
|
Swal.showLoading();
|
|
}
|
|
});
|
|
document.getElementById(formId).submit();
|
|
} else if (confirmPin) {
|
|
Swal.fire('Gagal!', 'PIN salah.', 'error');
|
|
}
|
|
}
|
|
|
|
function confirmDeletePakar(pakarNum) {
|
|
Swal.fire({
|
|
title: 'Apakah Anda yakin?',
|
|
text: `Seluruh data penilaian Pakar ${pakarNum} akan dihapus permanen!`,
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#d33',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'Ya, Hapus!',
|
|
cancelButtonText: 'Batal'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
Swal.fire({
|
|
title: 'Menghapus...',
|
|
allowOutsideClick: false,
|
|
didOpen: () => {
|
|
Swal.showLoading();
|
|
}
|
|
});
|
|
document.getElementById('deleteForm' + pakarNum).submit();
|
|
}
|
|
});
|
|
}
|
|
|
|
// ==========================================
|
|
// PERBAIKAN BUG CSRF TOKEN SAAT NAMBAH PAKAR
|
|
// ==========================================
|
|
document.getElementById('btnAddPakar').addEventListener('click', function() {
|
|
let pakarTab = document.getElementById('pakarTab');
|
|
let pakarTabContent = document.getElementById('pakarTabContent');
|
|
let nextPakar = pakarTab.querySelectorAll('li').length + 1;
|
|
|
|
pakarTab.insertAdjacentHTML('beforeend',
|
|
`<li class="nav-item"><a class="nav-link font-weight-bold" id="pakar${nextPakar}-tab" data-toggle="pill" href="#pakar${nextPakar}" role="tab">Pakar ${nextPakar}</a></li>`
|
|
);
|
|
|
|
let firstPane = document.querySelector('.tab-pane');
|
|
let newPane = firstPane.cloneNode(true);
|
|
newPane.id = `pakar${nextPakar}`;
|
|
newPane.classList.remove('show', 'active');
|
|
|
|
// PERBAIKAN: Menangkap token CSRF dari input yang sudah ada di halaman secara otomatis
|
|
let csrfToken = document.querySelector('input[name="_token"]').value;
|
|
|
|
let headerDiv = newPane.querySelector('.d-flex.justify-content-between.align-items-center.mb-3');
|
|
headerDiv.innerHTML = `
|
|
<h6 class="font-weight-bold mb-0 text-success">Form Input Pakar ${nextPakar}</h6>
|
|
<form action="{{ route('perbandingan.hapus_pakar') }}" method="POST" id="deleteForm${nextPakar}">
|
|
<input type="hidden" name="_token" value="${csrfToken}">
|
|
<input type="hidden" name="pakar_dihapus" value="${nextPakar}">
|
|
<button type="button" class="btn btn-outline-danger btn-sm" onclick="confirmDeletePakar(${nextPakar})">
|
|
<i class="icon-trash"></i> Hapus Pakar ${nextPakar}
|
|
</button>
|
|
</form>
|
|
`;
|
|
|
|
let form = newPane.querySelector('form.form-perbandingan');
|
|
form.id = `formPakar${nextPakar}`;
|
|
newPane.querySelector('input[name="pakar_ke"]').value = nextPakar;
|
|
newPane.querySelector('.final-pin-input').value = globalPin;
|
|
|
|
newPane.querySelectorAll('select').forEach(s => {
|
|
s.value = "1";
|
|
s.disabled = (globalPin === "");
|
|
});
|
|
|
|
let btn = newPane.querySelector('.btn-simpan');
|
|
btn.innerHTML = `<i class="icon-save"></i> Simpan Data Pakar ${nextPakar}`;
|
|
btn.disabled = (globalPin === "");
|
|
btn.setAttribute('onclick', `submitWithConfirm('formPakar${nextPakar}')`);
|
|
|
|
pakarTabContent.appendChild(newPane);
|
|
$(`#pakar${nextPakar}-tab`).tab('show');
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.compare-input:disabled {
|
|
background-color: #f8f9fa;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.sticky-top {
|
|
position: sticky;
|
|
top: 0;
|
|
background: white;
|
|
z-index: 10;
|
|
}
|
|
|
|
.table-responsive::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.table-responsive::-webkit-scrollbar-thumb {
|
|
background: #28a745;
|
|
border-radius: 10px;
|
|
}
|
|
</style>
|
|
@endsection
|