feat: DH-HIS Web Giao Ban Go v1.0.1 (Favicon, Exe icon, System Tray, Gitea auto-update)
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DH-HIS | Quản Trị & Cấu Hình Hệ Thống Giao Ban</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body class="bg-slate-100 text-slate-800 font-sans antialiased min-h-screen flex flex-col">
|
||||
|
||||
<!-- TOP HEADER -->
|
||||
<header class="bg-gradient-to-r from-slate-900 via-indigo-950 to-slate-900 text-white shadow-lg sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-3.5 flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="bg-indigo-600/30 p-2 rounded-xl border border-indigo-400/30 text-cyan-300">
|
||||
<i class="fa-solid fa-sliders text-2xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-lg font-bold tracking-tight uppercase flex items-center gap-2">
|
||||
DH-HIS Giao Ban | Quản Trị Hệ Thống
|
||||
<span class="text-xs bg-indigo-500/30 text-indigo-200 border border-indigo-400/30 px-2 py-0.5 rounded-full font-mono" id="appVersionBadge">v1.0.0</span>
|
||||
</h1>
|
||||
<p class="text-xs text-slate-400">Thiết lập kết nối CSDL, bảo mật mã hóa, kiểm tra cập nhật & theo dõi nhật ký</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="index.html?v=1.0.1" class="bg-blue-600 hover:bg-blue-700 text-white text-xs font-semibold px-3 py-2 rounded-lg shadow-sm flex items-center gap-1.5 transition-all">
|
||||
<i class="fa-solid fa-hospital-user"></i>
|
||||
<span>Mở Báo Cáo Giao Ban</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- NAVIGATION TABS -->
|
||||
<section class="max-w-7xl mx-auto px-4 sm:px-6 mt-4 w-full">
|
||||
<div class="flex border-b border-slate-200 gap-2 flex-wrap">
|
||||
<button class="admin-tab active px-5 py-2.5 text-sm font-bold border-b-2 border-indigo-600 text-indigo-600 flex items-center gap-2 transition-all" data-tab="tab-config">
|
||||
<i class="fa-solid fa-database"></i>
|
||||
<span>1. Cấu Hình Kết Nối & CSDL</span>
|
||||
</button>
|
||||
<button class="admin-tab px-5 py-2.5 text-sm font-bold border-b-2 border-transparent text-slate-500 hover:text-slate-800 flex items-center gap-2 transition-all" data-tab="tab-update">
|
||||
<i class="fa-solid fa-cloud-arrow-down"></i>
|
||||
<span>2. Cập Nhật Phiên Bản (Gitea)</span>
|
||||
</button>
|
||||
<button class="admin-tab px-5 py-2.5 text-sm font-bold border-b-2 border-transparent text-slate-500 hover:text-slate-800 flex items-center gap-2 transition-all" data-tab="tab-logs">
|
||||
<i class="fa-solid fa-terminal"></i>
|
||||
<span>3. Nhật Ký Hoạt Động (Live Logs)</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MAIN CONTAINER -->
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 py-6 flex-1 w-full">
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- TAB 1: CẤU HÌNH KẾT NỐI -->
|
||||
<!-- ========================================== -->
|
||||
<div id="tab-config" class="admin-content space-y-6">
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
|
||||
<!-- Server & PostgreSQL Config Form -->
|
||||
<div class="lg:col-span-6 bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-5 py-4 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-server text-indigo-600"></i>
|
||||
<span>Cấu Hình Web Server & CSDL PostgreSQL</span>
|
||||
</h2>
|
||||
<span class="text-[11px] bg-emerald-100 text-emerald-800 px-2 py-0.5 rounded font-bold">Mã hóa AES-256</span>
|
||||
</div>
|
||||
|
||||
<form id="formConfig" class="p-5 space-y-4 text-xs">
|
||||
|
||||
<!-- Server Settings -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Cổng Web (Port):</label>
|
||||
<input type="number" id="cfgPort" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500 font-bold" value="3000">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Đường dẫn quản trị:</label>
|
||||
<input type="text" id="cfgAdminPath" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500" value="/admin">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PostgreSQL Settings -->
|
||||
<div class="border-t border-slate-200 pt-3 space-y-3">
|
||||
<div class="font-bold text-slate-800 text-xs flex items-center gap-1.5">
|
||||
<i class="fa-solid fa-database text-blue-600"></i> Thông tin máy chủ PostgreSQL:
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="col-span-2">
|
||||
<label class="block font-medium text-slate-600 mb-1">Địa chỉ IP / Host:</label>
|
||||
<input type="text" id="cfgDbHost" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500 font-medium" value="192.168.50.79">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-medium text-slate-600 mb-1">Port:</label>
|
||||
<input type="number" id="cfgDbPort" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500 font-medium" value="5437">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block font-medium text-slate-600 mb-1">Người dùng (User):</label>
|
||||
<input type="text" id="cfgDbUser" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500 font-medium" value="postgres">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-medium text-slate-600 mb-1 flex items-center justify-between">
|
||||
<span>Mật khẩu:</span>
|
||||
<button type="button" id="btnToggleDbPass" class="text-[10px] text-indigo-600 hover:underline">Hiện</button>
|
||||
</label>
|
||||
<input type="password" id="cfgDbPassword" placeholder="•••••••• (Giữ nguyên hoặc nhập mới)" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500 font-medium">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block font-medium text-slate-600 mb-1">CSDL mặc định (Default DB):</label>
|
||||
<input type="text" id="cfgDbDefault" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500 font-medium" value="an_omon_full_04052026">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Connection Button -->
|
||||
<div class="pt-2 flex items-center justify-between">
|
||||
<button type="button" id="btnTestConnection" class="bg-slate-800 hover:bg-slate-900 text-white font-semibold py-2 px-3.5 rounded-lg flex items-center gap-1.5 transition-all">
|
||||
<i class="fa-solid fa-bolt text-amber-400"></i>
|
||||
<span>Thử Kết Nối (Test Connection)</span>
|
||||
</button>
|
||||
<div id="testConnResult" class="text-xs font-semibold"></div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="pt-4 border-t border-slate-200 flex justify-end">
|
||||
<button type="submit" id="btnSaveConfig" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2.5 px-6 rounded-lg shadow-md flex items-center gap-2 transition-all text-xs">
|
||||
<i class="fa-solid fa-floppy-disk"></i>
|
||||
<span>Lưu Cấu Hình & Mã Hóa</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Databases List Management -->
|
||||
<div class="lg:col-span-6 bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden flex flex-col">
|
||||
<div class="px-5 py-4 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-list text-blue-600"></i>
|
||||
<span>Danh Sách CSDL Bệnh Viện (Dropdown)</span>
|
||||
</h2>
|
||||
<button id="btnAddDbRow" class="bg-blue-50 text-blue-700 hover:bg-blue-100 border border-blue-200 px-2.5 py-1 rounded-lg text-xs font-bold flex items-center gap-1">
|
||||
<i class="fa-solid fa-plus"></i> Thêm CSDL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-4 flex-1 overflow-y-auto">
|
||||
<table class="w-full text-left text-xs border-collapse">
|
||||
<thead class="bg-slate-100 text-slate-700 font-semibold border-b border-slate-200">
|
||||
<tr>
|
||||
<th class="py-2 px-2.5 w-44">Tên CSDL (DB Name)</th>
|
||||
<th class="py-2 px-2.5">Tên Hiển Thị (Label)</th>
|
||||
<th class="py-2 px-2 w-16 text-center">Xóa</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="bodyDbList" class="divide-y divide-slate-200">
|
||||
<!-- Dynamically loaded -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-amber-50/70 border-t border-amber-200 text-amber-800 text-[11px] leading-relaxed">
|
||||
<i class="fa-solid fa-circle-info mr-1"></i> Các CSDL này sẽ hiển thị trong dropdown chọn CSDL ngoài trang báo cáo giao ban để người dùng chuyển đổi linh hoạt.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- TAB 2: CẬP NHẬT PHIÊN BẢN (GITEA) -->
|
||||
<!-- ========================================== -->
|
||||
<div id="tab-update" class="admin-content hidden space-y-6">
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
|
||||
<!-- Gitea Config -->
|
||||
<div class="lg:col-span-5 bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-5 py-4 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-gear text-indigo-600"></i>
|
||||
<span>Thiết Lập Gitea Package / Release</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form id="formGiteaConfig" class="p-5 space-y-3.5 text-xs">
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Gitea URL:</label>
|
||||
<input type="text" id="cfgGiteaUrl" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500 font-medium" value="https://i.dh-his.com">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Tổ chức / Owner:</label>
|
||||
<input type="text" id="cfgGiteaOwner" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500" value="hdhiswork">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Repository:</label>
|
||||
<input type="text" id="cfgGiteaRepo" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500" value="DUAN">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1 flex items-center justify-between">
|
||||
<span>Personal Access Token (Để tải bản private):</span>
|
||||
<button type="button" id="btnToggleToken" class="text-[10px] text-indigo-600 hover:underline">Hiện</button>
|
||||
</label>
|
||||
<input type="password" id="cfgGiteaToken" placeholder="•••••••• (Giữ nguyên hoặc nhập token mới)" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 font-mono outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="pt-2 flex justify-end">
|
||||
<button type="submit" class="bg-slate-800 hover:bg-slate-900 text-white font-bold py-2 px-4 rounded-lg flex items-center gap-1.5 transition-all">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
<span>Lưu Cấu Hình Gitea</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Release Status & Action -->
|
||||
<div class="lg:col-span-7 bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden flex flex-col">
|
||||
<div class="px-5 py-4 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-code-compare text-blue-600"></i>
|
||||
<span>Trạng Thái Phiên Bản & Tự Động Cập Nhật</span>
|
||||
</h2>
|
||||
<button id="btnCheckUpdate" class="bg-indigo-600 hover:bg-indigo-700 text-white px-3.5 py-1.5 rounded-lg text-xs font-bold shadow-sm flex items-center gap-1.5 transition-all">
|
||||
<i class="fa-solid fa-arrows-rotate"></i>
|
||||
<span>Kiểm Tra Cập Nhật</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 flex-1 space-y-4">
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="p-4 rounded-xl border border-slate-200 bg-slate-50">
|
||||
<div class="text-xs text-slate-500 font-semibold">Phiên Bản Đang Chạy:</div>
|
||||
<div class="mt-1 text-2xl font-black text-slate-800" id="currentVerDisplay">v1.0.0</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-xl border border-indigo-200 bg-indigo-50/40">
|
||||
<div class="text-xs text-indigo-700 font-semibold">Phiên Bản Mới Nhất Trên Gitea:</div>
|
||||
<div class="mt-1 text-2xl font-black text-indigo-700" id="latestVerDisplay">Đang kiểm tra...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="updateStatusBox" class="p-4 rounded-xl border border-slate-200 bg-white">
|
||||
<div class="font-bold text-xs text-slate-700 mb-1" id="updateStatusTitle">Nội dung phiên bản:</div>
|
||||
<div class="text-xs text-slate-600 whitespace-pre-wrap leading-relaxed max-h-48 overflow-y-auto" id="updateReleaseNotes">
|
||||
Bấm nút "Kiểm Tra Cập Nhật" để truy vấn phiên bản mới nhất từ Gitea.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Button Apply Update -->
|
||||
<div id="applyUpdateArea" class="hidden p-4 rounded-xl bg-emerald-50 border border-emerald-200 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div class="font-bold text-xs text-emerald-900">Phát hiện bản cập nhật mới!</div>
|
||||
<div class="text-[11px] text-emerald-700" id="updateAssetInfo">File: ...</div>
|
||||
</div>
|
||||
<button id="btnApplyUpdate" class="bg-emerald-600 hover:bg-emerald-700 text-white px-5 py-2.5 rounded-lg font-bold text-xs shadow-md flex items-center gap-2 transition-all">
|
||||
<i class="fa-solid fa-download"></i>
|
||||
<span>Tải & Cập Nhật Tự Động</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- TAB 3: NHẬT KÝ HOẠT ĐỘNG (LIVE LOGS) -->
|
||||
<!-- ========================================== -->
|
||||
<div id="tab-logs" class="admin-content hidden space-y-4">
|
||||
|
||||
<!-- Toolbar Logs -->
|
||||
<div class="bg-white p-3.5 rounded-2xl shadow-sm border border-slate-200 flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 text-xs">
|
||||
<span class="font-bold text-slate-700 flex items-center gap-1.5">
|
||||
<i class="fa-solid fa-filter text-indigo-600"></i> Cấp độ log:
|
||||
</span>
|
||||
<select id="selectLogLevel" class="rounded-lg border border-slate-300 px-3 py-1.5 bg-slate-50 outline-none font-medium">
|
||||
<option value="ALL">Tất cả (ALL)</option>
|
||||
<option value="INFO">Thông tin (INFO)</option>
|
||||
<option value="WARN">Cảnh báo (WARN)</option>
|
||||
<option value="ERROR">Lỗi (ERROR)</option>
|
||||
<option value="SQL">Truy vấn (SQL)</option>
|
||||
</select>
|
||||
|
||||
<label class="inline-flex items-center gap-1.5 cursor-pointer text-slate-600 font-medium">
|
||||
<input type="checkbox" id="chkAutoRefresh" class="rounded text-indigo-600" checked>
|
||||
<span>Tự động cập nhật (3s)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="btnRefreshLogs" class="bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold px-3 py-1.5 rounded-lg border border-slate-300 flex items-center gap-1">
|
||||
<i class="fa-solid fa-rotate-right"></i> Làm mới
|
||||
</button>
|
||||
<button id="btnClearLogs" class="bg-rose-50 hover:bg-rose-100 text-rose-700 border border-rose-200 text-xs font-semibold px-3 py-1.5 rounded-lg flex items-center gap-1">
|
||||
<i class="fa-solid fa-trash-can"></i> Xóa log
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Terminal Box -->
|
||||
<div class="bg-slate-950 rounded-2xl p-4 shadow-xl border border-slate-800 font-mono text-xs overflow-hidden flex flex-col h-[600px]">
|
||||
<div class="flex items-center justify-between pb-2 border-b border-slate-800 text-slate-500 text-[11px]">
|
||||
<span>TERMINAL LOG STREAM (Ring buffer 500 dòng)</span>
|
||||
<span id="logCountBadge" class="text-slate-400">0 dòng</span>
|
||||
</div>
|
||||
<div id="logTerminal" class="flex-1 overflow-y-auto space-y-1 mt-2 text-slate-300 leading-relaxed pr-2">
|
||||
<div class="text-slate-500 italic">Đang tải nhật ký...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- TOAST -->
|
||||
<div id="toast" class="fixed bottom-4 right-4 z-50 bg-slate-900 text-white text-xs px-4 py-2.5 rounded-lg shadow-xl flex items-center gap-2 transform translate-y-20 opacity-0 transition-all duration-300">
|
||||
<i class="fa-solid fa-circle-check text-emerald-400"></i>
|
||||
<span id="toastMsg">Thông báo</span>
|
||||
</div>
|
||||
|
||||
<script src="admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+446
@@ -0,0 +1,446 @@
|
||||
// Admin & Configuration JavaScript
|
||||
const API_BASE = window.location.pathname.startsWith('/web-giao-ban-go') ? '/web-giao-ban-go' : (window.location.pathname.startsWith('/web-giao-ban') ? '/web-giao-ban' : '');
|
||||
let autoRefreshTimer = null;
|
||||
let currentUpdateAsset = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initTabs();
|
||||
loadConfig();
|
||||
initLogViewer();
|
||||
initUpdateChecker();
|
||||
|
||||
// Handle URL hash tab
|
||||
const hash = window.location.hash;
|
||||
if (hash === '#tab-update') {
|
||||
switchTab('tab-update');
|
||||
checkUpdate();
|
||||
} else if (hash === '#tab-logs') {
|
||||
switchTab('tab-logs');
|
||||
}
|
||||
});
|
||||
|
||||
function showToast(msg) {
|
||||
const toast = document.getElementById('toast');
|
||||
const toastMsg = document.getElementById('toastMsg');
|
||||
if (!toast || !toastMsg) return;
|
||||
toastMsg.innerText = msg;
|
||||
toast.classList.remove('translate-y-20', 'opacity-0');
|
||||
toast.classList.add('translate-y-0', 'opacity-100');
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('translate-y-0', 'opacity-100');
|
||||
toast.classList.add('translate-y-20', 'opacity-0');
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function initTabs() {
|
||||
document.querySelectorAll('.admin-tab').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const target = btn.getAttribute('data-tab');
|
||||
switchTab(target);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function switchTab(targetId) {
|
||||
document.querySelectorAll('.admin-tab').forEach(b => {
|
||||
const isTarget = b.getAttribute('data-tab') === targetId;
|
||||
b.classList.toggle('active', isTarget);
|
||||
b.classList.toggle('border-indigo-600', isTarget);
|
||||
b.classList.toggle('text-indigo-600', isTarget);
|
||||
b.classList.toggle('border-transparent', !isTarget);
|
||||
b.classList.toggle('text-slate-500', !isTarget);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.admin-content').forEach(c => {
|
||||
c.classList.toggle('hidden', c.id !== targetId);
|
||||
});
|
||||
|
||||
if (targetId === 'tab-logs') {
|
||||
fetchLogs();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CONFIGURATION
|
||||
// -------------------------------------------------------------------
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/admin/config`);
|
||||
const json = await res.json();
|
||||
if (json.success && json.data) {
|
||||
const cfg = json.data;
|
||||
|
||||
// Server
|
||||
document.getElementById('cfgPort').value = cfg.server.port || 3000;
|
||||
document.getElementById('cfgAdminPath').value = cfg.server.admin_path || '/admin';
|
||||
|
||||
// Database
|
||||
document.getElementById('cfgDbHost').value = cfg.database.host || '192.168.50.79';
|
||||
document.getElementById('cfgDbPort').value = cfg.database.port || 5437;
|
||||
document.getElementById('cfgDbUser').value = cfg.database.user || 'postgres';
|
||||
document.getElementById('cfgDbPassword').value = '';
|
||||
document.getElementById('cfgDbDefault').value = cfg.database.default_db || '';
|
||||
|
||||
// Databases Table
|
||||
renderDbList(cfg.database.databases || []);
|
||||
|
||||
// Gitea Updater
|
||||
document.getElementById('cfgGiteaUrl').value = cfg.updater.gitea_url || 'https://i.dh-his.com';
|
||||
document.getElementById('cfgGiteaOwner').value = cfg.updater.owner || 'hdhiswork';
|
||||
document.getElementById('cfgGiteaRepo').value = cfg.updater.repo || 'DUAN';
|
||||
document.getElementById('cfgGiteaToken').value = '';
|
||||
|
||||
const ver = cfg.updater.current_version || 'v1.0.0';
|
||||
document.getElementById('appVersionBadge').innerText = ver;
|
||||
document.getElementById('currentVerDisplay').innerText = ver;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Lỗi nạp cấu hình:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function renderDbList(list) {
|
||||
const tbody = document.getElementById('bodyDbList');
|
||||
tbody.innerHTML = '';
|
||||
list.forEach(db => {
|
||||
addDbRow(db.id, db.name);
|
||||
});
|
||||
}
|
||||
|
||||
function addDbRow(id = '', name = '') {
|
||||
const tbody = document.getElementById('bodyDbList');
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = 'hover:bg-slate-50 transition-colors';
|
||||
tr.innerHTML = `
|
||||
<td class="py-2 px-2.5">
|
||||
<input type="text" class="w-full rounded border border-slate-300 px-2 py-1 font-mono text-xs db-row-id" value="${id}" placeholder="Tên CSDL trong Postgres">
|
||||
</td>
|
||||
<td class="py-2 px-2.5">
|
||||
<input type="text" class="w-full rounded border border-slate-300 px-2 py-1 text-xs db-row-name" value="${name}" placeholder="Tên hiển thị dropdown">
|
||||
</td>
|
||||
<td class="py-2 px-2 text-center">
|
||||
<button type="button" class="text-rose-500 hover:text-rose-700 p-1 text-xs btn-del-row" title="Xóa dòng">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tr.querySelector('.btn-del-row').addEventListener('click', () => tr.remove());
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
document.getElementById('btnAddDbRow')?.addEventListener('click', () => addDbRow());
|
||||
|
||||
// Password Toggle
|
||||
document.getElementById('btnToggleDbPass')?.addEventListener('click', () => {
|
||||
const inp = document.getElementById('cfgDbPassword');
|
||||
const btn = document.getElementById('btnToggleDbPass');
|
||||
if (inp.type === 'password') {
|
||||
inp.type = 'text';
|
||||
btn.innerText = 'Ẩn';
|
||||
} else {
|
||||
inp.type = 'password';
|
||||
btn.innerText = 'Hiện';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btnToggleToken')?.addEventListener('click', () => {
|
||||
const inp = document.getElementById('cfgGiteaToken');
|
||||
const btn = document.getElementById('btnToggleToken');
|
||||
if (inp.type === 'password') {
|
||||
inp.type = 'text';
|
||||
btn.innerText = 'Ẩn';
|
||||
} else {
|
||||
inp.type = 'password';
|
||||
btn.innerText = 'Hiện';
|
||||
}
|
||||
});
|
||||
|
||||
// Test Connection
|
||||
document.getElementById('btnTestConnection')?.addEventListener('click', async () => {
|
||||
const resEl = document.getElementById('testConnResult');
|
||||
resEl.innerHTML = '<span class="text-blue-600"><i class="fa-solid fa-spinner fa-spin mr-1"></i> Đang thử kết nối...</span>';
|
||||
|
||||
const body = {
|
||||
host: document.getElementById('cfgDbHost').value,
|
||||
port: parseInt(document.getElementById('cfgDbPort').value, 10),
|
||||
user: document.getElementById('cfgDbUser').value,
|
||||
password: document.getElementById('cfgDbPassword').value,
|
||||
database: document.getElementById('cfgDbDefault').value,
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/admin/test-db`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
resEl.innerHTML = '<span class="text-emerald-600"><i class="fa-solid fa-circle-check mr-1"></i> Kết nối thành công!</span>';
|
||||
} else {
|
||||
resEl.innerHTML = `<span class="text-rose-600"><i class="fa-solid fa-circle-xmark mr-1"></i> ${json.message}</span>`;
|
||||
}
|
||||
} catch (err) {
|
||||
resEl.innerHTML = `<span class="text-rose-600"><i class="fa-solid fa-circle-xmark mr-1"></i> ${err.message}</span>`;
|
||||
}
|
||||
});
|
||||
|
||||
// Save Config Form
|
||||
document.getElementById('formConfig')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('btnSaveConfig');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> <span>Đang lưu...</span>';
|
||||
|
||||
const dbs = [];
|
||||
document.querySelectorAll('#bodyDbList tr').forEach(tr => {
|
||||
const id = tr.querySelector('.db-row-id').value.trim();
|
||||
const name = tr.querySelector('.db-row-name').value.trim();
|
||||
if (id) {
|
||||
dbs.push({ id, name: name || id });
|
||||
}
|
||||
});
|
||||
|
||||
const body = {
|
||||
server: {
|
||||
port: parseInt(document.getElementById('cfgPort').value, 10),
|
||||
admin_path: document.getElementById('cfgAdminPath').value.trim(),
|
||||
},
|
||||
database: {
|
||||
host: document.getElementById('cfgDbHost').value.trim(),
|
||||
port: parseInt(document.getElementById('cfgDbPort').value, 10),
|
||||
user: document.getElementById('cfgDbUser').value.trim(),
|
||||
password: document.getElementById('cfgDbPassword').value,
|
||||
default_db: document.getElementById('cfgDbDefault').value.trim(),
|
||||
databases: dbs,
|
||||
},
|
||||
updater: {
|
||||
enabled: true,
|
||||
gitea_url: document.getElementById('cfgGiteaUrl').value.trim(),
|
||||
owner: document.getElementById('cfgGiteaOwner').value.trim(),
|
||||
repo: document.getElementById('cfgGiteaRepo').value.trim(),
|
||||
token: document.getElementById('cfgGiteaToken').value,
|
||||
current_version: document.getElementById('currentVerDisplay').innerText,
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/admin/config`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
showToast('Đã lưu cấu hình & mã hóa thành công!');
|
||||
loadConfig();
|
||||
} else {
|
||||
alert('Lỗi lưu cấu hình: ' + json.message);
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Lỗi kết nối server: ' + err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fa-solid fa-floppy-disk"></i> <span>Lưu Cấu Hình & Mã Hóa</span>';
|
||||
}
|
||||
});
|
||||
|
||||
// Save Gitea Config Form
|
||||
document.getElementById('formGiteaConfig')?.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById('formConfig').dispatchEvent(new Event('submit'));
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// LOG VIEWER
|
||||
// -------------------------------------------------------------------
|
||||
function initLogViewer() {
|
||||
document.getElementById('btnRefreshLogs')?.addEventListener('click', fetchLogs);
|
||||
document.getElementById('selectLogLevel')?.addEventListener('change', fetchLogs);
|
||||
document.getElementById('btnClearLogs')?.addEventListener('click', clearLogs);
|
||||
|
||||
const chk = document.getElementById('chkAutoRefresh');
|
||||
chk?.addEventListener('change', () => {
|
||||
if (chk.checked) {
|
||||
startAutoRefresh();
|
||||
} else {
|
||||
clearInterval(autoRefreshTimer);
|
||||
}
|
||||
});
|
||||
|
||||
startAutoRefresh();
|
||||
}
|
||||
|
||||
function startAutoRefresh() {
|
||||
clearInterval(autoRefreshTimer);
|
||||
autoRefreshTimer = setInterval(() => {
|
||||
const logsTab = document.getElementById('tab-logs');
|
||||
if (logsTab && !logsTab.classList.contains('hidden')) {
|
||||
fetchLogs();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
async function fetchLogs() {
|
||||
const level = document.getElementById('selectLogLevel')?.value || 'ALL';
|
||||
const box = document.getElementById('logTerminal');
|
||||
const countBadge = document.getElementById('logCountBadge');
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/admin/logs?limit=300&level=${level}`);
|
||||
const json = await res.json();
|
||||
if (json.success && Array.isArray(json.data)) {
|
||||
countBadge.innerText = `${json.data.length} dòng`;
|
||||
|
||||
if (json.data.length === 0) {
|
||||
box.innerHTML = '<div class="text-slate-500 italic">Không có dòng log nào.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
box.innerHTML = '';
|
||||
json.data.forEach(entry => {
|
||||
let colorClass = 'text-slate-300';
|
||||
let badgeColor = 'bg-slate-800 text-slate-300';
|
||||
|
||||
switch (entry.level) {
|
||||
case 'INFO':
|
||||
colorClass = 'text-emerald-400';
|
||||
badgeColor = 'bg-emerald-950 text-emerald-300 border border-emerald-800';
|
||||
break;
|
||||
case 'WARN':
|
||||
colorClass = 'text-amber-400';
|
||||
badgeColor = 'bg-amber-950 text-amber-300 border border-amber-800';
|
||||
break;
|
||||
case 'ERROR':
|
||||
colorClass = 'text-rose-400 font-bold';
|
||||
badgeColor = 'bg-rose-950 text-rose-300 border border-rose-800';
|
||||
break;
|
||||
case 'SQL':
|
||||
colorClass = 'text-cyan-400';
|
||||
badgeColor = 'bg-cyan-950 text-cyan-300 border border-cyan-800';
|
||||
break;
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'py-0.5 border-b border-slate-900/60 flex items-start gap-2';
|
||||
div.innerHTML = `
|
||||
<span class="text-slate-500 text-[10px] whitespace-nowrap">${entry.timestamp}</span>
|
||||
<span class="text-[10px] font-bold px-1.5 rounded ${badgeColor}">${entry.level}</span>
|
||||
<span class="${colorClass} break-all flex-1">${escapeHtml(entry.message)}</span>
|
||||
`;
|
||||
box.appendChild(div);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Lỗi tải log:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function clearLogs() {
|
||||
if (!confirm('Bạn có chắc muốn làm sạch toàn bộ bộ nhớ log không?')) return;
|
||||
try {
|
||||
await fetch(`${API_BASE}/api/admin/logs/clear`, { method: 'POST' });
|
||||
fetchLogs();
|
||||
showToast('Đã xóa bộ nhớ log!');
|
||||
} catch (err) {
|
||||
alert('Lỗi xóa log: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// UPDATE CHECKER (GITEA)
|
||||
// -------------------------------------------------------------------
|
||||
function initUpdateChecker() {
|
||||
document.getElementById('btnCheckUpdate')?.addEventListener('click', checkUpdate);
|
||||
document.getElementById('btnApplyUpdate')?.addEventListener('click', applyUpdate);
|
||||
}
|
||||
|
||||
async function checkUpdate() {
|
||||
const btn = document.getElementById('btnCheckUpdate');
|
||||
const latestDisplay = document.getElementById('latestVerDisplay');
|
||||
const notesEl = document.getElementById('updateReleaseNotes');
|
||||
const statusTitle = document.getElementById('updateStatusTitle');
|
||||
const applyArea = document.getElementById('applyUpdateArea');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> <span>Đang kiểm tra...</span>';
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/update/check`);
|
||||
const json = await res.json();
|
||||
|
||||
if (json.success && json.data) {
|
||||
const u = json.data;
|
||||
latestDisplay.innerText = u.latest_version || u.current_version;
|
||||
|
||||
if (u.has_update) {
|
||||
statusTitle.innerHTML = `<span class="text-emerald-600 font-bold"><i class="fa-solid fa-bell"></i> Có bản cập nhật mới: ${u.release_title || u.latest_version}</span>`;
|
||||
notesEl.innerText = u.release_notes || 'Không có mô tả chi tiết.';
|
||||
|
||||
if (u.asset_url) {
|
||||
currentUpdateAsset = u;
|
||||
applyArea.classList.remove('hidden');
|
||||
document.getElementById('updateAssetInfo').innerText = `File: ${u.asset_name} (${(u.asset_size / (1024 * 1024)).toFixed(2)} MB)`;
|
||||
} else {
|
||||
applyArea.classList.add('hidden');
|
||||
notesEl.innerText += '\n\n(Lưu ý: Chưa tìm thấy file nhị phân tương thích trong bản release).';
|
||||
}
|
||||
} else {
|
||||
statusTitle.innerHTML = `<span class="text-slate-600 font-bold"><i class="fa-solid fa-circle-check text-emerald-500"></i> Bạn đang sử dụng phiên bản mới nhất (${u.current_version})</span>`;
|
||||
notesEl.innerText = u.release_notes || 'Hệ thống đã cập nhật đầy đủ các tính năng mới nhất.';
|
||||
applyArea.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
latestDisplay.innerText = 'Lỗi kết nối';
|
||||
notesEl.innerText = json.message || 'Không thể kết nối đến máy chủ Gitea.';
|
||||
}
|
||||
} catch (err) {
|
||||
latestDisplay.innerText = 'Lỗi';
|
||||
notesEl.innerText = err.message;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fa-solid fa-arrows-rotate"></i> <span>Kiểm Tra Cập Nhật</span>';
|
||||
}
|
||||
}
|
||||
|
||||
async function applyUpdate() {
|
||||
if (!currentUpdateAsset || !currentUpdateAsset.asset_url) return;
|
||||
if (!confirm(`Bạn có chắc muốn tự động cập nhật lên phiên bản ${currentUpdateAsset.latest_version}?`)) return;
|
||||
|
||||
const btn = document.getElementById('btnApplyUpdate');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin mr-1"></i> Đang tải và cài đặt...';
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/update/apply`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
asset_url: currentUpdateAsset.asset_url,
|
||||
version: currentUpdateAsset.latest_version,
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
alert(json.message);
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Lỗi cập nhật: ' + json.message);
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Lỗi kết nối cập nhật: ' + err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fa-solid fa-download"></i> <span>Tải & Cập Nhật Tự Động</span>';
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
+1544
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,907 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DH-HIS | Quản Lý Giao Ban Hằng Ngày</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<!-- Tailwind CSS via CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Chart.js via CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body class="bg-slate-100 text-slate-800 font-sans antialiased">
|
||||
|
||||
<!-- TOP HEADER -->
|
||||
<header class="bg-gradient-to-r from-blue-700 via-sky-700 to-indigo-800 text-white shadow-lg sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-3 flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="bg-white/10 p-2 rounded-lg backdrop-blur-sm border border-white/20">
|
||||
<i class="fa-solid fa-hospital-user text-2xl text-cyan-300"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-xl font-bold tracking-tight uppercase flex items-center gap-2 flex-wrap">
|
||||
Phần Mềm Quản Lý Giao Ban Bệnh Viện
|
||||
<span class="text-xs bg-cyan-500/30 text-cyan-200 border border-cyan-400/40 px-2 py-0.5 rounded-full font-normal">DH-HIS Web</span>
|
||||
<span id="currentDbBadge" class="text-xs bg-emerald-500/20 text-emerald-300 border border-emerald-400/30 px-2.5 py-0.5 rounded-full font-medium flex items-center gap-1.5">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> BVĐK Ô Môn
|
||||
</span>
|
||||
</h1>
|
||||
<p class="text-xs text-sky-200">Báo cáo Giao ban Ngoại trú & Nội trú hằng ngày - Phân tích đối chiếu đa chu kỳ</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Database Selector -->
|
||||
<div class="flex items-center gap-2 text-xs bg-slate-900/40 px-3 py-1.5 rounded-lg border border-white/10">
|
||||
<label for="selectDb" class="text-sky-200 font-medium flex items-center gap-1 whitespace-nowrap">
|
||||
<i class="fa-solid fa-database text-amber-400"></i> CSDL:
|
||||
</label>
|
||||
<select id="selectDb" class="bg-slate-800 text-white rounded px-2 py-1 outline-none border border-slate-600 focus:border-cyan-400 text-xs font-medium cursor-pointer">
|
||||
<option value="an_omon_full_04052026">BVĐK Ô Môn - Cần Thơ (33 GB - Chuẩn khoa phòng)</option>
|
||||
<option value="tuan_dbgovap_full">BVĐK Gò Vấp - TP.HCM (65 GB - Dữ liệu lớn)</option>
|
||||
<option value="binh_bvq12_062026052026042026032026022026">BV Quận 12 - TP.HCM (21 GB)</option>
|
||||
<option value="bvq12_697">BV Quận 12 - Bản 697 (17 GB)</option>
|
||||
<option value="an_dalieu_full_05082026">BV Da Liễu (31 GB)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Admin Config Link -->
|
||||
<a href="admin.html?v=1.0.1" class="text-xs bg-slate-900/40 hover:bg-slate-800 text-sky-200 hover:text-white px-2.5 py-2 rounded-lg border border-white/10 flex items-center gap-1.5 transition-all" title="Quản trị & Cấu hình hệ thống">
|
||||
<i class="fa-solid fa-sliders text-cyan-300"></i>
|
||||
<span class="hidden sm:inline">Quản Trị</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- CONTROL PANEL / FILTER TOOLBAR -->
|
||||
<section class="max-w-7xl mx-auto px-4 sm:px-6 mt-4">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 p-4 transition-all">
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-3 items-end">
|
||||
|
||||
<!-- Mốc thời gian chọn nhanh -->
|
||||
<div class="md:col-span-3">
|
||||
<label class="block text-xs font-semibold text-slate-600 mb-1">
|
||||
<i class="fa-solid fa-calendar-check text-blue-600"></i> Chọn nhanh mốc thời gian:
|
||||
</label>
|
||||
<select id="quickPreset" class="w-full text-sm rounded-lg border border-slate-300 bg-slate-50 px-3 py-2 outline-none focus:ring-2 focus:ring-blue-500 font-medium">
|
||||
<option value="month-03-2026" selected>Tháng 03/2026 (Dữ liệu đầy đủ đối chiếu)</option>
|
||||
<option value="month-04-2026">Tháng 04/2026</option>
|
||||
<option value="month-01-2026">Tháng 01/2026</option>
|
||||
<option value="month-02-2026">Tháng 02/2026</option>
|
||||
<option value="today">Hôm nay</option>
|
||||
<option value="yesterday">Hôm qua</option>
|
||||
<option value="this-month">Tháng hiện tại</option>
|
||||
<option value="quarter-1">Quý I/2026</option>
|
||||
<option value="year-2026">Cả năm 2026</option>
|
||||
<option value="custom">Tùy chọn khoảng ngày...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Kỳ chính: Từ ngày - Đến ngày -->
|
||||
<div class="md:col-span-4 flex items-center gap-2">
|
||||
<div class="w-1/2">
|
||||
<label class="block text-xs font-semibold text-slate-600 mb-1">Từ ngày:</label>
|
||||
<input type="date" id="dateFrom" class="w-full text-sm rounded-lg border border-slate-300 px-2.5 py-1.5 bg-slate-50 outline-none focus:ring-2 focus:ring-blue-500" value="2026-03-01">
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<label class="block text-xs font-semibold text-slate-600 mb-1">Đến ngày:</label>
|
||||
<input type="date" id="dateTo" class="w-full text-sm rounded-lg border border-slate-300 px-2.5 py-1.5 bg-slate-50 outline-none focus:ring-2 focus:ring-blue-500" value="2026-03-31">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Checkbox so sánh -->
|
||||
<div class="md:col-span-2 flex flex-col justify-end pb-1.5">
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer select-none text-xs font-semibold text-indigo-700 bg-indigo-50 border border-indigo-200 px-3 py-2 rounded-lg hover:bg-indigo-100 transition-colors">
|
||||
<input type="checkbox" id="enableCompare" class="rounded text-indigo-600 focus:ring-indigo-500 h-4 w-4">
|
||||
<span>So sánh 2 kỳ</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="md:col-span-3 flex items-center gap-2">
|
||||
<button id="btnFilter" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold py-2 px-3 rounded-lg shadow-sm flex items-center justify-center gap-1.5 transition-all">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
<span>Xem Báo Cáo</span>
|
||||
</button>
|
||||
<button id="btnOpenIssueModal" class="bg-rose-600 hover:bg-rose-700 text-white text-sm font-semibold py-2 px-3 rounded-lg shadow-sm flex items-center gap-1.5 transition-all" title="Báo cáo lỗi / Góp ý">
|
||||
<i class="fa-solid fa-bug"></i>
|
||||
<span class="hidden sm:inline">Báo Lỗi</span>
|
||||
</button>
|
||||
<button id="btnPrint" class="bg-slate-100 hover:bg-slate-200 text-slate-700 text-sm font-semibold py-2 px-2.5 rounded-lg border border-slate-300" title="In báo cáo">
|
||||
<i class="fa-solid fa-print"></i>
|
||||
</button>
|
||||
<button id="btnExport" class="bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-semibold py-2 px-2.5 rounded-lg shadow-sm" title="Xuất dữ liệu Excel">
|
||||
<i class="fa-solid fa-file-excel"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KỲ SO SÁNH (Hiển thị khi bật enableCompare) -->
|
||||
<div id="compareContainer" class="hidden mt-3 pt-3 border-t border-dashed border-indigo-200 bg-indigo-50/50 p-3 rounded-lg">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<span class="text-xs font-bold text-indigo-900 flex items-center gap-1.5">
|
||||
<i class="fa-solid fa-code-compare text-indigo-600"></i> Kỳ so sánh (Ví dụ: Tháng 01/2026):
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-xs text-slate-600 font-medium">Từ:</label>
|
||||
<input type="date" id="compareFrom" class="text-sm rounded border border-indigo-300 px-2 py-1 bg-white outline-none" value="2026-01-01">
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-xs text-slate-600 font-medium">Đến:</label>
|
||||
<input type="date" id="compareTo" class="text-sm rounded border border-indigo-300 px-2 py-1 bg-white outline-none" value="2026-01-31">
|
||||
</div>
|
||||
<span class="text-xs text-slate-500 italic">* Hệ thống sẽ đối chiếu song song và vẽ biểu đồ cột tăng giảm tự động.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- NAVIGATION TABS -->
|
||||
<section class="max-w-7xl mx-auto px-4 sm:px-6 mt-4">
|
||||
<div class="flex border-b border-slate-200 gap-2 flex-wrap">
|
||||
<button class="tab-btn active px-4 py-2.5 text-sm font-bold border-b-2 border-blue-600 text-blue-600 flex items-center gap-2 transition-all" data-tab="tab-ngoaitru">
|
||||
<i class="fa-solid fa-user-doctor"></i>
|
||||
<span>1. Báo Cáo Giao Ban Ngoại Trú</span>
|
||||
</button>
|
||||
<button class="tab-btn px-4 py-2.5 text-sm font-bold border-b-2 border-transparent text-slate-500 hover:text-slate-800 flex items-center gap-2 transition-all" data-tab="tab-noitru">
|
||||
<i class="fa-solid fa-bed-pulse"></i>
|
||||
<span>2. Báo Cáo Giao Ban Nội Trú</span>
|
||||
</button>
|
||||
<button class="tab-btn px-4 py-2.5 text-sm font-bold border-b-2 border-transparent text-slate-500 hover:text-slate-800 flex items-center gap-2 transition-all" data-tab="tab-issues">
|
||||
<i class="fa-solid fa-clipboard-list text-rose-500"></i>
|
||||
<span>3. Quản Lý Issue & Báo Lỗi</span>
|
||||
<span id="badgeIssueCount" class="ml-1 px-1.5 py-0.2 text-[10px] font-bold rounded-full bg-rose-100 text-rose-700 border border-rose-300">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MAIN CONTENT CONTAINER -->
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 py-4">
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- TAB 1: BÁO CÁO GIAO BAN NGOẠI TRÚ -->
|
||||
<!-- ========================================== -->
|
||||
<div id="tab-ngoaitru" class="tab-content space-y-4">
|
||||
|
||||
<!-- KPI METRIC CARDS -->
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Tổng Khám</span>
|
||||
<i class="fa-solid fa-stethoscope text-blue-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-blue-700" id="kpiTongKham">0</div>
|
||||
<div class="text-[11px] text-slate-400 mt-1">Lượt khám hoàn tất</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Khám BHYT</span>
|
||||
<i class="fa-solid fa-id-card text-emerald-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-emerald-600" id="kpiBhyt">0</div>
|
||||
<div class="text-[11px] text-emerald-600/80 font-medium mt-1" id="kpiBhytPct">0% tổng lượt</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Khám Thu Phí</span>
|
||||
<i class="fa-solid fa-receipt text-amber-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-amber-600" id="kpiThuPhi">0</div>
|
||||
<div class="text-[11px] text-amber-600/80 font-medium mt-1" id="kpiThuPhiPct">0% tổng lượt</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Chuyển Viện</span>
|
||||
<i class="fa-solid fa-truck-medical text-rose-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-rose-600" id="kpiChuyenVien">0</div>
|
||||
<div class="text-[11px] text-rose-600/80 font-medium mt-1">Tuyến trên & ngang</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Tiêm Ngừa</span>
|
||||
<i class="fa-solid fa-syringe text-purple-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-purple-600" id="kpiTiemNgua">0</div>
|
||||
<div class="text-[11px] text-slate-400 mt-1">Lượt tiêm chủng</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Bệnh Án Ngoại Trú</span>
|
||||
<i class="fa-solid fa-folder-open text-sky-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-sky-700" id="kpiBantTong">0</div>
|
||||
<div class="text-[11px] text-slate-500 mt-1 flex justify-between">
|
||||
<span>Mới: <b class="text-sky-600" id="kpiBantMoi">0</b></span>
|
||||
<span>Tái khám: <b class="text-sky-600" id="kpiBantTk">0</b></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROW 1: BẢNG KHÁM THEO KHOA & SỐ LIỆU CHUYÊN KHOA YDCT -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-4">
|
||||
|
||||
<!-- Bảng khám chữa bệnh theo Khoa -->
|
||||
<div class="lg:col-span-8 bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-4 py-3 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-table-list text-blue-600"></i>
|
||||
<span>1.1. Số Liệu Khám Chữa Bệnh Theo Các Khoa</span>
|
||||
</h2>
|
||||
<span class="text-xs text-slate-500" id="subKhoaKcb">Khám bệnh, Nội, Ngoại, CSSKSS, Nhi, YDCT</span>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm border-collapse" id="tableKhoaKcb">
|
||||
<thead class="bg-slate-100 text-slate-700 font-semibold text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th class="py-2.5 px-3">Mã</th>
|
||||
<th class="py-2.5 px-3">Khoa Khám Bệnh</th>
|
||||
<th class="py-2.5 px-3 text-right">BHYT</th>
|
||||
<th class="py-2.5 px-3 text-right">Thu Phí</th>
|
||||
<th class="py-2.5 px-3 text-right">Chuyển Viện</th>
|
||||
<th class="py-2.5 px-3 text-right">Tổng Khám</th>
|
||||
<!-- Cột so sánh nếu bật -->
|
||||
<th class="py-2.5 px-3 text-right th-compare hidden">Kỳ So Sánh</th>
|
||||
<th class="py-2.5 px-3 text-right th-compare hidden">Chênh Lệch</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200 font-normal text-slate-800" id="bodyKhoaKcb">
|
||||
<tr><td colspan="6" class="text-center py-6 text-slate-400">Đang tải dữ liệu...</td></tr>
|
||||
</tbody>
|
||||
<tfoot class="bg-slate-100 font-bold text-slate-800 text-xs border-t-2 border-slate-300" id="footKhoaKcb">
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chuyên khoa YDCT & Bệnh án ngoại trú Card -->
|
||||
<div class="lg:col-span-4 space-y-4">
|
||||
<!-- Card YDCT -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-4 py-3 bg-amber-50/70 border-b border-amber-200 flex items-center justify-between">
|
||||
<h2 class="text-sm font-bold text-amber-900 flex items-center gap-2">
|
||||
<i class="fa-solid fa-leaf text-amber-600"></i>
|
||||
<span>1.2. Chuyên Khoa Y Dược Cổ Truyền</span>
|
||||
</h2>
|
||||
<span class="text-xs bg-amber-200/60 text-amber-800 px-2 py-0.5 rounded font-semibold">YDCT</span>
|
||||
</div>
|
||||
<div class="p-4 space-y-3">
|
||||
<div class="flex items-center justify-between border-b border-slate-100 pb-2">
|
||||
<span class="text-sm text-slate-600 font-medium flex items-center gap-2">
|
||||
<i class="fa-solid fa-bolt text-amber-500"></i> Điện châm
|
||||
</span>
|
||||
<span class="text-base font-bold text-slate-800" id="ydctDienCham">0</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between border-b border-slate-100 pb-2">
|
||||
<span class="text-sm text-slate-600 font-medium flex items-center gap-2">
|
||||
<i class="fa-solid fa-person-walking text-blue-500"></i> Vật lý trị liệu (VLTL)
|
||||
</span>
|
||||
<span class="text-base font-bold text-slate-800" id="ydctVltl">0</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between border-b border-slate-100 pb-2">
|
||||
<span class="text-sm text-slate-600 font-medium flex items-center gap-2">
|
||||
<i class="fa-solid fa-mug-hot text-emerald-500"></i> Sắc thuốc thang
|
||||
</span>
|
||||
<span class="text-base font-bold text-slate-800" id="ydctSacThuoc">0</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-slate-600 font-medium flex items-center gap-2">
|
||||
<i class="fa-solid fa-certificate text-purple-500"></i> Cấy chỉ (Chôn chỉ)
|
||||
</span>
|
||||
<span class="text-base font-bold text-slate-800" id="ydctCayChi">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top 10 Bệnh Ngoại Trú -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-4 py-3 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-ranking-star text-rose-500"></i>
|
||||
<span>Top 10 Chẩn Đoán Ngoại Trú</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-2 overflow-y-auto max-h-72">
|
||||
<table class="w-full text-left text-xs border-collapse">
|
||||
<tbody class="divide-y divide-slate-100 font-medium" id="bodyTop10">
|
||||
<tr><td class="text-center py-4 text-slate-400">Đang tải dữ liệu...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROW 2: TỔNG HỢP CẬN LÂM SÀNG & BIỂU ĐỒ CỘT SO SÁNH -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-4">
|
||||
|
||||
<!-- Bảng Cận Lâm Sàng -->
|
||||
<div class="lg:col-span-6 bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-4 py-3 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-flask-vial text-cyan-600"></i>
|
||||
<span>1.3. Tổng Hợp Số Liệu Cận Lâm Sàng</span>
|
||||
</h2>
|
||||
<span class="text-xs text-slate-500">Xét nghiệm & CĐHA</span>
|
||||
</div>
|
||||
<div class="overflow-x-auto max-h-96">
|
||||
<table class="w-full text-left text-xs border-collapse">
|
||||
<thead class="bg-slate-100 text-slate-700 font-semibold uppercase border-b border-slate-200 sticky top-0">
|
||||
<tr>
|
||||
<th class="py-2 px-3">Phân nhóm</th>
|
||||
<th class="py-2 px-3">Loại Kỹ Thuật</th>
|
||||
<th class="py-2 px-3 text-right">Số Bệnh Nhân</th>
|
||||
<th class="py-2 px-3 text-right">Số Lượt Thực Hiện</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100" id="bodyCls">
|
||||
<tr><td colspan="4" class="text-center py-4 text-slate-400">Đang tải dữ liệu...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Biểu đồ cột so sánh -->
|
||||
<div class="lg:col-span-6 bg-white rounded-xl shadow-sm border border-slate-200 p-4 flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between border-b border-slate-100 pb-2 mb-2">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-chart-column text-indigo-600"></i>
|
||||
<span>Biểu Đồ Cột So Sánh Số Liệu</span>
|
||||
</h2>
|
||||
<div class="flex gap-1 text-xs">
|
||||
<button class="chart-type-btn active px-2.5 py-1 rounded bg-blue-100 text-blue-700 font-semibold" data-chart="khoa">Theo Khoa</button>
|
||||
<button class="chart-type-btn px-2.5 py-1 rounded bg-slate-100 text-slate-600 font-medium" data-chart="cls">Theo CLS</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative w-full h-80">
|
||||
<canvas id="chartNgoaiTru"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- TAB 2: BÁO CÁO GIAO BAN NỘI TRÚ -->
|
||||
<!-- ========================================== -->
|
||||
<div id="tab-noitru" class="tab-content hidden space-y-4">
|
||||
|
||||
<!-- KPI METRICS NỘI TRÚ -->
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Bệnh Cũ (Tồn Đầu)</span>
|
||||
<i class="fa-solid fa-clock-rotate-left text-slate-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-slate-700" id="kpiNtBenhCu">0</div>
|
||||
<div class="text-[11px] text-slate-400 mt-1">Chuyển từ kỳ trước</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Bệnh Mới (Vào Viện)</span>
|
||||
<i class="fa-solid fa-user-plus text-blue-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-blue-600" id="kpiNtBenhMoi">0</div>
|
||||
<div class="text-[11px] text-blue-600/80 font-medium mt-1">Nhập viện trong kỳ</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Tổng Xuất Viện</span>
|
||||
<i class="fa-solid fa-person-walking-arrow-right text-emerald-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-emerald-600" id="kpiNtXuatVien">0</div>
|
||||
<div class="text-[11px] text-emerald-600/80 font-medium mt-1" id="kpiNtOnPct">Ổn về: 0 ca</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Bệnh Hiện Còn</span>
|
||||
<i class="fa-solid fa-bed text-indigo-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-indigo-700" id="kpiNtHienCon">0</div>
|
||||
<div class="text-[11px] text-indigo-700/80 font-medium mt-1">Đang nằm điều trị</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Bệnh Theo Dõi</span>
|
||||
<i class="fa-solid fa-heart-pulse text-rose-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-rose-600" id="kpiNtTheoDoi">0</div>
|
||||
<div class="text-[11px] text-rose-600/80 font-medium mt-1">Bệnh nhân nặng</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex flex-col justify-between">
|
||||
<div class="flex items-center justify-between text-slate-500">
|
||||
<span class="text-xs font-semibold">Chăm Sóc Cấp I</span>
|
||||
<i class="fa-solid fa-hand-holding-medical text-amber-500"></i>
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-extrabold text-amber-600" id="kpiNtCsCap1">0</div>
|
||||
<div class="text-[11px] text-amber-600/80 font-medium mt-1">Chế độ chăm sóc I</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BẢNG 1: LƯU LƯỢNG BỆNH NHÂN THEO CÁC KHOA -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-4 py-3 bg-slate-50 border-b border-slate-200 flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-hospital text-blue-600"></i>
|
||||
<span>2.1. Tổng Hợp Lưu Lượng Bệnh Nhân Theo Các Khoa Nội Trú</span>
|
||||
</h2>
|
||||
<p class="text-xs text-slate-500">Cấp cứu HS, Nội, Nhi, Ngoại TH, CSSKSS, YDCT,... (Khớp công thức: Tồn đầu + Vào viện - Ra viện = Hiện còn)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-xs border-collapse" id="tableNoiTru">
|
||||
<thead class="bg-slate-100 text-slate-700 font-semibold uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th class="py-2.5 px-3">Mã</th>
|
||||
<th class="py-2.5 px-3">Khoa Điều Trị</th>
|
||||
<th class="py-2.5 px-3 text-right">Bệnh Cũ</th>
|
||||
<th class="py-2.5 px-3 text-right">Bệnh Mới</th>
|
||||
<th class="py-2.5 px-3 text-right text-emerald-700">Ổn xin về</th>
|
||||
<th class="py-2.5 px-3 text-right text-amber-700">Nặng về</th>
|
||||
<th class="py-2.5 px-3 text-right text-slate-600">Trốn</th>
|
||||
<th class="py-2.5 px-3 text-right text-rose-700">Chuyển viện</th>
|
||||
<th class="py-2.5 px-3 text-right text-rose-900">Tử vong</th>
|
||||
<th class="py-2.5 px-3 text-right font-bold text-blue-700 bg-blue-50/50">Hiện Còn</th>
|
||||
<th class="py-2.5 px-3 text-right">Bệnh Theo Dõi</th>
|
||||
<th class="py-2.5 px-3 text-right">CS Cấp I</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200 font-normal text-slate-800" id="bodyNoiTru">
|
||||
<tr><td colspan="12" class="text-center py-6 text-slate-400">Đang tải dữ liệu...</td></tr>
|
||||
</tbody>
|
||||
<tfoot class="bg-slate-100 font-bold text-slate-800 text-xs border-t-2 border-slate-300" id="footNoiTru">
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BIỂU ĐỒ CỘT SO SÁNH NỘI TRÚ -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
|
||||
<div class="flex items-center justify-between border-b border-slate-100 pb-2 mb-3">
|
||||
<h2 class="text-sm font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-solid fa-chart-column text-indigo-600"></i>
|
||||
<span>Biểu Đồ Lưu Lượng Bệnh Nhân & So Sánh Đa Chu Kỳ</span>
|
||||
</h2>
|
||||
<span class="text-xs text-slate-500">So sánh số bệnh mới nhập viện giữa các khoa</span>
|
||||
</div>
|
||||
<div class="relative w-full h-80">
|
||||
<canvas id="chartNoiTru"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BẢNG 3: DANH SÁCH BỆNH NHÂN CHUYỂN VIỆN -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-4 py-3 bg-rose-50/80 border-b border-rose-200 flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-sm font-bold text-rose-900 flex items-center gap-2">
|
||||
<i class="fa-solid fa-ambulance text-rose-600"></i>
|
||||
<span>BẢNG 3: Danh Sách Bệnh Nhân Chuyển Viện</span>
|
||||
</h2>
|
||||
<p class="text-xs text-rose-700/80">Cho phép các khoa tự nhập liệu trực tiếp vào cột Ghi chú trên lưới trước giờ họp giao ban</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span class="text-slate-600 font-medium">Khoa:</span>
|
||||
<select id="filterKhoaCv" class="rounded border border-slate-300 px-2 py-1 bg-white outline-none">
|
||||
<option value="ALL">Tất cả khoa</option>
|
||||
</select>
|
||||
<span class="bg-rose-200 text-rose-800 px-2 py-0.5 rounded-full font-bold text-[11px]" id="countBang3">0 ca</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto max-h-[500px]">
|
||||
<table class="w-full text-left text-xs border-collapse">
|
||||
<thead class="bg-slate-100 text-slate-700 font-semibold uppercase sticky top-0 border-b border-slate-200 z-10">
|
||||
<tr>
|
||||
<th class="py-2.5 px-2.5 w-10 text-center">STT</th>
|
||||
<th class="py-2.5 px-2.5 w-24">Mã BN / BA</th>
|
||||
<th class="py-2.5 px-3 w-40">Họ và Tên</th>
|
||||
<th class="py-2.5 px-3 w-48">Địa Chỉ</th>
|
||||
<th class="py-2.5 px-3 w-32">Ngày Vào / Ra</th>
|
||||
<th class="py-2.5 px-3 w-32">Khoa Chuyển</th>
|
||||
<th class="py-2.5 px-3 w-56">Chẩn Đoán (Chính/Phụ)</th>
|
||||
<th class="py-2.5 px-3 w-64">Kết Quả CLS Tóm Tắt</th>
|
||||
<th class="py-2.5 px-3 w-64 bg-amber-50 text-amber-900 font-bold border-l border-amber-200">
|
||||
<i class="fa-solid fa-pen-to-square"></i> Ghi Chú (Khoa tự nhập)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200" id="bodyBang3">
|
||||
<tr><td colspan="9" class="text-center py-6 text-slate-400">Đang tải danh sách chuyển viện...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BẢNG 4: DANH SÁCH BỆNH NHÂN THEO DÕI & CHĂM SÓC CẤP I -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="px-4 py-3 bg-amber-50/80 border-b border-amber-200 flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-sm font-bold text-amber-900 flex items-center gap-2">
|
||||
<i class="fa-solid fa-eye text-amber-600"></i>
|
||||
<span>BẢNG 4: Danh Sách Bệnh Nhân Theo Dõi & Chăm Sóc Cấp I</span>
|
||||
</h2>
|
||||
<p class="text-xs text-amber-700/80">Theo dõi diễn biến nặng, can thiệp đặc biệt và chỉ định chăm sóc cấp I</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span class="text-slate-600 font-medium">Khoa:</span>
|
||||
<select id="filterKhoaTd" class="rounded border border-slate-300 px-2 py-1 bg-white outline-none">
|
||||
<option value="ALL">Tất cả khoa</option>
|
||||
</select>
|
||||
<span class="bg-amber-200 text-amber-800 px-2 py-0.5 rounded-full font-bold text-[11px]" id="countBang4">0 ca</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto max-h-[500px]">
|
||||
<table class="w-full text-left text-xs border-collapse">
|
||||
<thead class="bg-slate-100 text-slate-700 font-semibold uppercase sticky top-0 border-b border-slate-200 z-10">
|
||||
<tr>
|
||||
<th class="py-2.5 px-2.5 w-10 text-center">STT</th>
|
||||
<th class="py-2.5 px-2.5 w-24">Mã BN / BA</th>
|
||||
<th class="py-2.5 px-3 w-40">Họ và Tên</th>
|
||||
<th class="py-2.5 px-3 w-48">Địa Chỉ</th>
|
||||
<th class="py-2.5 px-3 w-32">Ngày Vào Viện</th>
|
||||
<th class="py-2.5 px-3 w-32">Khoa Điều Trị</th>
|
||||
<th class="py-2.5 px-3 w-56">Chẩn Đoán (Chính/Phụ)</th>
|
||||
<th class="py-2.5 px-3 w-64">Kết Quả CLS Tóm Tắt</th>
|
||||
<th class="py-2.5 px-3 w-64 bg-amber-50 text-amber-900 font-bold border-l border-amber-200">
|
||||
<i class="fa-solid fa-pen-to-square"></i> Ghi Chú (Khoa tự nhập)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200" id="bodyBang4">
|
||||
<tr><td colspan="9" class="text-center py-6 text-slate-400">Đang tải danh sách theo dõi...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- TAB 3: QUẢN LÝ ISSUE & BÁO LỖI -->
|
||||
<!-- ========================================== -->
|
||||
<div id="tab-issues" class="tab-content hidden space-y-4">
|
||||
|
||||
<!-- KPI METRIC CARDS CHO ISSUE -->
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
<div class="bg-white p-3.5 rounded-xl border border-slate-200 shadow-sm flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-xs font-semibold text-slate-500">Tổng Số Báo Lỗi</div>
|
||||
<div class="mt-1 text-2xl font-black text-slate-800" id="issueKpiTotal">0</div>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-lg bg-slate-100 flex items-center justify-center text-slate-600">
|
||||
<i class="fa-solid fa-list-check text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-amber-200 shadow-sm flex items-center justify-between bg-amber-50/40">
|
||||
<div>
|
||||
<div class="text-xs font-semibold text-amber-700">Chờ Xử Lý</div>
|
||||
<div class="mt-1 text-2xl font-black text-amber-600" id="issueKpiPending">0</div>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-lg bg-amber-100 flex items-center justify-center text-amber-600">
|
||||
<i class="fa-solid fa-clock text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-blue-200 shadow-sm flex items-center justify-between bg-blue-50/40">
|
||||
<div>
|
||||
<div class="text-xs font-semibold text-blue-700">Đang Xử Lý</div>
|
||||
<div class="mt-1 text-2xl font-black text-blue-600" id="issueKpiProcessing">0</div>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center text-blue-600">
|
||||
<i class="fa-solid fa-screwdriver-wrench text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3.5 rounded-xl border border-emerald-200 shadow-sm flex items-center justify-between bg-emerald-50/40">
|
||||
<div>
|
||||
<div class="text-xs font-semibold text-emerald-700">Đã Giải Quyết</div>
|
||||
<div class="mt-1 text-2xl font-black text-emerald-600" id="issueKpiResolved">0</div>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-lg bg-emerald-100 flex items-center justify-center text-emerald-600">
|
||||
<i class="fa-solid fa-circle-check text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FILTER & ACTION TOOLBAR -->
|
||||
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs">
|
||||
<span class="font-bold text-slate-700 flex items-center gap-1">
|
||||
<i class="fa-solid fa-filter text-blue-600"></i> Bộ lọc:
|
||||
</span>
|
||||
<select id="filterIssueStatus" class="rounded-lg border border-slate-300 px-2.5 py-1.5 bg-slate-50 outline-none">
|
||||
<option value="ALL">Tất cả trạng thái</option>
|
||||
<option value="CHO_XU_LY">Chờ xử lý</option>
|
||||
<option value="DANG_XU_LY">Đang xử lý</option>
|
||||
<option value="DA_XU_LY">Đã xử lý</option>
|
||||
<option value="DONG">Đã đóng</option>
|
||||
</select>
|
||||
<select id="filterIssueCategory" class="rounded-lg border border-slate-300 px-2.5 py-1.5 bg-slate-50 outline-none">
|
||||
<option value="ALL">Tất cả phân loại</option>
|
||||
<option value="SAI_LECH_SO_LIEU">Sai lệch số liệu</option>
|
||||
<option value="LOI_GIAO_DIEN">Lỗi giao diện / chức năng</option>
|
||||
<option value="LOI_KET_NOI">Lỗi kết nối / timeout</option>
|
||||
<option value="DE_XUAT_CAI_TIEN">Đề xuất cải tiến</option>
|
||||
<option value="YEU_CAU_MOI">Yêu cầu mới</option>
|
||||
<option value="KHAC">Khác</option>
|
||||
</select>
|
||||
<input type="text" id="filterIssueKeyword" placeholder="Tìm theo mã, tiêu đề, người báo..." class="rounded-lg border border-slate-300 px-2.5 py-1.5 bg-slate-50 outline-none w-56">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="btnExportIssuesExcel" class="bg-emerald-600 hover:bg-emerald-700 text-white text-xs font-semibold py-1.5 px-3 rounded-lg shadow-sm flex items-center gap-1.5 transition-all">
|
||||
<i class="fa-solid fa-file-excel"></i>
|
||||
<span>Xuất Excel Tổng Hợp</span>
|
||||
</button>
|
||||
<button id="btnOpenIssueModal2" class="bg-rose-600 hover:bg-rose-700 text-white text-xs font-semibold py-1.5 px-3 rounded-lg shadow-sm flex items-center gap-1.5 transition-all">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<span>Tạo Báo Lỗi Mới</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BẢNG TỔNG HỢP ISSUE -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-xs border-collapse" id="tableIssues">
|
||||
<thead class="bg-slate-100 text-slate-700 font-semibold uppercase border-b border-slate-200">
|
||||
<tr>
|
||||
<th class="py-2.5 px-3 w-28">Mã Issue</th>
|
||||
<th class="py-2.5 px-3">Tiêu Đề & Chi Tiết Sai Lệch</th>
|
||||
<th class="py-2.5 px-3 w-32">Phân Loại / Mức Độ</th>
|
||||
<th class="py-2.5 px-3 w-44">CSDL & Kỳ Xem</th>
|
||||
<th class="py-2.5 px-3 w-32">Người Báo</th>
|
||||
<th class="py-2.5 px-3 w-24 text-center">Tệp Đính Kèm</th>
|
||||
<th class="py-2.5 px-3 w-32 text-center">Trạng Thái</th>
|
||||
<th class="py-2.5 px-3 w-28 text-center">Thao Tác</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200" id="bodyIssuesList">
|
||||
<tr><td colspan="8" class="text-center py-8 text-slate-400">Đang tải danh sách issue...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- ======================================================== -->
|
||||
<!-- MODAL 1: TẠO BÁO LỖI / ISSUE MỚI -->
|
||||
<!-- ======================================================== -->
|
||||
<div id="modalIssue" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm flex items-center justify-center p-4 hidden">
|
||||
<div class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[92vh] flex flex-col overflow-hidden animate-in fade-in zoom-in-95 duration-150">
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="px-6 py-4 bg-gradient-to-r from-rose-600 to-pink-600 text-white flex items-center justify-between">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="w-8 h-8 rounded-lg bg-white/20 flex items-center justify-center">
|
||||
<i class="fa-solid fa-bug text-lg"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-bold">Gửi Báo Lỗi / Sự Cố Hệ Thống</h3>
|
||||
<p class="text-xs text-rose-100">Dữ liệu và file đính kèm sẽ được lưu lại để kỹ thuật tổng hợp xử lý</p>
|
||||
</div>
|
||||
</div>
|
||||
<button id="btnCloseIssueModal" class="text-white/80 hover:text-white p-1 rounded-lg hover:bg-white/10 text-lg transition-colors">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Modal Form Body -->
|
||||
<form id="formCreateIssue" class="p-6 overflow-y-auto space-y-4 flex-1 text-xs">
|
||||
|
||||
<!-- Tiêu đề -->
|
||||
<div>
|
||||
<label class="block font-bold text-slate-700 mb-1">
|
||||
Tiêu đề vấn đề / lỗi <span class="text-rose-500">*</span>:
|
||||
</label>
|
||||
<input type="text" id="issueTitle" required placeholder="Ví dụ: Sai lệch số lượt khám BHYT Khoa Khám Bệnh so với HIS gốc" class="w-full text-sm rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 outline-none focus:ring-2 focus:ring-rose-500 font-medium">
|
||||
</div>
|
||||
|
||||
<!-- Phân loại & Phân hệ & Mức độ -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Phân loại sự cố:</label>
|
||||
<select id="issueCategory" class="w-full rounded-lg border border-slate-300 px-2.5 py-2 bg-slate-50 outline-none focus:ring-2 focus:ring-rose-500">
|
||||
<option value="SAI_LECH_SO_LIEU">Sai lệch số liệu</option>
|
||||
<option value="LOI_GIAO_DIEN">Lỗi giao diện / chức năng</option>
|
||||
<option value="LOI_KET_NOI">Lỗi kết nối CSDL / timeout</option>
|
||||
<option value="DE_XUAT_CAI_TIEN">Đề xuất cải tiến</option>
|
||||
<option value="YEU_CAU_MOI">Yêu cầu báo cáo mới</option>
|
||||
<option value="KHAC">Khác</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Phân hệ liên quan:</label>
|
||||
<select id="issueModule" class="w-full rounded-lg border border-slate-300 px-2.5 py-2 bg-slate-50 outline-none focus:ring-2 focus:ring-rose-500">
|
||||
<option value="NGOAI_TRU">1. Báo cáo Ngoại trú</option>
|
||||
<option value="NOI_TRU">2. Báo cáo Nội trú</option>
|
||||
<option value="CHUYEN_VIEN">Bảng 3. Chuyển viện</option>
|
||||
<option value="THEO_DOI">Bảng 4. BN Theo dõi</option>
|
||||
<option value="CHUNG">Chung / Toàn hệ thống</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Mức độ ưu tiên:</label>
|
||||
<select id="issuePriority" class="w-full rounded-lg border border-slate-300 px-2.5 py-2 bg-slate-50 outline-none focus:ring-2 focus:ring-rose-500 font-bold text-amber-700">
|
||||
<option value="BINH_THUONG" class="text-slate-700">Bình thường</option>
|
||||
<option value="CAO" class="text-amber-700">Cao</option>
|
||||
<option value="KHAN_CAP" class="text-rose-700">Khẩn cấp (Họp giao ban gấp)</option>
|
||||
<option value="THAP" class="text-slate-500">Thấp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CSDL & Mốc thời gian tự động -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 bg-slate-50 p-2.5 rounded-lg border border-slate-200">
|
||||
<div>
|
||||
<label class="block text-slate-500 mb-0.5">CSDL đang xem:</label>
|
||||
<input type="text" id="issueDatabase" readonly class="w-full rounded border border-slate-300 px-2 py-1 bg-white text-slate-700 font-mono text-[11px]">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-slate-500 mb-0.5">Khoảng ngày đang xem:</label>
|
||||
<input type="text" id="issuePeriod" readonly class="w-full rounded border border-slate-300 px-2 py-1 bg-white text-slate-700 font-mono text-[11px]">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Người báo & Khoa phòng -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Người báo cáo:</label>
|
||||
<input type="text" id="issueReporter" placeholder="Ví dụ: BS. Nguyễn Văn A" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 outline-none focus:ring-2 focus:ring-rose-500 font-medium">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Khoa / Phòng ban:</label>
|
||||
<input type="text" id="issueDepartment" placeholder="Ví dụ: Khoa Khám Bệnh / KHTH" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 outline-none focus:ring-2 focus:ring-rose-500 font-medium">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nội dung mô tả chi tiết -->
|
||||
<div>
|
||||
<label class="block font-bold text-slate-700 mb-1">
|
||||
Mô tả chi tiết sai lệch / lỗi <span class="text-rose-500">*</span>:
|
||||
</label>
|
||||
<textarea id="issueDescription" rows="4" required placeholder="- Số liệu kỳ vọng trên HIS gốc: ... - Số liệu hiển thị trên Web: ... - Thao tác thực hiện / lỗi xuất hiện:" class="w-full rounded-lg border border-slate-300 p-2.5 bg-slate-50 outline-none focus:ring-2 focus:ring-rose-500 text-xs leading-relaxed font-sans"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Đính kèm file / Ảnh chụp màn hình -->
|
||||
<div>
|
||||
<label class="block font-bold text-slate-700 mb-1 flex items-center justify-between">
|
||||
<span>Đính kèm tệp / Ảnh chụp màn hình:</span>
|
||||
<span class="text-slate-400 font-normal">Hỗ trợ PNG, JPG, PDF, XLSX, Log (Tối đa 25MB)</span>
|
||||
</label>
|
||||
|
||||
<div id="issuePasteZone" class="border-2 border-dashed border-slate-300 hover:border-rose-400 rounded-xl p-3 text-center bg-slate-50/50 hover:bg-rose-50/20 transition-all cursor-pointer">
|
||||
<input type="file" id="issueFileInput" class="hidden" accept="image/*,.pdf,.xlsx,.xls,.doc,.docx,.txt,.csv">
|
||||
<div id="pasteZonePrompt" class="flex flex-col items-center justify-center gap-1 text-slate-500">
|
||||
<i class="fa-solid fa-cloud-arrow-up text-2xl text-rose-500 mb-1"></i>
|
||||
<div class="text-xs font-semibold text-slate-700">Nhấp để chọn tệp hoặc <span class="text-rose-600 underline">dán ảnh chụp màn hình (Ctrl+V)</span></div>
|
||||
<div class="text-[11px] text-slate-400">Có thể dán trực tiếp ảnh từ phím tắt PrintScreen / Snipping Tool</div>
|
||||
</div>
|
||||
<!-- File Preview Area -->
|
||||
<div id="issuePreviewArea" class="hidden mt-2 flex items-center justify-between bg-white p-2.5 rounded-lg border border-slate-200">
|
||||
<div class="flex items-center gap-2 overflow-hidden">
|
||||
<img id="issueImgPreview" src="" class="hidden w-12 h-12 object-cover rounded border border-slate-200">
|
||||
<i id="issueFileIcon" class="fa-solid fa-file text-2xl text-slate-400 hidden"></i>
|
||||
<div class="text-left truncate">
|
||||
<div id="issueFileName" class="font-bold text-slate-800 text-xs truncate">ten_file.png</div>
|
||||
<div id="issueFileSize" class="text-[10px] text-slate-400">0 KB</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="btnRemoveFile" class="text-rose-500 hover:text-rose-700 text-sm p-1.5 rounded hover:bg-rose-50">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="pt-2 flex items-center justify-end gap-2 border-t border-slate-200">
|
||||
<button type="button" id="btnCancelIssue" class="px-4 py-2 text-xs font-semibold text-slate-600 hover:bg-slate-100 rounded-lg transition-colors">
|
||||
Hủy bỏ
|
||||
</button>
|
||||
<button type="submit" id="btnSubmitIssue" class="bg-gradient-to-r from-rose-600 to-pink-600 hover:from-rose-700 hover:to-pink-700 text-white px-5 py-2 rounded-lg font-bold shadow-md flex items-center gap-2 transition-all">
|
||||
<i class="fa-solid fa-paper-plane"></i>
|
||||
<span>Gửi Báo Lỗi & Lưu File</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ======================================================== -->
|
||||
<!-- MODAL 2: CẬP NHẬT TRẠNG THÁI & XỬ LÝ ISSUE -->
|
||||
<!-- ======================================================== -->
|
||||
<div id="modalUpdateIssue" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm flex items-center justify-center p-4 hidden">
|
||||
<div class="bg-white rounded-2xl shadow-2xl max-w-lg w-full flex flex-col overflow-hidden animate-in fade-in zoom-in-95 duration-150">
|
||||
|
||||
<div class="px-5 py-3.5 bg-slate-800 text-white flex items-center justify-between">
|
||||
<h3 class="text-sm font-bold flex items-center gap-2">
|
||||
<i class="fa-solid fa-pen-nib text-amber-400"></i>
|
||||
<span>Cập Nhật Xử Lý Issue: <span id="updateModalIssueId" class="font-mono text-cyan-300"></span></span>
|
||||
</h3>
|
||||
<button id="btnCloseUpdateModal" class="text-white/80 hover:text-white p-1 rounded-lg hover:bg-white/10 text-base">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="formUpdateIssue" class="p-5 space-y-3.5 text-xs">
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Trạng thái xử lý:</label>
|
||||
<select id="updateStatus" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 outline-none font-bold">
|
||||
<option value="CHO_XU_LY" class="text-amber-700 font-bold">Chờ xử lý (Mới tiếp nhận)</option>
|
||||
<option value="DANG_XU_LY" class="text-blue-700 font-bold">Đang xử lý (Đội ngũ kỹ thuật đang sửa)</option>
|
||||
<option value="DA_XU_LY" class="text-emerald-700 font-bold">Đã xử lý (Đã fix lỗi / cập nhật số liệu)</option>
|
||||
<option value="DONG" class="text-slate-500 font-bold">Đóng issue (Không phải lỗi / Đã xong)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Cán bộ / Kỹ thuật xử lý:</label>
|
||||
<input type="text" id="updateResolvedBy" placeholder="Ví dụ: Dev IT / Othau" class="w-full rounded-lg border border-slate-300 px-3 py-2 bg-slate-50 outline-none font-medium">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block font-semibold text-slate-700 mb-1">Ghi chú giải pháp / Phản hồi xử lý:</label>
|
||||
<textarea id="updateHandlerNotes" rows="3" placeholder="Ví dụ: Đã bổ sung điều kiện lọc theo mã đơn vị, số liệu đã khớp 100% với report HIS gốc." class="w-full rounded-lg border border-slate-300 p-2.5 bg-slate-50 outline-none leading-relaxed"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="pt-2 flex items-center justify-end gap-2 border-t border-slate-200">
|
||||
<button type="button" id="btnCancelUpdate" class="px-4 py-2 font-semibold text-slate-600 hover:bg-slate-100 rounded-lg">
|
||||
Đóng
|
||||
</button>
|
||||
<button type="submit" id="btnSaveUpdateIssue" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-bold shadow flex items-center gap-1.5">
|
||||
<i class="fa-solid fa-floppy-disk"></i>
|
||||
<span>Lưu Cập Nhật</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ======================================================== -->
|
||||
<!-- MODAL 3: XEM ẢNH PHÓNG TO (LIGHTBOX) -->
|
||||
<!-- ======================================================== -->
|
||||
<div id="modalImagePreview" class="fixed inset-0 z-50 bg-slate-950/80 backdrop-blur-md flex items-center justify-center p-4 hidden">
|
||||
<div class="relative max-w-4xl max-h-[90vh] flex flex-col items-center">
|
||||
<button id="btnCloseLightbox" class="absolute -top-10 right-0 text-white/90 hover:text-white text-2xl p-1">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
<img id="lightboxImg" src="" class="max-w-full max-h-[85vh] rounded-lg shadow-2xl object-contain border border-white/20">
|
||||
<div id="lightboxCaption" class="mt-2 text-xs text-white/80 font-medium"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NOTIFICATION TOAST -->
|
||||
<div id="toast" class="fixed bottom-4 right-4 z-50 bg-slate-900 text-white text-xs px-4 py-2.5 rounded-lg shadow-xl flex items-center gap-2 transform translate-y-20 opacity-0 transition-all duration-300">
|
||||
<i class="fa-solid fa-circle-check text-emerald-400"></i>
|
||||
<span id="toastMsg">Thông báo</span>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,54 @@
|
||||
/* Custom styles for DH-HIS Daily Briefing Web */
|
||||
@layer base {
|
||||
body {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.table-cell-edit {
|
||||
background-color: #fffbeb;
|
||||
transition: all 0.2s;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
width: 100%;
|
||||
border: 1px dashed #f59e0b;
|
||||
}
|
||||
|
||||
.table-cell-edit:focus {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #2563eb;
|
||||
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
|
||||
}
|
||||
|
||||
.table-cell-edit.saving {
|
||||
background-color: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.table-cell-edit.saved {
|
||||
background-color: #ecfdf5;
|
||||
border-color: #10b981;
|
||||
}
|
||||
|
||||
/* Print styling */
|
||||
@media print {
|
||||
header, section, .tab-btn, .chart-type-btn, #btnFilter, #btnPrint, #btnExport, #toast {
|
||||
display: none !important;
|
||||
}
|
||||
body {
|
||||
background: white !important;
|
||||
font-size: 10pt;
|
||||
}
|
||||
.tab-content {
|
||||
display: block !important;
|
||||
}
|
||||
table {
|
||||
border: 1px solid #ccc !important;
|
||||
page-break-inside: auto;
|
||||
}
|
||||
tr {
|
||||
page-break-inside: avoid;
|
||||
page-break-after: auto;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user