Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,175 @@
<?php
// views/jobs/detail.php
require_once 'models/Job.php';
require_once 'models/Department.php';
$job_number = $_GET['id'] ?? '';
if (empty($job_number)) {
echo "<div class='text-center p-8 text-red-500 font-bold'>ไม่พบรหัสงาน</div>";
return;
}
$jobModel = new Job();
$job = $jobModel->getByJobNumber($job_number);
if (!$job) {
echo "<div class='text-center p-8 text-red-500 font-bold'>ไม่พบข้อมูลงาน {$job_number}</div>";
return;
}
$timelines = $jobModel->getJobTimelines($job['id']);
$statusColors = [
'pending' => 'bg-yellow-100 text-yellow-800 border-yellow-200',
'assigned' => 'bg-blue-100 text-blue-800 border-blue-200',
'accepted' => 'bg-indigo-100 text-indigo-800 border-indigo-200',
'in_progress' => 'bg-purple-100 text-purple-800 border-purple-200',
'completed' => 'bg-green-100 text-green-800 border-green-200',
'cancelled' => 'bg-red-100 text-red-800 border-red-200',
];
$statusNames = [
'pending' => 'รอดำเนินการ',
'assigned' => 'มอบหมายแล้ว',
'accepted' => 'กำลังไปรับ',
'in_progress' => 'กำลังไปส่ง',
'completed' => 'เสร็จสิ้น',
'cancelled' => 'ยกเลิก',
];
$priorityColors = [
'normal' => 'emerald',
'urgent' => 'amber',
'emergency' => 'red'
];
$priorityNames = [
'normal' => 'ทั่วไป (Normal)',
'urgent' => 'ด่วน (Urgent)',
'emergency' => 'ฉุกเฉิน (Emergency)'
];
$badgeClass = $statusColors[$job['status']] ?? 'bg-slate-100 text-slate-800';
$badgeName = $statusNames[$job['status']] ?? $job['status'];
$pColor = $priorityColors[$job['priority']] ?? 'emerald';
$pName = $priorityNames[$job['priority']] ?? $job['priority'];
?>
<div class="max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-6">
<div>
<h2 class="text-2xl font-bold text-slate-800 dark:text-white flex items-center flex-wrap gap-2">
รายละเอียดงาน <span class="px-3 py-1 text-sm rounded-full border <?= $badgeClass ?>"><?= $badgeName ?></span>
</h2>
<p class="text-slate-500 dark:text-slate-400 mt-1">หมายเลขงาน: <?= $job['job_number'] ?></p>
</div>
<button onclick="window.history.back()" class="bg-white/50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 hover:bg-slate-100 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 px-4 py-2 rounded-xl transition-colors shrink-0">
<i class="fa-solid fa-arrow-left me-1"></i> กลับ
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- ข้อมูลหลัก -->
<div class="md:col-span-2 space-y-6">
<div class="glass-card p-6 border-t-4 border-t-<?= $pColor ?>-500">
<h5 class="text-lg font-bold text-slate-800 dark:text-white mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">ข้อมูลผู้ป่วย</h5>
<div class="flex flex-col sm:flex-row gap-6">
<?php if (!empty($job['patient_image'])): ?>
<div class="w-full sm:w-1/3">
<img src="<?= htmlspecialchars($job['patient_image']) ?>" class="w-full h-auto rounded-lg shadow-sm border border-slate-200 object-cover" alt="รูปผู้ป่วย">
</div>
<?php endif; ?>
<div class="flex-1">
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">ชื่อผู้ป่วย</p>
<h4 class="text-xl font-bold text-slate-800 dark:text-white mb-3">
<?= htmlspecialchars($job['patient_name'] ?: 'ไม่ระบุชื่อ (ดูรูปภาพ)') ?>
<?php if ($job['patient_hn']): ?>
<span class="text-sm font-normal text-slate-500">(HN: <?= htmlspecialchars($job['patient_hn']) ?>)</span>
<?php endif; ?>
</h4>
<div class="grid grid-cols-2 gap-4 mt-4">
<div>
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">ความเร่งด่วน</p>
<span class="px-2 py-1 bg-<?= $pColor ?>-100 text-<?= $pColor ?>-800 rounded text-xs font-medium border border-<?= $pColor ?>-200">
<?= $pName ?>
</span>
</div>
<div>
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">อุปกรณ์</p>
<p class="font-medium text-slate-800 dark:text-white"><?= strtoupper($job['equipment_type']) ?></p>
</div>
</div>
<?php if ($job['infection_control'] == 1): ?>
<div class="mt-4 bg-red-100 border border-red-400 text-red-700 px-3 py-2 rounded-lg flex items-center text-sm shadow-sm font-medium">
<i class="fa-solid fa-biohazard me-2 text-lg"></i> ผู้ป่วยโรคติดต่อ / แยกกักโรค
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="glass-card p-6">
<h5 class="text-lg font-bold text-slate-800 dark:text-white mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">เส้นทางการเคลื่อนย้าย</h5>
<div class="flex items-center mb-4">
<div class="w-10 h-10 rounded-full bg-red-100 dark:bg-red-900/50 text-red-600 dark:text-red-400 flex items-center justify-center font-bold text-lg z-10 shadow-sm border border-red-200 shrink-0">A</div>
<div class="ml-4 flex-1">
<p class="text-xs text-slate-500 dark:text-slate-400 uppercase tracking-wider">รับที่ (From)</p>
<p class="font-bold text-lg text-slate-800 dark:text-white"><?= htmlspecialchars($job['from_department_name'] ?? 'ไม่ระบุ') ?></p>
</div>
</div>
<div class="relative ml-5 border-l-2 border-dashed border-slate-300 dark:border-slate-600 h-8 -my-5 z-0"></div>
<div class="flex items-center mt-4">
<div class="w-10 h-10 rounded-full bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400 flex items-center justify-center font-bold text-lg z-10 shadow-sm border border-green-200 shrink-0">B</div>
<div class="ml-4 flex-1">
<p class="text-xs text-slate-500 dark:text-slate-400 uppercase tracking-wider">ส่งที่ (To)</p>
<p class="font-bold text-lg text-slate-800 dark:text-white"><?= htmlspecialchars($job['to_department_name'] ?? $job['to_department_custom'] ?? 'ไม่ระบุ') ?></p>
</div>
</div>
</div>
<?php if (!empty($job['notes'])): ?>
<div class="glass-card p-6">
<h5 class="text-lg font-bold text-slate-800 dark:text-white mb-2 border-b border-slate-200 dark:border-slate-700 pb-2">หมายเหตุ</h5>
<p class="text-slate-700 dark:text-slate-300 whitespace-pre-line"><?= htmlspecialchars($job['notes']) ?></p>
</div>
<?php endif; ?>
</div>
<!-- Timeline -->
<div class="md:col-span-1">
<div class="glass-card p-6 h-full">
<h5 class="text-lg font-bold text-slate-800 dark:text-white mb-6 border-b border-slate-200 dark:border-slate-700 pb-2">ไทม์ไลน์ (Timeline)</h5>
<div class="relative border-l border-slate-200 dark:border-slate-700 ml-3 space-y-6 pb-4">
<?php if (empty($timelines)): ?>
<p class="text-slate-500 ml-4">ไม่มีข้อมูลไทม์ไลน์</p>
<?php else: ?>
<?php foreach($timelines as $index => $tl):
$isLast = ($index === count($timelines) - 1);
$tlColor = 'slate';
$icon = 'fa-circle-dot';
if (strpos($tl['status'], 'cancelled') !== false) { $tlColor = 'red'; $icon = 'fa-times'; }
elseif (strpos($tl['status'], 'completed') !== false) { $tlColor = 'green'; $icon = 'fa-check'; }
elseif (strpos($tl['status'], 'accepted') !== false) { $tlColor = 'blue'; $icon = 'fa-user-check'; }
?>
<div class="relative pl-6">
<div class="absolute -left-3 top-1 w-6 h-6 rounded-full bg-<?= $tlColor ?>-100 text-<?= $tlColor ?>-600 flex items-center justify-center border-2 border-white dark:border-slate-800 shadow-sm">
<i class="fa-solid <?= $icon ?> text-xs"></i>
</div>
<div class="<?= $isLast ? 'opacity-100' : 'opacity-75' ?>">
<p class="text-xs text-slate-500 mb-1"><?= date('d/m/Y H:i', strtotime($tl['created_at'])) ?></p>
<p class="font-medium text-sm text-slate-800 dark:text-white"><?= htmlspecialchars($tl['description']) ?></p>
<p class="text-xs text-slate-500 mt-1">โดย: <?= htmlspecialchars($tl['created_by_name'] ?? 'ระบบ') ?></p>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,184 @@
<?php
// views/jobs/request.php
?>
<div class="max-w-3xl mx-auto">
<div class="mb-6">
<h2 class="text-2xl font-bold text-slate-800 dark:text-white">ร้องของานเปล (Request Job)</h2>
<p class="text-slate-500 dark:text-slate-400">กรอกข้อมูลผู้ป่วยและสถานที่ให้ครบถ้วน</p>
</div>
<div class="glass-card p-6 md:p-8">
<form id="requestForm">
<input type="hidden" name="action" value="create_job">
<input type="hidden" name="csrf_token" value="<?= generateCSRFToken() ?>">
<h4 class="text-lg font-semibold text-emerald-600 dark:text-emerald-400 mb-4 border-b border-emerald-100 dark:border-emerald-900 pb-2">1. ข้อมูลผู้ป่วย</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 mb-6">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">HN (ถ้ามี)</label>
<input type="text" class="glass-input w-full px-4 py-2" name="hn" placeholder="เช่น 6601234">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชื่อ-สกุลผู้ป่วย <span class="text-red-500">*</span></label>
<input type="text" class="glass-input w-full px-4 py-2" name="patient_name" required placeholder="ชื่อ นามสกุล">
</div>
</div>
<h4 class="text-lg font-semibold text-emerald-600 dark:text-emerald-400 mb-4 border-b border-emerald-100 dark:border-emerald-900 pb-2">2. เส้นทางและอุปกรณ์</h4>
<?php
require_once dirname(__DIR__, 2) . '/models/Department.php';
require_once dirname(__DIR__, 2) . '/models/Setting.php';
$deptModel = new Department();
$departments = $deptModel->getAllActive();
$settingModel = new Setting();
$equipmentsJson = $settingModel->get('equipment_types');
$equipments = $equipmentsJson ? json_decode($equipmentsJson, true) : [
['id' => 'wheelchair', 'name' => 'รถนั่ง (Wheelchair)', 'icon' => 'fa-wheelchair'],
['id' => 'stretcher', 'name' => 'รถนอน (Stretcher)', 'icon' => 'fa-bed'],
['id' => 'bed', 'name' => 'เตียง (Bed)', 'icon' => 'fa-bed-pulse']
];
$prioritiesJson = $settingModel->get('job_priorities');
$priorities = $prioritiesJson ? json_decode($prioritiesJson, true) : [
['id' => 'normal', 'name' => 'ทั่วไป (Normal)', 'color' => 'blue'],
['id' => 'urgent', 'name' => 'ด่วน (Urgency)', 'color' => 'amber'],
['id' => 'emergency', 'name' => 'ฉุกเฉิน (Emergency)', 'color' => 'red']
];
?>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 mb-5">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">จุดรับผู้ป่วย <span class="text-red-500">*</span></label>
<select class="glass-input w-full px-4 py-2" name="from_department_id" required>
<option value="">-- เลือกจุดรับ --</option>
<?php foreach($departments as $dept): ?>
<option value="<?= $dept['id'] ?>"><?= escape($dept['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">จุดส่งผู้ป่วย <span class="text-red-500">*</span></label>
<select class="glass-input w-full px-4 py-2" name="to_department_id" required>
<option value="">-- เลือกจุดส่ง --</option>
<?php foreach($departments as $dept): ?>
<option value="<?= $dept['id'] ?>"><?= escape($dept['name']) ?></option>
<?php endforeach; ?>
</select>
<div class="mt-2 flex items-center bg-blue-50 dark:bg-blue-900/20 p-2 rounded-lg border border-blue-100 dark:border-blue-800">
<input type="checkbox" id="is_round_trip" name="is_round_trip" value="1" class="w-4 h-4 text-blue-600 rounded border-blue-300 focus:ring-blue-500 cursor-pointer">
<label for="is_round_trip" class="ml-2 text-sm font-medium text-blue-800 dark:text-blue-300 cursor-pointer">
<i class="fa-solid fa-rotate-left me-1"></i> ไป-กลับ (รอรับกลับทันที)
</label>
</div>
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">ประเภทอุปกรณ์ <span class="text-red-500">*</span></label>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<?php foreach($equipments as $index => $eq): ?>
<label class="relative flex items-center p-3 border border-slate-200 dark:border-slate-700 rounded-xl cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
<input type="radio" name="equipment_type" value="<?= escape($eq['id']) ?>" class="w-4 h-4 text-emerald-600 border-gray-300 focus:ring-emerald-500 dark:bg-slate-700 dark:border-slate-600" <?= $index === 0 ? 'required checked' : '' ?>>
<span class="ml-3 flex flex-col">
<span class="text-sm font-medium text-slate-900 dark:text-white flex items-center gap-2">
<i class="fa-solid <?= escape($eq['icon']) ?> text-emerald-500"></i> <?= escape($eq['name']) ?>
</span>
</span>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="mb-5">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ระดับความฉุกเฉิน (Priority) <span class="text-red-500">*</span></label>
<select class="glass-input w-full px-4 py-2" name="priority" required>
<?php foreach($priorities as $index => $pri): ?>
<option value="<?= escape($pri['id']) ?>" class="text-<?= escape($pri['color']) ?>-600 font-medium" <?= $index === 0 ? 'selected' : '' ?>>
<?= escape($pri['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-5 flex items-center gap-2 bg-red-50 dark:bg-red-900/20 p-4 rounded-xl border border-red-200 dark:border-red-800">
<input type="checkbox" id="infection_control" name="infection_control" value="1" class="w-5 h-5 text-red-600 rounded border-red-300 focus:ring-red-500">
<label for="infection_control" class="text-sm font-medium text-red-800 dark:text-red-300">
<i class="fa-solid fa-biohazard me-1"></i> ผู้ป่วยโรคติดต่อ / ต้องการการแยกกักโรค (Infection Control)
</label>
</div>
<div class="mb-5">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">เวลาที่ต้องการให้มารับ (Booking)</label>
<div class="flex gap-4 mb-2">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="is_scheduled" value="0" checked class="text-emerald-500 focus:ring-emerald-500" onchange="document.getElementById('scheduleTimeDiv').classList.add('hidden')">
<span class="text-sm dark:text-slate-300">ด่วนทันที</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="is_scheduled" value="1" class="text-emerald-500 focus:ring-emerald-500" onchange="document.getElementById('scheduleTimeDiv').classList.remove('hidden')">
<span class="text-sm dark:text-slate-300">จองเวลาล่วงหน้า</span>
</label>
</div>
<div id="scheduleTimeDiv" class="hidden">
<input type="datetime-local" class="glass-input w-full px-4 py-2" name="scheduled_time">
</div>
</div>
<div class="mb-8">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">หมายเหตุ (ถ้ามี)</label>
<textarea class="glass-input w-full px-4 py-2" name="notes" rows="2" placeholder="เช่น ผู้ป่วยให้ออกซิเจน, ระวังสายน้ำเกลือ"></textarea>
</div>
<div class="flex justify-end gap-3 pt-4 border-t border-slate-200 dark:border-slate-700">
<button type="reset" class="btn-secondary-glass py-2 px-6">
ล้างข้อมูล
</button>
<button type="submit" class="btn-primary-glass py-2 px-8 flex items-center">
<span class="text"><i class="fa-solid fa-paper-plane me-2"></i> ส่งคำขอ</span>
</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
$('#requestForm').submit(function(e) {
e.preventDefault();
const btn = $(this).find('button[type="submit"]');
const originalText = btn.html();
btn.addClass('btn-loading');
$.ajax({
url: 'api/jobs.php',
type: 'POST',
data: $(this).serialize(),
success: function(response) {
if (response.status === 'success') {
Swal.fire({
icon: 'success',
title: 'บันทึกสำเร็จ',
text: 'หมายเลขงาน: ' + response.job_number,
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b'
}).then(() => {
window.location.href = '?page=dashboard';
});
} else {
btn.removeClass('btn-loading').html(originalText);
Swal.fire('ผิดพลาด', response.message, 'error');
}
},
error: function() {
btn.removeClass('btn-loading').html(originalText);
Swal.fire('ผิดพลาด', 'เกิดข้อผิดพลาดในการเชื่อมต่อ', 'error');
}
});
});
});
</script>
@@ -0,0 +1,596 @@
<?php
// views/jobs/staff.php
?>
<div class="mb-6 flex justify-between items-center">
<div>
<h2 class="text-2xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-emerald-600 to-teal-400 dark:from-emerald-400 dark:to-teal-200">งานของฉัน</h2>
<p class="text-slate-500 dark:text-slate-400 text-sm">รายการงานเปลที่คุณได้รับมอบหมาย</p>
</div>
<button onclick="openMapModal()" class="bg-white/80 dark:bg-slate-800/80 hover:bg-white dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 shadow-sm text-slate-700 dark:text-slate-300 px-4 py-2 rounded-xl text-sm font-medium transition-all backdrop-blur-sm">
<i class="fa-solid fa-map-location-dot text-emerald-500 mr-2"></i> แผนผังตึก
</button>
</div>
<div id="jobContainer">
<div class="glass-card p-8 text-center text-slate-500 flex flex-col items-center justify-center min-h-[300px]">
<i class="fa-solid fa-spinner fa-spin text-4xl mb-4 text-emerald-500"></i>
<p>กำลังตรวจสอบงาน...</p>
</div>
</div>
<!-- Signature & QR Modal (Tailwind version) -->
<div id="signatureModal" class="fixed inset-0 z-[100] hidden">
<!-- Backdrop -->
<div class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity"></div>
<!-- Modal Content -->
<div class="fixed inset-0 z-10 overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div class="relative transform overflow-hidden rounded-2xl bg-white dark:bg-slate-800 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg border border-slate-200 dark:border-slate-700">
<div class="bg-emerald-600 px-4 py-3 sm:px-6 flex justify-between items-center">
<h3 class="text-base font-semibold leading-6 text-white">ยืนยันการส่งผู้ป่วย</h3>
<button type="button" class="text-white hover:text-emerald-100 close-modal">
<i class="fa-solid fa-xmark text-xl"></i>
</button>
</div>
<div class="px-4 pb-4 pt-5 sm:p-6 sm:pb-4 text-center">
<h6 class="text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">แสกน QR Code เพื่อดูประวัติ</h6>
<div id="qrcode" class="mb-4 flex justify-center bg-white p-2 rounded-lg inline-block mx-auto"></div>
<div class="mt-4 pt-4 border-t border-slate-200 dark:border-slate-700">
<h6 class="text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">ลายเซ็นพยาบาลผู้รับ (Digital Signature)</h6>
<div class="bg-slate-50 dark:bg-slate-900 rounded-xl p-1 border border-slate-200 dark:border-slate-700">
<canvas id="signature-pad" class="w-full h-48 rounded-lg cursor-crosshair touch-none"></canvas>
</div>
</div>
</div>
<div class="bg-slate-50 dark:bg-slate-700/50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" class="inline-flex w-full justify-center rounded-xl bg-emerald-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-emerald-500 sm:ml-3 sm:w-auto transition-colors" id="saveSignature">
บันทึกและปิดงาน
</button>
<button type="button" class="mt-3 inline-flex w-full justify-center rounded-xl bg-white dark:bg-slate-800 px-3 py-2 text-sm font-semibold text-slate-900 dark:text-slate-200 shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 sm:mt-0 sm:w-auto transition-colors" id="clearSignature">
ล้างลายเซ็น
</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/signature_pad@4.1.5/dist/signature_pad.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
let currentJob = null;
let signaturePad = null;
let qrGeneratedFor = null;
const modal = document.getElementById('signatureModal');
// Init Signature Pad
const canvas = document.getElementById('signature-pad');
signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgba(255, 255, 255, 0)'
});
function resizeCanvas() {
const ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
signaturePad.clear();
}
window.addEventListener("resize", resizeCanvas);
document.querySelectorAll('.close-modal').forEach(btn => {
btn.addEventListener('click', () => modal.classList.add('hidden'));
});
document.getElementById('clearSignature').addEventListener('click', () => {
signaturePad.clear();
});
function fetchActiveJob() {
$.ajax({
url: 'api/jobs.php',
type: 'GET',
data: { action: 'get_active_job' },
dataType: 'json',
success: function(res) {
if (res.status === 'success' && res.data) {
currentJob = res.data;
renderJob(currentJob);
} else {
renderNoJob();
}
},
error: function() {
renderNoJob(true);
}
});
}
function renderNoJob(isError = false) {
currentJob = null;
const msg = isError ? 'เกิดข้อผิดพลาดในการดึงข้อมูล' : 'ขณะนี้คุณยังไม่มีงานที่ได้รับมอบหมาย';
const icon = isError ? 'fa-triangle-exclamation text-red-500' : 'fa-check-circle text-emerald-500';
document.getElementById('jobContainer').innerHTML = `
<div class="glass-card p-8 text-center text-slate-500 flex flex-col items-center justify-center min-h-[300px]">
<div class="w-16 h-16 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center mb-4 text-3xl">
<i class="fa-solid ${icon}"></i>
</div>
<h3 class="text-xl font-medium text-slate-800 dark:text-white mb-2">${msg}</h3>
<p class="text-sm">ระบบจะแจ้งเตือนเมื่อมีงานใหม่เข้ามา</p>
<button onclick="location.reload()" class="mt-6 btn-secondary-glass px-6 py-2">
<i class="fa-solid fa-rotate-right me-2"></i> รีเฟรช
</button>
</div>
`;
}
function renderJob(job) {
let statusBadge = '';
let btnArrivedDisabled = '';
let btnPickedUpDisabled = 'opacity-50 cursor-not-allowed" disabled';
let btnCompletedDisabled = 'opacity-50 cursor-not-allowed" disabled';
let btnArrivedClass = 'btn-secondary-glass';
let btnPickedUpClass = 'btn-secondary-glass';
let btnArrivedText = '<i class="fa-solid fa-person-walking-arrow-right me-2"></i> ถึงจุดรับผู้ป่วย';
let btnPickedUpText = '<i class="fa-solid fa-bed-pulse me-2"></i> รับผู้ป่วยขึ้นเปล';
if (job.status === 'assigned') {
statusBadge = '<span class="bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300 text-xs font-semibold px-2.5 py-0.5 rounded border border-blue-200 dark:border-blue-800">กำลังเดินทางไปรับ</span>';
} else if (job.status === 'accepted') {
statusBadge = '<span class="bg-indigo-100 text-indigo-800 dark:bg-indigo-900/50 dark:text-indigo-300 text-xs font-semibold px-2.5 py-0.5 rounded border border-indigo-200 dark:border-indigo-800">ถึงจุดรับผู้ป่วยแล้ว</span>';
btnArrivedDisabled = 'opacity-50 cursor-not-allowed" disabled';
btnArrivedClass = 'bg-emerald-100 text-emerald-700 border-emerald-200';
btnArrivedText = '<i class="fa-solid fa-check me-2"></i> ถึงจุดรับแล้ว';
btnPickedUpDisabled = '';
} else if (job.status === 'in_progress') {
statusBadge = '<span class="bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-300 text-xs font-semibold px-2.5 py-0.5 rounded border border-amber-200 dark:border-amber-800">กำลังเดินทางไปส่ง</span>';
btnArrivedDisabled = 'opacity-50 cursor-not-allowed" disabled';
btnArrivedClass = 'bg-emerald-100 text-emerald-700 border-emerald-200';
btnArrivedText = '<i class="fa-solid fa-check me-2"></i> ถึงจุดรับแล้ว';
btnPickedUpDisabled = 'opacity-50 cursor-not-allowed" disabled';
btnPickedUpClass = 'bg-emerald-100 text-emerald-700 border-emerald-200';
btnPickedUpText = '<i class="fa-solid fa-check me-2"></i> รับผู้ป่วยแล้ว';
btnCompletedDisabled = '';
} else if (job.status === 'waiting_for_return') {
statusBadge = '<span class="bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300 text-xs font-semibold px-2.5 py-0.5 rounded border border-blue-200 dark:border-blue-800">กำลังรอรับกลับ</span>';
btnArrivedDisabled = 'opacity-50 cursor-not-allowed" disabled';
btnArrivedClass = 'bg-emerald-100 text-emerald-700 border-emerald-200';
btnArrivedText = '<i class="fa-solid fa-check me-2"></i> ถึงจุดรับแล้ว';
btnPickedUpDisabled = 'opacity-50 cursor-not-allowed" disabled';
btnPickedUpClass = 'bg-emerald-100 text-emerald-700 border-emerald-200';
btnPickedUpText = '<i class="fa-solid fa-check me-2"></i> รับผู้ป่วยแล้ว';
btnCompletedDisabled = '';
}
let equipmentIcon = job.equipment_icon || 'fa-wheelchair';
let equipmentName = job.equipment_name || job.equipment_type.toUpperCase();
const html = `
<div class="glass-card mb-8 border-l-4 border-${job.priority_color}-500" id="activeJobCard">
<div class="px-6 py-4 border-b border-emerald-500/20 bg-emerald-50/50 dark:bg-emerald-900/20 rounded-t-2xl flex justify-between items-center">
<h5 class="font-bold text-emerald-700 dark:text-emerald-400 m-0 flex items-center">
<span class="relative flex h-3 w-3 mr-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
</span>
งานที่กำลังดำเนินการ (Active)
</h5>
<div class="flex items-center gap-2">
${job.is_round_trip == 1 ? '<span class="bg-purple-100 text-purple-800 dark:bg-purple-900/50 dark:text-purple-300 text-xs font-bold px-2 py-1 rounded-full"><i class="fa-solid fa-rotate-left me-1"></i> ไป-กลับ</span>' : ''}
<button onclick="openChatModal(${job.id}, '${job.job_number}')" class="bg-blue-100 hover:bg-blue-200 text-blue-700 p-2 rounded-full transition-colors" title="แชทกับจุดรับ">
<i class="fa-solid fa-comment-dots"></i>
</button>
${statusBadge}
</div>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<div class="flex justify-between items-start mb-4">
<div>
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">หมายเลขงาน</p>
<h4 class="text-xl font-bold text-slate-800 dark:text-white">${job.job_number}</h4>
</div>
<span class="bg-${job.priority_color}-100 text-${job.priority_color}-800 text-xs font-medium px-2.5 py-0.5 rounded border border-${job.priority_color}-200">${job.priority_name}</span>
</div>
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">ชื่อผู้ป่วย / รูปถ่าย</p>
${job.patient_image ? `<div class="mb-3"><img src="${job.patient_image}" class="w-full max-w-[200px] h-auto rounded-lg border border-slate-200 shadow-sm object-cover" alt="รูปผู้เรียก"></div>` : ''}
<p class="text-lg font-medium text-slate-800 dark:text-white mb-4">${job.patient_name || 'ไม่ระบุชื่อ (ดูรูปภาพ)'} ${job.patient_hn ? '(HN: ' + job.patient_hn + ')' : ''}</p>
${job.infection_control == 1 ? '<div class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-2 rounded-lg flex items-center shadow-lg animate-pulse"><i class="fa-solid fa-biohazard text-xl mr-2"></i><span class="font-bold">ผู้ป่วยโรคติดต่อ / แยกกักโรค โปรดสวม PPE</span></div>' : ''}
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">อุปกรณ์</p>
<p class="font-medium text-slate-800 dark:text-white"><i class="fa-solid ${equipmentIcon} text-emerald-500 me-2"></i> ${equipmentName}</p>
</div>
<div class="bg-slate-50/50 dark:bg-slate-800/50 p-4 rounded-xl border border-slate-200 dark:border-slate-700">
<h6 class="font-semibold text-slate-700 dark:text-slate-300 mb-3 border-b border-slate-200 dark:border-slate-700 pb-2">เส้นทาง (Route)</h6>
<div class="flex items-center mb-4">
<div class="w-8 h-8 rounded-full bg-red-100 dark:bg-red-900/50 text-red-600 dark:text-red-400 flex items-center justify-center font-bold z-10">A</div>
<div class="ml-4">
<p class="text-xs text-slate-500 dark:text-slate-400">รับที่ (From)</p>
<p class="font-bold text-slate-800 dark:text-white">${job.from_department_name}</p>
</div>
</div>
<div class="relative ml-4 border-l-2 border-dashed border-slate-300 dark:border-slate-600 h-6 -my-4 z-0"></div>
<div class="flex items-center mt-4">
<div class="w-8 h-8 rounded-full bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400 flex items-center justify-center font-bold z-10">B</div>
<div class="ml-4">
<p class="text-xs text-slate-500 dark:text-slate-400">ส่งที่ (To)</p>
<p class="font-bold text-slate-800 dark:text-white">${job.to_department_name || job.to_department_custom}</p>
</div>
</div>
</div>
</div>
<div class="mt-8 pt-6 border-t border-slate-200 dark:border-slate-700">
<h6 class="font-medium text-slate-700 dark:text-slate-300 mb-4">อัปเดตสถานะงาน (Actions)</h6>
<div class="flex flex-col sm:flex-row gap-3">
<button class="flex-1 py-3 px-4 font-medium ${btnArrivedClass} ${btnArrivedDisabled}" id="btnArrived" onclick="updateJobStatus('accepted')">
${btnArrivedText}
</button>
<button class="flex-1 py-3 px-4 font-medium ${btnPickedUpClass} ${btnPickedUpDisabled}" id="btnPickedUp" onclick="updateJobStatus('in_progress')">
${btnPickedUpText}
</button>
${job.is_round_trip == 1 && job.status === 'in_progress' ?
`<button class="btn-primary-glass flex-1 py-3 px-4 font-medium ${btnCompletedDisabled}" id="btnWaitReturn" onclick="updateJobStatus('waiting_for_return')">
<i class="fa-solid fa-clock me-2"></i> รอรับกลับ
</button>` :
`<button class="btn-primary-glass flex-1 py-3 px-4 font-medium ${btnCompletedDisabled}" id="btnCompleted" onclick="openSignatureModal()">
<i class="fa-solid fa-check-double me-2"></i> ${job.status === 'waiting_for_return' ? 'ส่งกลับเรียบร้อย' : 'ส่งผู้ป่วยเรียบร้อย'}
</button>`}
</div>
<div class="mt-3">
<button class="w-full py-2 px-4 font-medium bg-white/50 dark:bg-slate-800 border border-red-200 dark:border-red-900/50 text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-xl transition-colors shadow-sm" onclick="promptCancelJob('${job.job_number}')">
<i class="fa-solid fa-times-circle me-1"></i> ปฏิเสธ/ยกเลิกงาน (พบปัญหาระหว่างงาน)
</button>
</div>
</div>
</div>
</div>
`;
document.getElementById('jobContainer').innerHTML = html;
}
window.updateJobStatus = function(newStatus) {
if (!currentJob) return;
$.ajax({
url: 'api/jobs.php',
type: 'POST',
data: {
action: 'update_status',
job_id: currentJob.job_number,
status: newStatus
},
dataType: 'json',
success: function(res) {
if (res.status === 'success') {
// Refetch job to update UI
fetchActiveJob();
} else {
Swal.fire('ผิดพลาด', res.message, 'error');
}
}
});
}
window.promptCancelJob = function(jobNumber) {
Swal.fire({
title: 'ปฏิเสธ/ยกเลิกงาน',
input: 'text',
inputLabel: 'โปรดระบุเหตุผลที่ยกเลิกงาน (เช่น ผู้ป่วยปฏิเสธ, หาผู้ป่วยไม่พบ)',
inputPlaceholder: 'พิมพ์เหตุผลที่นี่...',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ef4444',
cancelButtonColor: '#94a3b8',
confirmButtonText: 'ยืนยันยกเลิก',
cancelButtonText: 'ปิด',
inputValidator: (value) => {
if (!value) {
return 'กรุณาระบุเหตุผล!';
}
}
}).then((result) => {
if (result.isConfirmed) {
Swal.showLoading();
$.ajax({
url: 'api/jobs.php',
type: 'POST',
data: {
action: 'cancel_job',
job_id: jobNumber,
reason: result.value
},
dataType: 'json',
success: function(res) {
if (res.status === 'success') {
Swal.fire({
title: 'สำเร็จ!',
text: 'ยกเลิกงานเรียบร้อยแล้ว',
icon: 'success',
timer: 1500,
showConfirmButton: false
}).then(() => {
fetchActiveJob(); // Will show no jobs UI
});
} else {
Swal.fire('ข้อผิดพลาด', res.message, 'error');
}
},
error: function() {
Swal.fire('ข้อผิดพลาด', 'ไม่สามารถติดต่อเซิร์ฟเวอร์ได้', 'error');
}
});
}
});
}
window.openSignatureModal = function() {
if (!currentJob) return;
modal.classList.remove('hidden');
resizeCanvas();
const qrContainer = document.getElementById('qrcode');
if (qrGeneratedFor !== currentJob.job_number) {
qrContainer.innerHTML = "";
new QRCode(qrContainer, {
text: window.location.origin + window.location.pathname + "?page=job_detail&id=" + currentJob.job_number,
width: 128,
height: 128,
colorDark : "#0f172a",
colorLight : "#ffffff",
});
qrGeneratedFor = currentJob.job_number;
}
}
document.getElementById('saveSignature').addEventListener('click', function() {
if (signaturePad.isEmpty()) {
Swal.fire({
title: 'แจ้งเตือน',
text: 'กรุณาเซ็นชื่อรับผู้ป่วย',
icon: 'warning',
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b'
});
return;
}
const dataURL = signaturePad.toDataURL('image/png');
const formData = new FormData();
formData.append('action', 'save_signature');
formData.append('job_id', currentJob.job_number);
formData.append('signature', dataURL);
const btn = $(this);
const originalText = btn.html();
btn.html('<i class="fa-solid fa-spinner fa-spin"></i>').prop('disabled', true);
fetch('api/jobs.php', {
method: 'POST',
body: formData
}).then(res => res.json()).then(data => {
btn.html(originalText).prop('disabled', false);
if (data.status === 'success') {
modal.classList.add('hidden');
Swal.fire({
title: 'เสร็จสิ้น',
text: 'ปิดงานและบันทึกลายเซ็นเรียบร้อย',
icon: 'success',
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b'
}).then(() => {
fetchActiveJob(); // Should return No Job
});
} else {
Swal.fire('ผิดพลาด', data.message, 'error');
}
}).catch(err => {
btn.html(originalText).prop('disabled', false);
Swal.fire('ผิดพลาด', 'เกิดข้อผิดพลาดในการเชื่อมต่อ', 'error');
});
});
// Start fetching
fetchActiveJob();
});
// Chat Functions
let chatInterval;
function openChatModal(jobId, jobNumber) {
document.getElementById('chat_job_id').value = jobId;
document.getElementById('chatJobNumber').textContent = jobNumber;
document.getElementById('chatModal').classList.remove('hidden');
loadChatMessages();
chatInterval = setInterval(loadChatMessages, 3000);
}
function closeChatModal() {
document.getElementById('chatModal').classList.add('hidden');
clearInterval(chatInterval);
}
function loadChatMessages() {
const jobId = document.getElementById('chat_job_id').value;
fetch(`api/chat.php?action=get_messages&job_id=${jobId}`)
.then(res => res.json())
.then(res => {
if (res.status === 'success') {
const chatBox = document.getElementById('chatBox');
let html = '';
res.data.forEach(msg => {
const isMe = msg.user_id == <?= $_SESSION['user_id'] ?>;
if (isMe) {
html += `
<div class="flex justify-end mb-3">
<div class="bg-emerald-500 text-white rounded-l-xl rounded-br-xl px-4 py-2 max-w-[80%] shadow-sm">
<p class="text-sm">${msg.message}</p>
<p class="text-[10px] text-emerald-100 text-right mt-1">${new Date(msg.created_at).toLocaleTimeString('th-TH', {hour: '2-digit', minute:'2-digit'})}</p>
</div>
</div>`;
} else {
html += `
<div class="flex justify-start mb-3">
<div class="bg-white dark:bg-slate-700 text-slate-800 dark:text-white rounded-r-xl rounded-bl-xl px-4 py-2 max-w-[80%] shadow-sm border border-slate-100 dark:border-slate-600">
<p class="text-xs text-slate-500 dark:text-slate-400 font-bold mb-1">${msg.full_name || 'ผู้ใช้งาน'}</p>
<p class="text-sm">${msg.message}</p>
<p class="text-[10px] text-slate-400 text-right mt-1">${new Date(msg.created_at).toLocaleTimeString('th-TH', {hour: '2-digit', minute:'2-digit'})}</p>
</div>
</div>`;
}
});
chatBox.innerHTML = html;
chatBox.scrollTop = chatBox.scrollHeight;
}
});
}
function sendChatMessage() {
const jobId = document.getElementById('chat_job_id').value;
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (!message) return;
input.value = '';
fetch('api/chat.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `action=send_message&job_id=${jobId}&message=${encodeURIComponent(message)}`
}).then(() => loadChatMessages());
}
// Map Functions
let hospitalMapsData = [];
function openMapModal() {
document.getElementById('mapModal').classList.remove('hidden');
loadHospitalMaps();
}
function closeMapModal() {
document.getElementById('mapModal').classList.add('hidden');
}
function loadHospitalMaps() {
fetch('api/maps.php?action=list&status=active')
.then(res => res.json())
.then(res => {
if (res.status === 'success') {
hospitalMapsData = res.data;
const selector = document.getElementById('mapSelector');
if (hospitalMapsData.length === 0) {
selector.innerHTML = '<option value="">ไม่มีข้อมูลแผนผังในระบบ</option>';
selector.disabled = true;
showMapDetails(null);
return;
}
selector.disabled = false;
let html = '<option value="">-- เลือกอาคารและชั้น --</option>';
hospitalMapsData.forEach(map => {
html += `<option value="${map.id}">${map.name} (${map.building} ชั้น ${map.floor})</option>`;
});
selector.innerHTML = html;
// Auto select first map if available
if(hospitalMapsData.length > 0) {
selector.value = hospitalMapsData[0].id;
showMapDetails(hospitalMapsData[0].id);
}
}
});
}
function showMapDetails(mapId) {
const container = document.getElementById('mapImageContainer');
const locText = document.getElementById('mapTransportLocationText');
const locBox = document.getElementById('mapTransportLocationBox');
if (!mapId) {
container.innerHTML = '<div class="text-slate-500 py-12 text-center w-full">กรุณาเลือกแผนผังที่ต้องการดู</div>';
locBox.classList.add('hidden');
return;
}
const map = hospitalMapsData.find(m => m.id == mapId);
if (map) {
container.innerHTML = `<img src="${map.image_path}" alt="${map.name}" class="max-w-full rounded shadow-sm border border-slate-200 dark:border-slate-700 animate-fade-in">`;
if (map.transport_center_location) {
locText.textContent = map.transport_center_location;
locBox.classList.remove('hidden');
} else {
locBox.classList.add('hidden');
}
}
}
</script>
<!-- Chat Modal -->
<div id="chatModal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/50 backdrop-blur-sm">
<div class="bg-slate-50 dark:bg-slate-800 w-full max-w-md rounded-2xl shadow-2xl flex flex-col h-[600px] max-h-[90vh] animate-fade-in-up border border-slate-200 dark:border-slate-700 overflow-hidden">
<div class="bg-emerald-600 px-4 py-3 flex justify-between items-center text-white">
<h3 class="font-bold flex items-center">
<i class="fa-solid fa-comments me-2"></i> แชทงาน <span id="chatJobNumber" class="ml-2 bg-emerald-500 px-2 py-0.5 rounded text-sm"></span>
</h3>
<button onclick="closeChatModal()" class="text-white hover:text-emerald-200"><i class="fa-solid fa-xmark text-xl"></i></button>
</div>
<input type="hidden" id="chat_job_id">
<div id="chatBox" class="flex-1 p-4 overflow-y-auto bg-slate-100 dark:bg-slate-900/50 flex flex-col gap-1">
<!-- Messages will be loaded here -->
</div>
<div class="p-3 bg-white dark:bg-slate-800 border-t border-slate-200 dark:border-slate-700 flex gap-2">
<input type="text" id="chatInput" class="flex-1 bg-slate-100 dark:bg-slate-900 border-none rounded-full px-4 py-2 text-sm focus:ring-2 focus:ring-emerald-500 dark:text-white" placeholder="พิมพ์ข้อความ..." onkeypress="if(event.key === 'Enter') sendChatMessage()">
<button onclick="sendChatMessage()" class="bg-emerald-500 hover:bg-emerald-600 text-white w-10 h-10 rounded-full flex items-center justify-center transition-colors shadow-sm">
<i class="fa-solid fa-paper-plane text-sm"></i>
</button>
</div>
</div>
</div>
<!-- Map Modal -->
<div id="mapModal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/80 backdrop-blur-sm">
<div class="bg-white dark:bg-slate-800 w-full max-w-4xl rounded-2xl shadow-2xl flex flex-col max-h-[90vh] animate-fade-in-up overflow-hidden">
<div class="px-6 py-4 border-b border-slate-200 dark:border-slate-700 flex flex-col sm:flex-row justify-between items-start sm:items-center bg-slate-50 dark:bg-slate-800/50 gap-4">
<h3 class="font-bold text-slate-800 dark:text-white flex items-center text-lg whitespace-nowrap">
<i class="fa-solid fa-map-location-dot text-emerald-500 me-2"></i> แผนผังอาคารโรงพยาบาล
</h3>
<div class="flex items-center w-full sm:w-auto gap-3">
<select id="mapSelector" class="glass-input px-4 py-2 w-full sm:w-64 bg-white dark:bg-slate-900" onchange="showMapDetails(this.value)">
<option value="">กำลังโหลด...</option>
</select>
<button onclick="closeMapModal()" class="text-slate-500 hover:text-slate-700 dark:hover:text-white bg-slate-200 dark:bg-slate-700 w-10 h-10 rounded-xl flex items-center justify-center shrink-0"><i class="fa-solid fa-xmark"></i></button>
</div>
</div>
<div id="mapTransportLocationBox" class="bg-blue-50 dark:bg-blue-900/30 border-b border-blue-100 dark:border-blue-800 px-6 py-3 hidden">
<p class="text-blue-800 dark:text-blue-300 font-medium flex items-center text-sm">
<i class="fa-solid fa-location-crosshairs text-blue-500 mr-2"></i>
<strong>จุดประจำศูนย์เปล / จุดพักคอย:&nbsp;</strong> <span id="mapTransportLocationText"></span>
</p>
</div>
<div class="p-4 flex-1 overflow-auto bg-slate-100 dark:bg-slate-900 flex justify-center items-center" id="mapImageContainer">
<!-- Map Image -->
<div class="text-slate-500 py-12 text-center w-full">กำลังโหลดข้อมูล...</div>
</div>
</div>
</div>
@@ -0,0 +1,109 @@
<?php
// views/jobs/staff_summary.php
require_once 'models/Job.php';
$jobModel = new Job();
$user_id = $_SESSION['user_id'];
$period = $_GET['period'] ?? 'today';
$stats = $jobModel->getStaffStats($user_id, $period);
$history = $jobModel->getStaffHistory($user_id, 50); // Get last 50 jobs
$total_jobs = $stats['total_jobs'] ?? 0;
$completed_jobs = $stats['completed_jobs'] ?? 0;
$cancelled_jobs = $stats['cancelled_jobs'] ?? 0;
$avg_service_time = $stats['avg_service_time'] ? round($stats['avg_service_time']) : 0;
?>
<div class="mb-6 flex justify-between items-end flex-wrap gap-4">
<div>
<h2 class="text-2xl font-bold text-slate-800 dark:text-white">สรุปผลงานของฉัน</h2>
<p class="text-slate-500 dark:text-slate-400">ประวัติการทำงานและสถิติส่วนตัว</p>
</div>
<form method="GET" class="flex gap-2">
<input type="hidden" name="page" value="staff_summary">
<select name="period" class="glass-input text-sm py-2 px-3" onchange="this.form.submit()">
<option value="today" <?= $period == 'today' ? 'selected' : '' ?>>วันนี้</option>
<option value="week" <?= $period == 'week' ? 'selected' : '' ?>>สัปดาห์นี้</option>
<option value="month" <?= $period == 'month' ? 'selected' : '' ?>>เดือนนี้</option>
</select>
</form>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6 mb-8">
<div class="glass-card p-4 md:p-6 border-l-4 border-blue-500">
<h6 class="text-xs md:text-sm font-medium text-slate-500 dark:text-slate-400 mb-1">รับงานทั้งหมด</h6>
<h2 class="text-2xl md:text-3xl font-bold text-slate-800 dark:text-white"><?= $total_jobs ?> <span class="text-base text-slate-500 font-normal">งาน</span></h2>
</div>
<div class="glass-card p-4 md:p-6 border-l-4 border-emerald-500">
<h6 class="text-xs md:text-sm font-medium text-slate-500 dark:text-slate-400 mb-1">สำเร็จ</h6>
<h2 class="text-2xl md:text-3xl font-bold text-emerald-600 dark:text-emerald-400"><?= $completed_jobs ?> <span class="text-base text-slate-500 font-normal">งาน</span></h2>
</div>
<div class="glass-card p-4 md:p-6 border-l-4 border-red-500">
<h6 class="text-xs md:text-sm font-medium text-slate-500 dark:text-slate-400 mb-1">ยกเลิก/ปฏิเสธ</h6>
<h2 class="text-2xl md:text-3xl font-bold text-red-600 dark:text-red-400"><?= $cancelled_jobs ?> <span class="text-base text-slate-500 font-normal">งาน</span></h2>
</div>
<div class="glass-card p-4 md:p-6 border-l-4 border-purple-500">
<h6 class="text-xs md:text-sm font-medium text-slate-500 dark:text-slate-400 mb-1">เวลาบริการเฉลี่ย</h6>
<h2 class="text-2xl md:text-3xl font-bold text-purple-600 dark:text-purple-400"><?= $avg_service_time ?> <span class="text-base text-slate-500 font-normal">นาที</span></h2>
</div>
</div>
<div class="glass-card overflow-hidden">
<div class="p-4 md:p-6 border-b border-slate-200 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/50 flex justify-between items-center">
<h3 class="font-bold text-slate-800 dark:text-white">ประวัติงานล่าสุด (50 รายการ)</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-100/50 dark:bg-slate-800/50 text-slate-500 dark:text-slate-400 text-xs uppercase tracking-wider">
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700">เลขที่งาน</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700">วันที่-เวลา</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700 hidden md:table-cell">ผู้ป่วย</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700 hidden sm:table-cell">เส้นทาง</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700 text-center">สถานะ</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700 text-center">จัดการ</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800 text-sm">
<?php if(empty($history)): ?>
<tr>
<td colspan="6" class="p-8 text-center text-slate-500">ยังไม่มีประวัติการรับงาน</td>
</tr>
<?php else: ?>
<?php foreach($history as $job): ?>
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors group">
<td class="p-4 font-medium text-slate-800 dark:text-slate-200 whitespace-nowrap"><?= $job['job_number'] ?></td>
<td class="p-4 text-slate-600 dark:text-slate-400 whitespace-nowrap">
<?= date('d/m/Y', strtotime($job['created_at'])) ?><br>
<span class="text-xs text-slate-400"><?= date('H:i', strtotime($job['created_at'])) ?> น.</span>
</td>
<td class="p-4 text-slate-800 dark:text-slate-200 hidden md:table-cell">
<?= htmlspecialchars($job['patient_name'] ?: 'ไม่ระบุชื่อ') ?>
</td>
<td class="p-4 hidden sm:table-cell text-xs">
<div class="flex items-center gap-1 text-slate-500 dark:text-slate-400 max-w-[200px]">
<span class="truncate"><?= htmlspecialchars($job['from_department_name']) ?></span>
<i class="fa-solid fa-arrow-right text-[10px]"></i>
<span class="truncate"><?= htmlspecialchars($job['to_department_name'] ?? $job['to_department_custom']) ?></span>
</div>
</td>
<td class="p-4 text-center">
<?php if($job['status'] === 'completed'): ?>
<span class="bg-green-100 text-green-800 border-green-200 border text-xs font-medium px-2.5 py-0.5 rounded-full whitespace-nowrap"><i class="fa-solid fa-check me-1"></i>สำเร็จ</span>
<?php elseif($job['status'] === 'cancelled'): ?>
<span class="bg-red-100 text-red-800 border-red-200 border text-xs font-medium px-2.5 py-0.5 rounded-full whitespace-nowrap"><i class="fa-solid fa-times me-1"></i>ยกเลิก</span>
<?php endif; ?>
</td>
<td class="p-4 text-center">
<a href="?page=job_detail&id=<?= $job['job_number'] ?>" class="inline-block px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-100 rounded-lg text-xs font-medium transition-colors">
<i class="fa-solid fa-file-lines me-1"></i> รายละเอียด
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>