Files
gravity/Dashboard ลดแออัด-ลดรอคอย/views/tab_opd_2_condition.php
T
2026-09-16 23:20:08 +07:00

357 lines
26 KiB
PHP

<!-- TAB 2: ระยะเวลาตามเงื่อนไข (Condition) -->
<div id="tab-opd-condition" class="tab-content hidden space-y-4 md:space-y-6">
<form class="opd-condition-filter-form card-premium p-4 bg-gradient-to-r from-slate-50 to-white border-l-4 border-l-indigo-500 shadow-sm flex flex-col gap-3.5 mb-6">
<div class="grid grid-cols-2 md:grid-cols-5 gap-3">
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">การนัดหมาย</label>
<select name="appoint" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none">
<option value="">ทั้งหมด</option>
<option value="1">มาตามนัด</option>
<!--<option value="2">มาไม่ตรงนัด</option>-->
<option value="3">Walk in (ไม่นัด)</option>
</select>
</div>
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">จากจุด (Step From)</label>
<select name="step_from" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none">
<option value="vsttime">1. ยื่นบัตร (Register)</option>
<option value="service4">2. ซักประวัติ (Screen)</option>
<option value="service11">3. รอพบแพทย์ (Wait Doctor)</option>
<option value="service5">4. แพทย์ตรวจ (Doctor Check)</option>
<option value="service12">5. รับยา (RX)</option>
</select>
</div>
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">ถึงจุด (Step To)</label>
<select name="step_to" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none">
<option value="service6" selected>เสร็จสิ้นทั้งหมด (Service 6)</option>
<option value="service12">5. รับยา (RX)</option>
<option value="service5">4. แพทย์ตรวจ (Doctor Check)</option>
<option value="service11">3. รอพบแพทย์ (Wait Doctor)</option>
<option value="service4">2. ซักประวัติ (Screen)</option>
</select>
</div>
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">คลินิก</label>
<select name="clinic_id" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none">
<option value="">ทั้งหมด</option>
<option value="000">ผู้ป่วยนอก (ทั่วไป)</option>
<option value="130">คลินิกอายุรกรรม</option>
<option value="102">คลินิกศัลยกรรม</option>
<option value="149">คลินิกระบบประสาท</option>
<option value="168">คลินิกกุมารเวชกรรม</option>
<option value="220">คลินิกสูติ-นรีเวช</option>
<option value="073">คลินิกตา</option>
<option value="072">คลินิกกระดูก</option>
<option value="111">คลินิกจิตเวช</option>
<option value="139">คลินิกเบาหวาน</option>
<option value="140">คลินิกความดัน</option>
<option value="154">คลินิกไขมัน</option>
<option value="137">คลินิกไต</option>
<option value="110">หอบหืด</option>
<option value="009">ฝากครรภ์</option>
<option value="227">ทางเดินอาหารและตับ</option>
<option value="024">คลินิกวัณโรค</option>
<option value="225">คลินิกอายุรกรรมปอด</option>
<option value="017">ทันตกรรม</option>
<option value="132">คลินิกหัวใจ</option>
</select>
</div>
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">ช่วงเวลา</label>
<select name="time_period" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none">
<option value="">ทั้งหมด (08:00 - 16:00)</option>
<option value="morning">เช้า (08:00 - 12:00)</option>
<option value="afternoon">บ่าย (12:01 - 16:00)</option>
</select>
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-5 gap-3 items-end">
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">สถานะ Lab</label>
<select name="lab_status" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none">
<option value="">ทั้งหมด</option>
<option value="1">มี Lab</option>
<option value="0">ไม่มี Lab</option>
</select>
</div>
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">สถานะ X-ray</label>
<select name="xray_status" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none">
<option value="">ทั้งหมด</option>
<option value="1">มี X-ray</option>
<option value="0">ไม่มี X-ray</option>
</select>
</div>
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">ตั้งแต่วันที่</label>
<input type="date" name="start_date" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none" value="<?= date('Y-m-01') ?>">
</div>
<div>
<label class="block text-[10px] md:text-xs font-bold text-slate-600 mb-1">ถึงวันที่</label>
<input type="date" name="end_date" class="w-full bg-white border border-slate-300 rounded-lg p-2 text-xs md:text-sm outline-none" value="<?= date('Y-m-d') ?>">
</div>
<button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-700 active:scale-[0.98] text-white font-bold py-2 px-4 rounded-lg h-[38px] flex items-center justify-center transition-all shadow-md shadow-indigo-500/10">
<i class="fa-solid fa-magnifying-glass mr-2"></i> ค้นหาเงื่อนไข
</button>
</div>
</form>
<div class="card-premium p-4 md:p-8 bg-white relative overflow-visible mb-6">
<div class="absolute top-0 left-0 w-1 md:w-1.5 h-full bg-gradient-to-b from-indigo-500 to-purple-500 rounded-l-xl"></div>
<h3 class="font-bold text-slate-800 mb-4 md:mb-8 flex flex-col md:flex-row items-start md:items-center text-base md:text-lg">
<div class="flex items-center mb-2 md:mb-0">
<div class="w-6 h-6 md:w-8 md:h-8 rounded bg-indigo-100 text-indigo-600 flex items-center justify-center mr-2 md:mr-3"><i class="fa-solid fa-filter"></i></div>ผลลัพธ์เส้นทางระยะเวลารอคอย (ตามเงื่อนไข)
</div>
<span class="md:ml-auto bg-slate-100 text-slate-500 px-5 py-2 rounded-full text-xs md:text-sm font-extrabold shadow-md opd-total-time-display-cond">- นาที</span>
</h3>
<div class="flow-container flex flex-col md:flex-row justify-between items-center py-2 md:py-4 relative">
<div class="flow-line hidden md:block" style="background: linear-gradient(90deg, #6366f1, #a855f7);"></div>
<div class="flex md:flex-col items-center justify-between md:justify-start w-full md:w-auto mb-3 md:mb-0 bg-slate-50 md:bg-transparent p-3 md:p-0 rounded-lg border border-slate-100 md:border-transparent">
<div class="flex items-center md:flex-col">
<div class="w-9 h-9 md:w-16 md:h-16 bg-white border-[2px] md:border-[3px] border-blue-200 text-blue-500 rounded-full shadow-md flex items-center justify-center md:mb-3 mr-3 md:mr-0 z-10"><i class="fa-solid fa-id-card text-xs md:text-2xl"></i></div><span class="text-xs md:text-sm font-extrabold text-slate-700 bg-white md:px-2 z-10">ยื่นบัตร</span>
</div>
<span class="t-step-1-cond text-xs font-bold text-blue-700 bg-blue-50 px-2 md:px-3 py-0.5 rounded-full md:mt-1 border border-blue-100 z-10">- นาที</span>
</div>
<div class="flex md:flex-col items-center justify-between md:justify-start w-full md:w-auto mb-3 md:mb-0 bg-slate-50 md:bg-transparent p-3 md:p-0 rounded-lg border border-slate-100 md:border-transparent">
<div class="flex items-center md:flex-col">
<div class="w-9 h-9 md:w-16 md:h-16 bg-white border-[2px] md:border-[3px] border-indigo-200 text-indigo-500 rounded-full shadow-md flex items-center justify-center md:mb-3 mr-3 md:mr-0 z-10"><i class="fa-solid fa-clipboard-list text-xs md:text-2xl"></i></div><span class="text-xs md:text-sm font-extrabold text-slate-700 bg-white md:px-2 z-10">ซักประวัติ</span>
</div>
<span class="t-step-2-cond text-xs font-bold text-indigo-700 bg-indigo-50 px-2 md:px-3 py-0.5 rounded-full md:mt-1 border border-indigo-100 z-10">- นาที</span>
</div>
<div class="flex md:flex-col items-center justify-between md:justify-start w-full md:w-auto mb-3 md:mb-0 bg-slate-50 md:bg-transparent p-3 md:p-0 rounded-lg border border-slate-100 md:border-transparent">
<div class="flex items-center md:flex-col">
<div class="w-10 h-10 md:w-20 md:h-20 bg-gradient-to-br from-orange-500 to-amber-400 text-white border-2 md:border-4 rounded-full shadow-md flex items-center justify-center md:mb-3 mr-3 md:mr-0 z-10"><i class="fa-solid fa-user-clock text-sm md:text-3xl"></i></div><span class="text-xs md:text-sm font-extrabold text-slate-700 bg-white md:px-2 z-10">รอพบแพทย์</span>
</div>
<span class="t-step-3-cond text-xs font-bold text-orange-700 bg-orange-50 px-2 md:px-3 py-0.5 rounded-full md:mt-1 border border-orange-100 z-10">- นาที</span>
</div>
<div class="flex md:flex-col items-center justify-between md:justify-start w-full md:w-auto mb-3 md:mb-0 bg-slate-50 md:bg-transparent p-3 md:p-0 rounded-lg border border-slate-100 md:border-transparent">
<div class="flex items-center md:flex-col">
<div class="w-9 h-9 md:w-16 md:h-16 bg-white border-[2px] md:border-[3px] border-teal-200 text-teal-500 rounded-full shadow-md flex items-center justify-center md:mb-3 mr-3 md:mr-0 z-10"><i class="fa-solid fa-user-doctor text-xs md:text-2xl"></i></div><span class="text-xs md:text-sm font-extrabold text-slate-700 bg-white md:px-2 z-10">แพทย์ตรวจ</span>
</div>
<span class="t-step-4-cond text-xs font-bold text-teal-700 bg-teal-50 px-2 md:px-3 py-0.5 rounded-full md:mt-1 border border-teal-100 z-10">- นาที</span>
</div>
<div class="flex md:flex-col items-center justify-between md:justify-start w-full md:w-auto mb-3 md:mb-0 bg-slate-50 md:bg-transparent p-3 md:p-0 rounded-lg border border-slate-100 md:border-transparent">
<div class="flex items-center md:flex-col">
<div class="w-9 h-9 md:w-16 md:h-16 bg-white border-[2px] md:border-[3px] border-emerald-200 text-emerald-500 rounded-full shadow-md flex items-center justify-center md:mb-3 mr-3 md:mr-0 z-10"><i class="fa-solid fa-pills text-xs md:text-2xl"></i></div><span class="text-xs md:text-sm font-extrabold text-slate-700 bg-white md:px-2 z-10">รับยากลับบ้าน</span>
</div>
<span class="t-step-5-cond text-xs font-bold text-emerald-700 bg-emerald-50 px-2 md:px-3 py-0.5 rounded-full md:mt-1 border border-emerald-100 z-10">- นาที</span>
</div>
</div>
</div>
<h3 class="font-extrabold text-slate-800 text-sm md:text-base mt-6 mb-3 border-l-4 border-indigo-500 pl-3">ระยะเวลารอคอยแต่ละคลินิกเฉลี่ย</h3>
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-3 md:gap-4 mb-6" id="clinic-wait-cards-opd-cond">
<div class="col-span-full text-center text-slate-400 py-8 bg-white rounded-xl border border-dashed border-slate-300 font-medium">รอการค้นหาข้อมูล...</div>
</div>
<div class="card-premium p-4 md:p-6 mb-6">
<h3 class="font-extrabold text-slate-800 text-sm md:text-base mb-4 md:mb-6 flex items-center justify-between">
<span class="flex items-center"><i class="fa-solid fa-chart-area text-indigo-500 mr-2"></i> แนวโน้มระยะเวลารอคอย OPD (ตามเงื่อนไข)</span>
</h3>
<div class="h-[300px] md:h-[400px] w-full flex items-center justify-center bg-slate-50 rounded-lg border border-dashed border-slate-200" id="opdWaitTimeChartTabCond_container">
<span class="text-slate-400 font-medium">รอการค้นหาข้อมูล...</span>
</div>
</div>
</div>
<script>
async function fetchOpdConditionRealData(formElement) {
let params = new URLSearchParams(new FormData(formElement));
params.append('ajax_opd_cond', '1');
var overlay = document.getElementById('data-processing-overlay');
if (overlay) {
overlay.classList.remove('hidden');
overlay.classList.add('flex');
}
try {
const response = await fetch('?' + params.toString(), {
method: 'GET'
});
const text = await response.text();
let data = {};
if (text.includes('---AJAX_OPD_COND_START---')) {
data = JSON.parse(text.split('---AJAX_OPD_COND_START---')[1].split('---AJAX_OPD_COND_END---')[0]);
} else {
throw new Error("รูปแบบข้อมูลผิดพลาด");
}
// --- SQL Logs ---
if (data.sql_logs) {
let hasError = false;
let errorDetails = [];
const logContent = document.getElementById('sql-log-content');
if (logContent) {
if (data.sql_logs.length > 0) {
logContent.innerHTML = data.sql_logs.map((log, i) => {
if (log.status === 'Error') {
hasError = true;
errorDetails.push(log.name);
return `<div class="mb-3 p-3 border-l-4 border-red-500 bg-red-900/20 text-red-300 rounded-r"><div class="font-bold text-red-400 mb-1">[${i+1}] ❌ ${log.name}</div><div class="mb-2 text-red-200">Error: ${log.error}</div><div class="text-slate-500 text-[10px] break-all">${log.sql}</div></div>`;
} else {
return `<div class="mb-3 p-3 border-l-4 border-emerald-500 bg-emerald-900/20 text-emerald-300 rounded-r"><div class="font-bold text-emerald-400 mb-1">[${i+1}] ✅ ${log.name}</div><div class="text-slate-500 text-[10px] break-all">${log.sql}</div></div>`;
}
}).join('');
} else {
logContent.innerHTML = '<div class="text-slate-500 text-center py-4">ไม่มี Log บันทึก</div>';
}
}
if (data.debug_enabled && hasError) alert("⚠️ พบข้อผิดพลาดในการดึงข้อมูล (SQL Error):\n- " + errorDetails.join("\n- "));
}
// Update DOM Elements
const safeSet = (selector, text) => {
document.querySelectorAll(selector).forEach(el => el.innerText = text);
};
safeSet('.opd-total-time-display-cond', (data.flowchart.custom_total > 0) ? `ผลรวมช่วงเวลาที่เลือก ${data.flowchart.custom_total.toFixed(2)} นาที` : '- นาที');
safeSet('.t-step-1-cond', (data.flowchart.step1 > 0) ? `${data.flowchart.step1.toFixed(2)} นาที` : '- นาที');
safeSet('.t-step-2-cond', (data.flowchart.step2 > 0) ? `${data.flowchart.step2.toFixed(2)} นาที` : '- นาที');
safeSet('.t-step-3-cond', (data.flowchart.step3 > 0) ? `${data.flowchart.step3.toFixed(2)} นาที` : '- นาที');
safeSet('.t-step-4-cond', (data.flowchart.step4 > 0) ? `${data.flowchart.step4.toFixed(2)} นาที` : '- นาที');
safeSet('.t-step-5-cond', (data.flowchart.step5 > 0) ? `${data.flowchart.step5.toFixed(2)} นาที` : '- นาที');
// Clinic Grid
const clinicIconsDb = {
'ผู้ป่วยนอก (ทั่วไป)': 'fa-hospital-user',
'อายุรกรรม': 'fa-stethoscope',
'ศัลยกรรม': 'fa-syringe',
'กุมารเวชกรรม': 'fa-child-reaching',
'สูติ-นรีเวช': 'fa-person-pregnant',
'ตา': 'fa-eye',
'กระดูก': 'fa-bone',
'จิตเวช': 'fa-brain',
'ทันตกรรม': 'fa-tooth'
};
const el = document.getElementById('clinic-wait-cards-opd-cond');
if (el) {
if (data.clinics_wait && data.clinics_wait.length > 0) {
el.innerHTML = data.clinics_wait.map(c => {
let bgClass = c.wait >= 120 ? 'bg-gradient-to-br from-rose-500 via-red-500 to-pink-500' : 'bg-gradient-to-br from-indigo-500 via-purple-500 to-fuchsia-600';
let icon = clinicIconsDb[c.name] || 'fa-user-doctor';
return `<div class="rounded-xl p-3 md:p-4 shadow-md ${bgClass} transition-all duration-300 hover:scale-[1.03] hover:shadow-lg relative overflow-hidden group"><div class="absolute -right-4 -top-4 w-16 h-16 rounded-full bg-white/10 blur-xl group-hover:bg-white/20 transition-all"></div><div class="flex justify-between items-start mb-1 relative z-10"><p class="text-xs md:text-sm font-bold truncate pr-2 text-white">${c.name}</p><div class="w-6 h-6 md:w-8 md:h-8 rounded-full bg-white/25 text-white flex items-center justify-center shrink-0"><i class="fa-solid ${icon} text-[10px] md:text-xs"></i></div></div><h3 class="text-2xl md:text-3xl font-black mt-2.5 leading-none text-white drop-shadow-md relative z-10 flex items-end">${c.wait.toFixed(2)} <span class="text-[10px] md:text-xs font-semibold ml-1 mb-0.5 text-white/80">นาที</span></h3></div>`;
}).join('');
} else {
el.innerHTML = '<div class="col-span-full text-center text-slate-500 py-8 bg-white rounded-xl border border-dashed border-slate-300 font-medium">ไม่พบข้อมูลคลินิกที่เข้าเงื่อนไข</div>';
}
}
// Trend Chart
const chartId = 'opdWaitTimeChartTabCond';
const container = document.getElementById(chartId + '_container');
if (container && data.trend_labels && data.trend_labels.length > 0) {
container.innerHTML = `<canvas id="${chartId}"></canvas>`;
const ctx = document.getElementById(chartId).getContext('2d');
let grad = ctx.createLinearGradient(0, 0, 0, 400);
grad.addColorStop(0, 'rgba(99, 102, 241, 0.45)');
grad.addColorStop(1, 'rgba(99, 102, 241, 0.0)');
if (window.appCharts && window.appCharts[chartId]) window.appCharts[chartId].destroy();
window.appCharts[chartId] = new Chart(ctx, {
type: 'line',
data: {
labels: data.trend_labels,
datasets: [{
label: 'เวลาเฉลี่ย (นาที)',
data: data.trend_wait,
borderColor: '#6366f1',
backgroundColor: grad,
borderWidth: 3,
pointBackgroundColor: '#ffffff',
pointBorderColor: '#6366f1',
pointBorderWidth: 2,
fill: true,
tension: 0.4,
datalabels: {
anchor: 'end',
align: 'top',
color: '#4f46e5',
font: {
weight: 'bold'
},
formatter: v => Number(v).toFixed(1)
}
},
{
label: 'จำนวนผู้ป่วย (ราย)',
data: data.trend_pt,
borderColor: 'transparent',
backgroundColor: '#ec4899',
pointBackgroundColor: '#ec4899',
pointBorderColor: 'transparent',
pointRadius: 0,
hoverRadius: 0,
pointStyle: 'circle',
datalabels: {
anchor: 'center',
align: 'bottom',
offset: 12,
color: '#ec4899',
font: {
weight: 'bold',
size: 9
},
formatter: v => v.toLocaleString() + ' ราย'
}
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'bottom',
labels: {
usePointStyle: true
}
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
display: false,
min: 0,
suggestedMax: Math.max(...data.trend_wait, 0) * 1.5
}
},
layout: {
padding: {
top: 20
}
}
}
});
} else if (container) {
container.innerHTML = '<span class="text-slate-400 font-medium"><i class="fa-solid fa-box-open mr-2"></i>ไม่พบข้อมูลในช่วงเวลาที่เลือก</span>';
}
} catch (err) {
console.error("Fetch Condition Data Error:", err);
alert("เกิดข้อผิดพลาดในการดึงข้อมูลเงื่อนไข\nรายละเอียด: " + err.message);
} finally {
if (overlay) {
setTimeout(() => {
overlay.classList.remove('flex');
overlay.classList.add('hidden');
}, 500);
}
}
}
document.addEventListener('DOMContentLoaded', () => {
const form = document.querySelector('.opd-condition-filter-form');
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
fetchOpdConditionRealData(e.target);
});
}
});
</script>