578 lines
37 KiB
PHP
578 lines
37 KiB
PHP
<?php
|
|
// views/tab_stats.php
|
|
require_once __DIR__ . '/../includes/db.php';
|
|
require_once __DIR__ . '/../includes/sql_config.php';
|
|
|
|
$show_sql_log_btn = 'N';
|
|
$isStatsAjax = isset($_GET['ajax_stats']) && $_GET['ajax_stats'] == '1';
|
|
|
|
// =========================================================================
|
|
// 1. ส่วนประมวลผล AJAX ดึงข้อมูลสถิติจากฐานข้อมูล
|
|
// =========================================================================
|
|
if ($isStatsAjax) {
|
|
while (ob_get_level()) {
|
|
ob_end_clean();
|
|
}
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
$current_month = (int)date('m');
|
|
$current_year = (int)date('Y');
|
|
$current_fy = ($current_month >= 10) ? $current_year + 1 : $current_year;
|
|
$filter_type = $_GET['filter_type'] ?? 'fiscal';
|
|
$selected_fy = isset($_GET['fiscal_year']) ? (int)$_GET['fiscal_year'] : $current_fy;
|
|
|
|
if ($selected_fy > 2500) {
|
|
$selected_fy -= 543;
|
|
}
|
|
|
|
if ($filter_type === 'fiscal') {
|
|
$start_date = ($selected_fy - 1) . '-10-01';
|
|
$end_date = $selected_fy . '-09-30';
|
|
} else {
|
|
$start_date = $_GET['start_date'] ?? date('Y-m-01');
|
|
$end_date = $_GET['end_date'] ?? date('Y-m-d');
|
|
$s_parts = explode('-', $start_date);
|
|
if (count($s_parts) === 3 && (int)$s_parts[0] > 2500) {
|
|
$s_parts[0] = (int)$s_parts[0] - 543;
|
|
$start_date = implode('-', $s_parts);
|
|
}
|
|
$e_parts = explode('-', $end_date);
|
|
if (count($e_parts) === 3 && (int)$e_parts[0] > 2500) {
|
|
$e_parts[0] = (int)$e_parts[0] - 543;
|
|
$end_date = implode('-', $e_parts);
|
|
}
|
|
}
|
|
|
|
$response = [
|
|
'disease_opd' => [],
|
|
'disease_ipd' => [],
|
|
'disease_ipd_death' => [],
|
|
'disease_er_death' => [],
|
|
'refer_overview' => [],
|
|
'refer_ambulance' => [],
|
|
'refer_ship' => [],
|
|
'refer_air' => [],
|
|
'service_totals' => ['opd' => 0, 'ipd' => 0, 'er' => 0, 'ipd_death' => 0, 'er_death' => 0, 'doa' => 0],
|
|
'service_rights' => ['labels' => [], 'data' => []],
|
|
'nationality' => ['thai' => ['opd' => 0, 'ipd' => 0], 'foreign' => ['opd' => 0, 'ipd' => 0], 'alien' => ['opd' => 0, 'ipd' => 0]],
|
|
'cmi_avg' => 0,
|
|
'adjrw_sum' => 0,
|
|
'trend_cmi' => ['labels' => [], 'data' => []],
|
|
'trend_adjrw' => ['labels' => [], 'data' => []],
|
|
'display_start' => date('d/m/Y', strtotime($start_date)),
|
|
'display_end' => date('d/m/Y', strtotime($end_date)),
|
|
'sql_logs' => [],
|
|
'debug_enabled' => ($show_sql_log_btn === 'Y')
|
|
];
|
|
|
|
$addLog = function ($name, $sql, $error = null) use (&$response) {
|
|
$response['sql_logs'][] = ['name' => $name, 'sql' => trim(preg_replace('/\s+/', ' ', $sql)), 'status' => $error ? 'Error' : 'Success', 'error' => $error];
|
|
};
|
|
$addLog('ระบบตรวจสอบช่วงวันที่ Stats', "ค้นหาข้อมูลตั้งแต่วันที่: $start_date ถึง $end_date");
|
|
|
|
$to_utf8 = function ($str) {
|
|
if (empty($str)) return 'ไม่ระบุ';
|
|
if (!mb_check_encoding($str, 'UTF-8')) return @iconv('TIS-620', 'UTF-8//IGNORE', $str);
|
|
return $str;
|
|
};
|
|
|
|
$thai_months = ['01' => 'ม.ค.', '02' => 'ก.พ.', '03' => 'มี.ค.', '04' => 'เม.ย.', '05' => 'พ.ค.', '06' => 'มิ.ย.', '07' => 'ก.ค.', '08' => 'ส.ค.', '09' => 'ก.ย.', '10' => 'ต.ค.', '11' => 'พ.ย.', '12' => 'ธ.ค.'];
|
|
|
|
if ($pdo) {
|
|
// --- 1. สถิติโรค (10 อันดับโรค) ---
|
|
$queries_disease = [
|
|
'disease_opd' => [$sql_config['stats']['disease_opd'] ?? "SELECT v.icd10, i.name, count(v.vn) as a FROM ovstdiag v LEFT JOIN icd101 i ON v.icd10=i.code WHERE v.vstdate BETWEEN :start AND :end AND v.diagtype='1' GROUP BY v.icd10, i.name ORDER BY a DESC LIMIT 10", "10 อันดับโรคผู้ป่วยนอก (OPD)"],
|
|
'disease_ipd' => [$sql_config['stats']['disease_ipd'] ?? "SELECT v.icd10, i.name, count(v.an) as a FROM iptdiag v LEFT JOIN ipt ip ON v.an=ip.an LEFT JOIN icd101 i ON v.icd10=i.code WHERE ip.dchdate BETWEEN :start AND :end AND v.diagtype='1' GROUP BY v.icd10, i.name ORDER BY a DESC LIMIT 10", "10 อันดับโรคผู้ป่วยใน (IPD)"],
|
|
'disease_ipd_death' => [$sql_config['stats']['disease_ipd_death'] ?? "SELECT v.icd10, i.name, count(v.an) as a FROM iptdiag v LEFT JOIN ipt ip ON v.an=ip.an LEFT JOIN icd101 i ON v.icd10=i.code WHERE ip.dchdate BETWEEN :start AND :end AND v.diagtype='1' AND ip.dchtype IN ('08','09') GROUP BY v.icd10, i.name ORDER BY a DESC LIMIT 10", "10 อันดับเสียชีวิต IPD"],
|
|
'disease_er_death' => [$sql_config['stats']['disease_er_death'] ?? "SELECT v.icd10, i.name, count(v.vn) as a FROM ovstdiag v LEFT JOIN icd101 i ON v.icd10=i.code WHERE v.vstdate BETWEEN :start AND :end AND v.diagtype='1' AND v.vn IN(SELECT vn FROM er_regist WHERE er_dch_type='4') GROUP BY v.icd10, i.name ORDER BY a DESC LIMIT 10", "10 อันดับเสียชีวิตที่ ER"]
|
|
];
|
|
foreach ($queries_disease as $key => $q) {
|
|
try {
|
|
$stmt = $pdo->prepare($q[0]);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$response[$key][] = ['name' => $to_utf8($r['name']) . " ({$r['icd10']})", 'val' => (int)$r['a']];
|
|
}
|
|
$addLog($q[1], $q[0]);
|
|
} catch (\Exception $e) {
|
|
$addLog($q[1], $q[0], $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// --- 2. 10 อันดับโรคส่งต่อ (Refer) ---
|
|
$queries_refer = [
|
|
'refer_overview' => [$sql_config['stats']['refer_overview'] ?? "SELECT a.pdx, ic.name as icd10, count(a.pdx) as pdxcount FROM referout a LEFT JOIN icd101 ic ON a.pdx=ic.code WHERE a.refer_date BETWEEN :start AND :end AND ic.name IS NOT NULL AND a.pdx !='U000' GROUP BY a.pdx ORDER BY pdxcount DESC LIMIT 10", "ภาพรวมโรคส่งต่อ (Refer Out)"],
|
|
'refer_ambulance' => [$sql_config['stats']['refer_ambulance'] ?? "SELECT a.pdx, ic.name as icd10, count(a.pdx) as pdxcount FROM referout a LEFT JOIN icd101 ic ON a.pdx=ic.code WHERE a.refer_date BETWEEN :start AND :end AND ic.name IS NOT NULL AND a.with_ambulance='Y' AND a.pdx !='U000' GROUP BY a.pdx ORDER BY pdxcount DESC LIMIT 10", "10 อันดับโรคส่งต่อด้วยรถพยาบาล"],
|
|
'refer_ship' => [$sql_config['stats']['refer_ship'] ?? "SELECT a.pdx, ic.name as icd10, count(a.pdx) as pdxcount FROM referout a LEFT JOIN opitemrece op ON a.vn=op.vn LEFT JOIN icd101 ic ON a.pdx=ic.code WHERE a.refer_date BETWEEN :start AND :end AND ic.name IS NOT NULL AND a.pdx !='U000' AND op.icode IN ('3162878','3162879','3162880','3162881','3162882','3162883','3162884','3162885') GROUP BY a.pdx ORDER BY pdxcount DESC LIMIT 10", "10 อันดับโรคส่งต่อทางเรือ"],
|
|
'refer_air' => [$sql_config['stats']['refer_air'] ?? "SELECT a.pdx, ic.name as icd10, count(a.pdx) as pdxcount FROM referout a LEFT JOIN opitemrece op ON a.vn=op.vn LEFT JOIN icd101 ic ON a.pdx=ic.code WHERE a.refer_date BETWEEN :start AND :end AND ic.name IS NOT NULL AND a.pdx !='U000' AND op.icode IN ('3162862','3162863','3162864','3162865','3162866','3162867','3162868','3162869','3162870','3162871','3162872','3162873','3162874','3162875','3162876','3162877','3163699','3163891') GROUP BY a.pdx ORDER BY pdxcount DESC LIMIT 10", "10 อันดับโรคส่งต่อทางอากาศ"]
|
|
];
|
|
foreach ($queries_refer as $key => $q) {
|
|
try {
|
|
$stmt = $pdo->prepare($q[0]);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$response[$key][] = ['name' => $to_utf8($r['icd10']), 'val' => (int)$r['pdxcount']];
|
|
}
|
|
$addLog($q[1], $q[0]);
|
|
} catch (\Exception $e) {
|
|
$addLog($q[1], $q[0], $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// --- 3. สถิติรับบริการรวม (Totals) ---
|
|
$queries_totals = [
|
|
'opd' => [$sql_config['stats']['total_opd'] ?? "SELECT count(distinct vn) FROM ovst WHERE vstdate BETWEEN :start AND :end AND an IS NULL", "ยอดรวมผู้ป่วยนอก (OPD)"],
|
|
'ipd' => [$sql_config['stats']['total_ipd'] ?? "SELECT count(distinct an) FROM ipt WHERE dchdate BETWEEN :start AND :end", "ยอดรวมผู้ป่วยใน (IPD)"],
|
|
'er' => [$sql_config['stats']['total_er'] ?? "SELECT count(distinct vn) FROM ovst WHERE vstdate BETWEEN :start AND :end AND vn IN(SELECT vn FROM er_regist)", "ยอดรวมอุบัติเหตุ (ER)"],
|
|
'ipd_death' => [$sql_config['stats']['total_ipd_death'] ?? "SELECT count(distinct an) FROM ipt WHERE dchdate BETWEEN :start AND :end AND dchtype IN('08','09')", "จำนวนการเสียชีวิตผู้ป่วยใน"],
|
|
'er_death' => [$sql_config['stats']['total_er_death'] ?? "SELECT count(distinct vn) FROM ovst WHERE vstdate BETWEEN :start AND :end AND vn IN(SELECT vn FROM er_regist WHERE er_dch_type='4')", "จำนวนการเสียชีวิตที่ ER"],
|
|
'doa' => [$sql_config['stats']['total_doa'] ?? "SELECT count(distinct vn) FROM ovst WHERE vstdate BETWEEN :start AND :end AND vn IN(SELECT vn FROM er_regist WHERE dba='Y')", "เสียชีวิตก่อนถึง รพ. (DOA/DBA)"]
|
|
];
|
|
foreach ($queries_totals as $key => $q) {
|
|
try {
|
|
$stmt = $pdo->prepare($q[0]);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
$response['service_totals'][$key] = (int)$stmt->fetchColumn();
|
|
$addLog($q[1], $q[0]);
|
|
} catch (\Exception $e) {
|
|
$addLog($q[1], $q[0], $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// --- 4. สัดส่วนสิทธิการรักษา ---
|
|
$sql_rights = $sql_config['stats']['service_rights'] ?? "SELECT count(distinct a.vn) as a, c.pttype_spp_name FROM ovst a LEFT JOIN pttype b ON a.pttype=b.pttype LEFT JOIN pttype_spp c ON b.pttype_spp_id=c.pttype_spp_id WHERE a.vstdate BETWEEN :start AND :end GROUP BY b.pttype_spp_id";
|
|
try {
|
|
$stmt = $pdo->prepare($sql_rights);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$response['service_rights']['labels'][] = $to_utf8($r['pttype_spp_name']);
|
|
$response['service_rights']['data'][] = (int)$r['a'];
|
|
}
|
|
$addLog('สัดส่วนสิทธิการรักษา', $sql_rights);
|
|
} catch (\Exception $e) {
|
|
$addLog('สัดส่วนสิทธิการรักษา', $sql_rights, $e->getMessage());
|
|
}
|
|
|
|
// --- 5. สัญชาติ ---
|
|
$queries_nat = [
|
|
'thai_opd' => [$sql_config['stats']['thai_opd'] ?? "SELECT count(distinct a.vn) as a ,AEC_SAMUI FROM ovst a LEFT OUTER JOIN patient p ON a.hn=p.hn LEFT OUTER JOIN nationality n ON p.nationality=n.nationality WHERE a.vstdate BETWEEN :start AND :end AND AEC_SAMUI IN('N','P','T') AND p.nationality IS NOT NULL AND AEC_SAMUI IS NOT NULL GROUP BY AEC_SAMUI", "สัญชาติไทย OPD"],
|
|
'thai_ipd' => [$sql_config['stats']['thai_ipd'] ?? "SELECT count(distinct a.an) as a ,AEC_SAMUI FROM ipt a LEFT OUTER JOIN patient p ON a.hn=p.hn LEFT OUTER JOIN nationality n ON p.nationality=n.nationality WHERE a.dchdate BETWEEN :start AND :end AND AEC_SAMUI IN('N','P','T') AND p.nationality IS NOT NULL AND AEC_SAMUI IS NOT NULL GROUP BY AEC_SAMUI", "สัญชาติไทย IPD"],
|
|
'foreign_opd' => [$sql_config['stats']['foreign_opd'] ?? "SELECT count(distinct a.vn) as a ,AEC_SAMUI FROM ovst a LEFT OUTER JOIN patient p ON a.hn=p.hn LEFT OUTER JOIN nationality n ON p.nationality=n.nationality WHERE a.vstdate BETWEEN :start AND :end AND AEC_SAMUI IN('F') AND p.nationality IS NOT NULL AND AEC_SAMUI IS NOT NULL GROUP BY AEC_SAMUI", "ต่างชาติ OPD"],
|
|
'foreign_ipd' => [$sql_config['stats']['foreign_ipd'] ?? "SELECT count(distinct a.an) as a ,AEC_SAMUI FROM ipt a LEFT OUTER JOIN patient p ON a.hn=p.hn LEFT OUTER JOIN nationality n ON p.nationality=n.nationality WHERE a.dchdate BETWEEN :start AND :end AND AEC_SAMUI IN('F') AND p.nationality IS NOT NULL AND AEC_SAMUI IS NOT NULL GROUP BY AEC_SAMUI", "ต่างชาติ IPD"],
|
|
'alien_opd' => [$sql_config['stats']['alien_opd'] ?? "SELECT count(distinct a.vn) as a ,AEC_SAMUI FROM ovst a LEFT OUTER JOIN patient p ON a.hn=p.hn LEFT OUTER JOIN nationality n ON p.nationality=n.nationality WHERE a.vstdate BETWEEN :start AND :end AND AEC_SAMUI IN('ํY','Y') AND p.nationality IS NOT NULL AND AEC_SAMUI IS NOT NULL GROUP BY AEC_SAMUI", "ต่างด้าว OPD"],
|
|
'alien_ipd' => [$sql_config['stats']['alien_ipd'] ?? "SELECT count(distinct a.an) as a ,AEC_SAMUI FROM ipt a LEFT OUTER JOIN patient p ON a.hn=p.hn LEFT OUTER JOIN nationality n ON p.nationality=n.nationality WHERE a.dchdate BETWEEN :start AND :end AND AEC_SAMUI IN('Y','ํY') AND p.nationality IS NOT NULL AND AEC_SAMUI IS NOT NULL GROUP BY AEC_SAMUI", "ต่างด้าว IPD"]
|
|
];
|
|
|
|
foreach ($queries_nat as $key => $q) {
|
|
try {
|
|
$stmt = $pdo->prepare($q[0]);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
$sum = 0;
|
|
while ($r = $stmt->fetch()) {
|
|
$sum += (int)$r['a'];
|
|
}
|
|
$parts = explode('_', $key);
|
|
$response['nationality'][$parts[0]][$parts[1]] = $sum;
|
|
$addLog($q[1], $q[0]);
|
|
} catch (\Exception $e) {
|
|
$addLog($q[1], $q[0], $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// --- 6. CMI และ AdjRW (ภาพรวม + กราฟรายเดือน) ---
|
|
try {
|
|
$sql_cmi_avg = $sql_config['stats']['cmi_avg'] ?? "SELECT IFNULL((SUM(adjrw)/count(an)),0) FROM ipt WHERE dchdate BETWEEN :start AND :end";
|
|
$stmt = $pdo->prepare($sql_cmi_avg);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
$response['cmi_avg'] = (float)$stmt->fetchColumn();
|
|
|
|
$sql_adjrw_sum = $sql_config['stats']['adjrw_sum'] ?? "SELECT IFNULL(SUM(adjrw),0) FROM ipt WHERE dchdate BETWEEN :start AND :end";
|
|
$stmt = $pdo->prepare($sql_adjrw_sum);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
$response['adjrw_sum'] = (float)$stmt->fetchColumn();
|
|
$addLog('ค่า CMI และ Sum AdjRW ภาพรวม', $sql_cmi_avg . " | " . $sql_adjrw_sum);
|
|
|
|
// กราฟรายเดือน
|
|
$sql_trend_cmi = $sql_config['stats']['trend_cmi'] ?? "SELECT DATE_FORMAT(dchdate, '%Y-%m') as ym, IFNULL((SUM(adjrw)/count(an)),0) as cmi, IFNULL(SUM(adjrw),0) as adjrw FROM ipt WHERE dchdate BETWEEN :start AND :end GROUP BY ym ORDER BY ym";
|
|
$stmt = $pdo->prepare($sql_trend_cmi);
|
|
$stmt->execute(['start' => $start_date, 'end' => $end_date]);
|
|
while ($r = $stmt->fetch()) {
|
|
$parts = explode('-', $r['ym']);
|
|
if (count($parts) == 2) {
|
|
$lb = $thai_months[$parts[1]] . ' ' . substr((int)$parts[0] + 543, 2, 2);
|
|
$response['trend_cmi']['labels'][] = $lb;
|
|
$response['trend_cmi']['data'][] = (float)$r['cmi'];
|
|
$response['trend_adjrw']['labels'][] = $lb;
|
|
$response['trend_adjrw']['data'][] = (float)$r['adjrw'];
|
|
}
|
|
}
|
|
$addLog('กราฟ CMI / AdjRW รายเดือน', "SELECT DATE_FORMAT(dchdate), SUM(adjrw)...");
|
|
} catch (\Exception $e) {
|
|
$addLog('CMI / AdjRW', '-', $e->getMessage());
|
|
}
|
|
}
|
|
|
|
echo "---AJAX_STATS_START---";
|
|
$json_output = @json_encode($response, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
|
if (!$json_output) {
|
|
echo json_encode(['error' => 'JSON Encode Error']);
|
|
} else {
|
|
echo $json_output;
|
|
}
|
|
echo "---AJAX_STATS_END---";
|
|
exit;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ฟอร์มการกรอง
|
|
// -------------------------------------------------------------------------
|
|
$c_m = (int)date('m');
|
|
$c_y = (int)date('Y');
|
|
$c_fy = ($c_m >= 10) ? $c_y + 1 : $c_y;
|
|
$options_html = '';
|
|
for ($i = 0; $i <= 5; $i++) {
|
|
$y = $c_fy - $i;
|
|
$yth = $y + 543;
|
|
$options_html .= "<option value=\"$y\">ปีงบประมาณ $yth</option>";
|
|
}
|
|
$start_d = date('Y-m-01');
|
|
$end_d = date('Y-m-d');
|
|
|
|
$stats_filter_html = '
|
|
<form class="stats-filter-form card-premium p-4 flex flex-col md:flex-row gap-4 items-start md:items-end bg-gradient-to-r from-slate-50 to-white border-l-4 border-l-indigo-500 shadow-sm w-full mb-6">
|
|
<div class="w-full md:w-1/5">
|
|
<label class="block text-xs font-bold text-slate-600 mb-1.5"><i class="fa-solid fa-filter mr-1 text-indigo-500"></i> รูปแบบข้อมูล</label>
|
|
<select name="filter_type" class="w-full bg-white border border-slate-300 text-slate-700 rounded-lg p-2.5 text-sm outline-none shadow-sm cursor-pointer hover:border-indigo-400 transition-colors" onchange="toggleStatsFilterType(this)">
|
|
<option value="fiscal" selected>ปีงบประมาณ</option>
|
|
<option value="date">ช่วงวันที่กำหนดเอง</option>
|
|
</select>
|
|
</div>
|
|
<div class="w-full md:w-1/4 stats_fiscal_wrapper">
|
|
<label class="block text-xs font-bold text-slate-600 mb-1.5"><i class="fa-solid fa-calendar-days mr-1 text-indigo-500"></i> ปีงบประมาณ</label>
|
|
<select name="fiscal_year" class="w-full bg-white border border-slate-300 text-slate-700 rounded-lg p-2.5 text-sm outline-none shadow-sm cursor-pointer hover:border-indigo-400 transition-colors">' . $options_html . '</select>
|
|
</div>
|
|
<div class="w-full md:w-1/2 flex gap-3 stats_date_wrapper" style="display:none;">
|
|
<div class="flex-1"><label class="block text-xs font-bold text-slate-600 mb-1.5">ตั้งแต่วันที่</label><input type="date" name="start_date" class="w-full bg-white border border-slate-300 text-slate-700 rounded-lg p-2.5 text-sm outline-none shadow-sm cursor-pointer" value="' . $start_d . '"></div>
|
|
<div class="flex-1"><label class="block text-xs font-bold text-slate-600 mb-1.5">ถึงวันที่</label><input type="date" name="end_date" class="w-full bg-white border border-slate-300 text-slate-700 rounded-lg p-2.5 text-sm outline-none shadow-sm cursor-pointer" value="' . $end_d . '"></div>
|
|
</div>
|
|
<button type="submit" class="w-full md:w-[15%] bg-indigo-600 hover:bg-indigo-700 active:scale-[0.98] text-white font-bold py-2.5 px-4 rounded-lg transition-all shadow-md shadow-indigo-500/20 h-[42px] flex items-center justify-center">
|
|
<i class="fa-solid fa-magnifying-glass mr-2"></i> ค้นหา
|
|
</button>
|
|
</form>
|
|
';
|
|
?>
|
|
<script>
|
|
function toggleStatsFilterType(selectElement) {
|
|
const form = selectElement.closest('form');
|
|
const type = selectElement.value;
|
|
if (type === 'fiscal') {
|
|
form.querySelector('.stats_fiscal_wrapper').style.display = 'block';
|
|
form.querySelector('.stats_date_wrapper').style.display = 'none';
|
|
} else {
|
|
form.querySelector('.stats_fiscal_wrapper').style.display = 'none';
|
|
form.querySelector('.stats_date_wrapper').style.display = 'flex';
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<?php if ($show_sql_log_btn === 'Y'): ?>
|
|
<button onclick="toggleStatsSqlLogModal()" class="fixed bottom-6 left-[30%] bg-slate-800 text-white px-4 py-3 rounded-full shadow-[0_10px_25px_-5px_rgba(0,0,0,0.5)] z-50 hover:bg-slate-700 hover:scale-105 transition-all flex items-center gap-2 font-bold text-sm border border-slate-600"><i class="fa-solid fa-bug text-emerald-400"></i> ดู SQL Log (สถิติ)</button>
|
|
<div id="stats-sql-log-modal" class="fixed inset-0 bg-slate-900/80 backdrop-blur-sm z-[110] hidden flex-col items-center justify-center p-4">
|
|
<div class="bg-slate-900 border border-slate-700 rounded-xl shadow-2xl w-full max-w-5xl h-[85vh] flex flex-col overflow-hidden animate-fade-in relative">
|
|
<div class="p-4 border-b border-slate-700 flex justify-between items-center bg-slate-800 shrink-0">
|
|
<h3 class="font-bold text-white"><i class="fa-solid fa-terminal text-indigo-400 mr-2"></i> บันทึกการคิวรีฐานข้อมูล ข้อมูลสถิติ (SQL Logs)</h3>
|
|
<button onclick="toggleStatsSqlLogModal()" class="text-slate-400 hover:text-red-400 bg-slate-700 hover:bg-slate-600 w-8 h-8 rounded-lg flex items-center justify-center transition-colors"><i class="fa-solid fa-xmark text-lg"></i></button>
|
|
</div>
|
|
<div class="p-5 overflow-y-auto flex-1 font-mono text-[11px] md:text-xs text-slate-300 leading-relaxed custom-scrollbar block" id="stats-sql-log-content">
|
|
<div class="text-slate-500 text-center py-10"><i class="fa-solid fa-hourglass-half fa-spin mr-2"></i>รอผลการค้นหาข้อมูล...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function toggleStatsSqlLogModal() {
|
|
const modal = document.getElementById('stats-sql-log-modal');
|
|
if (modal.classList.contains('hidden')) {
|
|
modal.classList.remove('hidden');
|
|
modal.classList.add('flex');
|
|
} else {
|
|
modal.classList.remove('flex');
|
|
modal.classList.add('hidden');
|
|
}
|
|
}
|
|
</script>
|
|
<?php endif; ?>
|
|
|
|
<!-- INCLUDE FILES -->
|
|
<?php include __DIR__ . '/tab_stats_1_disease.php'; ?>
|
|
<?php include __DIR__ . '/tab_stats_2_service.php'; ?>
|
|
|
|
<!-- JAVASCRIPT -->
|
|
<script>
|
|
const safeSetTextStats = (id, text) => {
|
|
const el = document.getElementById(id);
|
|
if (el) el.innerText = text;
|
|
};
|
|
|
|
async function fetchStatsRealData(formElement = null) {
|
|
let params;
|
|
if (formElement) {
|
|
params = new URLSearchParams(new FormData(formElement));
|
|
} else {
|
|
const activeForm = document.querySelector('.tab-content:not(.hidden) .stats-filter-form');
|
|
if (activeForm) params = new URLSearchParams(new FormData(activeForm));
|
|
else params = new URLSearchParams();
|
|
}
|
|
params.append('ajax_stats', '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',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
});
|
|
const text = await response.text();
|
|
let data = {};
|
|
if (text.includes('---AJAX_STATS_START---')) {
|
|
data = JSON.parse(text.split('---AJAX_STATS_START---')[1].split('---AJAX_STATS_END---')[0]);
|
|
} else {
|
|
throw new Error("รูปแบบข้อมูลผิดพลาด หรือถูกระบบอื่นดักจับการทำงาน");
|
|
}
|
|
|
|
if (!data) throw new Error("Invalid Data Format");
|
|
|
|
// --- SQL Logs ---
|
|
if (data.sql_logs) {
|
|
let hasError = false;
|
|
let errorDetails = [];
|
|
const logContent = document.getElementById('stats-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 (สถิติ):\n- " + errorDetails.join("\n- "));
|
|
}
|
|
|
|
// Helper for Lists
|
|
const updateList = (id, arr) => {
|
|
const el = document.getElementById(id);
|
|
if (el) {
|
|
if (!arr || arr.length === 0) el.innerHTML = '<li class="text-center text-slate-500 py-4 font-medium"><i class="fa-solid fa-box-open mr-2 text-slate-300"></i>ไม่พบข้อมูล</li>';
|
|
else el.innerHTML = arr.map((d, i) => `<li class="flex justify-between border-b border-slate-100 pb-1.5 mb-1.5 hover:bg-slate-50 transition-colors px-1"><span class="truncate pr-2 font-medium text-slate-700">${i+1}. ${d.name}</span><span class="font-extrabold text-blue-600">${d.val.toLocaleString()}</span></li>`).join('');
|
|
}
|
|
};
|
|
|
|
// --- 1. สถิติโรค ---
|
|
updateList('list-disease-opd-top', data.disease_opd);
|
|
updateList('list-disease-ipd-top', data.disease_ipd);
|
|
updateList('list-disease-ipd-death', data.disease_ipd_death);
|
|
updateList('list-disease-er-death', data.disease_er_death);
|
|
|
|
// --- 2. Refer ---
|
|
updateList('list-disease-refer-overview', data.refer_overview);
|
|
updateList('list-disease-refer-ambulance', data.refer_ambulance);
|
|
updateList('list-disease-refer-ship', data.refer_ship);
|
|
updateList('list-disease-refer-air', data.refer_air);
|
|
|
|
// --- 3. ยอดบริการรวม ---
|
|
safeSetTextStats('stats-opd-total', data.service_totals.opd.toLocaleString());
|
|
safeSetTextStats('stats-ipd-total', data.service_totals.ipd.toLocaleString());
|
|
safeSetTextStats('stats-er-total', data.service_totals.er.toLocaleString());
|
|
safeSetTextStats('stats-ipd-death', data.service_totals.ipd_death.toLocaleString());
|
|
safeSetTextStats('stats-er-death', data.service_totals.er_death.toLocaleString());
|
|
safeSetTextStats('stats-doa', data.service_totals.doa.toLocaleString());
|
|
|
|
let ipdTotal = data.service_totals.ipd || 1;
|
|
safeSetTextStats('stats-ipd-death-pct', ((data.service_totals.ipd_death / ipdTotal) * 100).toFixed(2));
|
|
|
|
// --- 4. Service Type & Rights Charts ---
|
|
const updatePieChart = (id, labels, values, colors) => {
|
|
const el = document.getElementById(id);
|
|
if (el) {
|
|
const ctx = el.getContext('2d');
|
|
if (window.appCharts[id]) window.appCharts[id].destroy();
|
|
if (values.reduce((a, b) => a + b, 0) > 0) {
|
|
window.appCharts[id] = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
data: values,
|
|
backgroundColor: colors,
|
|
borderWidth: 2,
|
|
borderColor: '#ffffff'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
cutout: '55%',
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
datalabels: {
|
|
color: '#ffffff',
|
|
font: {
|
|
weight: 'bold',
|
|
size: 12
|
|
},
|
|
formatter: (v, ctx) => {
|
|
let s = 0;
|
|
ctx.chart.data.datasets[0].data.map(d => s += d);
|
|
return (v * 100 / s).toFixed(1) + '%';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
window.appCharts[id] = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['ไม่มีข้อมูล'],
|
|
datasets: [{
|
|
data: [1],
|
|
backgroundColor: ['#e2e8f0']
|
|
}]
|
|
},
|
|
options: {
|
|
plugins: {
|
|
datalabels: {
|
|
display: false
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
updatePieChart('serviceTypeChart', ['OPD', 'IPD', 'ER'], [data.service_totals.opd, data.service_totals.ipd, data.service_totals.er], ['#3b82f6', '#14b8a6', '#f43f5e']);
|
|
updatePieChart('serviceRightChart', data.service_rights.labels, data.service_rights.data, ['#ec4899', '#a855f7', '#f59e0b', '#94a3b8', '#10b981', '#6366f1']);
|
|
|
|
// --- 5. สัญชาติ ---
|
|
const nat = data.nationality;
|
|
safeSetTextStats('stats-nat-thai-total', (nat.thai.opd + nat.thai.ipd).toLocaleString());
|
|
safeSetTextStats('stats-nat-thai-opd', nat.thai.opd.toLocaleString());
|
|
safeSetTextStats('stats-nat-thai-ipd', nat.thai.ipd.toLocaleString());
|
|
|
|
safeSetTextStats('stats-nat-foreign-total', (nat.foreign.opd + nat.foreign.ipd).toLocaleString());
|
|
safeSetTextStats('stats-nat-foreign-opd', nat.foreign.opd.toLocaleString());
|
|
safeSetTextStats('stats-nat-foreign-ipd', nat.foreign.ipd.toLocaleString());
|
|
|
|
safeSetTextStats('stats-nat-alien-total', (nat.alien.opd + nat.alien.ipd).toLocaleString());
|
|
safeSetTextStats('stats-nat-alien-opd', nat.alien.opd.toLocaleString());
|
|
safeSetTextStats('stats-nat-alien-ipd', nat.alien.ipd.toLocaleString());
|
|
|
|
// --- 6. CMI & AdjRW ---
|
|
safeSetTextStats('stats-cmi-avg', data.cmi_avg.toFixed(2));
|
|
safeSetTextStats('stats-adjrw-sum', data.adjrw_sum.toLocaleString());
|
|
|
|
const updateLineChart = (id, labelText, dataObj, color, isFloat) => {
|
|
const el = document.getElementById(id);
|
|
if (el && dataObj.labels.length > 0) {
|
|
const ctx = el.getContext('2d');
|
|
if (window.appCharts[id]) window.appCharts[id].destroy();
|
|
let grad = ctx.createLinearGradient(0, 0, 0, 300);
|
|
grad.addColorStop(0, color.replace('1)', '0.4)'));
|
|
grad.addColorStop(1, color.replace('1)', '0)'));
|
|
window.appCharts[id] = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: dataObj.labels,
|
|
datasets: [{
|
|
label: labelText,
|
|
data: dataObj.data,
|
|
borderColor: color.replace('0.4)', '1)'),
|
|
backgroundColor: grad,
|
|
borderWidth: 3,
|
|
pointBackgroundColor: '#ffffff',
|
|
pointBorderColor: color.replace('0.4)', '1)'),
|
|
pointBorderWidth: 2,
|
|
pointRadius: 4,
|
|
fill: true,
|
|
tension: 0.4,
|
|
datalabels: {
|
|
align: 'top',
|
|
anchor: 'end',
|
|
offset: 6,
|
|
color: color.replace('0.4)', '1)'),
|
|
font: {
|
|
weight: 'bold'
|
|
},
|
|
formatter: v => isFloat ? Number(v).toFixed(2) : Number(v).toLocaleString()
|
|
}
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
display: false
|
|
}
|
|
},
|
|
y: {
|
|
display: false,
|
|
min: Math.min(...dataObj.data) * 0.8,
|
|
suggestedMax: Math.max(...dataObj.data) * 1.2
|
|
}
|
|
},
|
|
layout: {
|
|
padding: {
|
|
top: 25
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
updateLineChart('cmiChartTab', 'ค่า CMI', data.trend_cmi, 'rgba(14, 165, 233, 1)', true);
|
|
updateLineChart('adjrwChartTab', 'Sum AdjRW', data.trend_adjrw, 'rgba(59, 130, 246, 1)', false);
|
|
|
|
} catch (err) {
|
|
console.error("Fetch Data Error:", err);
|
|
alert("ไม่สามารถดึงข้อมูลได้ กรุณาตรวจสอบการเชื่อมต่อฐานข้อมูล หรือคำสั่ง SQL\nรายละเอียด: " + err.message);
|
|
} finally {
|
|
if (overlay) {
|
|
setTimeout(() => {
|
|
overlay.classList.remove('flex');
|
|
overlay.classList.add('hidden');
|
|
}, 500);
|
|
}
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (!window.appCharts) window.appCharts = {};
|
|
document.querySelectorAll('.stats-filter-form').forEach(form => {
|
|
form.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
fetchStatsRealData(e.target);
|
|
});
|
|
});
|
|
});
|
|
</script>
|