511 lines
18 KiB
PHP
511 lines
18 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'Proses SAW')
|
|
|
|
@section('content')
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/css/tom-select.css" rel="stylesheet">
|
|
|
|
<div class="max-w-7xl mx-auto space-y-8">
|
|
|
|
<!-- HEADER -->
|
|
<div class="flex flex-col md:flex-row md:items-end md:justify-between gap-5">
|
|
|
|
<div>
|
|
<h1 class="text-2xl sm:text-3xl lg:text-4xl font-bold tracking-tight text-gray-800">
|
|
Proses Rekomendasi
|
|
</h1>
|
|
|
|
<p class="text-gray-500 mt-2 text-base max-w-2xl">
|
|
Pilih data pasien untuk menjalankan proses perhitungan rekomendasi makanan
|
|
menggunakan metode Simple Additive Weighting (SAW).
|
|
</p>
|
|
</div>
|
|
|
|
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-green-50 text-green-700 text-sm font-semibold border border-green-100 w-fit">
|
|
<i class="ri-calculator-line text-lg"></i>
|
|
Metode SAW
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ALERT -->
|
|
@if(session('error'))
|
|
<div class="bg-red-50 border border-red-100 text-red-600 px-5 py-4 rounded-2xl text-sm font-medium">
|
|
{{ session('error') }}
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('success'))
|
|
<div class="bg-green-50 border border-green-100 text-green-700 px-5 py-4 rounded-2xl text-sm font-medium">
|
|
{{ session('success') }}
|
|
</div>
|
|
@endif
|
|
|
|
<!-- MAIN CARD -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
|
|
<!-- LEFT PANEL -->
|
|
<div class="lg:col-span-1">
|
|
|
|
<div class="bg-white border border-gray-200 rounded-3xl shadow-sm overflow-hidden">
|
|
|
|
<div class="p-7 border-b border-gray-100 bg-gradient-to-br from-green-50 to-white">
|
|
|
|
<div class="w-14 h-14 rounded-2xl bg-green-100 text-green-700 flex items-center justify-center text-2xl mb-5">
|
|
<i class="ri-user-search-line"></i>
|
|
</div>
|
|
|
|
<h2 class="text-2xl font-bold text-gray-800">
|
|
Pilih Pasien
|
|
</h2>
|
|
|
|
<p class="text-sm text-gray-500 mt-2 leading-6">
|
|
Ketik nama pasien pada kolom pencarian agar lebih mudah menemukan data pasien.
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div class="p-7">
|
|
|
|
<form id="form-proses" action="{{ route('proses_saw.proses') }}" method="POST">
|
|
@csrf
|
|
|
|
<!-- SELECT PASIEN -->
|
|
<div>
|
|
<label class="block text-xs font-bold uppercase tracking-wide text-gray-500 mb-3">
|
|
Cari / Pilih Pasien
|
|
</label>
|
|
|
|
<select id="pasien_id" name="pasien_id" required>
|
|
|
|
<option value=""></option>
|
|
|
|
@foreach($pasiens as $pasien)
|
|
<option value="{{ $pasien->id }}"
|
|
data-nama="{{ $pasien->nama }}"
|
|
data-umur="{{ $pasien->umur }}"
|
|
data-jenis_kelamin="{{ $pasien->jenis_kelamin }}"
|
|
data-gejala="{{ $pasien->gejala }}"
|
|
data-aktivitas="{{ $pasien->aktivitas_harian }}"
|
|
data-berat="{{ $pasien->berat_badan }}"
|
|
data-tinggi="{{ $pasien->tinggi_badan }}">
|
|
{{ $pasien->nama }} - {{ $pasien->umur }} tahun
|
|
</option>
|
|
@endforeach
|
|
|
|
</select>
|
|
|
|
<p class="text-xs text-gray-400 mt-3 leading-5">
|
|
Anda dapat mencari berdasarkan nama pasien, umur, atau gejala.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- BUTTON -->
|
|
<button type="submit" id="btn-proses"
|
|
class="mt-7 w-full inline-flex items-center justify-center gap-3 px-6 py-4 text-sm font-bold rounded-2xl bg-green-600 text-white hover:bg-green-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition shadow-sm"
|
|
disabled>
|
|
|
|
<svg id="spinner" class="hidden w-5 h-5 animate-spin"
|
|
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
|
|
<circle class="opacity-25" cx="12" cy="12" r="10"
|
|
stroke="currentColor" stroke-width="4"></circle>
|
|
|
|
<path class="opacity-75" fill="currentColor"
|
|
d="M4 12a8 8 0 018-8v8z"></path>
|
|
</svg>
|
|
|
|
<i id="btn-icon" class="ri-calculator-line text-lg"></i>
|
|
|
|
<span id="btn-text">
|
|
Proses Rekomendasi
|
|
</span>
|
|
|
|
</button>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- RIGHT PANEL -->
|
|
<div class="lg:col-span-2">
|
|
|
|
<div class="bg-white border border-gray-200 rounded-3xl shadow-sm overflow-hidden">
|
|
|
|
<!-- CARD HEADER -->
|
|
<div class="px-8 py-6 border-b border-gray-100 flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
|
|
<div>
|
|
<h2 class="text-2xl font-bold text-gray-800">
|
|
Informasi Pasien
|
|
</h2>
|
|
|
|
<p class="text-sm text-gray-500 mt-1">
|
|
Data pasien akan muncul setelah pasien dipilih.
|
|
</p>
|
|
</div>
|
|
|
|
<div id="status-badge"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gray-100 text-gray-500 text-xs font-bold uppercase tracking-wide w-fit">
|
|
|
|
<span class="w-2 h-2 rounded-full bg-gray-400"></span>
|
|
Belum Dipilih
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- EMPTY STATE -->
|
|
<div id="empty-state" class="px-8 py-16 text-center">
|
|
|
|
<div class="w-20 h-20 mx-auto rounded-full bg-green-50 text-green-600 flex items-center justify-center text-4xl mb-5">
|
|
<i class="ri-user-heart-line"></i>
|
|
</div>
|
|
|
|
<h3 class="text-xl font-bold text-gray-800">
|
|
Belum ada pasien dipilih
|
|
</h3>
|
|
|
|
<p class="text-sm text-gray-500 mt-2 max-w-md mx-auto leading-6">
|
|
Silakan cari dan pilih pasien pada panel sebelah kiri untuk melihat detail data pasien sebelum proses SAW dilakukan.
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<!-- INFO CONTENT -->
|
|
<div id="info-content" class="hidden p-8 space-y-7">
|
|
|
|
<!-- PATIENT NAME CARD -->
|
|
<div class="bg-gradient-to-r from-green-50 to-white border border-green-100 rounded-3xl p-7">
|
|
|
|
<p class="text-xs font-bold uppercase tracking-wide text-green-600 mb-2">
|
|
Pasien Terpilih
|
|
</p>
|
|
|
|
<h3 id="nama" class="text-3xl font-black text-gray-900">
|
|
-
|
|
</h3>
|
|
|
|
<p class="text-sm text-gray-500 mt-2">
|
|
Data pasien siap digunakan untuk proses rekomendasi.
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<!-- INFO GRID -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
|
|
|
|
<div class="info-card">
|
|
<div class="info-icon bg-blue-50 text-blue-600">
|
|
<i class="ri-calendar-line"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="info-label">Umur</p>
|
|
<p id="umur" class="info-value">-</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-card">
|
|
<div class="info-icon bg-purple-50 text-purple-600">
|
|
<i class="ri-user-line"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="info-label">Jenis Kelamin</p>
|
|
<p id="jenis_kelamin" class="info-value">-</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-card">
|
|
<div class="info-icon bg-orange-50 text-orange-600">
|
|
<i class="ri-run-line"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="info-label">Aktivitas</p>
|
|
<p id="aktivitas_harian" class="info-value">-</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-card">
|
|
<div class="info-icon bg-teal-50 text-teal-600">
|
|
<i class="ri-scales-3-line"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="info-label">Berat Badan</p>
|
|
<p id="berat" class="info-value">-</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-card">
|
|
<div class="info-icon bg-cyan-50 text-cyan-600">
|
|
<i class="ri-ruler-line"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="info-label">Tinggi Badan</p>
|
|
<p id="tinggi" class="info-value">-</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-card">
|
|
<div class="info-icon bg-red-50 text-red-600">
|
|
<i class="ri-heart-pulse-line"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="info-label">Gejala</p>
|
|
<p id="gejala" class="info-value text-red-600">-</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- NOTE -->
|
|
<div class="bg-gray-50 border border-gray-100 rounded-2xl p-5">
|
|
|
|
<div class="flex items-start gap-3">
|
|
|
|
<div class="w-9 h-9 rounded-xl bg-white border border-gray-200 flex items-center justify-center text-green-600 shrink-0">
|
|
<i class="ri-information-line"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 class="text-sm font-bold text-gray-800">
|
|
Informasi Proses
|
|
</h4>
|
|
|
|
<p class="text-sm text-gray-500 mt-1 leading-6">
|
|
Sistem akan memfilter makanan berdasarkan gejala pasien, menghitung normalisasi kriteria,
|
|
mengalikan dengan bobot, lalu menghasilkan ranking rekomendasi makanan terbaik.
|
|
</p>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<style>
|
|
.ts-wrapper.single .ts-control {
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 1rem;
|
|
padding: 0.95rem 1rem;
|
|
background: #f9fafb;
|
|
font-size: 0.875rem;
|
|
color: #374151;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.ts-wrapper.focus .ts-control {
|
|
border-color: #22c55e;
|
|
background: #ffffff;
|
|
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.12);
|
|
}
|
|
|
|
.ts-dropdown {
|
|
border-radius: 1rem;
|
|
border: 1px solid #e5e7eb;
|
|
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.10);
|
|
overflow: hidden;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.ts-dropdown .option {
|
|
padding: 0.85rem 1rem;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.ts-dropdown .active {
|
|
background: #f0fdf4;
|
|
color: #15803d;
|
|
}
|
|
|
|
.info-card {
|
|
background: #ffffff;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 1.5rem;
|
|
padding: 1.25rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
transition: 0.2s ease;
|
|
}
|
|
|
|
.info-card:hover {
|
|
background: #f9fafb;
|
|
}
|
|
|
|
.info-icon {
|
|
width: 46px;
|
|
height: 46px;
|
|
border-radius: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.35rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
color: #9ca3af;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.info-value {
|
|
font-size: 0.95rem;
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
line-height: 1.4;
|
|
}
|
|
</style>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js"></script>
|
|
|
|
<script>
|
|
const select = document.getElementById('pasien_id');
|
|
const btn = document.getElementById('btn-proses');
|
|
const btnText = document.getElementById('btn-text');
|
|
const btnIcon = document.getElementById('btn-icon');
|
|
const spinner = document.getElementById('spinner');
|
|
|
|
const emptyState = document.getElementById('empty-state');
|
|
const infoContent = document.getElementById('info-content');
|
|
const statusBadge = document.getElementById('status-badge');
|
|
|
|
const fields = {
|
|
nama: document.getElementById('nama'),
|
|
umur: document.getElementById('umur'),
|
|
jenis_kelamin: document.getElementById('jenis_kelamin'),
|
|
gejala: document.getElementById('gejala'),
|
|
aktivitas: document.getElementById('aktivitas_harian'),
|
|
berat: document.getElementById('berat'),
|
|
tinggi: document.getElementById('tinggi'),
|
|
};
|
|
|
|
function setStatusSelected() {
|
|
statusBadge.className =
|
|
'inline-flex items-center gap-2 px-4 py-2 rounded-full bg-green-50 text-green-700 text-xs font-bold uppercase tracking-wide w-fit';
|
|
|
|
statusBadge.innerHTML =
|
|
'<span class="w-2 h-2 rounded-full bg-green-500"></span> Siap Diproses';
|
|
}
|
|
|
|
function setStatusEmpty() {
|
|
statusBadge.className =
|
|
'inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gray-100 text-gray-500 text-xs font-bold uppercase tracking-wide w-fit';
|
|
|
|
statusBadge.innerHTML =
|
|
'<span class="w-2 h-2 rounded-full bg-gray-400"></span> Belum Dipilih';
|
|
}
|
|
|
|
function resetFields() {
|
|
fields.nama.innerText = '-';
|
|
fields.umur.innerText = '-';
|
|
fields.jenis_kelamin.innerText = '-';
|
|
fields.gejala.innerText = '-';
|
|
fields.aktivitas.innerText = '-';
|
|
fields.berat.innerText = '-';
|
|
fields.tinggi.innerText = '-';
|
|
|
|
btn.disabled = true;
|
|
emptyState.classList.remove('hidden');
|
|
infoContent.classList.add('hidden');
|
|
setStatusEmpty();
|
|
}
|
|
|
|
function fillPatientData(value) {
|
|
|
|
if (!value) {
|
|
resetFields();
|
|
return;
|
|
}
|
|
|
|
let option = select.querySelector('option[value="' + value + '"]');
|
|
|
|
if (!option) {
|
|
resetFields();
|
|
return;
|
|
}
|
|
|
|
fields.nama.innerText = option.dataset.nama || '-';
|
|
fields.umur.innerText = (option.dataset.umur || '-') + ' tahun';
|
|
fields.jenis_kelamin.innerText = option.dataset.jenis_kelamin || '-';
|
|
fields.gejala.innerText = option.dataset.gejala || '-';
|
|
fields.aktivitas.innerText = option.dataset.aktivitas || '-';
|
|
fields.berat.innerText = (option.dataset.berat || '-') + ' kg';
|
|
fields.tinggi.innerText = (option.dataset.tinggi || '-') + ' cm';
|
|
|
|
btn.disabled = false;
|
|
emptyState.classList.add('hidden');
|
|
infoContent.classList.remove('hidden');
|
|
setStatusSelected();
|
|
}
|
|
|
|
const selectPasien = new TomSelect('#pasien_id', {
|
|
create: false,
|
|
allowEmptyOption: true,
|
|
placeholder: 'Cari nama pasien...',
|
|
|
|
sortField: {
|
|
field: 'text',
|
|
direction: 'asc'
|
|
},
|
|
|
|
searchField: ['text'],
|
|
|
|
onFocus: function () {
|
|
this.control_input.placeholder = '';
|
|
},
|
|
|
|
onBlur: function () {
|
|
if (!this.getValue()) {
|
|
this.control_input.placeholder = 'Cari nama pasien...';
|
|
}
|
|
},
|
|
|
|
onChange: function(value) {
|
|
fillPatientData(value);
|
|
},
|
|
|
|
render: {
|
|
no_results: function(data, escape) {
|
|
return `
|
|
<div class="px-4 py-3 text-sm text-gray-500">
|
|
Pasien tidak ditemukan
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
document.getElementById('form-proses').addEventListener('submit', function () {
|
|
btn.disabled = true;
|
|
spinner.classList.remove('hidden');
|
|
btnIcon.classList.add('hidden');
|
|
btnText.innerText = 'Memproses...';
|
|
});
|
|
</script>
|
|
|
|
@endsection |