MIF_E31230680/resources/views/perhitungan/index.blade.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