485 lines
20 KiB
PHP
485 lines
20 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'Diagnosa Penyakit')
|
|
@section('page-title', 'Diagnosa Penyakit')
|
|
|
|
@section('content')
|
|
<div class="max-w-2xl mx-auto">
|
|
|
|
<div id="tour-form-card" class="bg-white p-6 rounded-2xl border border-[#ede8df]" style="box-shadow:0 4px 16px rgba(26,58,42,.08);">
|
|
|
|
{{-- Progress bar --}}
|
|
<div id="tour-progress" class="mb-6">
|
|
<div class="flex justify-between text-xs font-semibold mb-2" style="color:#5a7a67;">
|
|
<span>Step <span id="step-number">1</span> dari 3</span>
|
|
<span id="step-label">Pilih Gejala</span>
|
|
</div>
|
|
<div class="w-full h-2 rounded-full" style="background:#ede8df;">
|
|
<div id="progress-bar" class="h-2 rounded-full transition-all duration-300" style="background:linear-gradient(90deg,#40916c,#74c69d);width:33%;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="POST" action="{{ route('diagnosis.store') }}">
|
|
@csrf
|
|
|
|
{{-- STEP 1: Pilih Gejala --}}
|
|
<div id="step1">
|
|
<h3 class="text-lg font-bold mb-1" style="font-family:'Playfair Display',serif;color:#1a3a2a;">Pilih Gejala</h3>
|
|
<p class="text-xs mb-4" style="color:#8fa89a;">Pilih minimal 4 gejala yang sesuai dengan kondisi tanaman</p>
|
|
|
|
<input type="text" id="search" placeholder="Cari gejala..."
|
|
class="w-full mb-4 px-4 py-2.5 border rounded-xl text-sm"
|
|
style="border-color:#ede8df;outline:none;color:#1a3a2a;">
|
|
|
|
<div id="tour-symptom-list" class="space-y-2 overflow-y-auto pr-1" style="max-height:380px;">
|
|
@foreach($symptoms as $symptom)
|
|
<label class="symptom-item flex items-center gap-3 p-3 rounded-xl cursor-pointer border transition"
|
|
style="border-color:#ede8df;background:white;">
|
|
<input type="checkbox"
|
|
class="symptom-checkbox w-4 h-4 accent-green-700"
|
|
data-code="{{ $symptom->code }}"
|
|
onchange="updateCount(); updateCheckStyle(this)">
|
|
<span>
|
|
<span class="text-xs font-bold px-1.5 py-0.5 rounded mr-1" style="background:#d8f3dc;color:#2d6a4f;">{{ $symptom->code }}</span>
|
|
<span class="text-sm" style="color:#1a3a2a;">{{ $symptom->name }}</span>
|
|
</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
|
|
<div class="mt-4 flex items-center justify-between">
|
|
<span class="text-sm" style="color:#5a7a67;">
|
|
Dipilih: <strong id="count" style="color:#2d6a4f;">0</strong> gejala
|
|
<span id="count-warning" class="text-xs ml-1" style="color:#dc2626;display:none;">(minimal 4)</span>
|
|
</span>
|
|
</div>
|
|
|
|
<button type="button" onclick="nextStep()" id="tour-next-btn"
|
|
class="mt-4 w-full py-3 rounded-xl text-sm font-semibold text-white transition"
|
|
style="background:#1a3a2a;"
|
|
onmouseover="this.style.background='#2d6a4f'" onmouseout="this.style.background='#1a3a2a'">
|
|
Lanjut →
|
|
</button>
|
|
</div>
|
|
|
|
{{-- STEP 2: Tingkat Keyakinan --}}
|
|
<div id="step2" class="hidden">
|
|
<h3 class="text-lg font-bold mb-1" style="font-family:'Playfair Display',serif;color:#1a3a2a;">Tingkat Keyakinan</h3>
|
|
<p class="text-xs mb-5" style="color:#8fa89a;">Seberapa yakin kamu melihat gejala ini pada tanaman?</p>
|
|
|
|
<div id="cf-container" class="space-y-4"></div>
|
|
|
|
<div class="flex gap-3 mt-6">
|
|
<button type="button" onclick="prevStep()"
|
|
class="px-5 py-3 rounded-xl text-sm font-semibold border transition"
|
|
style="border-color:#ede8df;color:#5a7a67;background:white;"
|
|
onmouseover="this.style.background='#f8f4ee'" onmouseout="this.style.background='white'">
|
|
← Kembali
|
|
</button>
|
|
<button type="button" onclick="nextStep()"
|
|
class="flex-1 py-3 rounded-xl text-sm font-semibold text-white transition"
|
|
style="background:#1a3a2a;"
|
|
onmouseover="this.style.background='#2d6a4f'" onmouseout="this.style.background='#1a3a2a'">
|
|
Lanjut →
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- STEP 3: Konfirmasi --}}
|
|
<div id="step3" class="hidden">
|
|
<h3 class="text-lg font-bold mb-1" style="font-family:'Playfair Display',serif;color:#1a3a2a;">Konfirmasi</h3>
|
|
<p class="text-xs mb-5" style="color:#8fa89a;">Periksa kembali sebelum diagnosa dimulai</p>
|
|
|
|
<div id="summary" class="space-y-2 mb-6"></div>
|
|
|
|
<div class="flex gap-3">
|
|
<button type="button" onclick="prevStep()"
|
|
class="px-5 py-3 rounded-xl text-sm font-semibold border transition"
|
|
style="border-color:#ede8df;color:#5a7a67;background:white;"
|
|
onmouseover="this.style.background='#f8f4ee'" onmouseout="this.style.background='white'">
|
|
← Kembali
|
|
</button>
|
|
<button type="submit"
|
|
class="flex-1 py-3 rounded-xl text-sm font-semibold text-white transition"
|
|
style="background:#1a3a2a;"
|
|
onmouseover="this.style.background='#2d6a4f'" onmouseout="this.style.background='#1a3a2a'">
|
|
Diagnosa Sekarang
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let step = 1;
|
|
const stepLabels = ['', 'Pilih Gejala', 'Tingkat Keyakinan', 'Konfirmasi'];
|
|
const progressWidth = ['', '33%', '66%', '100%'];
|
|
|
|
function updateCount() {
|
|
const checked = document.querySelectorAll('.symptom-checkbox:checked').length;
|
|
document.getElementById('count').innerText = checked;
|
|
document.getElementById('count-warning').style.display = checked < 4 ? 'inline' : 'none';
|
|
}
|
|
|
|
function updateCheckStyle(cb) {
|
|
const label = cb.closest('label');
|
|
if (cb.checked) {
|
|
label.style.background = '#f0fdf4';
|
|
label.style.borderColor = '#b7ddc4';
|
|
} else {
|
|
label.style.background = 'white';
|
|
label.style.borderColor = '#ede8df';
|
|
}
|
|
}
|
|
|
|
function nextStep() {
|
|
if (step === 1) {
|
|
const count = document.querySelectorAll('.symptom-checkbox:checked').length;
|
|
if (count < 4) {
|
|
document.getElementById('count-warning').style.display = 'inline';
|
|
return;
|
|
}
|
|
buildCF();
|
|
}
|
|
|
|
// ── VALIDASI: blokir kalau semua CF = 0.0 ──
|
|
if (step === 2) {
|
|
const checked = document.querySelectorAll('.symptom-checkbox:checked');
|
|
let semuaTidakYakin = true;
|
|
checked.forEach(cb => {
|
|
const kode = cb.getAttribute('data-code');
|
|
const val = document.getElementById('cf-val-' + kode)?.value;
|
|
if (val && parseFloat(val) > 0) semuaTidakYakin = false;
|
|
});
|
|
|
|
if (semuaTidakYakin) {
|
|
showCFWarning();
|
|
return;
|
|
}
|
|
|
|
buildSummary();
|
|
}
|
|
|
|
document.getElementById('step' + step).classList.add('hidden');
|
|
step++;
|
|
document.getElementById('step' + step).classList.remove('hidden');
|
|
document.getElementById('step-number').innerText = step;
|
|
document.getElementById('step-label').innerText = stepLabels[step];
|
|
document.getElementById('progress-bar').style.width = progressWidth[step];
|
|
}
|
|
|
|
function prevStep() {
|
|
document.getElementById('step' + step).classList.add('hidden');
|
|
step--;
|
|
document.getElementById('step' + step).classList.remove('hidden');
|
|
document.getElementById('step-number').innerText = step;
|
|
document.getElementById('step-label').innerText = stepLabels[step];
|
|
document.getElementById('progress-bar').style.width = progressWidth[step];
|
|
}
|
|
|
|
const cfOptions = [
|
|
{ val: '0.8', label: 'Sangat Yakin' },
|
|
{ val: '0.6', label: 'Yakin' },
|
|
{ val: '0.4', label: 'Cukup Yakin' },
|
|
{ val: '0.2', label: 'Kurang Yakin' },
|
|
{ val: '0.0', label: 'Tidak Yakin' },
|
|
];
|
|
|
|
function buildCF() {
|
|
const container = document.getElementById('cf-container');
|
|
container.innerHTML = '';
|
|
document.querySelectorAll('.symptom-checkbox:checked').forEach(cb => {
|
|
const label = cb.closest('label').innerText.trim();
|
|
const kode = cb.getAttribute('data-code');
|
|
const buttons = cfOptions.map(opt => `
|
|
<button type="button"
|
|
onclick="selectCF(this, '${kode}', '${opt.val}')"
|
|
data-val="${opt.val}"
|
|
class="cf-btn-${kode} py-2 px-1 rounded-xl border-2 text-xs font-semibold transition text-center"
|
|
style="border-color:#ede8df;color:#5a7a67;background:white;">
|
|
${opt.label}
|
|
</button>
|
|
`).join('');
|
|
container.innerHTML += `
|
|
<div class="p-4 rounded-xl border border-[#ede8df]" style="background:#fafaf8;">
|
|
<p class="text-sm font-semibold mb-3" style="color:#1a3a2a;">${label}</p>
|
|
<input type="hidden" name="symptoms[${kode}]" id="cf-val-${kode}" value="0.8">
|
|
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;">${buttons}</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
// Set default "Sangat Yakin" terpilih
|
|
document.querySelectorAll('.symptom-checkbox:checked').forEach(cb => {
|
|
const kode = cb.getAttribute('data-code');
|
|
const defaultBtn = document.querySelector(`.cf-btn-${kode}[data-val="0.8"]`);
|
|
if (defaultBtn) {
|
|
setSelectedStyle(defaultBtn);
|
|
document.getElementById(`cf-val-${kode}`).value = '0.8';
|
|
}
|
|
});
|
|
}
|
|
|
|
function selectCF(btn, kode, val) {
|
|
document.querySelectorAll(`.cf-btn-${kode}`).forEach(b => {
|
|
b.style.borderColor = '#ede8df';
|
|
b.style.background = 'white';
|
|
b.style.color = '#5a7a67';
|
|
});
|
|
setSelectedStyle(btn);
|
|
document.getElementById(`cf-val-${kode}`).value = val;
|
|
}
|
|
|
|
function setSelectedStyle(btn) {
|
|
btn.style.borderColor = '#2d6a4f';
|
|
btn.style.background = '#d8f3dc';
|
|
btn.style.color = '#1a3a2a';
|
|
}
|
|
|
|
function buildSummary() {
|
|
const summary = document.getElementById('summary');
|
|
summary.innerHTML = '';
|
|
const cfMap = { '0.8':'Sangat Yakin','0.6':'Yakin','0.4':'Cukup Yakin','0.2':'Kurang Yakin','0.0':'Tidak Yakin' };
|
|
document.querySelectorAll('.symptom-checkbox:checked').forEach(cb => {
|
|
const label = cb.closest('label').innerText.trim();
|
|
const kode = cb.getAttribute('data-code');
|
|
const val = document.getElementById(`cf-val-${kode}`).value;
|
|
summary.innerHTML += `
|
|
<div class="flex items-center justify-between p-3 rounded-xl" style="background:#f0fdf4;border:1px solid #b7ddc4;">
|
|
<span class="text-sm" style="color:#1a3a2a;">${label}</span>
|
|
<span class="text-xs font-semibold px-2 py-1 rounded-full ml-2 flex-shrink-0" style="background:#d8f3dc;color:#2d6a4f;">${cfMap[val] ?? val}</span>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
document.getElementById('search').addEventListener('input', function () {
|
|
const keyword = this.value.toLowerCase();
|
|
document.querySelectorAll('.symptom-item').forEach(item => {
|
|
item.style.display = item.innerText.toLowerCase().includes(keyword) ? '' : 'none';
|
|
});
|
|
});
|
|
|
|
/* ── Popup warning CF semua 0.0 ── */
|
|
function showCFWarning() {
|
|
if (document.getElementById('cf-warn-overlay')) return;
|
|
|
|
const overlay = document.createElement('div');
|
|
overlay.id = 'cf-warn-overlay';
|
|
overlay.style.cssText = `
|
|
position:fixed;inset:0;z-index:99999;
|
|
background:rgba(15,30,20,.55);backdrop-filter:blur(4px);
|
|
display:flex;align-items:center;justify-content:center;padding:1rem;
|
|
`;
|
|
|
|
overlay.innerHTML = `
|
|
<div style="
|
|
background:#fff;border-radius:20px;padding:2rem 1.75rem;
|
|
max-width:360px;width:100%;text-align:center;
|
|
box-shadow:0 24px 60px rgba(10,25,15,.3);
|
|
animation:warnPop .3s cubic-bezier(.22,.97,.44,1) both;
|
|
">
|
|
<div style="font-size:2.5rem;margin-bottom:.75rem;">⚠️</div>
|
|
<h3 style="
|
|
font-family:'Playfair Display',serif;
|
|
font-size:1.1rem;color:#1a3a2a;margin-bottom:.5rem;
|
|
">Tingkat Keyakinan Terlalu Rendah</h3>
|
|
<p style="font-size:.875rem;color:#5a7a67;line-height:1.6;margin-bottom:1.5rem;">
|
|
Kamu memilih <strong>Tidak Yakin</strong> untuk semua gejala.<br>
|
|
Minimal satu gejala harus memiliki tingkat keyakinan di atas <em>Tidak Yakin</em>
|
|
agar diagnosa dapat diproses.
|
|
</p>
|
|
<button onclick="closeCFWarning()"
|
|
style="
|
|
width:100%;padding:.8rem;border-radius:12px;
|
|
background:#1a3a2a;color:white;border:none;
|
|
font-family:'DM Sans',sans-serif;font-size:.9rem;
|
|
font-weight:600;cursor:pointer;
|
|
"
|
|
onmouseover="this.style.background='#2d6a4f'"
|
|
onmouseout="this.style.background='#1a3a2a'">
|
|
Oke, Saya Ubah
|
|
</button>
|
|
</div>
|
|
<style>
|
|
@keyframes warnPop {
|
|
from { opacity:0; transform:scale(.92) translateY(16px); }
|
|
to { opacity:1; transform:none; }
|
|
}
|
|
</style>
|
|
`;
|
|
|
|
document.body.appendChild(overlay);
|
|
overlay.addEventListener('click', function(e) {
|
|
if (e.target === overlay) closeCFWarning();
|
|
});
|
|
}
|
|
|
|
function closeCFWarning() {
|
|
const el = document.getElementById('cf-warn-overlay');
|
|
if (el) el.remove();
|
|
}
|
|
|
|
/* ============================================================
|
|
GUIDED TOUR — Halaman Diagnosa
|
|
============================================================ */
|
|
const diagTourSteps = [
|
|
{
|
|
title: 'Halaman Diagnosa 🌿',
|
|
desc: 'Di sini kamu bisa mendiagnosa penyakit tanaman tebumu. Ada <b>3 langkah</b> yang perlu kamu ikuti.',
|
|
target: null,
|
|
},
|
|
{
|
|
title: 'Progress Bar',
|
|
desc: 'Bar ini menunjukkan kamu sedang di langkah berapa. Total ada <b>3 langkah</b>: Pilih Gejala → Tingkat Keyakinan → Konfirmasi.',
|
|
targetSel: '#tour-progress',
|
|
pos: 'bottom',
|
|
},
|
|
{
|
|
title: 'Daftar Gejala',
|
|
desc: '<b>Centang minimal 4 gejala</b> yang kamu temukan pada tanaman tebu. Bisa juga cari gejala pakai kolom pencarian di atas.',
|
|
targetSel: '#tour-symptom-list',
|
|
pos: 'top',
|
|
},
|
|
{
|
|
title: 'Tombol Lanjut',
|
|
desc: 'Setelah memilih minimal 4 gejala, klik tombol ini untuk ke langkah berikutnya yaitu mengisi <b>tingkat keyakinan</b> tiap gejala.',
|
|
targetSel: '#tour-next-btn',
|
|
pos: 'top',
|
|
offsetY: -120
|
|
},
|
|
];
|
|
|
|
let _dTourStep = 0;
|
|
|
|
function _dTourEl(id) { return document.getElementById(id); }
|
|
|
|
function _dTourFindTarget(step) {
|
|
if (!step.targetSel) return null;
|
|
return document.querySelector(step.targetSel);
|
|
}
|
|
|
|
function _dTourStart() {
|
|
_dTourStep = 0;
|
|
if (!_dTourEl('_dtour_hl')) { const d = document.createElement('div'); d.id = '_dtour_hl'; document.body.appendChild(d); }
|
|
if (!_dTourEl('_dtour_tip')) { const d = document.createElement('div'); d.id = '_dtour_tip'; document.body.appendChild(d); }
|
|
if (!_dTourEl('_dtour_dim')) { const d = document.createElement('div'); d.id = '_dtour_dim'; document.body.appendChild(d); }
|
|
_dTourRender();
|
|
}
|
|
|
|
function _dTourRender() {
|
|
const step = diagTourSteps[_dTourStep];
|
|
const total = diagTourSteps.length;
|
|
const hl = _dTourEl('_dtour_hl');
|
|
const tip = _dTourEl('_dtour_tip');
|
|
const dim = _dTourEl('_dtour_dim');
|
|
const target = _dTourFindTarget(step);
|
|
|
|
dim.style.cssText = `position:fixed;inset:0;z-index:99990;background:rgba(0,0,0,0.52);pointer-events:auto;`;
|
|
|
|
if (target) {
|
|
const r = target.getBoundingClientRect();
|
|
const pad = 8;
|
|
hl.style.cssText = `
|
|
position:fixed;z-index:99992;pointer-events:none;
|
|
top:${r.top-pad}px;left:${r.left-pad}px;
|
|
width:${r.width+pad*2}px;height:${r.height+pad*2}px;
|
|
border:2.5px solid #2d6a4f;border-radius:12px;
|
|
box-shadow:0 0 0 9999px rgba(0,0,0,0.52);
|
|
transition:all .35s ease;`;
|
|
dim.style.background = 'transparent';
|
|
} else {
|
|
hl.style.cssText = 'display:none;';
|
|
}
|
|
|
|
const dots = Array.from({length:total},(_,i)=>
|
|
`<div style="width:${i===_dTourStep?16:6}px;height:6px;border-radius:3px;
|
|
background:${i===_dTourStep?'#2d6a4f':'#d1d5db'};
|
|
transition:all .3s;display:inline-block;margin-right:4px;"></div>`
|
|
).join('');
|
|
|
|
let tipPos = 'top:50%;left:50%;transform:translate(-50%,-50%);';
|
|
if (target) {
|
|
const r = target.getBoundingClientRect();
|
|
const vw = window.innerWidth;
|
|
const vh = window.innerHeight;
|
|
const tipH = 340;
|
|
const tipW = 310;
|
|
|
|
if (step.pos === 'bottom' && r.bottom + tipH < vh) {
|
|
tipPos = `top:${r.bottom + 16}px;left:${Math.max(16, Math.min(r.left, vw - tipW))}px;transform:none;`;
|
|
} else if (step.pos === 'top') {
|
|
let topPos = r.top - tipH - 20 + (step.offsetY || 0);
|
|
if (topPos < 20) topPos = 20;
|
|
tipPos = `top:${topPos}px;left:${Math.max(16, Math.min(r.left, vw - tipW))}px;transform:none;`;
|
|
} else if (step.pos === 'right' && r.right + tipW < vw) {
|
|
tipPos = `top:${Math.min(r.top, vh - tipH - 20)}px;left:${r.right + 16}px;transform:none;`;
|
|
} else {
|
|
tipPos = `top:50%;left:50%;transform:translate(-50%,-50%);`;
|
|
}
|
|
}
|
|
|
|
tip.style.cssText = `
|
|
position:fixed;${tipPos}
|
|
z-index:99999;background:#fff;border-radius:16px;
|
|
padding:22px 24px;width:290px;
|
|
box-shadow:0 12px 40px rgba(0,0,0,0.18);
|
|
font-family:inherit;pointer-events:auto;`;
|
|
|
|
tip.innerHTML = `
|
|
<div style="font-size:11px;color:#2d6a4f;font-weight:700;text-transform:uppercase;
|
|
letter-spacing:.8px;margin-bottom:6px;">
|
|
Langkah ${_dTourStep+1} dari ${total}
|
|
</div>
|
|
<div style="font-size:15px;font-weight:700;color:#1a3a2a;margin-bottom:8px;
|
|
font-family:'Playfair Display',serif;">
|
|
${step.title}
|
|
</div>
|
|
<div style="font-size:13px;color:#5a7a67;line-height:1.65;margin-bottom:18px;">
|
|
${step.desc}
|
|
</div>
|
|
<div style="display:flex;align-items:center;justify-content:space-between;">
|
|
<div>${dots}</div>
|
|
<div style="display:flex;gap:10px;align-items:center;">
|
|
<button onclick="_dTourEnd()"
|
|
style="background:none;border:none;font-size:12px;color:#9ca3af;cursor:pointer;padding:4px;">
|
|
Lewati
|
|
</button>
|
|
<button onclick="_dTourNext()"
|
|
style="background:#2d6a4f;color:#fff;border:none;
|
|
padding:9px 18px;border-radius:10px;font-size:13px;font-weight:600;cursor:pointer;">
|
|
${_dTourStep === total-1 ? 'Siap! ✓' : 'Lanjut →'}
|
|
</button>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
function _dTourNext() {
|
|
_dTourStep++;
|
|
if (_dTourStep >= diagTourSteps.length) { _dTourEnd(); return; }
|
|
_dTourRender();
|
|
}
|
|
|
|
function _dTourEnd() {
|
|
['_dtour_hl','_dtour_tip','_dtour_dim'].forEach(id => {
|
|
const el = _dTourEl(id);
|
|
if (el) el.remove();
|
|
});
|
|
localStorage.setItem('sipakartebu_diag_tour_done', '1');
|
|
}
|
|
|
|
window.addEventListener('load', function () {
|
|
if (!localStorage.getItem('sipakartebu_diag_tour_done')) {
|
|
setTimeout(_dTourStart, 800);
|
|
}
|
|
});
|
|
|
|
function ulangiTourDiagnosa() {
|
|
localStorage.removeItem('sipakartebu_diag_tour_done');
|
|
_dTourStart();
|
|
}
|
|
</script>
|
|
|
|
@endsection |