Files
gravity/hosxp-webservice/views/patient_detail.php
T
2026-09-16 23:20:08 +07:00

702 lines
50 KiB
PHP

<?php
// views/patient_detail.php
require_once("../core/auth.php");
require_once("../config/db.php");
require_once("../core/utils.php");
$hn_raw = $_POST['hn'] ?? $_GET['hn'] ?? '';
$vdate_raw = $_POST['vdate'] ?? $_GET['vdate'] ?? '';
$nn_raw = $_POST['nn'] ?? $_GET['nn'] ?? '';
$hn = !empty($hn_raw) ? decrypt_param($hn_raw) : '';
$vdate = !empty($vdate_raw) ? decrypt_param($vdate_raw) : '';
$nn = !empty($nn_raw) ? decrypt_param($nn_raw) : '';
// Filters
$filter_type = $_POST['filter_type'] ?? $_GET['filter_type'] ?? 'all';
$start_date = $_POST['start_date'] ?? $_GET['start_date'] ?? '';
$end_date = $_POST['end_date'] ?? $_GET['end_date'] ?? '';
$filter_ipd = $_POST['filter_ipd'] ?? $_GET['filter_ipd'] ?? '';
// Fetch Patient Info
$patient_name = "ไม่พบข้อมูล";
$is_ipd_visit = false;
$upcoming_appointments = [];
if ($hn) {
$sql_pt = "SELECT * FROM patient WHERE hn=?";
$stmt = $conn1->prepare($sql_pt);
if($stmt) {
$stmt->bind_param("s", $hn);
$stmt->execute();
$res = $stmt->get_result();
if($row = $res->fetch_assoc()) {
$patient_name = $row['pname'] . $row['fname'] . " " . $row['lname'];
}
$stmt->close();
}
// Auto-select latest visit if $vdate is empty so user doesn't see a blank page
if (empty($vdate)) {
$sql_latest = "SELECT vstdate, vn, an FROM ovst WHERE hn=? ORDER BY vstdate DESC LIMIT 1";
$stmt_latest = $conn1->prepare($sql_latest);
if($stmt_latest) {
$stmt_latest->bind_param("s", $hn);
$stmt_latest->execute();
$res_latest = $stmt_latest->get_result();
if($row_latest = $res_latest->fetch_assoc()) {
$vdate = $row_latest['vstdate'];
$nn = ($row_latest['an'] != NULL) ? $row_latest['an'] : $row_latest['vn'];
}
$stmt_latest->close();
}
}
// Log that the patient was viewed
if (!empty($_SESSION['account'])) {
system_log($conn2, $_SESSION['account'], 'VIEW_PATIENT', ['hn' => $hn, 'vdate' => $vdate, 'nn' => $nn]);
}
// Check if current visit is IPD
if (!empty($vdate) && !empty($nn)) {
$sql_check_ipd = "SELECT an FROM ovst WHERE hn=? AND vstdate=? AND (vn=? OR an=?)";
$stmt_check = $conn1->prepare($sql_check_ipd);
if($stmt_check) {
$stmt_check->bind_param("ssss", $hn, $vdate, $nn, $nn);
$stmt_check->execute();
$res_check = $stmt_check->get_result();
if($row_check = $res_check->fetch_assoc()) {
if (!empty($row_check['an'])) {
$is_ipd_visit = true;
}
}
$stmt_check->close();
}
}
// Fetch Upcoming Appointments
$upcoming_appointments = [];
try {
$sql_app = "SELECT a.nextdate, a.nexttime, c.name as clinic_name
FROM oapp a
LEFT OUTER JOIN clinic c ON a.clinic = c.clinic
LEFT OUTER JOIN patient p ON a.hn = p.hn
WHERE DATEDIFF(a.nextdate, CURDATE()) > 1 AND a.hn = ?
ORDER BY a.nextdate ASC, a.nexttime ASC";
$stmt_app = $conn1->prepare($sql_app);
if($stmt_app) {
$stmt_app->bind_param("s", $hn);
$stmt_app->execute();
$res_app = $stmt_app->get_result();
while($row_app = $res_app->fetch_assoc()) {
$upcoming_appointments[] = $row_app;
}
$stmt_app->close();
}
} catch (Exception $e) {
// Log the error but don't break the page
error_log("Failed to fetch appointments: " . $e->getMessage());
}
// Fetch Vaccines
$vaccines_by_group = [];
try {
$app_settings = get_app_settings();
$vaccine_sql_template = $app_settings['vaccine_sql'] ?? '';
if (!empty($vaccine_sql_template)) {
// Protect against basic SQL injection while supporting the arbitrary query design
$safe_hn = $conn1->real_escape_string($hn);
$vaccine_sql = str_replace("'ระบุ_HN'", "'" . $safe_hn . "'", $vaccine_sql_template);
$res_vax = $conn1->query($vaccine_sql);
if ($res_vax) {
while($row_vax = $res_vax->fetch_assoc()) {
$group = $row_vax['vaccine_group'] ?? 'อื่นๆ (Others)';
if (!isset($vaccines_by_group[$group])) {
$vaccines_by_group[$group] = [];
}
$vaccines_by_group[$group][] = $row_vax;
}
}
}
} catch (Exception $e) {
error_log("Failed to fetch vaccines: " . $e->getMessage());
}
}
$bg_class = $is_ipd_visit ? "bg-gradient-to-br from-yellow-50 to-amber-100" : "bg-gradient-to-br from-emerald-50 to-emerald-100";
ob_start();
?>
<!DOCTYPE html>
<html lang="th">
<head>
<?php require_once("../components/head.php"); ?>
</head>
<body class="<?php echo $bg_class; ?> text-slate-800 font-sans antialiased min-h-screen flex">
<?php require_once("../components/layout_sidebar.php"); ?>
<main class="flex-1 flex flex-col min-w-0">
<?php require_once("../components/layout_topbar.php"); ?>
<div class="p-4 md:p-8 lg:p-10 flex-1 overflow-y-auto">
<div class="max-w-screen-2xl mx-auto flex flex-col xl:flex-row gap-8 animate-enter">
<!-- Left: Side Panel -->
<div class="w-full xl:w-80 flex-shrink-0 flex flex-col gap-6">
<!-- Visit History List -->
<div class="glass-card flex flex-col h-[500px] max-h-[calc(100vh-16rem)]">
<div class="p-6 pb-4 border-b border-emerald-500/10">
<div class="flex justify-between items-start">
<div>
<h3 class="font-bold text-lg text-emerald-600">ประวัติรับบริการ</h3>
<p class="text-sm text-slate-500 mt-1">HN: <?php echo htmlspecialchars($hn); ?></p>
</div>
</div>
<!-- Filter Form -->
<form method="POST" action="patient_detail.php" class="mt-4 border-t border-emerald-500/10 pt-3">
<input type="hidden" name="hn" value="<?php echo htmlspecialchars(encrypt_param($hn)); ?>">
<input type="hidden" name="vdate" value="<?php echo htmlspecialchars(encrypt_param($vdate)); ?>">
<input type="hidden" name="nn" value="<?php echo htmlspecialchars(encrypt_param($nn)); ?>">
<div class="mb-2">
<label class="flex items-center gap-2 mb-2 text-xs text-slate-700 font-medium cursor-pointer hover:bg-slate-50 p-1.5 rounded-md transition-colors">
<input type="checkbox" name="filter_ipd" value="1" <?php echo !empty($filter_ipd) ? 'checked' : ''; ?> class="rounded text-emerald-500 focus:ring-emerald-500 border-slate-300" onchange="this.form.submit()">
<span>แสดงเฉพาะผู้ป่วยใน (IPD)</span>
</label>
<select name="filter_type" class="w-full text-xs px-2 py-1.5 border border-slate-200 rounded-md focus:ring-1 focus:ring-emerald-500 focus:border-emerald-500 bg-white text-slate-700 outline-none" onchange="if(this.value === 'custom') { document.getElementById('custom_date_range').classList.remove('hidden'); } else { document.getElementById('custom_date_range').classList.add('hidden'); this.form.submit(); }">
<option value="all" <?php echo $filter_type === 'all' ? 'selected' : ''; ?>>รายการล่าสุด (50 ครั้ง)</option>
<option value="1_year" <?php echo $filter_type === '1_year' ? 'selected' : ''; ?>>1 ปีย้อนหลัง</option>
<option value="3_years" <?php echo $filter_type === '3_years' ? 'selected' : ''; ?>>3 ปีย้อนหลัง</option>
<option value="5_years" <?php echo $filter_type === '5_years' ? 'selected' : ''; ?>>5 ปีย้อนหลัง</option>
<option value="all_time" <?php echo $filter_type === 'all_time' ? 'selected' : ''; ?>>ทั้งหมด (ประวัติทั้งหมด)</option>
<option value="custom" <?php echo $filter_type === 'custom' ? 'selected' : ''; ?>>เลือกช่วงวันที่...</option>
</select>
</div>
<div id="custom_date_range" class="<?php echo $filter_type === 'custom' ? '' : 'hidden'; ?> space-y-2">
<div class="flex gap-2">
<input type="date" name="start_date" value="<?php echo htmlspecialchars($start_date); ?>" class="w-1/2 text-[11px] px-2 py-1 border border-slate-200 rounded-md focus:ring-1 focus:ring-emerald-500 outline-none text-slate-600">
<input type="date" name="end_date" value="<?php echo htmlspecialchars($end_date); ?>" class="w-1/2 text-[11px] px-2 py-1 border border-slate-200 rounded-md focus:ring-1 focus:ring-emerald-500 outline-none text-slate-600">
</div>
<button type="submit" class="w-full text-xs bg-emerald-50 text-emerald-700 font-semibold py-1.5 rounded-md border border-emerald-200 hover:bg-emerald-100 transition-colors shadow-sm">
กรองข้อมูล
</button>
</div>
</form>
</div>
<ul class="overflow-y-auto flex-1 p-0 m-0 list-none divide-y divide-emerald-500/10 custom-scrollbar">
<?php
if($hn) {
$where_clause = "hn=?";
$params = [$hn];
$types = "s";
$limit_clause = "LIMIT 50";
if (!empty($filter_ipd)) {
$where_clause .= " AND an IS NOT NULL AND an != ''";
}
if ($filter_type === '1_year') {
$where_clause .= " AND vstdate >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)";
$limit_clause = "";
} else if ($filter_type === '3_years') {
$where_clause .= " AND vstdate >= DATE_SUB(CURDATE(), INTERVAL 3 YEAR)";
$limit_clause = "";
} else if ($filter_type === '5_years') {
$where_clause .= " AND vstdate >= DATE_SUB(CURDATE(), INTERVAL 5 YEAR)";
$limit_clause = "";
} else if ($filter_type === 'all_time') {
$limit_clause = "";
} else if ($filter_type === 'custom' && !empty($start_date) && !empty($end_date)) {
$where_clause .= " AND vstdate BETWEEN ? AND ?";
$params[] = $start_date;
$params[] = $end_date;
$types .= "ss";
$limit_clause = "";
}
$sql3 = "SELECT * FROM ovst WHERE $where_clause ORDER BY vstdate DESC $limit_clause";
$stmt3 = $conn1->prepare($sql3);
if($stmt3) {
$stmt3->bind_param($types, ...$params);
$stmt3->execute();
$res3 = $stmt3->get_result();
if ($res3->num_rows === 0) {
echo "<div class='text-center p-6 text-sm text-slate-400'>ไม่พบประวัติในช่วงเวลาที่เลือก</div>";
}
$all_visits = [];
$selected_visit = null;
while ($row3 = $res3->fetch_assoc()) {
$current_nn_val = ($row3['an'] != NULL) ? $row3['an'] : $row3['vn'];
if ($vdate == $row3['vstdate'] && $nn == $current_nn_val) {
$selected_visit = $row3;
} else {
$all_visits[] = $row3;
}
}
// Check if a visit was selected but not found in the list (e.g. it's older than the limit/filter)
if ($vdate && $nn && !$selected_visit) {
$sql_missing = "SELECT * FROM ovst WHERE hn=? AND vstdate=? AND (vn=? OR an=?)";
$stmt_m = $conn1->prepare($sql_missing);
if($stmt_m) {
$stmt_m->bind_param("ssss", $hn, $vdate, $nn, $nn);
$stmt_m->execute();
$res_m = $stmt_m->get_result();
if ($row_m = $res_m->fetch_assoc()) {
$selected_visit = $row_m;
}
$stmt_m->close();
}
}
// Render Selected Visit First (Pinned at Top)
if ($selected_visit) {
$nn_val = ($selected_visit['an'] != NULL) ? $selected_visit['an'] : $selected_visit['vn'];
$enc_hn = htmlspecialchars(encrypt_param($hn));
$enc_vdate = htmlspecialchars(encrypt_param($selected_visit['vstdate']));
$enc_nn = htmlspecialchars(encrypt_param($nn_val));
echo "<div class='p-3 bg-slate-50/95 sticky top-0 z-10 border-b border-emerald-500/10 backdrop-blur-xl'>";
echo "<form action='patient_detail.php' method='POST' class='m-0'>";
echo "<input type='hidden' name='hn' value='{$enc_hn}'>";
echo "<input type='hidden' name='vdate' value='{$enc_vdate}'>";
echo "<input type='hidden' name='nn' value='{$enc_nn}'>";
echo "<button type='button' class='w-full flex items-center justify-between p-4 bg-white border-2 border-emerald-400 rounded-xl shadow-[0_4px_15px_rgba(16,185,129,0.15)] ring-4 ring-emerald-500/10 transition-all cursor-default'>";
echo "<div class='flex flex-col items-start'>";
echo "<span class='text-xs font-semibold text-emerald-500 mb-0.5'>รายการที่กำลังดู</span>";
echo "<span class='text-base font-bold text-emerald-800'>" . thai_date2($selected_visit['vstdate']) . "</span>";
echo "</div>";
echo "<div class='flex gap-2'>";
if ($selected_visit['an'] != NULL) { echo '<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-amber-100 text-amber-800 border border-amber-200">IPD</span>'; }
echo '<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold bg-emerald-500 text-white shadow-sm">กำลังดูข้อมูล</span>';
echo "</div></button></form></div>";
}
// Render the rest of the visits
foreach ($all_visits as $row3) {
$nn_val = ($row3['an'] != NULL) ? $row3['an'] : $row3['vn'];
$enc_hn = htmlspecialchars(encrypt_param($hn));
$enc_vdate = htmlspecialchars(encrypt_param($row3['vstdate']));
$enc_nn = htmlspecialchars(encrypt_param($nn_val));
$filter_inputs = "<input type='hidden' name='filter_type' value='" . htmlspecialchars($filter_type) . "'>" .
"<input type='hidden' name='filter_ipd' value='" . htmlspecialchars($filter_ipd) . "'>" .
"<input type='hidden' name='start_date' value='" . htmlspecialchars($start_date) . "'>" .
"<input type='hidden' name='end_date' value='" . htmlspecialchars($end_date) . "'>";
echo "<form action='patient_detail.php' method='POST' class='m-0'>";
echo "<input type='hidden' name='hn' value='{$enc_hn}'>";
echo "<input type='hidden' name='vdate' value='{$enc_vdate}'>";
echo "<input type='hidden' name='nn' value='{$enc_nn}'>";
echo $filter_inputs;
echo "<button type='submit' class='w-full flex items-center justify-between p-4 transition-all duration-300 hover:bg-white/60 border-l-4 border-transparent hover:border-emerald-300'>";
echo "<span class='text-sm text-slate-600 font-medium'>" . thai_date2($row3['vstdate']) . "</span>";
echo "<div class='flex gap-2'>";
if ($row3['an'] != NULL) { echo '<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800">IPD</span>'; }
echo "</div></button></form>";
}
$stmt3->close();
}
}
?>
</ul>
</div>
<!-- Search History -->
<?php if($vdate && $nn): ?>
<div class="glass-card flex flex-col h-[380px]">
<div class="p-6 pb-4 border-b border-emerald-500/10">
<h3 class="font-bold text-lg text-emerald-600 flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
ค้นหารายการ
</h3>
<div class="mt-3 relative">
<input type="text" id="historySearchInput" placeholder="พิมพ์ชื่อยา, แล็บ, เอกซเรย์..." class="w-full pl-10 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 transition-all">
<div class="absolute left-3 top-2.5 text-slate-400">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
</div>
<!-- Loader -->
<div id="historySearchLoader" class="absolute right-3 top-2.5 hidden">
<svg class="w-4 h-4 text-emerald-500 animate-spin" 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-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
</div>
</div>
</div>
<div class="flex-1 overflow-y-auto p-4 custom-scrollbar" id="historySearchResults">
<div class="text-center text-slate-400 text-sm py-8">
พิมพ์คำค้นหาเพื่อเริ่มค้นหา
</div>
</div>
</div>
<?php endif; ?>
</div>
<!-- Right: Patient Detail -->
<div class="flex-1 min-w-0 flex flex-col gap-8">
<?php if($hn && $vdate && $nn): ?>
<div class="flex flex-col gap-6">
<div class="glass-card relative z-50">
<div class="glass-header flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-b-0">
<div>
<h2 class="text-2xl font-bold text-slate-800"><?php echo htmlspecialchars($patient_name); ?></h2>
<div class="flex flex-wrap gap-4 mt-2 text-sm text-slate-500 font-medium">
<span class="flex items-center gap-1"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg> วันที่รับบริการ: <?php echo thai_date_full($vdate); ?></span>
<span class="flex items-center gap-1"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"></path></svg> VN/AN: <?php echo htmlspecialchars($nn); ?></span>
</div>
</div>
<div class="shrink-0 flex flex-wrap items-center gap-3">
<?php if (!empty($upcoming_appointments)): ?>
<div class="relative group">
<button class="inline-flex items-center gap-2 px-3 py-2 bg-amber-50 text-amber-600 rounded-lg border border-amber-200 hover:bg-amber-100 transition-colors shadow-sm focus:outline-none">
<svg class="w-5 h-5 animate-ring origin-top" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path></svg>
<span class="font-bold text-sm">นัดหมาย (<?php echo count($upcoming_appointments); ?>)</span>
</button>
<!-- Dropdown -->
<div class="absolute right-0 top-full pt-2 w-72 sm:w-80 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 transform origin-top-right scale-95 group-hover:scale-100">
<div class="bg-white rounded-xl shadow-xl border border-slate-100">
<div class="p-3 border-b border-slate-100 bg-amber-50/50 rounded-t-xl">
<h3 class="font-bold text-amber-800 flex items-center gap-2 text-sm">
<svg class="w-4 h-4 text-amber-500" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>
รายการนัดหมายล่วงหน้า
</h3>
</div>
<div class="max-h-64 overflow-y-auto p-1.5 custom-scrollbar">
<?php foreach($upcoming_appointments as $app): ?>
<div class="p-3 hover:bg-slate-50 rounded-lg border-b border-slate-50 last:border-0 transition-colors">
<div class="text-sm font-bold text-slate-700 flex items-center gap-2 mb-1">
<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
<?php echo $app['nextdate'] ? thai_date2($app['nextdate']) : 'ไม่ระบุวันที่'; ?>
</div>
<div class="flex items-center gap-3 mt-2">
<div class="text-xs text-slate-600 flex items-center gap-1">
<svg class="w-3.5 h-3.5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<?php echo $app['nexttime'] ? substr($app['nexttime'], 0, 5) . ' น.' : 'ไม่ระบุเวลา'; ?>
</div>
<div class="text-xs text-indigo-600 font-medium bg-indigo-50 px-2 py-0.5 rounded border border-indigo-100 flex-1 truncate">
<?php echo htmlspecialchars($app['clinic_name'] ?? 'ไม่ระบุคลินิก'); ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if (!empty($vaccines_by_group)):
$total_vax = 0;
foreach($vaccines_by_group as $vax_list) { $total_vax += count($vax_list); }
?>
<div class="relative group">
<button class="inline-flex items-center gap-2 px-3 py-2 bg-pink-50 text-pink-600 rounded-lg border border-pink-200 hover:bg-pink-100 transition-colors shadow-sm focus:outline-none">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg>
<span class="font-bold text-sm">วัคซีน (<?php echo $total_vax; ?>)</span>
</button>
<!-- Dropdown -->
<div class="absolute right-0 top-full pt-2 w-80 sm:w-96 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 transform origin-top-right scale-95 group-hover:scale-100">
<div class="bg-white rounded-xl shadow-xl border border-slate-100">
<div class="p-3 border-b border-slate-100 bg-pink-50/50 rounded-t-xl">
<h3 class="font-bold text-pink-800 flex items-center gap-2 text-sm">
<svg class="w-4 h-4 text-pink-500" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>
ประวัติการรับวัคซีน
</h3>
</div>
<div class="max-h-80 overflow-y-auto p-1.5 custom-scrollbar space-y-2">
<?php foreach($vaccines_by_group as $group_name => $vax_list): ?>
<div class="bg-slate-50 rounded-lg p-2">
<div class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-2 px-1 flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-pink-400"></span>
<?php echo htmlspecialchars($group_name); ?>
</div>
<div class="space-y-1.5">
<?php foreach($vax_list as $vax): ?>
<div class="bg-white p-2.5 rounded border border-slate-100 shadow-sm hover:border-pink-200 transition-colors">
<div class="flex items-start justify-between gap-2">
<div class="text-sm font-bold text-slate-700 leading-tight">
<?php echo htmlspecialchars($vax['vaccine_name'] ?? '-'); ?>
</div>
<div class="text-[10px] font-medium px-1.5 py-0.5 rounded bg-slate-100 text-slate-600 whitespace-nowrap">
<?php echo htmlspecialchars($vax['vaccine_code'] ?? ''); ?>
</div>
</div>
<div class="flex flex-wrap items-center gap-2 mt-1.5">
<div class="text-[11px] text-slate-500 flex items-center gap-1">
<svg class="w-3 h-3 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
<?php echo $vax['vaccine_date'] ? thai_date2($vax['vaccine_date']) : '-'; ?>
</div>
<?php if (!empty($vax['vaccine_lot_no'])): ?>
<div class="text-[11px] text-slate-500 flex items-center gap-1 border-l border-slate-200 pl-2">
<span class="font-semibold">Lot:</span> <?php echo htmlspecialchars($vax['vaccine_lot_no']); ?>
</div>
<?php endif; ?>
</div>
<?php if (!empty($vax['vaccine_place_name'])): ?>
<div class="text-[10px] text-slate-400 mt-1 flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
<?php echo htmlspecialchars($vax['vaccine_place_name']); ?>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<?php endif; ?>
<a href="search.php" class="inline-flex items-center gap-2 px-4 py-2 bg-emerald-50 text-emerald-600 rounded-lg hover:bg-emerald-100 hover:text-emerald-700 transition-all font-semibold shadow-sm border border-emerald-100 text-sm">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
ค้นหาคนอื่น
</a>
</div>
</div>
</div>
<div class="w-full">
<?php require_once("../components/card_patient_details.php"); ?>
</div>
</div>
<?php else: ?>
<div class="glass-card flex flex-col items-center justify-center min-h-[400px] text-center p-8">
<div class="w-20 h-20 bg-emerald-100 rounded-full flex items-center justify-center mb-6 text-emerald-500">
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</div>
<h3 class="text-xl font-bold text-slate-700 mb-2">ยังไม่ได้เลือกรายการรับบริการ</h3>
<p class="text-slate-500 max-w-sm mb-6">กรุณาคลิกเลือกวันที่มารับบริการจากเมนูด้านซ้ายเพื่อดูรายละเอียดการตรวจรักษาและผลการวินิจฉัยโรค</p>
<a href="search.php" class="inline-flex items-center gap-2 px-6 py-3 bg-emerald-500 text-white rounded-xl hover:bg-emerald-600 transition-all font-bold shadow-sm hover:shadow-md">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
ค้นหาผู้รับบริการคนอื่น
</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php require_once("../components/layout_footer.php"); ?>
</main>
<script src="../assets/js/micro-interactions.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('historySearchInput');
const resultsContainer = document.getElementById('historySearchResults');
const loader = document.getElementById('historySearchLoader');
// The encrypted HN is available in the PHP variable $enc_hn from the loop above,
// but we can just grab it from one of the hidden inputs in the page.
const hnInput = document.querySelector('input[name="hn"]');
const enc_hn = hnInput ? hnInput.value : '';
let debounceTimer;
if (searchInput) {
searchInput.addEventListener('input', function(e) {
clearTimeout(debounceTimer);
const keyword = e.target.value.trim();
if (keyword.length < 2) {
resultsContainer.innerHTML = '<div class="text-center text-slate-400 text-sm py-8">พิมพ์คำค้นหาเพื่อเริ่มค้นหา</div>';
loader.classList.add('hidden');
return;
}
loader.classList.remove('hidden');
debounceTimer = setTimeout(() => {
fetch('../api/search_history.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `hn=${encodeURIComponent(enc_hn)}&keyword=${encodeURIComponent(keyword)}`
})
.then(response => response.json())
.then(res => {
loader.classList.add('hidden');
if (res.status === 'success') {
renderResults(res.data);
} else {
resultsContainer.innerHTML = `<div class="text-center text-red-400 text-sm py-8">เกิดข้อผิดพลาด: ${res.message}</div>`;
}
})
.catch(err => {
loader.classList.add('hidden');
resultsContainer.innerHTML = '<div class="text-center text-red-400 text-sm py-8">ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้</div>';
});
}, 500);
});
}
function renderResults(data) {
if (!data || data.length === 0) {
resultsContainer.innerHTML = '<div class="text-center text-slate-400 text-sm py-8">ไม่พบข้อมูลที่ค้นหา</div>';
return;
}
let html = '<div class="space-y-2">';
data.forEach(item => {
const color = item.color; // e.g. indigo, purple, amber
html += `
<form action="patient_detail.php" method="POST" class="m-0">
<input type="hidden" name="hn" value="${enc_hn}">
<input type="hidden" name="vdate" value="${item.enc_vdate}">
<input type="hidden" name="nn" value="${item.enc_nn}">
<button type="submit" class="w-full text-left p-3 bg-white rounded-lg border border-slate-100 shadow-sm hover:border-${color}-300 hover:shadow-md transition-all group">
<div class="flex items-start justify-between gap-2 mb-1.5">
<span class="text-xs font-bold text-${color}-600 bg-${color}-50 px-2 py-0.5 rounded border border-${color}-100 shrink-0">
${item.type_label}
</span>
<span class="text-xs text-slate-400 font-medium whitespace-nowrap group-hover:text-${color}-500 transition-colors">
${item.vstdate_thai}
</span>
</div>
<div class="text-sm font-bold text-slate-700 leading-tight group-hover:text-${color}-700 transition-colors">
${item.item_name}
</div>
</button>
</form>
`;
});
html += '</div>';
resultsContainer.innerHTML = html;
}
});
window.openCheckupCompareModal = function() {
const modal = document.getElementById('checkupCompareModal');
const loader = document.getElementById('checkupCompareLoader');
const content = document.getElementById('checkupCompareContent');
const table = document.getElementById('checkupCompareTable');
modal.classList.remove('hidden');
loader.classList.remove('hidden');
content.classList.add('hidden');
const hnInput = document.querySelector('input[name="hn"]');
const enc_hn = hnInput ? hnInput.value : '';
const showAll = document.getElementById('showAllCheckups').checked;
fetch('../api/get_health_checkup_history.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `hn=${encodeURIComponent(enc_hn)}&show_all=${showAll}`
})
.then(res => res.json())
.then(data => {
loader.classList.add('hidden');
content.classList.remove('hidden');
if(data.status === 'success' && data.data && data.data.length > 0) {
renderCheckupCompareTable(data.data, table);
} else {
table.innerHTML = '<tr><td class="p-8 text-center text-slate-500">ไม่พบประวัติการตรวจสุขภาพย้อนหลัง</td></tr>';
}
})
.catch(err => {
loader.classList.add('hidden');
content.classList.remove('hidden');
table.innerHTML = '<tr><td class="p-8 text-center text-red-500">เกิดข้อผิดพลาดในการดึงข้อมูล</td></tr>';
});
};
window.closeCheckupCompareModal = function() {
document.getElementById('checkupCompareModal').classList.add('hidden');
};
function renderCheckupCompareTable(visits, table) {
if (visits.length === 0) return;
const allLabs = new Map();
visits.forEach(v => v.labs.forEach(l => {
if(!allLabs.has(l.lab_items_name)) {
allLabs.set(l.lab_items_name, l.lab_items_normal_value || '-');
}
}));
const allXrays = new Set();
visits.forEach(v => v.xrays.forEach(x => allXrays.add(x.item_name)));
let html = `<thead><tr class="bg-indigo-50/50">`;
html += `<th class="p-3 border-b border-indigo-100 text-indigo-900 font-bold sticky left-0 top-0 bg-indigo-50/95 backdrop-blur z-30 w-56 shadow-[1px_1px_0_rgba(224,231,255,1)]">รายการตรวจ</th>`;
visits.forEach((v, index) => {
html += `<th class="p-3 border-b border-indigo-100 text-center min-w-[150px] sticky top-0 bg-indigo-50/95 backdrop-blur z-20 shadow-[0_1px_0_rgba(224,231,255,1)]">
<div class="text-indigo-600 font-bold">${v.vstdate_thai}</div>`;
if(index < visits.length - 1 && v.gap_days > 0) {
html += `<div class="text-[10px] text-slate-500 font-bold mt-1 bg-white rounded-full px-2 py-0.5 inline-flex items-center gap-1 shadow-sm border border-slate-100 whitespace-nowrap"><svg class="w-3 h-3 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> ${v.gap_text}</div>`;
} else if (index === visits.length - 1) {
html += `<div class="text-[10px] text-slate-400 font-medium mt-1">ครั้งแรกสุด</div>`;
}
html += `</th>`;
});
html += `</tr></thead><tbody>`;
if(allLabs.size > 0) {
html += `<tr><td colspan="${visits.length + 1}" class="p-2 bg-slate-50 font-bold text-slate-600 text-sm border-b border-slate-100 sticky left-0 z-10">🔬 ผลแล็บ (Lab)</td></tr>`;
Array.from(allLabs.entries()).sort((a, b) => a[0].localeCompare(b[0])).forEach(([labName, normalVal]) => {
html += `<tr class="hover:bg-slate-50 transition-colors">`;
html += `<td class="p-3 border-b border-slate-100 text-sm text-slate-700 font-medium sticky left-0 bg-white z-10 shadow-[1px_0_0_rgba(241,245,249,1)] group-hover:bg-slate-50">
${labName}
<div class="text-[10px] text-slate-400 font-normal mt-0.5">Normal: ${normalVal}</div>
</td>`;
visits.forEach(v => {
const labFound = v.labs.find(l => l.lab_items_name === labName);
if (labFound) {
if (labFound.is_abnormal) {
html += `<td class="p-3 border-b border-slate-100 text-center text-sm font-bold text-rose-600 bg-rose-50/50">${labFound.lab_order_result}</td>`;
} else {
html += `<td class="p-3 border-b border-slate-100 text-center text-sm font-bold text-emerald-600 bg-emerald-50/10">${labFound.lab_order_result}</td>`;
}
} else {
html += `<td class="p-3 border-b border-slate-100 text-center text-sm text-slate-300">-</td>`;
}
});
html += `</tr>`;
});
}
if(allXrays.size > 0) {
html += `<tr><td colspan="${visits.length + 1}" class="p-2 bg-slate-50 font-bold text-slate-600 text-sm border-b border-slate-100 sticky left-0 z-10 mt-2">🩻 ผลเอกซเรย์ (X-Ray)</td></tr>`;
Array.from(allXrays).sort().forEach(xrayName => {
html += `<tr class="hover:bg-slate-50 transition-colors">`;
html += `<td class="p-3 border-b border-slate-100 text-sm text-slate-700 font-medium sticky left-0 bg-white z-10 shadow-[1px_0_0_rgba(241,245,249,1)] group-hover:bg-slate-50">${xrayName}</td>`;
visits.forEach(v => {
const xrayFound = v.xrays.find(x => x.item_name === xrayName);
if (xrayFound) {
html += `<td class="p-3 border-b border-slate-100 text-center text-xs text-slate-600 bg-amber-50/10" title="${xrayFound.result}">${xrayFound.result ? "มีผลอ่าน" : "รอผล"}</td>`;
} else {
html += `<td class="p-3 border-b border-slate-100 text-center text-sm text-slate-300">-</td>`;
}
});
html += `</tr>`;
});
}
html += `</tbody>`;
table.innerHTML = html;
}
</script>
</body>
</html>
<?php
$html = ob_get_clean();
if ($is_ipd_visit) {
// Replace all tailwind emerald classes with amber classes
$html = preg_replace('/\b(bg|text|border|ring|shadow|from|to|via|fill|stroke)-emerald-([0-9]{2,3}(?:\/[0-9]{1,3})?)\b/', '$1-amber-$2', $html);
// Replace the specific rgba emerald green (16, 185, 129) with amber-500 (245, 158, 11) for box-shadows and scrollbars
$html = preg_replace('/rgba\(\s*16\s*,\s*185\s*,\s*129/i', 'rgba(245, 158, 11', $html);
}
echo $html;
?>