Files
2026-09-16 23:20:08 +07:00

3416 lines
205 KiB
PHP

<?php
require_once __DIR__ . '/config/db.php';
$dbInfo = getDbConnection();
$driverName = strtoupper($dbInfo['driver']);
?>
<!DOCTYPE html>
<html lang="th" class="h-full bg-slate-50">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ระบบข่าวประชาสัมพันธ์และหนังสือเวียนภายใน - กระทรวงสาธารณสุข / โรงพยาบาล</title>
<link rel="icon" type="image/png" href="favicon.png">
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Simple DataTables -->
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" type="text/javascript"></script>
<!-- Google Fonts: Sarabun for official Thai medical UI -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet">
<!-- FontAwesome 6 Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
moph: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
500: '#10b981',
600: '#056839', /* Primary MOPH Green */
700: '#04542e',
800: '#034124',
900: '#022e19',
light: '#077b44'
}
},
fontFamily: {
sans: ['Sarabun', 'sans-serif'],
official: ['Sarabun', 'sans-serif']
}
}
}
}
</script>
<style>
body, input, select, textarea, button, p, span, div, h1, h2, h3, h4, h5, h6, label, a {
font-family: 'Sarabun', sans-serif;
}
i, .fa, .fas, .far, .fab, .fa-solid, .fa-regular, .fa-brands {
font-family: "Font Awesome 6 Free", "Font Awesome 6 Brands" !important;
}
body {
font-family: 'Sarabun', sans-serif;
background-color: #f8fafc;
}
/* Shimmer / Metallic Shine Effect on Logo */
.logo-container {
position: relative;
overflow: hidden;
border-radius: 9999px;
display: inline-block;
}
.logo-container::after {
content: '';
position: absolute;
top: -50%;
left: -150%;
width: 100%;
height: 200%;
background: linear-gradient(
60deg,
rgba(255, 255, 255, 0) 20%,
rgba(255, 255, 255, 0.6) 50%,
rgba(255, 255, 255, 0) 80%
);
transform: rotate(25deg);
animation: shimmerEffect 4s infinite ease-in-out;
}
@keyframes shimmerEffect {
0% { left: -150%; }
30% { left: 150%; }
100% { left: 150%; }
}
/* Header Ambient Radial Glow Effect */
.header-glow {
background: radial-gradient(circle at 15% 50%, rgba(16, 185, 129, 0.25), transparent 50%),
radial-gradient(circle at 85% 50%, rgba(245, 158, 11, 0.18), transparent 50%);
}
/* Print Mode Optimization */
@media print {
header, footer, #tickerBar, #filterSection, #actionButtons, .no-print {
display: none !important;
}
body {
background: white !important;
color: black !important;
}
#newsDetailModal {
position: static !important;
background: white !important;
display: block !important;
padding: 0 !important;
}
.modal-box {
box-shadow: none !important;
border: none !important;
max-width: 100% !important;
}
}
/* Font Size Accessibility Scaling */
html.font-scale-sm {
font-size: 14px;
}
html.font-scale-md {
font-size: 16px;
}
html.font-scale-lg {
font-size: 18.5px;
}
/* Scrollbar custom styling */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #056839;
border-radius: 4px;
}
/* Marquee Ticker Animation with Pause on Hover */
.marquee-wrapper {
display: inline-block;
white-space: nowrap;
padding-left: 100%;
animation: tickerScroll 50s linear infinite;
will-change: transform;
}
#tickerBar:hover .marquee-wrapper,
.marquee-wrapper:hover {
animation-play-state: paused;
}
@keyframes tickerScroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
</style>
</head>
<body class="min-h-full flex flex-col text-slate-800">
<!-- Combined Sticky Container for Header & Breaking News Ticker -->
<div class="sticky top-0 z-40 shadow-xl">
<header class="sticky top-0 z-[100] bg-gradient-to-r from-[#034124] via-[#056839] to-[#04542e] text-white border-b border-emerald-400/20">
<!-- Ambient Backlight Overlay -->
<div class="absolute inset-0 header-glow pointer-events-none"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4 relative z-50">
<div class="flex flex-col md:flex-row md:items-center justify-between gap-3 md:gap-6">
<!-- Brand & Logo -->
<div class="flex items-center gap-3 sm:gap-4 cursor-pointer group" onclick="resetFilters()">
<div class="logo-container bg-white p-0 shadow-xl ring-4 ring-emerald-400/30 group-hover:ring-amber-300/60 transition-all duration-300 flex items-center justify-center shrink-0 rounded-full w-11 h-11 sm:w-14 sm:h-14 overflow-hidden">
<img src="https://samuihospital.moph.go.th/dashboard/waiting/assets/img/logo.png" alt="ตรากระทรวงสาธารณสุข" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" onerror="this.onerror=null; this.src='https://placehold.co/100x100/056839/ffffff?text=MOPH';">
</div>
<div class="space-y-0.5">
<div class="flex items-center flex-wrap gap-1.5 sm:gap-2">
<span class="bg-amber-400 text-slate-900 text-[10px] sm:text-[11px] font-bold px-2 sm:px-2.5 py-0.5 rounded-full uppercase tracking-wider shadow-sm flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-800 animate-pulse"></span>
ระบบภายใน
</span>
<span id="headerHospitalName" class="text-[11px] sm:text-xs text-emerald-100 hidden sm:inline font-light border-l border-emerald-300/30 pl-2">
โรงพยาบาลศูนย์ / โรงพยาบาลทั่วไป กระทรวงสาธารณสุข
</span>
</div>
<h1 id="headerSystemTitle" class="text-sm sm:text-lg md:text-xl font-bold tracking-tight text-white leading-tight drop-shadow-sm group-hover:text-amber-200 transition-colors">
ระบบข่าวประชาสัมพันธ์และหนังสือเวียน
</h1>
</div>
</div>
<!-- Right Action Buttons -->
<div class="flex flex-wrap items-center gap-2 sm:gap-3 shrink-0" id="actionButtons">
<!-- Font Size Accessibility Controls (ก- ก ก+) -->
<div class="flex items-center bg-white/10 p-0.5 sm:p-1 rounded-xl border border-white/20 text-white backdrop-blur-md shadow-sm h-[38px] sm:h-[42px]" title="ปรับขนาดตัวอักษร">
<button onclick="setFontSize('sm')" id="fontBtnSm" class="px-2 sm:px-2.5 h-full rounded-lg flex items-center justify-center text-xs sm:text-sm font-bold transition-all hover:bg-white/20 text-emerald-100" title="ขนาดตัวอักษรเล็ก (ก-)">ก-</button>
<button onclick="setFontSize('md')" id="fontBtnMd" class="px-2 sm:px-2.5 h-full rounded-lg flex items-center justify-center text-xs sm:text-sm font-bold transition-all hover:bg-white/20 bg-white/25 text-amber-300 shadow-sm ring-1 ring-amber-300/40" title="ขนาดตัวอักษรปกติ (ก)">ก</button>
<button onclick="setFontSize('lg')" id="fontBtnLg" class="px-2 sm:px-2.5 h-full rounded-lg flex items-center justify-center text-xs sm:text-sm font-bold transition-all hover:bg-white/20 text-emerald-100" title="ขนาดตัวอักษรใหญ่ (ก+)">ก+</button>
</div>
<button onclick="openAnalyticsModal()" class="px-3 sm:px-4 py-2 sm:py-2.5 rounded-xl bg-white/10 hover:bg-white/20 text-white text-xs sm:text-sm font-bold transition-all flex items-center gap-1.5 sm:gap-2 border border-white/20 backdrop-blur-md shadow-sm hover:border-emerald-300/50 hover:shadow-md" title="ภาพรวมสถิติ">
<i class="fa-solid fa-chart-pie text-amber-300 text-xs sm:text-sm"></i>
<span class="inline">สถิติระบบ</span>
</button>
<button onclick="openUserManualModal()" class="px-3 sm:px-4 py-2 sm:py-2.5 rounded-xl bg-white/10 hover:bg-white/20 text-white text-xs sm:text-sm font-bold transition-all flex items-center gap-1.5 sm:gap-2 border border-white/20 backdrop-blur-md shadow-sm hover:border-emerald-300/50 hover:shadow-md" title="คู่มือการใช้งาน">
<i class="fa-solid fa-book-open text-amber-300 text-xs sm:text-sm"></i>
<span class="inline">คู่มือ</span>
</button>
<!-- Login / User Profile Container -->
<div id="userAuthContainer" class="flex flex-wrap items-center gap-2 sm:gap-3">
<!-- Injected via JS -->
</div>
</div>
</div>
</div>
</header>
<div id="tickerBar" class="bg-[#022c18] text-slate-200 border-b border-[#011a0e] text-xs py-2 px-4 overflow-hidden shadow-md flex items-center group cursor-pointer relative z-0" title="เลื่อนเมาส์มาวางเพื่อหยุดอ่านข่าวด่วน">
<div class="max-w-7xl mx-auto w-full flex items-center gap-3 md:gap-4 relative z-20">
<div class="shrink-0 flex items-center z-30 bg-[#022c18] pr-2 md:pr-4 py-0.5">
<span class="bg-gradient-to-r from-red-600 to-rose-500 text-white font-bold px-3 py-1 rounded-full text-[10px] sm:text-[11px] tracking-wide flex items-center gap-1.5 shadow-[0_0_12px_rgba(225,29,72,0.5)] relative border border-red-400/30">
<span class="absolute -top-1 -right-1 flex h-2.5 w-2.5">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-red-500 shadow-[0_0_5px_#f87171]"></span>
</span>
<i class="fa-solid fa-bolt text-amber-300"></i> ข่าวด่วนที่สุด
</span>
</div>
<div class="overflow-hidden whitespace-nowrap w-full relative flex items-center">
<!-- Text Fade Left Overlay -->
<div class="absolute left-0 top-0 bottom-0 w-8 md:w-20 bg-gradient-to-r from-[#022c18] to-transparent z-10 pointer-events-none"></div>
<!-- Text Fade Right Overlay -->
<div class="absolute right-0 top-0 bottom-0 w-8 md:w-20 bg-gradient-to-l from-[#022c18] to-transparent z-10 pointer-events-none"></div>
<div id="tickerText" class="marquee-wrapper font-medium text-amber-50 text-[12px] sm:text-[13px] tracking-wide flex items-center">
กำลังดึงข้อมูลข่าวสารด่วนจากฐานข้อมูล SQL...
</div>
</div>
</div>
</div>
</div>
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 flex-1 w-full space-y-6">
<!-- Top Hero Section / Pinned Highlights -->
<section id="heroSection" class="bg-white rounded-3xl p-5 md:p-6 shadow-sm border border-slate-200/80 relative overflow-hidden">
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-100">
<div class="flex items-center gap-2">
<span class="w-8 h-8 rounded-xl bg-emerald-100 text-[#056839] flex items-center justify-center font-bold">
<i class="fa-solid fa-thumbtack text-sm"></i>
</span>
<div>
<h2 class="font-bold text-slate-800 text-base md:text-lg leading-tight">หนังสือเวียนสำคัญ / ปักหมุดด่วน</h2>
<p class="text-xs text-slate-500">ข่าวสารและประกาศที่ต้องถือปฏิบัติอย่างเคร่งครัด</p>
</div>
</div>
<div class="text-xs text-slate-400 font-medium" id="currentDateDisplay"></div>
</div>
<!-- Featured Cards Grid -->
<div id="pinnedNewsContainer" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Injected via JS -->
</div>
</section>
<!-- Unified Filter Section -->
<section id="filterSection" class="bg-white p-4 sm:p-5 rounded-2xl shadow-sm border border-slate-200 space-y-4">
<!-- Header of Filter Panel -->
<div class="flex items-center justify-between pb-3 border-b border-slate-100 flex-wrap gap-2">
<div class="flex items-center gap-2">
<span class="w-7 h-7 rounded-lg bg-emerald-100 text-[#056839] flex items-center justify-center font-bold text-xs">
<i class="fa-solid fa-filter"></i>
</span>
<h3 class="font-bold text-slate-800 text-xs sm:text-sm">ชุดตัวกรองค้นหาเอกสาร</h3>
</div>
<button onclick="resetFilters()" class="text-xs text-[#056839] hover:text-[#077b44] font-medium flex items-center gap-1 hover:underline transition-all">
<i class="fa-solid fa-rotate-left text-[10px]"></i> ล้างตัวกรองทั้งหมด
</button>
</div>
<!-- Unified Filter Controls Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
<!-- Agency List Select -->
<div>
<label class="block text-[11px] font-bold text-slate-500 mb-1 flex items-center gap-1">
<i class="fa-solid fa-sitemap text-[#056839]"></i> กลุ่มงาน / ฝ่าย
</label>
<select id="agencySelectFilter" onchange="handleAgencyChange(this.value)" class="w-full h-[38px] px-3 text-xs bg-slate-50 border border-slate-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#056839] text-slate-800 font-medium cursor-pointer transition-all">
<!-- Injected via JS -->
</select>
</div>
<!-- Category List Select -->
<div>
<label class="block text-[11px] font-bold text-slate-500 mb-1 flex items-center gap-1">
<i class="fa-solid fa-layer-group text-blue-600"></i> ประเภทเอกสาร / หมวดหมู่
</label>
<select id="categorySelectFilter" onchange="handleCategoryChange(this.value)" class="w-full h-[38px] px-3 text-xs bg-slate-50 border border-slate-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#056839] text-slate-800 font-medium cursor-pointer transition-all">
<!-- Injected via JS -->
</select>
</div>
<!-- Search Input -->
<div>
<label class="block text-[11px] font-bold text-slate-500 mb-1 flex items-center gap-1">
<i class="fa-solid fa-magnifying-glass text-slate-400"></i> ค้นหาด้วยคำสำคัญ
</label>
<div class="relative">
<input type="text" id="searchInput" oninput="handleSearch()" autocomplete="off" readonly onfocus="this.removeAttribute('readonly');" placeholder="เลขที่หนังสือ / เรื่อง..." class="w-full h-[38px] pl-8 pr-3 text-xs bg-slate-50 border border-slate-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#056839] transition-all">
<i class="fa-solid fa-magnifying-glass absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-400 text-xs pointer-events-none"></i>
</div>
</div>
<!-- Year Select -->
<div>
<label class="block text-[11px] font-bold text-slate-500 mb-1 flex items-center gap-1">
<i class="fa-regular fa-calendar text-rose-500"></i> ปี พ.ศ.
</label>
<select id="yearSelectFilter" onchange="handleYearChange(this.value)" class="w-full h-[38px] px-3 text-xs bg-slate-50 border border-slate-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#056839] text-slate-800 font-medium cursor-pointer transition-all">
<option value="ALL">ทุกปี พ.ศ.</option>
<!-- Injected via JS -->
</select>
</div>
</div>
</section>
<section id="newsFeedSection">
<div id="newsCountHeader" class="text-xs font-semibold text-slate-500 mb-3 flex items-center justify-between flex-wrap gap-2">
<div class="flex items-center gap-2">
<span>รายการข่าวประชาสัมพันธ์ (<span id="totalNewsCount">0</span> เรื่อง)</span>
<span id="activeFilterLabel" class="text-[#056839] font-medium border-l border-slate-200 pl-2"></span>
</div>
<!-- View Mode Toggle Buttons -->
<div class="flex items-center bg-slate-200/70 p-0.5 rounded-xl text-slate-600">
<button id="viewGridBtn" onclick="setViewMode('grid')" class="px-3 py-1 rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 shadow-sm bg-white text-[#056839]" title="มุมมองการ์ด">
<i class="fa-solid fa-border-all"></i> <span class="hidden sm:inline">การ์ด</span>
</button>
<button id="viewListBtn" onclick="setViewMode('list')" class="px-3 py-1 rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 text-slate-500 hover:text-slate-800" title="มุมมองตารางรายการ">
<i class="fa-solid fa-list"></i> <span class="hidden sm:inline">รายการ</span>
</button>
</div>
</div>
<!-- News Main Container -->
<div id="newsGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
<!-- Injected via JS -->
</div>
<!-- Pagination Control Bar -->
<div id="paginationContainer" class="mt-8 flex items-center justify-center gap-1.5">
<!-- Injected via JS -->
</div>
<!-- Empty State -->
<div id="emptyState" class="hidden text-center py-16 bg-white rounded-3xl border border-dashed border-slate-300 p-8">
<div class="w-16 h-16 rounded-full bg-slate-100 text-slate-400 flex items-center justify-center text-2xl mx-auto mb-3">
<i class="fa-solid fa-folder-open"></i>
</div>
<h3 class="font-bold text-slate-700 text-base mb-1">ไม่พบหนังสือเวียนหรือข่าวสาร</h3>
<p class="text-xs text-slate-500 max-w-sm mx-auto mb-4">ไม่มีข้อมูลที่ตรงกับเงื่อนไขการค้นหาหรือกลุ่มงานที่คุณเลือก ลองเปลี่ยนคำค้นหาหรือเลือกหมวดหมู่อื่น</p>
<button onclick="resetFilters()" class="px-4 py-2 bg-[#056839] text-white rounded-xl text-xs font-medium hover:bg-[#077b44] transition-colors">
ล้างตัวกรองทั้งหมด
</button>
</div>
</section>
</main>
<footer class="bg-slate-900 text-slate-400 text-xs py-8 border-t border-slate-800 mt-12">
<div class="max-w-7xl mx-auto px-4 text-center space-y-2">
<div class="flex justify-center items-center gap-2 mb-2">
<img src="https://samuihospital.moph.go.th/dashboard/waiting/assets/img/logo.png" alt="MOPH" class="w-6 h-6 object-contain opacity-80" onerror="this.style.display='none'">
<span class="text-slate-200 font-bold"><span id="footerHospitalName">ระบบบริหารจัดการข่าวสารและหนังสือเวียนภายในองค์กร</span> &copy; <span id="footerYear"></span></span>
</div>
<div id="footerTextContainer" class="space-y-1">
<p>กระทรวงสาธารณสุข - Ministry of Public Health Thailand</p>
<p class="text-slate-600 text-[11px]">พัฒนาเพื่อการปฏิบัติงานภายในโรงพยาบาลและหน่วยงานในสังกัด | รองรับระบบรักษาความปลอดภัยสารสนเทศ</p>
</div>
</div>
</footer>
<!-- News Detail View Modal -->
<div id="newsDetailModal" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center p-3 sm:p-4 overflow-y-auto">
<div class="bg-white rounded-3xl max-w-3xl w-full max-h-[90vh] overflow-y-auto shadow-2xl border border-slate-100 flex flex-col my-auto modal-box">
<!-- Modal Header -->
<div class="p-5 bg-gradient-to-r from-[#056839] to-[#077b44] text-white flex justify-between items-start rounded-t-3xl sticky top-0 z-10 shadow-sm">
<div class="space-y-1 pr-4">
<div class="flex flex-wrap items-center gap-2">
<span id="detailAgencyBadge" class="px-2.5 py-0.5 rounded-full text-[11px] font-bold bg-white/20 text-white backdrop-blur-sm"></span>
<span id="detailCategoryBadge" class="px-2.5 py-0.5 rounded-full text-[11px] font-medium bg-emerald-900/40 text-emerald-200"></span>
<span id="detailDocNum" class="text-xs bg-amber-400 text-slate-900 font-bold px-2 py-0.5 rounded shadow-sm"></span>
</div>
<h2 id="detailTitle" class="text-base sm:text-lg font-bold leading-snug text-white pt-1"></h2>
</div>
<button onclick="closeNewsDetailModal()" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-colors shrink-0">
<i class="fa-solid fa-xmark text-base"></i>
</button>
</div>
<!-- Modal Content Body -->
<div class="p-6 space-y-6 flex-1">
<!-- Metadata Bar -->
<div class="flex flex-wrap items-center justify-between text-xs text-slate-500 pb-4 border-b border-slate-100 gap-2">
<div class="flex items-center gap-4">
<span><i class="fa-regular fa-calendar-check text-[#056839] mr-1"></i> วันที่ประกาศ: <strong id="detailDate" class="text-slate-700"></strong></span>
<span><i class="fa-regular fa-eye text-blue-600 mr-1"></i> เข้าชม: <strong id="detailViews" class="text-slate-700">0</strong> ครั้ง</span>
</div>
<div id="detailBadges" class="flex items-center gap-2"></div>
</div>
<!-- Text Content -->
<div class="prose prose-slate max-w-none text-sm leading-relaxed text-slate-700 whitespace-pre-line bg-slate-50/60 p-4 rounded-2xl border border-slate-100 font-official text-base" id="detailContent">
</div>
<!-- Attached Files Section -->
<div class="bg-emerald-50/50 p-4 rounded-2xl border border-emerald-100/80">
<h3 class="text-xs font-bold text-[#056839] uppercase tracking-wider mb-3 flex items-center gap-2">
<i class="fa-solid fa-paperclip text-base"></i> เอกสารแนบคำสั่ง / บันทึกข้อความ (<span id="detailAttachmentCount">0</span>)
</h3>
<div id="detailAttachmentsList" class="space-y-2"></div>
</div>
</div>
<!-- Modal Footer Controls -->
<div class="p-4 bg-slate-50 border-t border-slate-200 flex flex-wrap items-center justify-between gap-3 rounded-b-3xl no-print">
<div class="flex items-center gap-2">
<button onclick="shareNews('line', currentDetailItem?.title, currentDetailItem?.docNum)" class="px-3 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-700 text-white text-xs font-medium transition-colors flex items-center gap-1.5 shadow-sm">
<i class="fa-brands fa-line"></i> แชร์ LINE
</button>
</div>
<div class="flex items-center gap-2">
<button id="editNewsBtn" onclick="" class="px-3.5 py-2 rounded-xl bg-amber-500 hover:bg-amber-600 text-slate-900 text-xs font-bold transition-colors flex items-center gap-1.5">
<i class="fa-regular fa-pen-to-square"></i> แก้ไข
</button>
<button id="deleteNewsBtn" onclick="" class="px-3.5 py-2 rounded-xl bg-red-600 hover:bg-red-700 text-white text-xs font-medium transition-colors flex items-center gap-1.5">
<i class="fa-regular fa-trash-can"></i> ลบ
</button>
<button onclick="closeNewsDetailModal()" class="px-4 py-2 rounded-xl bg-slate-200 hover:bg-slate-300 text-slate-700 text-xs font-medium transition-colors">
ปิด
</button>
</div>
</div>
</div>
</div>
<!-- Create/Edit Document Modal -->
<div id="newsFormModal" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center p-3 sm:p-4 overflow-y-auto">
<div class="bg-white rounded-3xl max-w-2xl w-full max-h-[90vh] overflow-y-auto shadow-2xl border border-slate-100 flex flex-col my-auto">
<div class="p-5 bg-[#056839] text-white flex justify-between items-center rounded-t-3xl sticky top-0 z-10">
<div class="flex items-center gap-2">
<i class="fa-solid fa-file-signature text-xl text-amber-300"></i>
<h3 id="formModalTitle" class="font-bold text-base sm:text-lg">สร้างบันทึกข้อความ / ออกหนังสือเวียน</h3>
</div>
<button onclick="closeNewsModal()" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-colors">
<i class="fa-solid fa-xmark text-base"></i>
</button>
</div>
<form id="newsForm" onsubmit="saveNews(event)" class="p-6 space-y-4 flex-1">
<input type="hidden" id="newsIdInput">
<!-- Template Selector Quick Helpers -->
<div class="bg-amber-50 p-3 rounded-2xl border border-amber-200 flex items-center justify-between gap-2">
<div class="flex items-center justify-between gap-2 shrink-0">
<span class="text-xs font-bold text-amber-900 flex items-center gap-1.5">
<i class="fa-solid fa-wand-magic-sparkles text-amber-600"></i> แม่แบบด่วน:
</span>
<button type="button" onclick="openTemplateManageModal()" class="w-6 h-6 rounded-md bg-amber-100 hover:bg-amber-200 text-amber-700 flex items-center justify-center transition-colors" title="จัดการแม่แบบด่วน">
<i class="fa-solid fa-cog text-[10px]"></i>
</button>
</div>
<div class="flex flex-wrap items-center gap-1.5" id="quickTemplatesContainer">
<!-- Rendered by JS -->
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">เลขที่หนังสือ / บันทึกข้อความ</label>
<input type="text" id="docNumInput" value="-" placeholder="เช่น สธ 0201/ว 123 (เว้นว่างหรือใส่ - ได้)" class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-[#056839] focus:outline-none">
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">วันที่ออกหนังสือ <span class="text-red-500">*</span></label>
<input type="date" id="newsDateInput" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-[#056839] focus:outline-none">
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">เรื่อง / หัวข้อข่าวประชาสัมพันธ์ <span class="text-red-500">*</span></label>
<input type="text" id="newsTitleInput" placeholder="กรอกหัวข้อหนังสือหรือข่าวเวียน..." required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-[#056839] focus:outline-none">
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">หน่วยงานผู้รับผิดชอบ <span class="text-red-500">*</span></label>
<select id="newsAgencyInput" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-[#056839] focus:outline-none bg-white">
<!-- Injected via JS -->
</select>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">หมวดหมู่เอกสาร <span class="text-red-500">*</span></label>
<select id="newsCategoryInput" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-[#056839] focus:outline-none bg-white">
<!-- Injected via JS -->
</select>
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">รายละเอียดเนื้อหาบันทึกข้อความ</label>
<textarea id="newsContentInput" rows="6" placeholder="กรอกรายละเอียดสาระสำคัญ เรียนหัวหน้างาน ตัวอย่างแนวทางปฏิบัติ..." class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-[#056839] focus:outline-none font-official"></textarea>
</div>
<!-- Attachment Modern Upload Zone -->
<div class="space-y-3">
<label class="text-xs font-bold text-slate-700 flex items-center justify-between">
<span>เอกสารแนบประกอบหนังสือ</span>
<span class="font-normal text-slate-400">PDF, Word, Excel, JPG, PNG, GIF</span>
</label>
<div id="uploadDropZone" class="relative group border-2 border-dashed border-slate-300 hover:border-emerald-500 rounded-2xl p-6 transition-all bg-slate-50 hover:bg-emerald-50/50 flex flex-col items-center justify-center gap-2 cursor-pointer" onclick="document.getElementById('fileUploadInput').click()" ondragover="event.preventDefault(); this.classList.add('border-emerald-500', 'bg-emerald-50/50');" ondragleave="event.preventDefault(); this.classList.remove('border-emerald-500', 'bg-emerald-50/50');" ondrop="event.preventDefault(); this.classList.remove('border-emerald-500', 'bg-emerald-50/50'); handleFileDrop(event);">
<input type="file" id="fileUploadInput" multiple class="hidden" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png,.gif" onchange="handleFileUpload(event)">
<div class="w-12 h-12 rounded-full bg-white shadow-sm border border-slate-200 flex items-center justify-center text-slate-400 group-hover:text-emerald-500 group-hover:scale-110 transition-all">
<i class="fa-solid fa-cloud-arrow-up text-xl"></i>
</div>
<div class="text-sm font-bold text-slate-700 group-hover:text-emerald-700 text-center">
คลิกเพื่ออัพโหลด หรือลากไฟล์มาวางที่นี่
</div>
<div class="text-[11px] text-slate-500 text-center">
รองรับหลายไฟล์พร้อมกัน <span class="text-red-500 font-medium ml-1">(ขนาดสูงสุดไม่เกิน 35 MB ต่อไฟล์)</span>
</div>
</div>
<div id="attachmentListContainer" class="space-y-2 empty:hidden"></div>
</div>
<!-- Status Checkboxes -->
<div class="flex items-center gap-6 pt-2 border-t border-slate-100">
<label class="flex items-center gap-2 cursor-pointer text-xs font-bold text-slate-700">
<input type="checkbox" id="isPinnedInput" class="w-4 h-4 text-[#056839] rounded border-slate-300 focus:ring-[#056839]">
<span>📌 ปักหมุดสำคัญ (แสดงในแถบไฮไลท์)</span>
</label>
<label class="flex items-center gap-2 cursor-pointer text-xs font-bold text-red-600">
<input type="checkbox" id="isUrgentInput" class="w-4 h-4 text-red-600 rounded border-slate-300 focus:ring-red-500">
<span>⚡ ด่วนที่สุด (แสดงแถบข่าวด่วน)</span>
</label>
</div>
<div class="p-4 bg-slate-50 border-t border-slate-200 flex justify-end gap-2 -mx-6 -mb-6 mt-4 rounded-b-3xl">
<button type="button" onclick="closeNewsModal()" class="px-4 py-2 rounded-xl bg-slate-200 hover:bg-slate-300 text-slate-700 text-xs font-medium transition-colors">
ยกเลิก
</button>
<button type="submit" class="px-5 py-2 rounded-xl bg-[#056839] hover:bg-[#077b44] text-white text-xs font-bold transition-all shadow-md">
บันทึกเผยแพร่ลงฐานข้อมูล
</button>
</div>
</form>
</div>
</div>
<!-- User Manual Modal -->
<div id="userManualModal" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center p-3 sm:p-4 overflow-y-auto">
<div class="bg-white rounded-3xl max-w-4xl w-full max-h-[92vh] overflow-y-auto shadow-2xl border border-slate-100 flex flex-col my-auto">
<div class="p-5 bg-gradient-to-r from-blue-700 to-blue-900 text-white flex justify-between items-center rounded-t-3xl sticky top-0 z-10 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-2xl bg-white/10 flex items-center justify-center border border-white/20 text-amber-300 text-lg">
<i class="fa-solid fa-book-open"></i>
</div>
<div>
<h3 class="font-bold text-base sm:text-lg">คู่มือการใช้งานระบบ (User Manual)</h3>
<p class="text-xs text-blue-200">คำแนะนำการใช้งานสำหรับผู้ใช้แต่ละระดับ</p>
</div>
</div>
<button onclick="closeUserManualModal()" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-colors">
<i class="fa-solid fa-xmark text-base"></i>
</button>
</div>
<!-- Tab Navigation Bar -->
<div class="bg-slate-50 border-b border-slate-200 px-6 pt-3 flex items-center gap-2 overflow-x-auto">
<button id="manualTabBtnGeneral" onclick="switchManualTab('general')" class="px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all border-b-2 border-blue-600 text-blue-900 bg-white shadow-sm flex items-center gap-2 shrink-0">
<i class="fa-solid fa-users"></i> <span>ผู้ใช้ทั่วไป</span>
</button>
<button id="manualTabBtnAgency" onclick="switchManualTab('agency')" class="px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all text-slate-600 hover:text-blue-900 flex items-center gap-2 shrink-0 hidden">
<i class="fa-solid fa-user-tie"></i> <span>เจ้าหน้าที่</span>
</button>
<button id="manualTabBtnSuper" onclick="switchManualTab('super')" class="px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all text-slate-600 hover:text-blue-900 flex items-center gap-2 shrink-0 hidden">
<i class="fa-solid fa-sliders"></i> <span>ผู้ดูแลระบบ</span>
</button>
</div>
<div class="p-6 space-y-6 flex-1 text-sm text-slate-700 leading-relaxed">
<!-- General User Manual -->
<div id="manualTabGeneral" class="space-y-4">
<h4 class="font-bold text-base text-blue-900 border-b border-slate-200 pb-2"><i class="fa-solid fa-users text-blue-600 mr-2"></i>สำหรับผู้ใช้งานทั่วไป (General User)</h4>
<p>ระบบข่าวประชาสัมพันธ์และหนังสือเวียนโรงพยาบาล ได้รับการออกแบบมาเพื่อให้ค้นหาและติดตามข่าวสารได้อย่างรวดเร็ว</p>
<div class="bg-blue-50/50 p-4 rounded-xl border border-blue-100">
<h5 class="font-bold text-blue-800 mb-2">1. การค้นหาและกรองข่าวสาร</h5>
<ul class="list-disc list-inside space-y-1 ml-2">
<li><b>หมวดหมู่:</b> สามารถคลิกที่แถบหมวดหมู่ด้านบน เพื่อกรองเฉพาะประเภทข่าวที่ต้องการได้ เช่น ข่าวทั่วไป, ประกาศจัดซื้อจัดจ้าง</li>
<li><b>หน่วยงาน:</b> เลือกกรองข่าวตามหน่วยงานหรือกลุ่มงานที่เผยแพร่ได้จาก Dropdown ด้านซ้ายมือ</li>
<li><b>ค้นหาด้วยข้อความ:</b> พิมพ์ชื่อเรื่อง เลขที่เอกสาร หรือเนื้อหาที่ต้องการในช่องค้นหา ระบบจะกรองข่าวให้ทันที (Real-time)</li>
</ul>
</div>
<div class="bg-blue-50/50 p-4 rounded-xl border border-blue-100">
<h5 class="font-bold text-blue-800 mb-2">2. การปรับแต่งการแสดงผล</h5>
<ul class="list-disc list-inside space-y-1 ml-2">
<li><b>ขนาดตัวอักษร:</b> กดปุ่ม <b>ก-</b>, <b>ก</b>, หรือ <b>ก+</b> ที่มุมขวาบน เพื่อลดขนาด กลับค่าเดิม หรือขยายขนาดตัวอักษรตามลำดับ เพื่อให้อ่านได้ชัดเจนขึ้น</li>
<li><b>มุมมอง (View):</b> สามารถสลับรูปแบบการแสดงผลระหว่าง "แบบตาราง (Grid)" และ "แบบรายการ (List)" ได้ที่ปุ่มมุมมองข้างช่องค้นหา</li>
</ul>
</div>
<div class="bg-blue-50/50 p-4 rounded-xl border border-blue-100">
<h5 class="font-bold text-blue-800 mb-2">3. การอ่านข่าวและดาวน์โหลดไฟล์</h5>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>คลิกที่การ์ดข่าวเพื่อเปิดอ่านรายละเอียดแบบเต็มหน้าจอ</li>
<li>หากข่าวมีไฟล์แนบ จะแสดงรายการไฟล์อยู่ส่วนท้าย สามารถคลิกที่ไฟล์เพื่อดาวน์โหลดหรือเปิดดูได้ทันที</li>
</ul>
</div>
</div>
<!-- Agency Admin Manual -->
<div id="manualTabAgency" class="hidden space-y-4">
<h4 class="font-bold text-base text-blue-900 border-b border-slate-200 pb-2"><i class="fa-solid fa-user-tie text-blue-600 mr-2"></i>สำหรับเจ้าหน้าที่หน่วยงาน (Agency Admin)</h4>
<p>สิทธิ์ในการเผยแพร่ข่าวสาร การจัดการหมวดหมู่ และการสร้างเทมเพลตสำหรับหน่วยงานของตนเอง</p>
<div class="bg-amber-50/50 p-4 rounded-xl border border-amber-100">
<h5 class="font-bold text-amber-800 mb-2">1. การเพิ่มข่าวใหม่</h5>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>คลิกที่ปุ่ม <b><i class="fa-solid fa-plus"></i> เพิ่มข่าวใหม่</b> มุมขวาบน</li>
<li>กรอกข้อมูลให้ครบถ้วน: เลขที่เอกสาร, ชื่อเรื่อง, หมวดหมู่, และเนื้อหา</li>
<li><b>การอัพโหลดไฟล์:</b> ในส่วนไฟล์แนบ ให้คลิกเลือกไฟล์หรือลากไฟล์มาวาง (รองรับ PDF, Word, Excel, รูปภาพ) ไฟล์จะถูกอัพโหลดขึ้น Google Drive ของหน่วยงานโดยอัตโนมัติ</li>
<li><b>ข่าวด่วน/ปักหมุด:</b> สามารถเลือกตั้งเป็น "ข่าวด่วนที่สุด" เพื่อให้แสดงบนแถบวิ่ง (Ticker) หรือ "ปักหมุด" เพื่อให้อยู่ด้านบนสุดของรายการ</li>
</ul>
</div>
<div class="bg-amber-50/50 p-4 rounded-xl border border-amber-100">
<h5 class="font-bold text-amber-800 mb-2">2. การจัดการข่าวที่โพสต์ไปแล้ว</h5>
<ul class="list-disc list-inside space-y-1 ml-2">
<li><b>ซ่อนข่าว:</b> หากไม่ต้องการให้ผู้ใช้ทั่วไปเห็น สามารถกดปุ่ม <i class="fa-solid fa-eye-slash"></i> บนการ์ดข่าว (เจ้าหน้าที่จะยังคงเห็นและแก้ไขได้)</li>
<li><b>ลบข่าว:</b> กดปุ่ม <i class="fa-solid fa-trash-can"></i> เพื่อลบข่าวและไฟล์แนบที่เกี่ยวข้องอย่างถาวร</li>
</ul>
</div>
<div class="bg-amber-50/50 p-4 rounded-xl border border-amber-100">
<h5 class="font-bold text-amber-800 mb-2">3. การจัดการหมวดหมู่และเทมเพลต</h5>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>คลิกเมนู <b>"เครื่องมือจัดการ"</b> เลื่อนเมาส์ไปที่ <b>"จัดการหมวดหมู่"</b> หรือ <b>"จัดการเทมเพลต"</b></li>
<li>สามารถเพิ่ม/ลบ/แก้ไขหมวดหมู่ข่าวเฉพาะของหน่วยงานท่านได้</li>
<li>สามารถสร้างเทมเพลตข้อความสำเร็จรูป เพื่อดึงมาใช้งานได้ทันทีเวลาโพสต์ข่าว โดยไม่ต้องพิมพ์ใหม่ทั้งหมด</li>
</ul>
</div>
</div>
<!-- Super Admin Manual -->
<div id="manualTabSuper" class="hidden space-y-4">
<h4 class="font-bold text-base text-blue-900 border-b border-slate-200 pb-2"><i class="fa-solid fa-sliders text-blue-600 mr-2"></i>สำหรับผู้ดูแลระบบกลาง (Super Admin)</h4>
<p>สิทธิ์สูงสุดในการตั้งค่าระบบทั้งหมด</p>
<div class="bg-purple-50/50 p-4 rounded-xl border border-purple-100">
<h5 class="font-bold text-purple-800 mb-2">1. ศูนย์ควบคุมและจัดการระบบ (Super Admin Panel)</h5>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>คลิกเมนู <b>"เครื่องมือจัดการ"</b> -> <b>"ศูนย์ควบคุมระบบ (Super Admin)"</b></li>
<li><b>จัดสร้างหน่วยงาน:</b> สามารถเพิ่มหน่วยงานใหม่ กำหนดชื่อ อักษรย่อ และ Google Drive Folder ID</li>
<li><b>รหัสผ่าน:</b> สามารถดูรหัสผ่านของทุกหน่วยงาน และตั้งรหัสผ่านใหม่สำหรับ Super Admin ได้</li>
<li><b>ตั้งค่าทั่วไป:</b> ปรับแต่งชื่อโรงพยาบาล ข้อความแถบวิ่ง URL โลโก้ ข้อความ Footer และกำหนด Google Apps Script URL กลางสำหรับการอัพโหลด</li>
</ul>
</div>
<div class="bg-purple-50/50 p-4 rounded-xl border border-purple-100">
<h5 class="font-bold text-purple-800 mb-2">2. ประวัติการทำงาน (Activity Logs)</h5>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>ระบบจะบันทึกการกระทำสำคัญ เช่น การเข้าสู่ระบบ, การเพิ่ม/แก้ไข/ลบข่าว, การเปลี่ยนแปลงการตั้งค่า และการจัดการหน่วยงาน</li>
<li>ในเมนู <b>ศูนย์ควบคุมระบบ</b> ไปที่แท็บ <b>"4. ประวัติการทำงาน"</b></li>
<li>สามารถเรียกดู ค้นหา และกรองตามช่วงวันที่ เพื่อตรวจสอบการใช้งานและความปลอดภัยของระบบได้</li>
</ul>
</div>
</div>
</div>
<!-- Modal Footer -->
<div class="p-4 bg-slate-50 border-t border-slate-200 flex justify-end rounded-b-3xl">
<button onclick="closeUserManualModal()" class="px-4 py-2 rounded-xl bg-slate-200 hover:bg-slate-300 text-slate-700 text-xs font-medium transition-colors">
ปิดคู่มือ
</button>
</div>
</div>
</div>
<!-- Analytics Dashboard Modal -->
<div id="analyticsModal" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center p-4">
<div class="bg-white rounded-3xl max-w-3xl w-full max-h-[90vh] overflow-y-auto shadow-2xl border border-slate-100 flex flex-col">
<div class="p-5 bg-[#056839] text-white flex justify-between items-center rounded-t-3xl sticky top-0 z-10">
<div class="flex items-center gap-2">
<i class="fa-solid fa-chart-pie text-emerald-300 text-lg"></i>
<h3 class="font-bold text-base sm:text-lg">สถิติและภาพรวมหนังสือเวียนโรงพยาบาล</h3>
</div>
<button onclick="closeAnalyticsModal()" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-colors">
<i class="fa-solid fa-xmark text-base"></i>
</button>
</div>
<div class="p-6 space-y-6">
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div class="p-4 rounded-2xl bg-emerald-50 border border-emerald-100 flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-[#056839] text-white flex items-center justify-center text-lg">
<i class="fa-solid fa-newspaper"></i>
</div>
<div>
<span class="text-[11px] font-bold text-emerald-800 uppercase">หนังสือทั้งหมด</span>
<div id="analyticsTotalNews" class="text-xl font-bold text-[#056839]">0</div>
</div>
</div>
<div class="p-4 rounded-2xl bg-blue-50 border border-blue-100 flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-blue-600 text-white flex items-center justify-center text-lg">
<i class="fa-solid fa-eye"></i>
</div>
<div>
<span class="text-[11px] font-bold text-blue-700 uppercase">เปิดอ่านรวม</span>
<div id="analyticsTotalViews" class="text-xl font-bold text-blue-900">0</div>
</div>
</div>
<div class="p-4 rounded-2xl bg-purple-50 border border-purple-100 flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-purple-600 text-white flex items-center justify-center text-lg">
<i class="fa-solid fa-paperclip"></i>
</div>
<div>
<span class="text-[11px] font-bold text-purple-700 uppercase">ไฟล์เอกสารแนบ</span>
<div id="analyticsTotalFiles" class="text-xl font-bold text-purple-900">0</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-slate-50 p-4 rounded-2xl border border-slate-200">
<h4 class="font-bold text-slate-800 text-xs mb-3 flex items-center gap-2">
<i class="fa-solid fa-hospital text-[#056839]"></i> หนังสือแบ่งตามกลุ่มงาน
</h4>
<div id="analyticsAgencyStats" class="space-y-2.5"></div>
</div>
<div class="bg-slate-50 p-4 rounded-2xl border border-slate-200">
<h4 class="font-bold text-slate-800 text-xs mb-3 flex items-center gap-2">
<i class="fa-solid fa-layer-group text-blue-600"></i> หนังสือแบ่งตามหมวดหมู่
</h4>
<div id="analyticsCategoryStats" class="space-y-2.5"></div>
</div>
</div>
</div>
<div class="p-4 bg-slate-50 border-t border-slate-200 flex justify-end rounded-b-3xl">
<button onclick="closeAnalyticsModal()" class="px-4 py-2 rounded-xl bg-slate-200 hover:bg-slate-300 text-slate-700 text-xs font-medium transition-colors">
ปิดหน้าต่าง
</button>
</div>
</div>
</div>
<div id="toastContainer" class="fixed bottom-5 right-5 z-50 flex flex-col gap-2 pointer-events-none"></div>
<!-- Super Admin Control Panel Modal -->
<div id="superAdminModal" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center p-3 sm:p-4 overflow-y-auto">
<div class="bg-white rounded-3xl max-w-4xl w-full max-h-[92vh] overflow-y-auto shadow-2xl border border-slate-100 flex flex-col my-auto">
<!-- Header -->
<div class="p-5 bg-gradient-to-r from-purple-900 via-purple-800 to-indigo-900 text-white flex justify-between items-center rounded-t-3xl sticky top-0 z-10 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-2xl bg-white/10 flex items-center justify-center border border-white/20 text-amber-300 text-lg">
<i class="fa-solid fa-sliders"></i>
</div>
<div>
<h3 class="font-bold text-base sm:text-lg">ศูนย์ควบคุมและจัดการระบบ (Super Admin Control Panel)</h3>
<p class="text-xs text-purple-200">บริหารจัดการหน่วยงาน ผู้ดูแลระบบประจำฝ่าย และตั้งค่าระบบสารบรรณ</p>
</div>
</div>
<button onclick="closeSuperAdminModal()" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-colors">
<i class="fa-solid fa-xmark text-base"></i>
</button>
</div>
<!-- Tab Navigation Bar -->
<div class="bg-slate-100 border-b border-slate-200 px-6 pt-3 flex items-center gap-2 overflow-x-auto">
<button id="adminTabBtnAgencies" onclick="switchAdminTab('agencies')" class="px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all border-b-2 border-purple-600 text-purple-900 bg-white shadow-sm flex items-center gap-2">
<i class="fa-solid fa-sitemap"></i> <span>1. จัดสร้างและแก้ไขหน่วยงาน</span>
</button>
<button id="adminTabBtnAdmins" onclick="switchAdminTab('admins')" class="px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all text-slate-600 hover:text-purple-900 flex items-center gap-2">
<i class="fa-solid fa-user-shield"></i> <span>2. ผู้ดูแลและรหัสผ่าน</span>
</button>
<button id="adminTabBtnSettings" onclick="switchAdminTab('settings')" class="px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all text-slate-600 hover:text-purple-900 flex items-center gap-2">
<i class="fa-solid fa-gear"></i> <span>3. ตั้งค่าทั่วไปของระบบ</span>
</button>
<button id="adminTabBtnLogs" onclick="switchAdminTab('logs'); loadActivityLogs();" class="px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all text-slate-600 hover:text-purple-900 flex items-center gap-2 shrink-0">
<i class="fa-solid fa-clock-rotate-left"></i> <span>4. ประวัติการทำงาน</span>
</button>
</div>
<!-- Body Contents -->
<div class="p-6 space-y-6 flex-1">
<!-- TAB 1: AGENCIES MANAGEMENT -->
<div id="adminTabAgencies" class="space-y-6">
<!-- Create / Edit Agency Form -->
<div class="bg-purple-50/60 p-4 sm:p-5 rounded-2xl border border-purple-100 space-y-4">
<div class="flex items-center justify-between">
<h4 id="agencyFormTitle" class="font-bold text-sm text-purple-900 flex items-center gap-2">
<i class="fa-solid fa-circle-plus text-purple-600"></i> เพิ่มหน่วยงาน / กลุ่มงานใหม่ในโรงพยาบาล
</h4>
<button type="button" id="agencyCancelEditBtn" onclick="resetAgencyForm()" class="hidden text-xs text-slate-500 hover:text-red-600 font-medium">
<i class="fa-solid fa-xmark"></i> ยกเลิกการแก้ไข
</button>
</div>
<form id="agencyForm" onsubmit="saveAgencySubmit(event)" class="grid grid-cols-1 sm:grid-cols-4 gap-3 items-end">
<input type="hidden" id="agencyIsEditInput" value="0">
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">รหัสหน่วยงาน (ID) <span class="text-red-500">*</span></label>
<input type="text" id="agencyIdInput" placeholder="เช่น OPD, ER, LAB" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none uppercase font-mono font-bold">
</div>
<div class="sm:col-span-2">
<label class="block text-xs font-bold text-slate-700 mb-1">ชื่อเต็มกลุ่มงาน / ฝ่าย <span class="text-red-500">*</span></label>
<input type="text" id="agencyNameInput" placeholder="เช่น กลุ่มงานผู้ป่วยนอก (OPD)" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none">
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">อักษรย่อ (Code) <span class="text-red-500">*</span></label>
<input type="text" id="agencyCodeInput" placeholder="เช่น OPD" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none uppercase font-mono font-bold">
</div>
<div class="sm:col-span-2">
<label class="block text-xs font-bold text-slate-700 mb-1">รหัสผ่านเข้าสู่ระบบผู้ดูแลหน่วยงาน <span class="text-red-500">*</span></label>
<input type="text" id="agencyPasswordInput" value="1234" placeholder="ตั้งรหัสผ่าน (เริ่มต้น: 1234)" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none font-mono">
</div>
<div class="sm:col-span-2">
<label class="block text-xs font-bold text-slate-700 mb-1">Google Drive Folder ID (สำหรับเก็บไฟล์แนบ)</label>
<input type="text" id="agencyGdriveInput" placeholder="เช่น 1A2b3C4d5E..." class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none font-mono">
</div>
<div>
<button type="submit" class="w-full py-2 px-4 bg-purple-700 hover:bg-purple-800 text-white font-bold text-xs rounded-xl transition-all shadow-md flex items-center justify-center gap-1.5 h-[38px]">
<i class="fa-solid fa-floppy-disk"></i> <span id="agencySubmitBtnText">บันทึกสร้างหน่วยงาน</span>
</button>
</div>
</form>
</div>
<!-- Agencies List Table -->
<div class="space-y-3">
<h4 class="font-bold text-slate-800 text-xs flex items-center justify-between">
<span>รายชื่อกลุ่มงาน / ฝ่ายทั้งหมดในระบบ (<span id="adminAgencyCount">0</span> หน่วยงาน)</span>
</h4>
<div class="overflow-x-auto rounded-2xl border border-slate-200">
<table class="w-full text-left text-xs">
<thead class="bg-slate-100 text-slate-700 font-bold border-b border-slate-200">
<tr>
<th class="p-3">รหัส (ID)</th>
<th class="p-3">ชื่อกลุ่มงาน / ฝ่าย</th>
<th class="p-3">อักษรย่อ</th>
<th class="p-3">รหัสผ่าน</th>
<th class="p-3 text-right">จัดการ</th>
</tr>
</thead>
<tbody id="adminAgenciesTableBody" class="divide-y divide-slate-100">
<!-- Injected via JS -->
</tbody>
</table>
</div>
</div>
</div>
<!-- TAB 2: ADMIN USERS & PASSWORDS -->
<div id="adminTabAdmins" class="hidden space-y-6">
<div class="bg-amber-50 p-4 rounded-2xl border border-amber-200 space-y-3">
<h4 class="font-bold text-xs text-amber-900 flex items-center gap-2">
<i class="fa-solid fa-key text-amber-600"></i> เปลี่ยนรหัสผ่านผู้ดูแลระบบกลาง (Super Admin)
</h4>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div class="sm:col-span-2">
<label class="block text-[11px] font-bold text-slate-600 mb-1">รหัสผ่านผู้ดูแลระบบกลางใหม่</label>
<input type="password" id="superAdminNewPassInput" placeholder="กรอกรหัสผ่านใหม่..." class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:outline-none">
</div>
<div class="flex items-end">
<button onclick="saveSuperAdminPasswordOnly()" class="w-full py-2 px-4 bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold text-xs rounded-xl transition-all shadow-sm h-[38px]">
เปลี่ยนรหัสผ่าน Super Admin
</button>
</div>
</div>
</div>
<div class="space-y-3">
<h4 class="font-bold text-slate-800 text-xs">ตารางรหัสผ่านผู้ดูแลประจำหน่วยงาน</h4>
<div id="agencyPasswordCards" class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<!-- Injected via JS -->
</div>
</div>
</div>
<!-- TAB 3: GENERAL SETTINGS -->
<div id="adminTabSettings" class="hidden space-y-4">
<form onsubmit="saveSystemSettingsSubmit(event)" class="space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">ชื่อโรงพยาบาล / สังกัดหลัก</label>
<input type="text" id="settingHospitalName" placeholder="เช่น โรงพยาบาลศูนย์ / โรงพยาบาลทั่วไป กระทรวงสาธารณสุข" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none">
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">ชื่อระบบสารบรรณ / ระบบข่าว</label>
<input type="text" id="settingSystemTitle" placeholder="เช่น ระบบข่าวประชาสัมพันธ์และหนังสือเวียน" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none">
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">URL ตราโลโก้โรงพยาบาล</label>
<input type="text" id="settingLogoUrl" placeholder="https://..." required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none font-mono">
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">ข้อความข่าวประกาศวิ่ง (Marquee Ticker Standard)</label>
<textarea id="settingTickerText" rows="3" placeholder="ข้อความประกาศข่าววิ่งเริ่มต้น..." class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none"></textarea>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">ข้อความส่วนท้าย (Footer)</label>
<textarea id="settingFooterText" rows="3" placeholder="ข้อความที่จะแสดงส่วนท้ายเว็บ (ขึ้นบรรทัดใหม่ได้)" class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none"></textarea>
<p class="text-[10px] text-slate-500 mt-1"><i class="fa-solid fa-info-circle mr-1"></i> รองรับการขึ้นบรรทัดใหม่, ปีปัจจุบันจะแสดงอัตโนมัติ</p>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1 text-emerald-700"><i class="fa-brands fa-google-drive"></i> Google Apps Script Web App URL (สำหรับอัพโหลดไฟล์)</label>
<input type="text" id="settingGasUrl" placeholder="https://script.google.com/macros/s/.../exec" class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-emerald-600 focus:outline-none font-mono">
<p class="text-[10px] text-slate-500 mt-1">ต้องตั้งค่า Web App ให้ Anyone can access จึงจะสามารถอัพโหลดได้</p>
</div>
<div class="pt-3 border-t border-slate-100 flex justify-end">
<button type="submit" class="px-5 py-2.5 rounded-xl bg-purple-700 hover:bg-purple-800 text-white text-xs font-bold transition-all shadow-md flex items-center gap-2">
<i class="fa-solid fa-floppy-disk"></i> บันทึกการตั้งค่าทั้งหมด
</button>
</div>
</form>
</div>
<!-- TAB 4: ACTIVITY LOGS -->
<div id="adminTabLogs" class="hidden space-y-4">
<div class="flex flex-col sm:flex-row gap-3 items-end">
<div class="flex-1 w-full">
<label class="block text-xs font-bold text-slate-700 mb-1">ค้นหาประวัติ (ชื่อ, การกระทำ, รายละเอียด)</label>
<input type="text" id="logSearchInput" placeholder="ค้นหา..." class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none">
</div>
<div class="w-full sm:w-auto">
<label class="block text-xs font-bold text-slate-700 mb-1">ตั้งแต่วันที่</label>
<input type="date" id="logStartDate" class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none">
</div>
<div class="w-full sm:w-auto">
<label class="block text-xs font-bold text-slate-700 mb-1">ถึงวันที่</label>
<input type="date" id="logEndDate" class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-purple-600 focus:outline-none">
</div>
<div class="w-full sm:w-auto">
<button type="button" onclick="loadActivityLogs()" class="w-full px-4 py-2 rounded-xl bg-purple-700 hover:bg-purple-800 text-white text-xs font-bold transition-colors">
<i class="fa-solid fa-search"></i> ค้นหา
</button>
</div>
</div>
<div class="bg-white rounded-xl border border-slate-200 overflow-x-auto shadow-sm">
<table id="activityLogsTable" class="w-full text-left text-xs text-slate-600">
<thead class="bg-slate-50 text-slate-700 border-b border-slate-200">
<tr>
<th class="px-4 py-3 font-bold">วัน-เวลา</th>
<th class="px-4 py-3 font-bold">ผู้กระทำ</th>
<th class="px-4 py-3 font-bold">การกระทำ (Action)</th>
<th class="px-4 py-3 font-bold">รายละเอียดเพิ่มเติม</th>
</tr>
</thead>
<tbody id="activityLogsTbody" class="divide-y divide-slate-100">
<tr><td colspan="4" class="px-4 py-8 text-center text-slate-400">กำลังโหลดประวัติ...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal Footer -->
<div class="p-4 bg-slate-50 border-t border-slate-200 flex justify-end rounded-b-3xl">
<button onclick="closeSuperAdminModal()" class="px-4 py-2 rounded-xl bg-slate-200 hover:bg-slate-300 text-slate-700 text-xs font-medium transition-colors">
ปิดหน้าต่าง
</button>
</div>
</div>
</div>
<!-- Login Modal -->
<div id="loginModal" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center p-4">
<div class="bg-white rounded-3xl max-w-md w-full shadow-2xl border border-slate-100 overflow-hidden flex flex-col">
<div class="p-5 bg-gradient-to-r from-[#034124] to-[#056839] text-white flex justify-between items-center">
<div class="flex items-center gap-2.5">
<div class="w-9 h-9 rounded-xl bg-white/10 flex items-center justify-center border border-white/20">
<i class="fa-solid fa-user-shield text-amber-300 text-base"></i>
</div>
<div>
<h3 class="font-bold text-sm sm:text-base">เข้าสู่ระบบผู้ดูแลข่าวประจำหน่วยงาน</h3>
<p class="text-[11px] text-emerald-200">เข้าใช้งานเพื่อออกหนังสือเวียนและจัดการข่าวสาร</p>
</div>
</div>
<button onclick="closeLoginModal()" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-colors">
<i class="fa-solid fa-xmark text-sm"></i>
</button>
</div>
<form onsubmit="handleLoginSubmit(event)" class="p-6 space-y-4">
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">เลือกหน่วยงาน / ฝ่ายงาน <span class="text-red-500">*</span></label>
<select id="loginAgencySelect" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-[#056839] focus:outline-none bg-white font-medium">
<option value="SUPER_ADMIN">🔑 ผู้ดูแลระบบ (Super Admin)</option>
<!-- Injected via JS -->
</select>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 mb-1">รหัสผ่านยืนยันตัวตน <span class="text-red-500">*</span></label>
<div class="relative">
<input type="password" id="loginPasswordInput" placeholder="กรอกรหัสผ่าน" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-[#056839] focus:outline-none pr-10">
<i class="fa-solid fa-lock absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-xs"></i>
</div>
</div>
<div class="pt-2 flex justify-end gap-2">
<button type="button" onclick="closeLoginModal()" class="px-4 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-medium transition-colors">
ยกเลิก
</button>
<button type="submit" class="px-5 py-2 rounded-xl bg-[#056839] hover:bg-[#077b44] text-white text-xs font-bold transition-all shadow-md flex items-center gap-1.5">
<i class="fa-solid fa-right-to-bracket"></i>
<span>เข้าสู่ระบบ</span>
</button>
</div>
</form>
</div>
</div>
<!-- Category Manage Modal -->
<div id="categoryManageModal" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center p-3 sm:p-4 overflow-y-auto">
<div class="bg-white rounded-3xl w-full max-w-2xl shadow-2xl flex flex-col my-auto border border-slate-100 overflow-hidden transform transition-all">
<!-- Header -->
<div class="bg-gradient-to-r from-blue-600 to-indigo-700 p-4 sm:p-5 flex items-center justify-between shadow-sm">
<h3 class="font-bold text-white text-sm sm:text-base flex items-center gap-2">
<i class="fa-solid fa-tags text-blue-200"></i> จัดการหมวดหมู่ข่าว (ส่วนกลาง)
</h3>
<button onclick="closeCategoryManageModal()" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-colors">
<i class="fa-solid fa-xmark text-base"></i>
</button>
</div>
<div class="p-4 sm:p-6 space-y-5 bg-slate-50/50 flex-1">
<!-- Add Category Form -->
<div class="bg-white p-4 rounded-2xl border border-blue-100 shadow-sm space-y-3">
<div class="flex items-center justify-between">
<h4 id="categoryFormTitle" class="font-bold text-xs text-blue-900 flex items-center gap-1.5">
<i class="fa-solid fa-plus-circle text-blue-600"></i> เพิ่มหมวดหมู่ใหม่
</h4>
<button type="button" id="categoryCancelEditBtn" onclick="resetCategoryForm()" class="hidden text-[11px] text-slate-500 hover:text-red-600 font-medium bg-red-50 px-2 py-1 rounded">
<i class="fa-solid fa-xmark"></i> ยกเลิก
</button>
</div>
<form id="categoryForm" onsubmit="saveCategorySubmit(event)" class="flex flex-col sm:flex-row gap-3">
<input type="hidden" id="categoryIsEdit" value="0">
<div class="flex-1 space-y-1">
<input type="text" id="categoryIdInput" placeholder="รหัสหมวดหมู่ (เช่น HR, PR)" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:outline-none uppercase font-bold" maxlength="10">
</div>
<div class="flex-[2] space-y-1">
<input type="text" id="categoryNameInput" placeholder="ชื่อหมวดหมู่ (พร้อม Emoji นำหน้า ถ้ามี)" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:outline-none">
</div>
<div class="flex items-end">
<button type="submit" class="w-full sm:w-auto h-[38px] px-4 bg-blue-600 hover:bg-blue-700 text-white font-bold text-xs rounded-xl shadow-sm transition-colors flex items-center justify-center gap-1.5">
<i class="fa-solid fa-save"></i> บันทึก
</button>
</div>
</form>
</div>
<!-- Categories Table -->
<div class="bg-white rounded-2xl border border-slate-200 overflow-hidden shadow-sm">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-100 border-b border-slate-200">
<th class="py-2.5 px-4 font-bold text-slate-600 text-[11px] w-24">รหัส (ID)</th>
<th class="py-2.5 px-4 font-bold text-slate-600 text-[11px]">ชื่อหมวดหมู่</th>
<th class="py-2.5 px-4 font-bold text-slate-600 text-[11px] text-right w-24">จัดการ</th>
</tr>
</thead>
<tbody id="adminCategoriesTableBody" class="divide-y divide-slate-100">
<!-- Injected via JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Template Manage Modal -->
<div id="templateManageModal" class="fixed inset-0 z-50 bg-slate-900/50 backdrop-blur-sm hidden flex items-center justify-center p-4">
<div class="bg-white rounded-3xl max-w-4xl w-full max-h-[90vh] shadow-2xl flex flex-col overflow-hidden border border-slate-100">
<div class="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-gradient-to-r from-amber-600 to-amber-500 rounded-t-3xl text-white">
<h3 class="font-bold text-base sm:text-lg flex items-center gap-2">
<i class="fa-solid fa-wand-magic-sparkles text-amber-200"></i> จัดการแม่แบบด่วน
</h3>
<button onclick="closeTemplateManageModal()" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-colors">
<i class="fa-solid fa-xmark text-base"></i>
</button>
</div>
<div class="p-4 sm:p-6 space-y-5 bg-slate-50/50 flex-1 overflow-y-auto">
<!-- Add/Edit Template Form -->
<div class="bg-white p-4 rounded-2xl border border-amber-100 shadow-sm space-y-3">
<div class="flex items-center justify-between">
<h4 id="templateFormTitle" class="font-bold text-xs text-amber-900 flex items-center gap-1.5">
<i class="fa-solid fa-plus-circle text-amber-600"></i> เพิ่มแม่แบบใหม่
</h4>
<button type="button" id="templateCancelEditBtn" onclick="resetTemplateForm()" class="hidden text-[11px] text-slate-500 hover:text-red-600 font-medium bg-red-50 px-2 py-1 rounded">
<i class="fa-solid fa-xmark"></i> ยกเลิก
</button>
</div>
<form id="templateForm" onsubmit="saveTemplateSubmit(event)" class="space-y-3">
<input type="hidden" id="templateIdInput">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div class="space-y-1">
<label class="text-[10px] font-bold text-slate-500">ชื่อปุ่มแม่แบบ (เช่น แนวทาง CPG) <span class="text-red-500">*</span></label>
<input type="text" id="tplNameInput" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:outline-none">
</div>
<div class="space-y-1">
<label class="text-[10px] font-bold text-slate-500">หมวดหมู่เริ่มต้น <span class="text-red-500">*</span></label>
<select id="tplCategoryInput" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:outline-none bg-white"></select>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div class="space-y-1">
<label class="text-[10px] font-bold text-slate-500">รูปแบบเลขที่หนังสือ (พิมพ์ {random} สำหรับสุ่มตัวเลข) <span class="text-red-500">*</span></label>
<input type="text" id="tplDocNumInput" placeholder="เช่น สธ 0202/ว {random}" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:outline-none">
</div>
<div class="space-y-1">
<label class="text-[10px] font-bold text-slate-500">เรื่อง / หัวข้อเริ่มต้น <span class="text-red-500">*</span></label>
<input type="text" id="tplTitleInput" required class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:outline-none">
</div>
</div>
<div class="space-y-1">
<label class="text-[10px] font-bold text-slate-500">รายละเอียดเนื้อหา <span class="text-red-500">*</span></label>
<textarea id="tplContentInput" required rows="3" class="w-full px-3 py-2 text-xs border border-slate-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:outline-none resize-none"></textarea>
</div>
<div class="flex justify-end pt-2">
<button type="submit" class="w-full sm:w-auto px-6 py-2.5 bg-amber-600 hover:bg-amber-700 text-white font-bold text-xs rounded-xl shadow-sm transition-colors flex items-center justify-center gap-1.5">
<i class="fa-solid fa-save"></i> บันทึกแม่แบบ
</button>
</div>
</form>
</div>
<!-- Templates Table -->
<div class="bg-white rounded-2xl border border-slate-200 overflow-hidden shadow-sm">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-100 border-b border-slate-200">
<th class="py-2.5 px-4 font-bold text-slate-600 text-[11px] w-48">ชื่อแม่แบบ</th>
<th class="py-2.5 px-4 font-bold text-slate-600 text-[11px]">รูปแบบหัวข้อ / เนื้อหา</th>
<th class="py-2.5 px-4 font-bold text-slate-600 text-[11px] text-right w-24">จัดการ</th>
</tr>
</thead>
<tbody id="adminTemplatesTableBody" class="divide-y divide-slate-100">
<!-- Injected via JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Confirm Dialog Modal -->
<div id="confirmModal" class="fixed inset-0 z-[60] bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center p-4">
<div class="bg-white rounded-3xl max-w-sm w-full shadow-2xl overflow-hidden flex flex-col p-6 text-center border border-slate-100">
<div class="w-16 h-16 rounded-full bg-red-100 mx-auto flex items-center justify-center mb-4 text-red-500">
<i class="fa-solid fa-triangle-exclamation text-2xl"></i>
</div>
<h3 id="confirmModalTitle" class="font-bold text-base text-slate-800 mb-2">ยืนยันการดำเนินการ</h3>
<p id="confirmModalMessage" class="text-sm text-slate-500 mb-6 font-medium">คุณแน่ใจหรือไม่?</p>
<div class="flex gap-2">
<button onclick="closeConfirmModal(false)" class="flex-1 py-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 text-sm font-bold transition-colors">
ยกเลิก
</button>
<button onclick="closeConfirmModal(true)" class="flex-1 py-2.5 rounded-xl bg-red-500 hover:bg-red-600 text-white text-sm font-bold transition-all shadow-md">
ยืนยัน
</button>
</div>
</div>
</div>
<script>
// System Master Data Constants
let AGENCIES = [
{ id: 'MED', name: 'องค์กรแพทย์ / กลุ่มงานการแพทย์', code: 'MED', admin_password: '1234' },
{ id: 'NURSE', name: 'กลุ่มงานการพยาบาล', code: 'NRS', admin_password: '1234' },
{ id: 'PHARM', name: 'กลุ่มงานเภสัชกรรมและคลังยา', code: 'RX', admin_password: '1234' },
{ id: 'IT', name: 'ศูนย์เทคโนโลยีสารสนเทศ (HIS)', code: 'IT', admin_password: '1234' },
{ id: 'ADMIN', name: 'กลุ่มงานบริหารทั่วไป / สารบรรณ', code: 'ADM', admin_password: '1234' }
];
let CATEGORIES = [
{ id: 'ALL', name: 'หมวดหมู่ทั้งหมด' }
];
let TEMPLATES = [];
let newsList = [];
let systemSettings = {
hospital_name: 'โรงพยาบาลศูนย์ / โรงพยาบาลทั่วไป กระทรวงสาธารณสุข',
system_title: 'ระบบข่าวประชาสัมพันธ์และหนังสือเวียน',
hospital_logo_url: 'https://samuihospital.moph.go.th/dashboard/waiting/assets/img/logo.png',
super_admin_password: '1234',
ticker_text: 'ระบบข่าวสารและหนังสือเวียนภายในองค์กร โรงพยาบาลเปิดให้บริการตามปกติ'
};
let selectedCategory = 'ALL';
let selectedAgency = 'ALL';
let selectedYear = 'ALL';
let searchQuery = '';
let currentDetailItem = null;
let confirmCallback = null;
let currentUser = null; // Current Authenticated User State
let currentView = 'grid'; // 'grid' or 'list'
let currentPage = 1;
let activityLogsDataTable = null;
const itemsPerPage = 9;
// Intercept fetch to automatically attach action_user_id and action_user_name for logging
const originalFetch = window.fetch;
window.fetch = async function() {
let [resource, config] = arguments;
if (typeof resource === 'string' && resource.startsWith('api/') && config && config.method === 'POST') {
if (config.body && typeof config.body === 'string') {
try {
const parsed = JSON.parse(config.body);
if (currentUser) {
parsed.action_user_id = currentUser.agencyId;
parsed.action_user_name = currentUser.name || currentUser.code;
}
config.body = JSON.stringify(parsed);
} catch (e) {
// Ignore if not JSON
}
}
}
return originalFetch.apply(this, arguments);
};
window.onload = function() {
initYearFilter();
initApp();
};
function initYearFilter() {
const yearSelect = document.getElementById('yearSelectFilter');
if (!yearSelect) return;
const currentYear = new Date().getFullYear();
const thaiCurrentYear = currentYear + 543;
let optionsHTML = '<option value="ALL">ทุกปี พ.ศ.</option>';
for (let i = 0; i < 3; i++) {
const y = thaiCurrentYear - i;
optionsHTML += `<option value="${y}">ปี พ.ศ. ${y}</option>`;
}
yearSelect.innerHTML = optionsHTML;
}
async function initApp() {
initFontSize();
// Restore User Auth Session
const storedUser = localStorage.getItem('moph_user_session');
if (storedUser) {
try {
currentUser = JSON.parse(storedUser);
} catch (e) {
currentUser = null;
}
}
renderCurrentDate();
renderCategoryPills();
renderAgencyFilters();
renderAuthUI();
// Load live news data & system settings from PHP MySQL Backend API
await loadNewsFromDatabase();
populateFormDropdowns();
}
function setFontSize(size) {
document.documentElement.classList.remove('font-scale-sm', 'font-scale-md', 'font-scale-lg');
document.documentElement.classList.add(`font-scale-${size}`);
['sm', 'md', 'lg'].forEach(s => {
const btn = document.getElementById(`fontBtn${s.charAt(0).toUpperCase() + s.slice(1)}`);
if (btn) {
if (s === size) {
btn.className = "px-2.5 h-full rounded-lg flex items-center justify-center text-xs md:text-sm font-bold transition-all bg-white/25 text-amber-300 shadow-sm ring-1 ring-amber-300/40";
} else {
btn.className = "px-2.5 h-full rounded-lg flex items-center justify-center text-xs md:text-sm font-bold transition-all hover:bg-white/20 text-emerald-100";
}
}
});
localStorage.setItem('moph_font_scale', size);
}
function initFontSize() {
const savedSize = localStorage.getItem('moph_font_scale') || 'md';
setFontSize(savedSize);
}
async function loadNewsFromDatabase() {
try {
const response = await fetch('api/get_news.php');
const contentType = response.headers.get("content-type");
if (contentType && contentType.indexOf("application/json") !== -1) {
const result = await response.json();
if (result.status === 'success' && Array.isArray(result.data)) {
newsList = result.data;
if (Array.isArray(result.agencies) && result.agencies.length > 0) {
AGENCIES = result.agencies;
}
if (Array.isArray(result.categories) && result.categories.length > 0) {
CATEGORIES = [
{ id: 'ALL', name: 'หมวดหมู่ทั้งหมด' },
...result.categories
];
}
if (Array.isArray(result.templates)) {
TEMPLATES = result.templates;
}
if (result.settings) {
systemSettings = { ...systemSettings, ...result.settings };
applySystemSettingsUI();
}
} else {
console.error('Failed to load news:', result.message);
showToast('ฐานข้อมูลตอบกลับผิดพลาด: ' + (result.message || 'ไม่ทราบสาเหตุ'), 'error');
}
} else {
const text = await response.text();
console.error('Non-JSON response:', text);
showToast('เซิร์ฟเวอร์ตอบกลับผิดพลาด (ตรวจสอบ Error ใน Console)', 'error');
}
} catch (err) {
console.error('API Error:', err);
showToast('ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์หรือ API ได้', 'error');
}
renderAgencyFilters();
renderCategoryPills();
populateFormDropdowns();
populateLoginDropdowns();
renderBreakingNewsTicker();
renderPinnedList();
renderNews();
}
function applySystemSettingsUI() {
// Apply setting hospital_logo_url
const logoImgs = document.querySelectorAll('.logo-container img');
logoImgs.forEach(img => {
if (systemSettings.hospital_logo_url) {
img.src = systemSettings.hospital_logo_url;
}
});
const headerHosp = document.getElementById('headerHospitalName');
const headerTitle = document.getElementById('headerSystemTitle');
if (headerHosp && systemSettings.hospital_name) {
headerHosp.textContent = systemSettings.hospital_name;
}
if (headerTitle && systemSettings.system_title) {
headerTitle.textContent = systemSettings.system_title;
}
// Populate settings modal inputs
const inputHosp = document.getElementById('settingHospitalName');
const inputTitle = document.getElementById('settingSystemTitle');
const inputLogo = document.getElementById('settingLogoUrl');
const inputTicker = document.getElementById('settingTickerText');
const inputGas = document.getElementById('settingGasUrl');
const inputFooter = document.getElementById('settingFooterText');
if (inputHosp) inputHosp.value = systemSettings.hospital_name || '';
if (inputTitle) inputTitle.value = systemSettings.system_title || '';
if (inputLogo) inputLogo.value = systemSettings.hospital_logo_url || '';
if (inputTicker) inputTicker.value = systemSettings.ticker_text || '';
if (inputGas) inputGas.value = systemSettings.gdrive_gas_url || '';
if (inputFooter) inputFooter.value = systemSettings.footer_text || '';
// Apply footer text dynamically
const footerYear = document.getElementById('footerYear');
if (footerYear) {
footerYear.textContent = new Date().getFullYear();
}
const footerHospital = document.getElementById('footerHospitalName');
if (footerHospital && systemSettings.hospital_name) {
footerHospital.textContent = systemSettings.hospital_name;
}
const footerContainer = document.getElementById('footerTextContainer');
if (footerContainer && systemSettings.footer_text) {
const lines = systemSettings.footer_text.split('\n');
let footerHtml = '';
lines.forEach((line, index) => {
if (index === 0) {
footerHtml += `<p>${line}</p>`;
} else {
footerHtml += `<p class="text-slate-600 text-[11px]">${line}</p>`;
}
});
footerContainer.innerHTML = footerHtml;
}
}
function renderAuthUI() {
const authContainer = document.getElementById('userAuthContainer');
if (!authContainer) return;
if (currentUser) {
const isSuperAdmin = currentUser.agencyId === 'SUPER_ADMIN';
const agencyName = isSuperAdmin
? 'ผู้ดูแลระบบ'
: (AGENCIES.find(a => a.id === currentUser.agencyId)?.code || currentUser.code || currentUser.name || 'เจ้าหน้าที่');
let managementDropdown = `
<div class="relative group z-50">
<button class="px-3 sm:px-4 py-2 sm:py-2.5 rounded-xl bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-400 hover:to-teal-500 text-white text-xs sm:text-sm font-bold transition-all shadow-lg hover:shadow-emerald-500/30 flex items-center gap-1.5 sm:gap-2 transform active:scale-95 shrink-0" title="เครื่องมือจัดการ">
<i class="fa-solid fa-bars text-xs sm:text-sm"></i>
<span class="hidden sm:inline">เครื่องมือจัดการ</span>
<i class="fa-solid fa-chevron-down text-[10px] opacity-70"></i>
</button>
<div class="absolute right-0 top-full pt-1 w-56 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-200 z-[100] origin-top-right">
<div class="bg-white rounded-xl shadow-xl border border-slate-200 py-2 transform scale-95 transition-transform group-hover:scale-100">
${isSuperAdmin ? `
<a onclick="openSuperAdminModal()" class="flex items-center gap-3 px-4 py-2.5 text-sm text-slate-700 hover:bg-emerald-50 hover:text-emerald-700 cursor-pointer border-b border-slate-100">
<div class="w-8 h-8 rounded-lg bg-emerald-100 flex items-center justify-center text-emerald-600"><i class="fa-solid fa-gears"></i></div>
<div><div class="font-bold">ตั้งค่าระบบ</div><div class="text-[10px] text-slate-500 font-normal">จัดการหน่วยงานและตั้งค่า</div></div>
</a>
` : ''}
<a onclick="openCategoryManageModal()" class="flex items-center gap-3 px-4 py-2.5 text-sm text-slate-700 hover:bg-blue-50 hover:text-blue-700 cursor-pointer">
<div class="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center text-blue-600"><i class="fa-solid fa-tags"></i></div>
<div><div class="font-bold">จัดการหมวดหมู่</div><div class="text-[10px] text-slate-500 font-normal">เพิ่ม/แก้ไขหมวดหมู่ข่าว</div></div>
</a>
</div>
</div>
</div>
`;
authContainer.innerHTML = `
${managementDropdown}
<button onclick="openNewsModal()" class="px-3 sm:px-4 py-2 sm:py-2.5 rounded-xl bg-gradient-to-r from-amber-400 to-amber-500 hover:from-amber-300 hover:to-amber-400 text-slate-900 text-xs sm:text-sm font-bold transition-all shadow-lg hover:shadow-amber-500/20 flex items-center gap-1.5 sm:gap-2 transform active:scale-95 border border-amber-300/50 shrink-0" title="สร้างหนังสือเวียนใหม่">
<i class="fa-solid fa-plus text-xs sm:text-sm"></i>
<span>เพิ่มข่าวใหม่</span>
</button>
<div class="flex items-center gap-1.5 sm:gap-2 bg-white/10 text-white px-3 sm:px-4 py-2 sm:py-2.5 rounded-xl border border-white/20 backdrop-blur-md shadow-sm text-xs sm:text-sm font-bold shrink-0">
<i class="fa-solid fa-user-check text-amber-300"></i>
<span class="max-w-[100px] sm:max-w-[160px] truncate">${agencyName}</span>
<button onclick="logout()" class="ml-0.5 pl-1.5 sm:pl-2 border-l border-white/30 text-emerald-100 hover:text-amber-300 transition-colors" title="ออกจากระบบ">
<i class="fa-solid fa-right-from-bracket"></i>
</button>
</div>
`;
} else {
authContainer.innerHTML = `
<button onclick="openLoginModal()" class="px-3 sm:px-4 py-2 sm:py-2.5 rounded-xl bg-white/10 hover:bg-white/20 text-white text-xs sm:text-sm font-bold transition-all flex items-center gap-1.5 sm:gap-2 border border-white/20 backdrop-blur-md shadow-sm hover:border-amber-300/50 shrink-0" title="เข้าสู่ระบบสำหรับเจ้าหน้าที่">
<i class="fa-solid fa-user-lock text-amber-300 text-xs sm:text-sm"></i>
<span class="inline">เข้าสู่ระบบ</span>
</button>
`;
}
}
function populateLoginDropdowns() {
const select = document.getElementById('loginAgencySelect');
if (!select) return;
let options = `<option value="SUPER_ADMIN">🔑 ผู้ดูแลระบบ (Super Admin)</option>`;
AGENCIES.forEach(a => {
options += `<option value="${a.id}">🏢 ${a.name} (${a.code})</option>`;
});
select.innerHTML = options;
}
function openLoginModal() {
document.getElementById('loginPasswordInput').value = '';
document.getElementById('loginModal').classList.remove('hidden');
}
function closeLoginModal() {
document.getElementById('loginModal').classList.add('hidden');
}
function quickLogin(agencyId) {
document.getElementById('loginAgencySelect').value = agencyId;
document.getElementById('loginPasswordInput').value = '1234';
handleLoginSubmit(new Event('submit'));
}
function handleLoginSubmit(e) {
if (e) e.preventDefault();
const agencyId = document.getElementById('loginAgencySelect').value;
const password = document.getElementById('loginPasswordInput').value;
const agencyObj = AGENCIES.find(a => a.id === agencyId);
if (agencyId === 'SUPER_ADMIN') {
if (password !== (systemSettings.super_admin_password || '1234')) {
showToast('รหัสผ่านผู้ดูแลระบบกลางไม่ถูกต้อง', 'error');
return;
}
} else {
if (!agencyObj || password !== (agencyObj.admin_password || '1234')) {
showToast('รหัสผ่านไม่ถูกต้อง', 'error');
return;
}
}
currentUser = {
agencyId,
name: agencyId === 'SUPER_ADMIN' ? 'ผู้ดูแลระบบ' : agencyObj?.name,
code: agencyId === 'SUPER_ADMIN' ? 'ผู้ดูแลระบบ' : agencyObj?.code,
role: agencyId === 'SUPER_ADMIN' ? 'SUPER_ADMIN' : 'AGENCY_ADMIN'
};
localStorage.setItem('moph_user_session', JSON.stringify(currentUser));
showToast(`เข้าสู่ระบบสำเร็จ: ${currentUser.name}`);
// Log Login Action
fetch('api/log_auth.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: currentUser.agencyId, user_name: currentUser.name || currentUser.code, action: 'LOGIN', details: '' })
});
closeLoginModal();
renderAuthUI();
renderNews();
}
function logout() {
showConfirmDialog(
'ออกจากระบบ',
'คุณต้องการออกจากระบบสิทธิ์เจ้าหน้าที่ใช่หรือไม่?',
() => {
// Log Logout Action before clearing user
if (currentUser) {
fetch('api/log_auth.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: currentUser.agencyId, user_name: currentUser.name || currentUser.code, action: 'LOGOUT', details: '' })
});
}
currentUser = null;
localStorage.removeItem('moph_user_session');
showToast('ออกจากระบบเรียบร้อยแล้ว');
renderAuthUI();
renderNews();
}
);
}
function renderCurrentDate() {
const now = new Date();
const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' };
document.getElementById('currentDateDisplay').textContent = now.toLocaleDateString('th-TH', options);
}
function showToast(message, type = 'success') {
const container = document.getElementById('toastContainer');
if (!container) return;
const toast = document.createElement('div');
const bgColor = type === 'error' ? 'bg-red-600' : type === 'warning' ? 'bg-amber-600' : 'bg-[#056839]';
const icon = type === 'error' ? 'fa-circle-xmark' : type === 'warning' ? 'fa-triangle-exclamation' : 'fa-circle-check';
toast.className = `pointer-events-auto flex items-center gap-2.5 ${bgColor} text-white px-4 py-2.5 rounded-xl shadow-xl transform transition-all duration-300 translate-y-2 opacity-0 text-xs font-medium border border-white/20`;
toast.innerHTML = `<i class="fa-solid ${icon} text-sm"></i><span>${message}</span>`;
container.appendChild(toast);
setTimeout(() => {
toast.classList.remove('translate-y-2', 'opacity-0');
}, 10);
setTimeout(() => {
toast.classList.add('opacity-0', 'translate-y-2');
setTimeout(() => toast.remove(), 300);
}, 3500);
}
function showConfirmDialog(title, message, onConfirm) {
document.getElementById('confirmModalTitle').textContent = title;
document.getElementById('confirmModalMessage').textContent = message;
confirmCallback = onConfirm;
document.getElementById('confirmModal').classList.remove('hidden');
}
function closeConfirmModal(result) {
document.getElementById('confirmModal').classList.add('hidden');
if (result && typeof confirmCallback === 'function') {
confirmCallback();
}
confirmCallback = null;
}
function renderAgencyFilters() {
const select = document.getElementById('agencySelectFilter');
if (!select) return;
let options = `<option value="ALL">ทุกกลุ่มงาน / ฝ่าย (${newsList.length} เรื่อง)</option>`;
AGENCIES.forEach(agency => {
const count = newsList.filter(n => n.agencyId === agency.id).length;
options += `<option value="${agency.id}" ${selectedAgency === agency.id ? 'selected' : ''}>[${agency.code}] ${agency.name} (${count})</option>`;
});
select.innerHTML = options;
}
function renderCategoryPills() {
const select = document.getElementById('categorySelectFilter');
if (!select) return;
let options = '';
// Filter categories based on selected agency
let displayCats = CATEGORIES;
if (selectedAgency && selectedAgency !== 'ALL') {
displayCats = CATEGORIES.filter(cat => cat.id === 'ALL' || cat.agency_id === selectedAgency || cat.agency_id === 'SUPER_ADMIN');
}
displayCats.forEach(cat => {
const count = cat.id === 'ALL' ? newsList.length : newsList.filter(n => n.categoryId === cat.id).length;
options += `<option value="${cat.id}" ${selectedCategory === cat.id ? 'selected' : ''}>${cat.name} (${count})</option>`;
});
select.innerHTML = options;
}
function handleAgencyChange(val) {
selectedAgency = val;
currentPage = 1;
renderCategoryPills(); // Re-render categories based on new agency
renderNews();
}
function handleCategoryChange(val) {
selectedCategory = val;
currentPage = 1;
renderNews();
}
function handleYearChange(val) {
selectedYear = val;
currentPage = 1;
renderNews();
}
function handleSearch() {
searchQuery = document.getElementById('searchInput').value;
currentPage = 1;
renderNews();
}
function resetFilters() {
selectedAgency = 'ALL';
selectedCategory = 'ALL';
searchQuery = '';
currentPage = 1;
const searchInput = document.getElementById('searchInput');
if (searchInput) searchInput.value = '';
renderAgencyFilters();
renderCategoryPills();
renderNews();
}
function setViewMode(mode) {
currentView = mode;
const gridBtn = document.getElementById('viewGridBtn');
const listBtn = document.getElementById('viewListBtn');
if (mode === 'grid') {
gridBtn.className = "px-3 py-1 rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 shadow-sm bg-white text-[#056839]";
listBtn.className = "px-3 py-1 rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 text-slate-500 hover:text-slate-800";
} else {
listBtn.className = "px-3 py-1 rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 shadow-sm bg-white text-[#056839]";
gridBtn.className = "px-3 py-1 rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 text-slate-500 hover:text-slate-800";
}
renderNews();
}
function renderNews() {
const newsGrid = document.getElementById('newsGrid');
const emptyState = document.getElementById('emptyState');
const paginationContainer = document.getElementById('paginationContainer');
if (!newsGrid) return;
// Apply Filters & Search
let filtered = newsList.filter(item => {
// Filter out hidden items if not authorized
if (item.isHidden) {
if (!currentUser || (currentUser.agencyId !== 'SUPER_ADMIN' && currentUser.agencyId !== item.agencyId)) {
return false;
}
}
const matchesCategory = (selectedCategory === 'ALL' || item.categoryId === selectedCategory);
const matchesAgency = (selectedAgency === 'ALL' || item.agencyId === selectedAgency);
let matchesYear = true;
if (selectedYear !== 'ALL') {
// Extract year from item.date (YYYY-MM-DD)
const itemYearStr = item.date ? item.date.substring(0, 4) : '';
if (itemYearStr) {
const itemThaiYear = parseInt(itemYearStr) + 543;
matchesYear = (itemThaiYear.toString() === selectedYear);
}
}
const query = searchQuery.toLowerCase().trim();
const matchesSearch = !query ||
item.title.toLowerCase().includes(query) ||
item.docNum.toLowerCase().includes(query) ||
item.content.toLowerCase().includes(query);
return matchesCategory && matchesAgency && matchesYear && matchesSearch;
});
// Always Sort by Newest First
filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
// Update Counters
document.getElementById('totalNewsCount').textContent = filtered.length;
const activeAgency = AGENCIES.find(a => a.id === selectedAgency);
document.getElementById('activeFilterLabel').textContent = activeAgency ? `กลุ่มงาน: ${activeAgency.name}` : 'ทุกกลุ่มงาน';
if (filtered.length === 0) {
newsGrid.innerHTML = '';
paginationContainer.innerHTML = '';
emptyState.classList.remove('hidden');
return;
}
emptyState.classList.add('hidden');
// Calculate Pagination
const totalPages = Math.ceil(filtered.length / itemsPerPage);
if (currentPage > totalPages) currentPage = totalPages || 1;
const startIndex = (currentPage - 1) * itemsPerPage;
const paginatedItems = filtered.slice(startIndex, startIndex + itemsPerPage);
if (currentView === 'grid') {
renderGridView(paginatedItems, newsGrid);
} else {
renderListView(paginatedItems, newsGrid);
}
renderPagination(filtered.length, totalPages);
}
function renderGridView(items, container) {
container.className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5";
let html = '';
items.forEach(item => {
const agency = AGENCIES.find(a => a.id === item.agencyId);
html += `
<div onclick="openNewsDetailModal('${item.id}')" class="bg-white rounded-2xl border border-slate-200/90 shadow-sm hover:shadow-lg transition-all duration-200 p-5 flex flex-col justify-between cursor-pointer group hover:-translate-y-0.5">
<div class="space-y-3">
<div class="flex items-center justify-between gap-2">
<span class="px-2.5 py-0.5 rounded text-[11px] font-bold bg-amber-400 text-slate-900 shadow-sm">${item.docNum}</span>
<div class="flex items-center gap-1.5">
${getStatusIcons(item)}
${getNewsAdminButtonsHTML(item)}
</div>
</div>
<div class="space-y-1">
<span class="text-[10px] font-bold text-[#056839] uppercase tracking-wider block">${agency ? agency.name : ''}</span>
<h3 class="font-bold text-slate-800 text-sm leading-snug group-hover:text-[#056839] transition-colors line-clamp-2">
${item.title}
</h3>
</div>
<p class="text-xs text-slate-600 line-clamp-3 leading-relaxed font-official bg-slate-50/70 p-3 rounded-xl border border-slate-100">
${item.content}
</p>
</div>
<div class="pt-3 mt-4 border-t border-slate-100 flex items-center justify-between text-xs text-slate-400">
<div class="flex items-center gap-3">
<span><i class="fa-regular fa-calendar text-[#056839] mr-1"></i>${formatThaiDate(item.date)}</span>
<span><i class="fa-regular fa-eye text-blue-500 mr-1"></i>${item.views}</span>
</div>
<span class="text-slate-600 font-medium flex items-center gap-1">
<i class="fa-solid fa-paperclip text-slate-400"></i> ${item.attachments ? item.attachments.length : 0}
</span>
</div>
</div>
`;
});
container.innerHTML = html;
}
function getNewsAdminButtonsHTML(item) {
if (!currentUser) return '';
const isOwner = currentUser.agencyId === item.agencyId;
const isSuperAdmin = currentUser.agencyId === 'SUPER_ADMIN';
if (isOwner || isSuperAdmin) {
const hideIcon = item.isHidden ? 'fa-eye-slash text-red-500' : 'fa-eye text-slate-400';
const hideTitle = item.isHidden ? 'เลิกซ่อนข่าวนี้' : 'ซ่อนข่าวนี้';
const hiddenBadge = item.isHidden ? `<span class="px-2 py-0.5 rounded text-[10px] font-bold bg-red-100 text-red-600 border border-red-200">ถูกซ่อน</span>` : '';
return `
<div class="flex items-center gap-1 shrink-0 ml-2" onclick="event.stopPropagation();">
${hiddenBadge}
<button onclick="toggleHideNews('${item.id}')" class="w-7 h-7 rounded-lg hover:bg-slate-100 flex items-center justify-center transition-colors" title="${hideTitle}">
<i class="fa-solid ${hideIcon} text-xs"></i>
</button>
<button onclick="openNewsModal('${item.id}')" class="w-7 h-7 rounded-lg hover:bg-blue-50 text-blue-500 flex items-center justify-center transition-colors" title="แก้ไข">
<i class="fa-solid fa-pen text-xs"></i>
</button>
<button onclick="deleteNews('${item.id}')" class="w-7 h-7 rounded-lg hover:bg-red-50 text-red-500 flex items-center justify-center transition-colors" title="ลบ">
<i class="fa-solid fa-trash text-xs"></i>
</button>
</div>
`;
}
return '';
}
function renderListView(items, container) {
container.className = "flex flex-col space-y-2.5";
let html = `
<div class="hidden sm:grid grid-cols-12 px-4 py-2 text-[11px] font-bold text-slate-400 uppercase tracking-wider border-b border-slate-200">
<span class="col-span-2">เลขที่หนังสือ</span>
<span class="col-span-5">เรื่อง / รายละเอียด</span>
<span class="col-span-3">กลุ่มงานผู้รับผิดชอบ</span>
<span class="col-span-2 text-right">วันที่ประกาศ</span>
</div>
`;
items.forEach(item => {
const agency = AGENCIES.find(a => a.id === item.agencyId);
html += `
<div onclick="openNewsDetailModal('${item.id}')" class="bg-white rounded-xl border border-slate-200 p-3.5 sm:p-4 hover:border-[#056839] hover:shadow-md transition-all cursor-pointer group flex flex-col sm:grid sm:grid-cols-12 gap-2 sm:gap-4 items-start sm:items-center">
<div class="sm:col-span-2 flex items-center justify-between sm:justify-start gap-2 w-full">
<span class="px-2 py-0.5 rounded text-[11px] font-bold bg-amber-400 text-slate-900 shrink-0">${item.docNum}</span>
<div class="flex items-center gap-1 sm:hidden">
${getStatusIcons(item)}
</div>
</div>
<div class="sm:col-span-5 space-y-0.5 w-full">
<div class="flex items-center gap-2">
<h3 class="font-bold text-xs sm:text-sm text-slate-800 group-hover:text-[#056839] transition-colors line-clamp-1">
${item.title}
</h3>
<div class="hidden sm:flex items-center gap-1">
${getStatusIcons(item)}
${getNewsAdminButtonsHTML(item)}
</div>
</div>
<p class="text-[11px] text-slate-500 line-clamp-1 font-official">${item.content}</p>
</div>
<div class="sm:col-span-3 text-xs text-[#056839] font-medium truncate w-full">
<i class="fa-solid fa-sitemap sm:hidden mr-1"></i>${agency ? agency.name : ''}
</div>
<div class="sm:col-span-2 flex items-center justify-between sm:justify-end text-xs text-slate-400 w-full gap-3">
<span><i class="fa-regular fa-calendar text-[#056839] mr-1"></i>${formatThaiDate(item.date)}</span>
<span class="text-slate-600 font-medium shrink-0"><i class="fa-solid fa-paperclip text-slate-400"></i> ${item.attachments ? item.attachments.length : 0}</span>
</div>
</div>
`;
});
container.innerHTML = html;
}
function renderPagination(totalItems, totalPages) {
const container = document.getElementById('paginationContainer');
if (!container || totalPages <= 1) {
container.innerHTML = '';
return;
}
let html = `
<button onclick="changePage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''} class="w-8 h-8 rounded-xl bg-white border border-slate-200 hover:border-[#056839] text-slate-600 disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-center text-xs transition-colors shadow-sm">
<i class="fa-solid fa-chevron-left"></i>
</button>
`;
for (let i = 1; i <= totalPages; i++) {
if (i === currentPage) {
html += `<button class="w-8 h-8 rounded-xl bg-[#056839] text-white text-xs font-bold shadow-md">${i}</button>`;
} else {
html += `<button onclick="changePage(${i})" class="w-8 h-8 rounded-xl bg-white border border-slate-200 hover:border-[#056839] text-slate-700 text-xs font-medium transition-colors shadow-sm">${i}</button>`;
}
}
html += `
<button onclick="changePage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''} class="w-8 h-8 rounded-xl bg-white border border-slate-200 hover:border-[#056839] text-slate-600 disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-center text-xs transition-colors shadow-sm">
<i class="fa-solid fa-chevron-right"></i>
</button>
`;
container.innerHTML = html;
}
function changePage(page) {
currentPage = page;
renderNews();
const section = document.getElementById('newsFeedSection');
if (section) section.scrollIntoView({ behavior: 'smooth' });
}
function renderBreakingNewsTicker() {
const tickerText = document.getElementById('tickerText');
if (!tickerText) return;
const urgentItems = newsList.filter(n => {
if (!n.isUrgent) return false;
if (n.isHidden) {
if (!currentUser || (currentUser.agencyId !== 'SUPER_ADMIN' && currentUser.agencyId !== n.agencyId)) {
return false;
}
}
return true;
});
if (urgentItems.length === 0) {
tickerText.innerHTML = 'ไม่มีประกาศด่วนที่สุดในขณะนี้ — ระบบข่าวสารและหนังสือเวียนภายในองค์กร โรงพยาบาลเปิดให้บริการตามปกติ';
return;
}
const tickerHtml = urgentItems.map(item => {
const agency = AGENCIES.find(a => a.id === item.agencyId);
return `<span class="inline-flex items-center gap-2 cursor-pointer hover:opacity-80 transition-opacity mx-6" onclick="openNewsDetailModal('${item.id}')">
<span class="bg-red-600 text-white px-1.5 py-0.5 rounded text-[10px] font-bold">${item.docNum}</span>
<strong class="text-amber-300">${item.title}</strong>
<span class="text-emerald-200 font-normal">(${agency ? agency.name : ''})</span>
</span>`;
}).join(' <span class="text-emerald-600/60">|</span> ');
tickerText.innerHTML = tickerHtml;
}
function renderPinnedList() {
const container = document.getElementById('pinnedNewsContainer');
if (!container) return;
const pinnedItems = newsList.filter(n => {
if (!n.isPinned) return false;
if (n.isHidden) {
if (!currentUser || (currentUser.agencyId !== 'SUPER_ADMIN' && currentUser.agencyId !== n.agencyId)) {
return false;
}
}
return true;
}).slice(0, 3);
if (pinnedItems.length === 0) {
document.getElementById('heroSection').style.display = 'none';
return;
}
document.getElementById('heroSection').style.display = 'block';
let html = '';
pinnedItems.forEach(item => {
const agency = AGENCIES.find(a => a.id === item.agencyId);
html += `
<div onclick="openNewsDetailModal('${item.id}')" class="bg-gradient-to-br from-emerald-50/90 to-white border border-emerald-200/80 p-4 rounded-2xl shadow-sm hover:shadow-md transition-all cursor-pointer flex flex-col justify-between group relative">
<div class="space-y-2">
<div class="flex items-center justify-between gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold bg-[#056839] text-white">${item.docNum}</span>
<div class="flex items-center gap-1">
${getStatusIcons(item)}
${getNewsAdminButtonsHTML(item)}
</div>
</div>
<h3 class="font-bold text-slate-800 text-xs sm:text-sm line-clamp-2 group-hover:text-[#056839] transition-colors leading-snug">
${item.title}
</h3>
<p class="text-[11px] text-slate-600 line-clamp-2 font-official leading-relaxed">
${item.content}
</p>
</div>
<div class="pt-3 mt-3 border-t border-emerald-100 flex items-center justify-between text-[11px] text-slate-500">
<span class="font-medium text-[#056839] truncate">${agency ? agency.name : ''}</span>
<span class="shrink-0"><i class="fa-solid fa-paperclip text-slate-400 mr-1"></i>${item.attachments ? item.attachments.length : 0}</span>
</div>
</div>
`;
});
container.innerHTML = html;
}
function getStatusIcons(item) {
let icons = '';
if (item.isPinned) {
icons += `<span class="w-6 h-6 rounded-full bg-amber-400 text-slate-900 flex items-center justify-center text-xs shadow-md ring-2 ring-white/40" title="ปักหมุดสำคัญ"><i class="fa-solid fa-thumbtack text-[11px] transform -rotate-45"></i></span>`;
}
if (item.isUrgent) {
icons += `<span class="w-6 h-6 rounded-full bg-red-600 text-amber-300 flex items-center justify-center text-xs shadow-md ring-2 ring-white/40 animate-pulse" title="ด่วนที่สุด"><i class="fa-solid fa-bolt text-[11px]"></i></span>`;
}
return icons;
}
function formatThaiDate(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr);
return d.toLocaleDateString('th-TH', { day: 'numeric', month: 'short', year: '2-digit' });
}
async function openNewsDetailModal(id) {
const item = newsList.find(n => n.id === id);
if (!item) return;
currentDetailItem = item;
item.views = (item.views || 0) + 1;
// Increment view count in database
fetch(`api/get_news.php?view_id=${encodeURIComponent(id)}`).catch(e => console.error(e));
const agency = AGENCIES.find(a => a.id === item.agencyId);
const category = CATEGORIES.find(c => c.id === item.categoryId);
document.getElementById('detailAgencyBadge').textContent = agency ? agency.name : '';
document.getElementById('detailCategoryBadge').textContent = category ? category.name : '';
document.getElementById('detailDocNum').textContent = item.docNum;
document.getElementById('detailTitle').textContent = item.title;
document.getElementById('detailDate').textContent = formatThaiDate(item.date);
document.getElementById('detailViews').textContent = item.views;
document.getElementById('detailContent').textContent = item.content;
document.getElementById('detailBadges').innerHTML = getStatusIcons(item);
// Render Attachments
const attachmentsList = document.getElementById('detailAttachmentsList');
document.getElementById('detailAttachmentCount').textContent = item.attachments ? item.attachments.length : 0;
if (item.attachments && item.attachments.length > 0) {
attachmentsList.innerHTML = item.attachments.map(file => {
const downloadClick = file.url ? `window.open('${file.url}', '_blank')` : `downloadAttachment('${file.name}')`;
return `
<div class="flex items-center justify-between p-3 bg-white rounded-xl border border-slate-200 hover:border-[#056839] transition-all group">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-emerald-100 text-[#056839] flex items-center justify-center text-sm font-bold">
<i class="fa-solid ${getFileIcon(file.type)}"></i>
</div>
<div>
<h4 class="font-bold text-xs text-slate-800 group-hover:text-[#056839] transition-colors">${file.name}</h4>
<span class="text-[10px] text-slate-400">${file.size || '1.2 MB'}</span>
</div>
</div>
<button onclick="${downloadClick}" class="px-3 py-1.5 rounded-lg bg-[#056839] hover:bg-[#077b44] text-white text-xs font-medium transition-colors flex items-center gap-1 shadow-sm">
<i class="fa-solid fa-download"></i> ดาวน์โหลด/เปิด
</button>
</div>
`}).join('');
} else {
attachmentsList.innerHTML = '<p class="text-xs text-slate-400 italic">ไม่มีไฟล์เอกสารแนบ</p>';
}
// Check permissions for Edit & Delete
const canManage = currentUser && (currentUser.role === 'SUPER_ADMIN' || currentUser.agencyId === item.agencyId);
const editBtn = document.getElementById('editNewsBtn');
const deleteBtn = document.getElementById('deleteNewsBtn');
if (canManage) {
editBtn.style.display = 'inline-flex';
deleteBtn.style.display = 'inline-flex';
editBtn.onclick = () => openNewsModal(item.id);
deleteBtn.onclick = () => deleteNews(item.id);
} else {
editBtn.style.display = 'none';
deleteBtn.style.display = 'none';
}
document.getElementById('newsDetailModal').classList.remove('hidden');
}
function closeNewsDetailModal() {
document.getElementById('newsDetailModal').classList.add('hidden');
currentDetailItem = null;
renderNews();
}
function getFileIcon(type) {
if (type === 'pdf') return 'fa-file-pdf text-red-600';
if (type === 'word' || type === 'doc' || type === 'docx') return 'fa-file-word text-blue-600';
if (type === 'excel' || type === 'xls' || type === 'xlsx') return 'fa-file-excel text-emerald-600';
if (type === 'image' || type === 'jpg' || type === 'png') return 'fa-file-image text-amber-500';
return 'fa-file-lines text-slate-600';
}
function populateFormDropdowns() {
const agencySelect = document.getElementById('newsAgencyInput');
const catSelect = document.getElementById('newsCategoryInput');
if (agencySelect) {
agencySelect.innerHTML = AGENCIES.map(a => `<option value="${a.id}">${a.name} (${a.code})</option>`).join('');
// Add change listener to update categories when agency changes (for SUPER_ADMIN)
agencySelect.removeEventListener('change', updateFormCategoryDropdown);
agencySelect.addEventListener('change', updateFormCategoryDropdown);
}
if (catSelect) {
updateFormCategoryDropdown();
}
}
function updateFormCategoryDropdown() {
const agencySelect = document.getElementById('newsAgencyInput');
const catSelect = document.getElementById('newsCategoryInput');
if (!catSelect) return;
const selectedAgencyId = agencySelect ? agencySelect.value : (currentUser ? currentUser.agencyId : null);
let filteredCats = CATEGORIES.filter(c => c.id !== 'ALL');
if (selectedAgencyId) {
filteredCats = filteredCats.filter(c => c.agency_id === selectedAgencyId || c.agency_id === 'SUPER_ADMIN');
}
const currentVal = catSelect.value;
catSelect.innerHTML = filteredCats.map(c => `<option value="${c.id}">${c.name}</option>`).join('');
// Try to keep selection if it still exists
if (currentVal && filteredCats.find(c => c.id === currentVal)) {
catSelect.value = currentVal;
}
}
// --- Category Management Functions ---
function openCategoryManageModal() {
document.getElementById('categoryManageModal').classList.remove('hidden');
renderAdminCategoriesTable();
resetCategoryForm();
}
function closeCategoryManageModal() {
document.getElementById('categoryManageModal').classList.add('hidden');
}
// --- Template Management Functions ---
function openTemplateManageModal() {
if (!currentUser) return;
document.getElementById('templateManageModal').classList.remove('hidden');
renderAdminTemplatesTable();
// Populate category select
const tplCat = document.getElementById('tplCategoryInput');
tplCat.innerHTML = CATEGORIES.filter(c => c.id !== 'ALL').map(c => `<option value="${c.id}">${c.name}</option>`).join('');
}
function closeTemplateManageModal() {
document.getElementById('templateManageModal').classList.add('hidden');
resetTemplateForm();
}
function renderAdminTemplatesTable() {
const tbody = document.getElementById('adminTemplatesTableBody');
if (!tbody) return;
const isSuperAdmin = currentUser.agencyId === 'SUPER_ADMIN';
const templatesToShow = TEMPLATES.filter(t => isSuperAdmin || t.agency_id === currentUser.agencyId || t.agency_id === 'SUPER_ADMIN');
if (templatesToShow.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="py-4 text-center text-xs text-slate-500">ไม่มีข้อมูลแม่แบบ</td></tr>';
return;
}
let html = '';
templatesToShow.forEach(t => {
const canEdit = isSuperAdmin || t.agency_id === currentUser.agencyId;
let actions = '';
if (canEdit) {
actions = `
<button onclick="editTemplate('${t.id}')" class="text-xs bg-amber-100 text-amber-600 px-2 py-1 rounded hover:bg-amber-200 mr-1" title="แก้ไข">
<i class="fa-solid fa-pen"></i>
</button>
<button onclick="deleteTemplate('${t.id}')" class="text-xs bg-red-100 text-red-600 px-2 py-1 rounded hover:bg-red-200" title="ลบ">
<i class="fa-solid fa-trash"></i>
</button>
`;
} else {
actions = `<span class="text-[10px] text-slate-400">ส่วนกลาง</span>`;
}
html += `
<tr class="border-b border-slate-100 hover:bg-slate-50">
<td class="py-2.5 px-4 text-xs font-bold text-slate-800">${t.name}</td>
<td class="py-2.5 px-4 text-[10px] text-slate-500">
<strong>หมวด:</strong> ${t.category_id} <br>
<strong>รูปแบบเลข:</strong> ${t.doc_num_format}
</td>
<td class="py-2.5 px-4 text-right whitespace-nowrap">
${actions}
</td>
</tr>
`;
});
tbody.innerHTML = html;
}
function editTemplate(id) {
const t = TEMPLATES.find(x => x.id === id);
if (!t) return;
document.getElementById('templateIdInput').value = t.id;
document.getElementById('tplNameInput').value = t.name;
document.getElementById('tplCategoryInput').value = t.category_id;
document.getElementById('tplDocNumInput').value = t.doc_num_format;
document.getElementById('tplTitleInput').value = t.title;
document.getElementById('tplContentInput').value = t.content;
document.getElementById('templateFormTitle').innerHTML = '<i class="fa-solid fa-pen text-amber-600"></i> แก้ไขแม่แบบ';
document.getElementById('templateCancelEditBtn').classList.remove('hidden');
}
function resetTemplateForm() {
document.getElementById('templateForm').reset();
document.getElementById('templateIdInput').value = '';
document.getElementById('templateFormTitle').innerHTML = '<i class="fa-solid fa-plus-circle text-amber-600"></i> เพิ่มแม่แบบใหม่';
document.getElementById('templateCancelEditBtn').classList.add('hidden');
}
async function saveTemplateSubmit(e) {
e.preventDefault();
const id = document.getElementById('templateIdInput').value;
const name = document.getElementById('tplNameInput').value.trim();
const categoryId = document.getElementById('tplCategoryInput').value;
const docNumFormat = document.getElementById('tplDocNumInput').value.trim();
const title = document.getElementById('tplTitleInput').value.trim();
const content = document.getElementById('tplContentInput').value.trim();
if (!name || !categoryId || !docNumFormat || !title || !content) {
showToast('กรุณากรอกข้อมูลให้ครบถ้วน', 'warning');
return;
}
try {
const response = await fetch('api/template_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: id ? 'edit_template' : 'add_template',
id, name, categoryId, docNumFormat, title, content,
agencyId: currentUser.agencyId
})
});
const res = await response.json();
if (res.status === 'success') {
showToast(res.message, 'success');
resetTemplateForm();
await loadNewsFromDatabase(); // Reload templates
renderAdminTemplatesTable();
renderQuickTemplates(); // Refresh buttons in add form
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการบันทึกแม่แบบ', 'error');
}
}
function deleteTemplate(id) {
showConfirmDialog('ลบแม่แบบด่วน', 'ยืนยันการลบแม่แบบด่วนนี้หรือไม่?', async () => {
try {
const response = await fetch('api/template_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete_template', id, agencyId: currentUser.agencyId })
});
const res = await response.json();
if (res.status === 'success') {
showToast(res.message, 'success');
await loadNewsFromDatabase();
renderAdminTemplatesTable();
renderQuickTemplates();
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการลบ', 'error');
}
});
}
function renderAdminCategoriesTable() {
const tbody = document.getElementById('adminCategoriesTableBody');
if (!tbody) return;
let html = '';
// Filter categories based on user role
let displayCats = CATEGORIES.filter(c => c.id !== 'ALL');
if (currentUser.agencyId !== 'SUPER_ADMIN') {
displayCats = displayCats.filter(c => c.agency_id === currentUser.agencyId);
}
if (currentUser.agencyId === 'SUPER_ADMIN') {
// Group by agency for SUPER_ADMIN
const grouped = {};
displayCats.forEach(c => {
const agId = c.agency_id || 'SUPER_ADMIN';
if (!grouped[agId]) grouped[agId] = [];
grouped[agId].push(c);
});
for (const agId in grouped) {
const agName = AGENCIES.find(a => a.id === agId)?.name || 'ผู้ดูแลระบบสูงสุด (SUPER_ADMIN)';
html += `
<tr class="bg-slate-100/80 border-b border-slate-200">
<td colspan="3" class="py-2 px-4 text-xs font-bold text-slate-700">
<i class="fa-solid fa-building text-blue-500 mr-1.5"></i> ${agName}
</td>
</tr>
`;
grouped[agId].forEach(c => {
html += `
<tr class="border-b border-slate-100 hover:bg-slate-50">
<td class="py-2.5 px-4 pl-8 text-xs text-slate-800 font-bold">${c.id}</td>
<td class="py-2.5 px-4 text-xs text-slate-700">${c.name}</td>
<td class="py-2.5 px-4 text-right">
<button onclick="editCategory('${c.id}', '${c.name}')" class="text-xs bg-blue-100 text-blue-600 px-2 py-1 rounded hover:bg-blue-200 mr-1" title="แก้ไข">
<i class="fa-solid fa-pen"></i>
</button>
<button onclick="deleteCategory('${c.id}')" class="text-xs bg-red-100 text-red-600 px-2 py-1 rounded hover:bg-red-200" title="ลบ">
<i class="fa-solid fa-trash"></i>
</button>
</td>
</tr>
`;
});
}
} else {
// Standard list for normal agencies
displayCats.forEach(c => {
html += `
<tr class="border-b border-slate-100 hover:bg-slate-50">
<td class="py-2.5 px-4 text-xs text-slate-800 font-bold">${c.id}</td>
<td class="py-2.5 px-4 text-xs text-slate-700">${c.name}</td>
<td class="py-2.5 px-4 text-right">
<button onclick="editCategory('${c.id}', '${c.name}')" class="text-xs bg-blue-100 text-blue-600 px-2 py-1 rounded hover:bg-blue-200 mr-1" title="แก้ไข">
<i class="fa-solid fa-pen"></i>
</button>
<button onclick="deleteCategory('${c.id}')" class="text-xs bg-red-100 text-red-600 px-2 py-1 rounded hover:bg-red-200" title="ลบ">
<i class="fa-solid fa-trash"></i>
</button>
</td>
</tr>
`;
});
}
if (html === '') {
html = '<tr><td colspan="3" class="py-4 text-center text-xs text-slate-500">ยังไม่มีหมวดหมู่ของหน่วยงานนี้</td></tr>';
}
tbody.innerHTML = html;
}
function editCategory(id, name) {
document.getElementById('categoryIdInput').value = id;
document.getElementById('categoryIdInput').readOnly = true;
document.getElementById('categoryIdInput').classList.add('bg-slate-100');
document.getElementById('categoryNameInput').value = name;
document.getElementById('categoryIsEdit').value = '1';
document.getElementById('categoryFormTitle').innerHTML = '<i class="fa-solid fa-pen text-blue-600"></i> แก้ไขหมวดหมู่';
document.getElementById('categoryCancelEditBtn').classList.remove('hidden');
}
function resetCategoryForm() {
document.getElementById('categoryForm').reset();
document.getElementById('categoryIdInput').readOnly = false;
document.getElementById('categoryIdInput').classList.remove('bg-slate-100');
document.getElementById('categoryIsEdit').value = '0';
document.getElementById('categoryFormTitle').innerHTML = '<i class="fa-solid fa-plus-circle text-blue-600"></i> เพิ่มหมวดหมู่ใหม่';
document.getElementById('categoryCancelEditBtn').classList.add('hidden');
}
async function saveCategorySubmit(e) {
e.preventDefault();
const id = document.getElementById('categoryIdInput').value.trim().toUpperCase();
const name = document.getElementById('categoryNameInput').value.trim();
const isEdit = document.getElementById('categoryIsEdit').value === '1';
if (!id || !name) {
showToast('กรุณากรอกรหัสและชื่อหมวดหมู่ให้ครบถ้วน', 'warning');
return;
}
try {
const response = await fetch('api/category_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: isEdit ? 'edit_category' : 'add_category',
id,
name,
agencyId: currentUser.agencyId
})
});
const res = await response.json();
if (res.status === 'success') {
showToast(res.message, 'success');
resetCategoryForm();
await loadNewsFromDatabase(); // Reload categories and news
renderAdminCategoriesTable();
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการบันทึกหมวดหมู่', 'error');
}
}
function deleteCategory(id) {
showConfirmDialog('ลบหมวดหมู่', 'ยืนยันการลบหมวดหมู่นี้หรือไม่? (ข่าวในหมวดหมู่นี้อาจไม่แสดงผลหมวดหมู่ที่ถูกต้อง)', async () => {
try {
const response = await fetch('api/category_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete_category', id })
});
const res = await response.json();
if (res.status === 'success') {
showToast(res.message, 'success');
await loadNewsFromDatabase(); // Reload categories and news
renderAdminCategoriesTable();
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการลบหมวดหมู่', 'error');
}
});
}
// --- Super Admin Functions ---
function openSuperAdminModal() {
document.getElementById('superAdminModal').classList.remove('hidden');
switchAdminTab('agencies');
renderAdminAgenciesTable();
renderAgencyPasswordCards();
}
function closeSuperAdminModal() {
document.getElementById('superAdminModal').classList.add('hidden');
}
function switchAdminTab(tabId) {
// Hide all tab contents
document.getElementById('adminTabAgencies').classList.add('hidden');
document.getElementById('adminTabAdmins').classList.add('hidden');
document.getElementById('adminTabSettings').classList.add('hidden');
document.getElementById('adminTabLogs').classList.add('hidden');
// Show the active tab content
const tabIdCapitalized = tabId.charAt(0).toUpperCase() + tabId.slice(1);
document.getElementById('adminTab' + tabIdCapitalized).classList.remove('hidden');
// Reset button styles
const allBtns = [
document.getElementById('adminTabBtnAgencies'),
document.getElementById('adminTabBtnAdmins'),
document.getElementById('adminTabBtnSettings'),
document.getElementById('adminTabBtnLogs')
];
allBtns.forEach(btn => {
if(btn) {
btn.className = "px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all text-slate-600 hover:text-purple-900 flex items-center gap-2";
}
});
// Set active button style
const activeBtn = document.getElementById('adminTabBtn' + tabIdCapitalized);
if (activeBtn) {
activeBtn.className = "px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all border-b-2 border-purple-600 text-purple-900 bg-white shadow-sm flex items-center gap-2 shrink-0";
}
}
async function loadActivityLogs() {
if (!currentUser || currentUser.role !== 'SUPER_ADMIN') return;
if (activityLogsDataTable) {
activityLogsDataTable.destroy();
activityLogsDataTable = null;
}
const tbody = document.getElementById('activityLogsTbody');
tbody.innerHTML = '<tr><td colspan="4" class="px-4 py-8 text-center text-slate-400"><i class="fa-solid fa-circle-notch fa-spin"></i> กำลังโหลดประวัติ...</td></tr>';
const start = document.getElementById('logStartDate').value;
const end = document.getElementById('logEndDate').value;
const search = document.getElementById('logSearchInput').value;
try {
const query = new URLSearchParams({
agency_id: currentUser.agencyId,
start_date: start,
end_date: end,
search: search
});
const response = await fetch(`api/get_logs.php?${query.toString()}`, { method: 'GET' });
const res = await response.json();
if (res.status === 'success') {
if (!res.data || res.data.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="px-4 py-8 text-center text-slate-400">ไม่พบประวัติการทำงานในระบบ</td></tr>';
return;
}
tbody.innerHTML = res.data.map(log => {
const date = new Date(log.created_at).toLocaleString('th-TH');
let actionLabel = log.action;
let actionClass = 'bg-slate-100 text-slate-700';
if (log.action.includes('LOGIN')) { actionLabel = 'เข้าสู่ระบบ'; actionClass = 'bg-blue-100 text-blue-700'; }
else if (log.action.includes('LOGOUT')) { actionLabel = 'ออกจากระบบ'; actionClass = 'bg-slate-100 text-slate-700'; }
else if (log.action.includes('CREATE')) { actionLabel = 'สร้าง'; actionClass = 'bg-emerald-100 text-emerald-700'; }
else if (log.action.includes('UPDATE') || log.action.includes('EDIT') || log.action.includes('SAVE')) { actionLabel = 'แก้ไข/บันทึก'; actionClass = 'bg-amber-100 text-amber-700'; }
else if (log.action.includes('DELETE')) { actionLabel = 'ลบ'; actionClass = 'bg-red-100 text-red-700'; }
return `
<tr class="hover:bg-slate-50 transition-colors">
<td class="px-4 py-3 whitespace-nowrap">${date}</td>
<td class="px-4 py-3 font-medium">${log.user_name}</td>
<td class="px-4 py-3 whitespace-nowrap"><span class="px-2 py-1 rounded-md text-[10px] font-bold ${actionClass}">${actionLabel}</span><br><span class="text-[9px] text-slate-400">${log.action}</span></td>
<td class="px-4 py-3 break-all font-mono text-[10px] text-slate-500">${log.details || '-'}</td>
</tr>
`;
}).join('');
// Initialize Simple DataTable
activityLogsDataTable = new simpleDatatables.DataTable("#activityLogsTable", {
searchable: false, // We already have a custom backend search field above
fixedHeight: true,
perPage: 10,
labels: {
placeholder: "ค้นหาในหน้านี้...",
perPage: "รายการต่อหน้า",
noRows: "ไม่พบข้อมูล",
info: "แสดง {start} ถึง {end} จากทั้งหมด {rows} รายการ"
}
});
} else {
tbody.innerHTML = `<tr><td colspan="4" class="px-4 py-8 text-center text-red-500">เกิดข้อผิดพลาด: ${res.message}</td></tr>`;
}
} catch (err) {
tbody.innerHTML = '<tr><td colspan="4" class="px-4 py-8 text-center text-red-500">เกิดข้อผิดพลาดในการเชื่อมต่อ</td></tr>';
}
}
async function saveSystemSettingsSubmit(e) {
e.preventDefault();
const data = {
action: 'save_settings',
hospital_name: document.getElementById('settingHospitalName').value,
system_title: document.getElementById('settingSystemTitle').value,
hospital_logo_url: document.getElementById('settingLogoUrl').value,
ticker_text: document.getElementById('settingTickerText').value,
gdrive_gas_url: document.getElementById('settingGasUrl').value,
footer_text: document.getElementById('settingFooterText').value
};
try {
const response = await fetch('api/admin_settings.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const res = await response.json();
if (res.status === 'success') {
showToast('บันทึกการตั้งค่าระบบเรียบร้อยแล้ว', 'success');
systemSettings = { ...systemSettings, ...data };
applySystemSettingsUI();
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการบันทึก', 'error');
}
}
function renderAdminAgenciesTable() {
const tbody = document.getElementById('adminAgenciesTableBody');
if (!tbody) return;
let html = '';
AGENCIES.forEach(a => {
if (a.id === 'SUPER_ADMIN') return;
html += `
<tr class="border-b border-slate-100 hover:bg-slate-50">
<td class="py-2.5 px-3 text-sm text-slate-800 font-medium">${a.id}</td>
<td class="py-2.5 px-3 text-sm text-slate-600">${a.name}</td>
<td class="py-2.5 px-3 text-sm text-slate-500 font-mono text-[10px] truncate max-w-[100px]">${a.gdrive_folder_id || '-'}</td>
<td class="py-2.5 px-3 text-right">
<button onclick='editAgency(${JSON.stringify(a).replace(/'/g, "&#39;")})' class="text-xs bg-blue-100 text-blue-700 px-2 py-1 rounded hover:bg-blue-200 mr-1" title="แก้ไข">
<i class="fa-solid fa-pen"></i> แก้ไข
</button>
<button onclick="resetAgencyPassword('${a.id}')" class="text-xs bg-amber-100 text-amber-700 px-2 py-1 rounded hover:bg-amber-200 mr-1" title="รีเซ็ตรหัสผ่านเป็น 1234">
<i class="fa-solid fa-key"></i>
</button>
<button onclick="deleteAgency('${a.id}')" class="text-xs bg-red-100 text-red-600 px-2 py-1 rounded hover:bg-red-200">
<i class="fa-solid fa-trash"></i>
</button>
</td>
</tr>
`;
});
tbody.innerHTML = html;
}
function editAgency(agency) {
document.getElementById('agencyIsEditInput').value = '1';
document.getElementById('agencyIdInput').value = agency.id;
document.getElementById('agencyIdInput').readOnly = true;
document.getElementById('agencyIdInput').classList.add('bg-slate-100', 'text-slate-500');
document.getElementById('agencyNameInput').value = agency.name;
document.getElementById('agencyCodeInput').value = agency.code;
document.getElementById('agencyPasswordInput').value = agency.admin_password || '';
document.getElementById('agencyGdriveInput').value = agency.gdrive_folder_id || '';
document.getElementById('agencySubmitBtnText').innerText = 'บันทึกแก้ไขหน่วยงาน';
document.getElementById('agencyFormTitle').innerHTML = '<i class="fa-solid fa-pen-to-square text-blue-600"></i> แก้ไขหน่วยงาน: ' + agency.name;
document.getElementById('agencyCancelEditBtn').classList.remove('hidden');
}
function resetAgencyForm() {
document.getElementById('agencyIsEditInput').value = '0';
document.getElementById('agencyIdInput').readOnly = false;
document.getElementById('agencyIdInput').classList.remove('bg-slate-100', 'text-slate-500');
document.getElementById('agencyForm').reset();
document.getElementById('agencySubmitBtnText').innerText = 'บันทึกสร้างหน่วยงาน';
document.getElementById('agencyFormTitle').innerHTML = '<i class="fa-solid fa-circle-plus text-purple-600"></i> เพิ่มหน่วยงาน / กลุ่มงานใหม่ในโรงพยาบาล';
}
function addAttachmentRow(name = '', type = 'pdf', size = '1.5 MB', url = '') {
const container = document.getElementById('attachmentListContainer');
const rowId = 'att_' + Date.now() + Math.random().toString(36).substr(2, 4);
const row = document.createElement('div');
row.className = 'attachment-item group flex items-center gap-3 bg-white p-2.5 rounded-xl border border-slate-200 shadow-sm hover:border-emerald-300 hover:shadow-md transition-all';
row.id = rowId;
row.innerHTML = `
<div class="w-10 h-10 rounded-lg bg-slate-50 flex items-center justify-center shrink-0 border border-slate-100">
<i class="fa-solid ${getFileIcon(type)} text-xl"></i>
</div>
<div class="flex-1 min-w-0">
<div class="text-sm font-bold text-slate-700 truncate" title="${name}">${name || 'ไฟล์แนบใหม่'}</div>
<div class="text-[11px] text-slate-500 font-medium">${size} &bull; พร้อมแนบ <i class="fa-solid fa-circle-check text-emerald-500 ml-0.5"></i></div>
</div>
<!-- Hidden inputs for form submission -->
<input type="hidden" class="attach-name" value="${name}">
<input type="hidden" class="attach-type" value="${type}">
<input type="hidden" class="attach-size" value="${size}">
<input type="hidden" class="attach-url" value="${url}">
<button type="button" onclick="deleteAttachment('${rowId}', '${url}', this)" class="w-8 h-8 rounded-lg bg-slate-50 text-slate-400 hover:bg-red-50 hover:text-red-500 flex items-center justify-center transition-colors shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100" title="ลบไฟล์นี้">
<i class="fa-solid fa-trash-can text-sm"></i>
</button>
`;
container.appendChild(row);
}
async function deleteAttachment(rowId, url, btnElement) {
// If no URL (e.g. not uploaded to Drive yet, or just a dummy row), just remove it from DOM
if (!url) {
document.getElementById(rowId).remove();
return;
}
// Confirm deletion
showConfirmDialog(
'ยืนยันการลบไฟล์',
'คุณแน่ใจหรือไม่ว่าต้องการลบไฟล์นี้ออกจากระบบและ Google Drive อย่างถาวร?',
async () => {
// Show loading on button
const originalHtml = btnElement.innerHTML;
btnElement.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin text-sm text-red-500"></i>';
btnElement.disabled = true;
try {
const response = await fetch('api/delete_file.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url })
});
const res = await response.json();
if (res.status === 'success') {
document.getElementById(rowId).remove();
showToast('ลบไฟล์เรียบร้อยแล้ว', 'success');
} else {
showToast('เกิดข้อผิดพลาด: ' + res.message, 'error');
btnElement.innerHTML = originalHtml;
btnElement.disabled = false;
}
} catch (err) {
showToast('ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์เพื่อลบไฟล์ได้', 'error');
btnElement.innerHTML = originalHtml;
btnElement.disabled = false;
}
}
);
}
function handleFileDrop(event) {
const dt = event.dataTransfer;
const files = dt.files;
// Create a fake event object to pass to handleFileUpload
const fakeEvent = {
target: {
files: files,
value: '' // Reset equivalent
}
};
handleFileUpload(fakeEvent);
}
async function handleFileUpload(event) {
const files = event.target.files;
if (!files || files.length === 0) return;
const agencyId = document.getElementById('newsAgencyInput').value;
if (!agencyId) {
showToast('กรุณาเลือกหน่วยงานเจ้าของเรื่องก่อนอัพโหลดไฟล์', 'warning');
event.target.value = '';
return;
}
const agency = AGENCIES.find(a => a.id === agencyId);
if (!agency || !agency.gdrive_folder_id) {
showToast('หน่วยงานนี้ยังไม่ได้ตั้งค่า Google Drive Folder ID', 'error');
event.target.value = '';
return;
}
const uploadPromises = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
// Safe limit for Google Apps Script Base64 upload is ~35MB
const maxSizeMB = 35;
if (file.size > maxSizeMB * 1024 * 1024) {
showToast(`ไฟล์ "${file.name}" มีขนาดใหญ่เกินไป (จำกัด ${maxSizeMB} MB เพื่อป้องกันระบบ Google ปฏิเสธการรับข้อมูล)`, 'error');
continue;
}
// Show modern loading placeholder immediately
const container = document.getElementById('attachmentListContainer');
const rowId = 'upload_' + Date.now() + i;
const row = document.createElement('div');
row.className = 'attachment-item flex items-center gap-3 bg-white p-2.5 rounded-xl border border-slate-200 shadow-sm relative overflow-hidden';
row.id = rowId;
let fileExt = file.name.split('.').pop().toLowerCase();
let iconClass = 'fa-file';
if (fileExt === 'pdf') iconClass = 'fa-file-pdf text-red-500';
else if (['doc', 'docx'].includes(fileExt)) iconClass = 'fa-file-word text-blue-500';
else if (['xls', 'xlsx'].includes(fileExt)) iconClass = 'fa-file-excel text-green-500';
else if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) iconClass = 'fa-file-image text-purple-500';
let fileSizeStr = (file.size / (1024 * 1024)).toFixed(2) + ' MB';
row.innerHTML = `
<div class="absolute bottom-0 left-0 h-1 bg-emerald-500/20 w-full animate-pulse"></div>
<div class="w-10 h-10 rounded-lg bg-slate-50 flex items-center justify-center shrink-0 border border-slate-100">
<i class="fa-solid ${iconClass} text-xl opacity-50"></i>
</div>
<div class="flex-1 min-w-0">
<div class="text-sm font-bold text-slate-700 truncate">${file.name}</div>
<div class="text-[11px] text-slate-500 font-medium flex items-center gap-1.5">
<i class="fa-solid fa-circle-notch fa-spin text-emerald-500"></i> กำลังอัพโหลด...
</div>
</div>
`;
container.appendChild(row);
const formData = new FormData();
formData.append('file', file);
formData.append('folderId', agency.gdrive_folder_id);
// Add to promises array for concurrent upload
uploadPromises.push(
fetch('api/upload_file.php', { method: 'POST', body: formData })
.then(async (response) => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
const text = await response.text();
try {
return JSON.parse(text);
} catch (e) {
throw new Error('Invalid JSON response from server');
}
})
.then(res => {
const el = document.getElementById(rowId);
if (el) el.remove();
if (res.status === 'success') {
let type = 'pdf';
if (['doc', 'docx'].includes(fileExt)) type = 'word';
else if (['xls', 'xlsx'].includes(fileExt)) type = 'excel';
else if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) type = 'image';
addAttachmentRow(file.name, type, fileSizeStr, res.url);
} else {
showToast(res.message || 'เกิดข้อผิดพลาดจากเซิร์ฟเวอร์', 'error');
}
})
.catch(err => {
const el = document.getElementById(rowId);
if (el) el.remove();
showToast('เกิดข้อผิดพลาดในการอัพโหลด: ' + err.message, 'error');
})
);
}
// Await all uploads to finish
await Promise.all(uploadPromises);
event.target.value = '';
}
async function saveAgencySubmit(e) {
e.preventDefault();
const isEdit = document.getElementById('agencyIsEditInput').value === '1';
const action = isEdit ? 'edit_agency' : 'add_agency';
const data = {
action: action,
id: document.getElementById('agencyIdInput').value,
code: document.getElementById('agencyCodeInput').value,
name: document.getElementById('agencyNameInput').value,
password: document.getElementById('agencyPasswordInput').value,
gdriveFolderId: document.getElementById('agencyGdriveInput').value
};
try {
const response = await fetch('api/agency_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const res = await response.json();
if (res.status === 'success') {
showToast(res.message, 'success');
resetAgencyForm();
await loadNewsFromDatabase();
renderAdminAgenciesTable();
renderAgencyPasswordCards();
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการบันทึก', 'error');
}
}
function deleteAgency(id) {
showConfirmDialog('ลบหน่วยงาน', 'ยืนยันการลบหน่วยงานนี้หรือไม่? (ข่าวของหน่วยงานนี้อาจได้รับผลกระทบ)', async () => {
try {
const response = await fetch('api/agency_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete_agency', id })
});
const res = await response.json();
if (res.status === 'success') {
showToast('ลบหน่วยงานเรียบร้อยแล้ว', 'success');
await loadNewsFromDatabase();
renderAdminAgenciesTable();
renderAgencyPasswordCards();
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการลบ', 'error');
}
});
}
function resetAgencyPassword(id) {
showConfirmDialog('รีเซ็ตรหัสผ่าน', 'ต้องการรีเซ็ตรหัสผ่านของหน่วยงานนี้ให้เป็น "1234" ใช่หรือไม่?', async () => {
try {
const response = await fetch('api/agency_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'reset_password', id })
});
const res = await response.json();
if (res.status === 'success') {
showToast('รีเซ็ตรหัสผ่านเรียบร้อยแล้ว', 'success');
await loadNewsFromDatabase();
renderAgencyPasswordCards();
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการรีเซ็ตรหัสผ่าน', 'error');
}
});
}
async function saveSuperAdminPasswordOnly() {
const pass = document.getElementById('superAdminNewPassInput').value;
if (!pass) {
showToast('กรุณากรอกรหัสผ่าน', 'warning');
return;
}
try {
const response = await fetch('api/admin_settings.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'save_settings', super_admin_password: pass })
});
const res = await response.json();
if (res.status === 'success') {
showToast('เปลี่ยนรหัสผ่าน Super Admin เรียบร้อยแล้ว', 'success');
document.getElementById('superAdminNewPassInput').value = '';
} else {
showToast(res.message, 'error');
}
} catch (err) {
showToast('เกิดข้อผิดพลาดในการเปลี่ยนรหัสผ่าน', 'error');
}
}
function renderAgencyPasswordCards() {
const container = document.getElementById('agencyPasswordCards');
if (!container) return;
let html = '';
AGENCIES.forEach(a => {
if (a.id === 'SUPER_ADMIN') return;
html += `
<div class="bg-white border border-slate-200 rounded-xl p-3 flex justify-between items-center shadow-sm">
<div>
<p class="text-xs font-bold text-slate-800">${a.name}</p>
<p class="text-[10px] text-slate-500">รหัสผ่านปัจจุบัน: <span class="font-mono text-amber-600 font-bold">${a.admin_password || '1234'}</span></p>
</div>
<button onclick="resetAgencyPassword('${a.id}')" class="w-8 h-8 rounded-lg bg-amber-50 hover:bg-amber-100 text-amber-600 flex items-center justify-center transition-colors" title="รีเซ็ตเป็น 1234">
<i class="fa-solid fa-rotate-left text-xs"></i>
</button>
</div>
`;
});
container.innerHTML = html;
}
// -----------------------------
function openNewsModal(id = null) {
if (!currentUser) {
showToast('กรุณาเข้าสู่ระบบก่อนดำเนินการ', 'warning');
openLoginModal();
return;
}
renderQuickTemplates();
closeNewsDetailModal();
const form = document.getElementById('newsForm');
form.reset();
document.getElementById('attachmentListContainer').innerHTML = '';
const agencySelect = document.getElementById('newsAgencyInput');
if (id) {
const item = newsList.find(n => n.id === id);
if (item) {
if (currentUser.role !== 'SUPER_ADMIN' && currentUser.agencyId !== item.agencyId) {
showToast('คุณไม่มีสิทธิ์แก้ไขหนังสือเวียนของกลุ่มงานอื่น', 'error');
return;
}
document.getElementById('formModalTitle').textContent = 'แก้ไขหนังสือเวียน / ข่าวประชาสัมพันธ์';
document.getElementById('newsIdInput').value = item.id;
document.getElementById('docNumInput').value = item.docNum;
document.getElementById('newsTitleInput').value = item.title;
agencySelect.value = item.agencyId;
document.getElementById('newsCategoryInput').value = item.categoryId;
document.getElementById('newsDateInput').value = item.date;
document.getElementById('newsContentInput').value = item.content;
document.getElementById('isPinnedInput').checked = !!item.isPinned;
document.getElementById('isUrgentInput').checked = !!item.isUrgent;
if (item.attachments && item.attachments.length > 0) {
item.attachments.forEach(att => addAttachmentRow(att.name, att.type, att.size));
}
}
} else {
document.getElementById('formModalTitle').textContent = 'สร้างบันทึกข้อความ / ออกหนังสือเวียน';
document.getElementById('newsIdInput').value = '';
document.getElementById('docNumInput').value = '-';
document.getElementById('newsDateInput').value = new Date().toISOString().split('T')[0];
// Preselect user's agency if not Super Admin
if (currentUser.role !== 'SUPER_ADMIN') {
agencySelect.value = currentUser.agencyId;
}
}
// Lock agency select for non-Super Admins
if (currentUser.role !== 'SUPER_ADMIN') {
agencySelect.disabled = true;
agencySelect.classList.add('bg-slate-100');
} else {
agencySelect.disabled = false;
agencySelect.classList.remove('bg-slate-100');
}
document.getElementById('newsFormModal').classList.remove('hidden');
}
function closeNewsModal() {
document.getElementById('newsFormModal').classList.add('hidden');
}
function renderQuickTemplates() {
const container = document.getElementById('quickTemplatesContainer');
if (!container) return;
if (!currentUser) {
container.innerHTML = '';
return;
}
const isSuperAdmin = currentUser.agencyId === 'SUPER_ADMIN';
const templatesToShow = TEMPLATES.filter(t => isSuperAdmin || t.agency_id === currentUser.agencyId || t.agency_id === 'SUPER_ADMIN');
let html = '';
templatesToShow.forEach(t => {
html += `
<button type="button" onclick="applyTemplate('${t.id}')" class="px-2.5 py-1 bg-white hover:bg-amber-100 text-amber-900 border border-amber-300 rounded-lg text-[11px] font-medium transition-colors">
+ ${t.name}
</button>
`;
});
container.innerHTML = html;
}
function applyTemplate(id) {
const t = TEMPLATES.find(x => x.id === id);
if (!t) return;
let parsedDocNum = t.doc_num_format;
if (parsedDocNum.includes('{random}')) {
parsedDocNum = parsedDocNum.replace('{random}', Math.floor(Math.random() * 800 + 100));
}
document.getElementById('newsCategoryInput').value = t.category_id;
document.getElementById('docNumInput').value = parsedDocNum;
document.getElementById('newsTitleInput').value = t.title;
document.getElementById('newsContentInput').value = t.content;
showToast('ปรับใช้แม่แบบเรียบร้อยแล้ว');
}
async function saveNews(event) {
event.preventDefault();
const id = document.getElementById('newsIdInput').value;
const docNum = document.getElementById('docNumInput').value.trim();
const title = document.getElementById('newsTitleInput').value.trim();
const agencyId = document.getElementById('newsAgencyInput').value;
const categoryId = document.getElementById('newsCategoryInput').value;
const date = document.getElementById('newsDateInput').value;
const content = document.getElementById('newsContentInput').value.trim();
const isPinned = document.getElementById('isPinnedInput').checked;
const isUrgent = document.getElementById('isUrgentInput').checked;
if (!title) {
showToast('กรุณากรอกหัวข้อหนังสือ/ข่าวประชาสัมพันธ์', 'warning');
return;
}
const attachmentRows = document.querySelectorAll('#attachmentListContainer .attachment-item');
const attachments = [];
attachmentRows.forEach(row => {
const nameInput = row.querySelector('.attach-name');
const typeInput = row.querySelector('.attach-type');
const sizeInput = row.querySelector('.attach-size');
const urlInput = row.querySelector('.attach-url');
if (nameInput && nameInput.value.trim()) {
attachments.push({
name: nameInput.value.trim(),
type: typeInput ? typeInput.value : 'pdf',
size: sizeInput && sizeInput.value.trim() ? sizeInput.value.trim() : '1.0 MB',
url: urlInput ? urlInput.value.trim() : ''
});
}
});
const payload = {
id,
docNum,
title,
agencyId,
categoryId,
date,
content,
isPinned,
isUrgent,
attachments
};
try {
const response = await fetch('api/save_news.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const resData = await response.json();
if (resData.status === 'success') {
showToast(resData.message || 'บันทึกข้อมูลเรียบร้อยแล้ว');
closeNewsModal();
await loadNewsFromDatabase();
} else {
showToast(resData.message || 'เกิดข้อผิดพลาดในการบันทึก', 'error');
}
} catch (err) {
console.error(err);
showToast('เกิดข้อผิดพลาดในการส่งข้อมูลไปยัง PHP API', 'error');
}
}
function deleteNews(id) {
const item = newsList.find(n => n.id === id);
if (!item) return;
if (!currentUser || (currentUser.agencyId !== 'SUPER_ADMIN' && currentUser.agencyId !== item.agencyId)) {
showToast('คุณไม่มีสิทธิ์ลบหนังสือเวียนของกลุ่มงานอื่น', 'error');
return;
}
showConfirmDialog(
'ลบข่าวประชาสัมพันธ์',
`คุณแน่ใจหรือไม่ว่าต้องการลบเอกสาร "${item.title}" ออกจากระบบ?`,
async () => {
try {
const response = await fetch('api/news_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete_news', id })
});
const resData = await response.json();
if (resData.status === 'success') {
closeNewsDetailModal();
showToast(resData.message, 'success');
await loadNewsFromDatabase();
} else {
showToast(resData.message || 'ไม่สามารถลบข้อมูลได้', 'error');
}
} catch (err) {
console.error(err);
showToast('เกิดข้อผิดพลาดในการส่งคำขอลบ', 'error');
}
}
);
}
async function toggleHideNews(id) {
const item = newsList.find(n => n.id === id);
if (!item) return;
if (!currentUser || (currentUser.agencyId !== 'SUPER_ADMIN' && currentUser.agencyId !== item.agencyId)) {
showToast('คุณไม่มีสิทธิ์ซ่อนหนังสือเวียนของกลุ่มงานอื่น', 'error');
return;
}
try {
const response = await fetch('api/news_actions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'toggle_hide_news', id })
});
const resData = await response.json();
if (resData.status === 'success') {
showToast(resData.message, 'success');
await loadNewsFromDatabase();
} else {
showToast(resData.message || 'ไม่สามารถอัปเดตข้อมูลได้', 'error');
}
} catch (err) {
console.error(err);
showToast('เกิดข้อผิดพลาดในการส่งคำขอ', 'error');
}
}
function openAnalyticsModal() {
document.getElementById('analyticsTotalNews').textContent = newsList.length;
const totalViews = newsList.reduce((sum, n) => sum + (n.views || 0), 0);
document.getElementById('analyticsTotalViews').textContent = totalViews.toLocaleString();
const totalFiles = newsList.reduce((sum, n) => sum + (n.attachments ? n.attachments.length : 0), 0);
document.getElementById('analyticsTotalFiles').textContent = totalFiles.toLocaleString();
// Agency Breakdown
const agencyStatsContainer = document.getElementById('analyticsAgencyStats');
agencyStatsContainer.innerHTML = AGENCIES.map(agency => {
const count = newsList.filter(n => n.agencyId === agency.id).length;
const percent = newsList.length ? Math.round((count / newsList.length) * 100) : 0;
return `
<div>
<div class="flex justify-between text-xs font-bold text-slate-700 mb-1">
<span>${agency.name}</span>
<span>${count} เรื่อง (${percent}%)</span>
</div>
<div class="w-full bg-slate-200 rounded-full h-2 overflow-hidden">
<div class="bg-[#056839] h-2 rounded-full" style="width: ${percent}%"></div>
</div>
</div>
`;
}).join('');
// Category Breakdown
const catStatsContainer = document.getElementById('analyticsCategoryStats');
catStatsContainer.innerHTML = CATEGORIES.filter(c => c.id !== 'ALL').map(cat => {
const count = newsList.filter(n => n.categoryId === cat.id).length;
const percent = newsList.length ? Math.round((count / newsList.length) * 100) : 0;
return `
<div>
<div class="flex justify-between text-xs font-bold text-slate-700 mb-1">
<span>${cat.name}</span>
<span>${count} เรื่อง (${percent}%)</span>
</div>
<div class="w-full bg-slate-200 rounded-full h-2 overflow-hidden">
<div class="bg-blue-600 h-2 rounded-full" style="width: ${percent}%"></div>
</div>
</div>
`;
}).join('');
document.getElementById('analyticsModal').classList.remove('hidden');
}
function closeAnalyticsModal() {
document.getElementById('analyticsModal').classList.add('hidden');
}
// Sharing & Utility Actions
function shareNews(type, title, docNum) {
const shareUrl = window.location.href;
const text = `[ข่าวแจ้งเวียน รพ.] ${docNum ? docNum + ' ' : ''}${title}`;
if (type === 'line') {
window.open(`https://line.me/R/msg/text/?${encodeURIComponent(text + '\n' + shareUrl)}`, '_blank');
} else if (type === 'copy') {
const dummy = document.createElement('textarea');
document.body.appendChild(dummy);
dummy.value = `${text}\n${shareUrl}`;
dummy.select();
document.execCommand('copy');
document.body.removeChild(dummy);
showToast('คัดลอกข้อความและลิงก์เรียบร้อยแล้ว');
}
}
function printNews() {
window.print();
showToast('สั่งพิมพ์เอกสารเรียบร้อยแล้ว');
}
function downloadAttachment(name) {
showToast(`เริ่มดาวน์โหลดเอกสาร: ${name}`);
}
// --- User Manual Functions ---
function openUserManualModal() {
document.getElementById('userManualModal').classList.remove('hidden');
// Adjust visible tabs based on user role
const btnAgency = document.getElementById('manualTabBtnAgency');
const btnSuper = document.getElementById('manualTabBtnSuper');
if (currentUser && currentUser.role === 'SUPER_ADMIN') {
btnAgency.classList.remove('hidden');
btnSuper.classList.remove('hidden');
} else if (currentUser && currentUser.role === 'AGENCY_ADMIN') {
btnAgency.classList.remove('hidden');
btnSuper.classList.add('hidden');
} else {
btnAgency.classList.add('hidden');
btnSuper.classList.add('hidden');
}
// Default to general tab
switchManualTab('general');
}
function closeUserManualModal() {
document.getElementById('userManualModal').classList.add('hidden');
}
function switchManualTab(tabId) {
// Hide all tab contents
document.getElementById('manualTabGeneral').classList.add('hidden');
document.getElementById('manualTabAgency').classList.add('hidden');
document.getElementById('manualTabSuper').classList.add('hidden');
// Show the active tab content
const tabIdCapitalized = tabId.charAt(0).toUpperCase() + tabId.slice(1);
document.getElementById('manualTab' + tabIdCapitalized).classList.remove('hidden');
// Reset button styles
const allBtns = [
document.getElementById('manualTabBtnGeneral'),
document.getElementById('manualTabBtnAgency'),
document.getElementById('manualTabBtnSuper')
];
allBtns.forEach(btn => {
if(btn) {
btn.className = "px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all text-slate-600 hover:text-blue-900 flex items-center gap-2 shrink-0" + (btn.classList.contains('hidden') ? " hidden" : "");
}
});
// Set active button style
const activeBtn = document.getElementById('manualTabBtn' + tabIdCapitalized);
if (activeBtn) {
activeBtn.className = "px-4 py-2.5 rounded-t-xl text-xs font-bold transition-all border-b-2 border-blue-600 text-blue-900 bg-white shadow-sm flex items-center gap-2 shrink-0" + (activeBtn.classList.contains('hidden') ? " hidden" : "");
}
}
</script>
</body>
</html>