3416 lines
205 KiB
PHP
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> © <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, "'")})' 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} • พร้อมแนบ <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>
|