Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,217 @@
<?php
// components/card_patient_stats.php
$timezone = "Asia/Bangkok";
date_default_timezone_set($timezone);
$today = date("Y-m-d H:i:s");
// News (Changelogs from new system)
require_once(__DIR__."/../core/settings.php");
$all_logs = get_changelogs();
$news_items = array_slice($all_logs, 0, 5);
?>
<div class="mb-8 flex flex-col md:flex-row md:items-end justify-between gap-4 animate-enter">
<div>
<h2 class="text-3xl font-bold text-slate-800 tracking-tight">สวัสดี, <?php echo htmlspecialchars($_SESSION['name'] ?? 'ผู้ใช้งาน'); ?> 👋</h2>
<p class="text-slate-500 mt-2 text-lg">ภาพรวมการให้บริการประจำวันที่ <?php echo thai_date5(datetime_to_date($today)); ?></p>
</div>
<div class="flex gap-3">
<a href="search.php" class="btn-primary py-2.5 px-5 text-sm shadow-md">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
ค้นหาผู้ป่วย
</a>
</div>
</div>
<!-- Top Stat Widgets -->
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-8 animate-enter" style="animation-delay: 0.1s;">
<!-- ER Widget -->
<div class="glass-card !p-5 border-l-4 !border-l-rose-500 hover:-translate-y-1 transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-slate-500 text-sm font-medium mb-1">ฉุกเฉิน (ER)</p>
<h4 class="text-3xl font-bold text-slate-800" id="stat-er">-</h4>
</div>
<div class="w-10 h-10 rounded-full bg-rose-50 text-rose-500 flex items-center justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path></svg>
</div>
</div>
</div>
<!-- PQ Widget -->
<div class="glass-card !p-5 border-l-4 !border-l-blue-500 hover:-translate-y-1 transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-slate-500 text-sm font-medium mb-1">ซักประวัติ</p>
<h4 class="text-3xl font-bold text-slate-800" id="stat-pq">-</h4>
</div>
<div class="w-10 h-10 rounded-full bg-blue-50 text-blue-500 flex items-center justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path></svg>
</div>
</div>
</div>
<!-- Dental Widget -->
<div class="glass-card !p-5 border-l-4 !border-l-purple-500 hover:-translate-y-1 transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-slate-500 text-sm font-medium mb-1">ทันตกรรม</p>
<h4 class="text-3xl font-bold text-slate-800" id="stat-dt">-</h4>
</div>
<div class="w-10 h-10 rounded-full bg-purple-50 text-purple-500 flex items-center justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</div>
</div>
</div>
<!-- Rx Widget -->
<div class="glass-card !p-5 border-l-4 !border-l-emerald-500 hover:-translate-y-1 transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-slate-500 text-sm font-medium mb-1">สั่งจ่ายยา</p>
<h4 class="text-3xl font-bold text-slate-800" id="stat-rx">-</h4>
</div>
<div class="w-10 h-10 rounded-full bg-emerald-50 text-emerald-500 flex items-center justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 8.5a5.5 5.5 0 00-7.778 0L5.5 14.222a5.5 5.5 0 007.778 7.778l5.722-5.722A5.5 5.5 0 0019 8.5z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.3 14.7l5.4-5.4" />
</svg>
</div>
</div>
</div>
<!-- Lab Widget -->
<div class="glass-card !p-5 border-l-4 !border-l-amber-500 hover:-translate-y-1 transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-slate-500 text-sm font-medium mb-1">ห้องแล็บ</p>
<h4 class="text-3xl font-bold text-slate-800" id="stat-lab">-</h4>
</div>
<div class="w-10 h-10 rounded-full bg-amber-50 text-amber-500 flex items-center justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-8 animate-enter">
<!-- Quick Actions / Menu -->
<div class="glass-card">
<div class="glass-header">
<h3 class="text-emerald-600 flex items-center gap-3 font-bold text-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path></svg>
เมนูลัด (Quick Actions)
</h3>
</div>
<div class="glass-body p-6">
<div class="grid grid-cols-2 gap-4">
<a href="search.php" class="flex flex-col items-center justify-center p-6 bg-emerald-50 rounded-2xl hover:bg-emerald-100 hover:shadow-md transition-all text-emerald-700 font-semibold group cursor-pointer border border-emerald-100">
<div class="w-14 h-14 bg-white rounded-full flex items-center justify-center mb-3 shadow-sm group-hover:scale-110 transition-transform">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
</div>
ค้นหาผู้ป่วย
</a>
<a href="#" class="flex flex-col items-center justify-center p-6 bg-blue-50 rounded-2xl hover:bg-blue-100 hover:shadow-md transition-all text-blue-700 font-semibold group cursor-pointer border border-blue-100">
<div class="w-14 h-14 bg-white rounded-full flex items-center justify-center mb-3 shadow-sm group-hover:scale-110 transition-transform">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
</div>
รายงานสถิติ
</a>
<a href="#" class="flex flex-col items-center justify-center p-6 bg-purple-50 rounded-2xl hover:bg-purple-100 hover:shadow-md transition-all text-purple-700 font-semibold group cursor-pointer border border-purple-100">
<div class="w-14 h-14 bg-white rounded-full flex items-center justify-center mb-3 shadow-sm group-hover:scale-110 transition-transform">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
</div>
ลงทะเบียนใหม่
</a>
<a href="#" class="flex flex-col items-center justify-center p-6 bg-amber-50 rounded-2xl hover:bg-amber-100 hover:shadow-md transition-all text-amber-700 font-semibold group cursor-pointer border border-amber-100">
<div class="w-14 h-14 bg-white rounded-full flex items-center justify-center mb-3 shadow-sm group-hover:scale-110 transition-transform">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
</div>
ตั้งค่าระบบ
</a>
</div>
</div>
</div>
<!-- News Right -->
<div class="glass-card">
<div class="glass-header">
<h3 class="text-amber-500 flex items-center gap-3 font-bold text-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"></path></svg>
ประกาศข่าวสาร
</h3>
</div>
<div class="glass-body flex flex-col gap-4">
<?php if(count($news_items) > 0): ?>
<?php foreach($news_items as $news):
$d = new DateTime($news['release_date']);
$thai_year = $d->format('Y') + 543;
$display_date = $d->format('d-m-') . $thai_year;
?>
<div class="p-5 bg-white/60 rounded-2xl border-l-4 border-amber-400 shadow-sm hover:shadow-md transition-shadow">
<div class="text-xs text-amber-600 mb-1.5 font-bold tracking-wide">
<?php echo $display_date; ?>
</div>
<div class="text-slate-800 font-medium leading-relaxed flex flex-wrap items-center gap-y-2">
<?php echo htmlspecialchars($news['version']); ?>
<?php if(!empty($news['description'])): ?>
<span class="ml-2 inline-flex items-center px-3 py-1 rounded-full text-[11px] font-bold bg-emerald-100 text-emerald-800 shadow-sm border border-emerald-200/50">
<?php echo htmlspecialchars($news['description']); ?>
</span>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="text-center text-slate-400 py-12 flex flex-col items-center">
<svg class="w-12 h-12 mb-3 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9.5a2.5 2.5 0 00-2.5-2.5H15"></path></svg>
ไม่มีข้อมูลประกาศ
</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('../core/ajax_dashboard_stats.php')
.then(response => response.json())
.then(data => {
// Animate numbers
animateValue('stat-er', 0, parseInt(data.er.replace(/,/g, '')), 1000);
animateValue('stat-pq', 0, parseInt(data.pq.replace(/,/g, '')), 1000);
animateValue('stat-dt', 0, parseInt(data.dt.replace(/,/g, '')), 1000);
animateValue('stat-rx', 0, parseInt(data.rx.replace(/,/g, '')), 1000);
animateValue('stat-lab', 0, parseInt(data.lab.replace(/,/g, '')), 1000);
})
.catch(err => {
console.error('Error loading stats:', err);
['stat-er','stat-pq','stat-dt','stat-rx','stat-lab'].forEach(id => {
document.getElementById(id).innerText = "Err";
});
});
function animateValue(id, start, end, duration) {
if (isNaN(end)) return;
let obj = document.getElementById(id);
let startTimestamp = null;
const step = (timestamp) => {
if (!startTimestamp) startTimestamp = timestamp;
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
// easeOutQuad
const easeProgress = progress * (2 - progress);
obj.innerHTML = Math.floor(easeProgress * (end - start) + start).toLocaleString('en-US');
if (progress < 1) {
window.requestAnimationFrame(step);
} else {
obj.innerHTML = end.toLocaleString('en-US');
}
};
window.requestAnimationFrame(step);
}
});
</script>
+160
View File
@@ -0,0 +1,160 @@
<?php
// components/head.php
$page_title = $page_title ?? "HOSxP Web Service";
?>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><?php echo htmlspecialchars($page_title); ?></title>
<link rel="icon" type="image/png" href="../img/logo.png">
<!-- Google Fonts: Sarabun -->
<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:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Tailwind CSS Play CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
emerald: {
50: '#F2F9F5',
100: '#E0F1E6',
200: '#C2E3CE',
300: '#94CFA8',
400: '#60B47F',
500: '#38975D',
600: '#267948', // Exact MOPH Green
700: '#1F603B',
800: '#1A4D30',
900: '#154029',
950: '#0B2316',
}
},
fontFamily: {
sans: ['Sarabun', 'sans-serif'],
},
boxShadow: {
'glass': '0 10px 40px -10px rgba(38, 121, 72, 0.15)',
},
keyframes: {
ring: {
'0%, 100%': { transform: 'rotate(0deg)' },
'20%': { transform: 'rotate(-20deg)' },
'40%': { transform: 'rotate(15deg)' },
'60%': { transform: 'rotate(-10deg)' },
'80%': { transform: 'rotate(5deg)' },
}
},
animation: {
ring: 'ring 1.5s ease-in-out infinite',
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.glass-card {
@apply bg-white/70 backdrop-blur-xl border border-white/50 rounded-[24px] shadow-glass transition-all duration-300;
}
.glass-card:hover {
@apply shadow-2xl shadow-emerald-500/20;
}
.glass-header {
@apply px-6 py-5 bg-white/60 border-b border-white/50;
}
.glass-body {
@apply p-6;
}
.btn-primary {
@apply inline-flex items-center justify-center gap-2 px-6 py-3 bg-emerald-500 text-white font-medium rounded-full shadow-lg shadow-emerald-500/30 hover:bg-emerald-600 hover:shadow-emerald-500/40 hover:-translate-y-0.5 active:scale-95 transition-all duration-300 outline-none;
}
.btn-outline {
@apply inline-flex items-center justify-center gap-2 px-6 py-3 border border-emerald-500 text-emerald-600 font-medium rounded-full hover:bg-emerald-50 active:scale-95 transition-all duration-300 outline-none;
}
.form-input {
@apply w-full px-4 py-3 rounded-2xl border border-emerald-500/20 bg-white/90 text-slate-800 transition-all duration-300 focus:outline-none focus:border-emerald-500 focus:ring-4 focus:ring-emerald-100;
}
.table-header {
@apply bg-emerald-50 text-emerald-900 px-4 py-3 font-semibold text-sm uppercase tracking-wide border-b-2 border-emerald-500/20 text-left;
}
.table-cell {
@apply px-4 py-4 border-b border-black/5 text-slate-600 text-sm;
}
.table-row-hover {
@apply hover:bg-emerald-50/50 transition-colors duration-300;
}
/* Optional Ripple */
.ripple-wrapper {
@apply relative overflow-hidden;
}
.ripple {
@apply absolute rounded-full bg-white/40;
transform: scale(0);
animation: ripple 0.6s linear;
}
}
@keyframes ripple {
to {
transform: scale(4);
opacity: 0;
}
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-enter {
animation: slideIn 0.5s ease forwards;
}
/* Beautiful Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.02);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: rgba(16, 185, 129, 0.3);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(16, 185, 129, 0.5);
}
</style>
<?php
if (!empty($_SESSION['sess_userid']) && isset($app_settings['auto_logout_enable']) && $app_settings['auto_logout_enable'] === '1') {
$timeout_minutes = (int)($app_settings['auto_logout_minutes'] ?? 30);
if ($timeout_minutes < 1) $timeout_minutes = 30;
$timeout_ms = $timeout_minutes * 60 * 1000;
?>
<script>
let inactivityTimer;
const INACTIVITY_LIMIT = <?php echo $timeout_ms; ?>;
function resetTimer() {
clearTimeout(inactivityTimer);
inactivityTimer = setTimeout(() => {
window.location.href = '../views/login.php?action=timeout';
}, INACTIVITY_LIMIT);
}
// List of events to track for user activity
const activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
activityEvents.forEach(event => {
document.addEventListener(event, resetTimer, true);
});
// Initialize timer on load
resetTimer();
</script>
<?php } ?>
@@ -0,0 +1,37 @@
<?php
// components/layout_footer.php
require_once(__DIR__."/../core/settings.php");
$app_settings = get_app_settings();
$changelogs = get_changelogs();
$latest_version = !empty($changelogs) ? $changelogs[0]['version'] : '';
$current_page = basename($_SERVER['PHP_SELF']);
$is_settings_page = ($current_page === 'admin_settings.php');
$t = function($class_str) use ($is_settings_page) {
if ($is_settings_page) {
$class_str = str_replace('emerald', 'blue', $class_str);
$class_str = str_replace('teal', 'indigo', $class_str);
}
return $class_str;
};
?>
<!-- Spacer to prevent content from being hidden behind fixed footer -->
<div class="h-12 w-full shrink-0"></div>
<footer class="fixed bottom-0 w-full lg:w-[calc(100%-16rem)] lg:right-0 bg-white/90 backdrop-blur-md border-t border-slate-200 px-6 py-2.5 flex justify-between items-center text-xs text-slate-500 z-30 shrink-0">
<div class="flex items-center gap-3 font-medium">
<span><?php
$footer_html = str_replace('{YEAR}', date('Y'), $app_settings['footer_text']);
echo $footer_html;
?></span>
<?php if (!empty($latest_version)): ?>
<span class="px-2 py-0.5 bg-slate-100 text-slate-600 rounded-md border border-slate-200 font-semibold shadow-sm text-[10px] tracking-wider">
<?php echo htmlspecialchars($latest_version); ?>
</span>
<?php endif; ?>
</div>
<div class="flex items-center gap-1.5 font-medium">
Powered By <span class="<?php echo $t('text-emerald-600'); ?> font-bold tracking-wide">น้องมะลิ</span>
<svg class="w-3.5 h-3.5 text-rose-500 inline-block animate-pulse" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd"></path></svg>
</div>
</footer>
@@ -0,0 +1,179 @@
<?php
// components/layout_sidebar.php
$current_page = basename($_SERVER['PHP_SELF']);
$is_settings_page = ($current_page === 'admin_settings.php');
$t = function($class_str) use ($is_settings_page) {
return $is_settings_page ? str_replace('emerald', 'blue', $class_str) : $class_str;
};
?>
<div id="sidebar-backdrop" class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-30 hidden lg:hidden transition-opacity opacity-0 pointer-events-none"></div>
<aside id="sidebar" class="w-64 bg-white/90 lg:bg-white/70 backdrop-blur-xl border-r border-white/50 flex flex-col shadow-[4px_0_24px_rgba(34,197,94,0.05)] z-40 transition-transform duration-300 fixed lg:sticky top-0 h-screen overflow-y-auto -translate-x-full lg:translate-x-0">
<div class="p-5 border-b <?php echo $t('border-emerald-500/10'); ?> mb-6 relative">
<div class="flex items-center gap-3.5">
<!-- Logo with effects -->
<div class="relative shrink-0 w-14 h-14 group">
<div class="absolute inset-0 <?php echo $t('bg-emerald-400/30'); ?> rounded-full filter blur-md group-hover:<?php echo $t('bg-emerald-400/50'); ?> transition-colors duration-500"></div>
<div class="relative w-full h-full bg-white/90 backdrop-blur-md rounded-full <?php echo $t('shadow-[0_2px_12px_rgba(16,185,129,0.15)]'); ?> border-2 border-white flex items-center justify-center p-0 overflow-hidden">
<img src="../img/logo.png" alt="Logo" class="w-full h-full object-cover scale-110 drop-shadow-sm rounded-full relative z-10">
<div class="animate-shine"></div>
</div>
</div>
<!-- Text -->
<div class="flex flex-col pt-1">
<?php
require_once(__DIR__."/../core/settings.php");
$app_settings = get_app_settings();
?>
<h2 class="<?php echo $t('text-emerald-700'); ?> font-extrabold text-[17px] leading-tight tracking-tight">
<?php
// Split the text to make it break nicely if needed, though Tailwind leading-tight helps
echo htmlspecialchars($app_settings['sidebar_header_main']);
?>
</h2>
<div class="text-[11px] text-slate-500 mt-1 font-medium tracking-wide">
<?php echo htmlspecialchars($app_settings['sidebar_header_sub']); ?>
</div>
</div>
</div>
<!-- Mobile Close Button -->
<button id="sidebar-close" class="absolute top-4 right-4 lg:hidden p-1.5 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-lg transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
</button>
</div>
<nav class="flex flex-col gap-2 px-4 flex-1">
<?php
$is_dashboard = ($current_page == 'dashboard.php');
$dash_classes = $is_dashboard
? $t('bg-emerald-100 text-emerald-700 font-semibold shadow-sm')
: $t('text-slate-500 font-medium hover:bg-emerald-50/60 hover:text-emerald-600');
?>
<a href="dashboard.php" class="flex items-center gap-3 px-4 py-3 rounded-2xl transition-all duration-300 group <?php echo $dash_classes; ?>">
<svg class="w-5 h-5 transition-transform duration-300 group-hover:scale-125" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg>
หน้าหลัก (Dashboard)
</a>
<?php
$is_search = ($current_page == 'search.php' || $current_page == 'patient_detail.php');
$search_classes = $is_search
? $t('bg-emerald-100 text-emerald-700 font-semibold shadow-sm')
: $t('text-slate-500 font-medium hover:bg-emerald-50/60 hover:text-emerald-600');
?>
<a href="search.php" class="flex items-center gap-3 px-4 py-3 rounded-2xl transition-all duration-300 group <?php echo $search_classes; ?>">
<svg class="w-5 h-5 transition-transform duration-300 group-hover:scale-125" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
ค้นหาเวชระเบียน
</a>
<div class="mt-4 mb-2 border-t <?php echo $t('border-emerald-500/10'); ?> pt-4"></div>
<?php
$is_ncd = ($current_page == 'ncd_registry.php');
$ncd_classes = $is_ncd
? $t('bg-emerald-100 text-emerald-700 font-semibold shadow-sm')
: $t('text-slate-500 font-medium hover:bg-emerald-50/60 hover:text-emerald-600');
?>
<div class="flex flex-col">
<a href="ncd_registry.php?clinic=ALL" class="flex items-center gap-3 px-4 py-3 rounded-2xl transition-all duration-300 group <?php echo $ncd_classes; ?>">
<svg class="w-5 h-5 transition-transform duration-300 group-hover:scale-125" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"></path></svg>
ทะเบียนผู้ป่วย NCD
</a>
<?php if ($is_ncd): ?>
<div class="ml-11 mt-1 flex flex-col gap-1 border-l-2 <?php echo $t('border-emerald-500/20'); ?> pl-3">
<?php
$curr_clinic = $_GET['clinic'] ?? 'ALL';
$ncd_clinics = [
'ALL' => 'DASHBOARD รวม',
'DM' => 'คลินิกเบาหวาน (DM)',
'HT' => 'คลินิกความดันฯ (HT)',
'DLP' => 'คลินิกไขมันฯ (DLP)',
'CKD' => 'คลินิกโรคไต (CKD)'
];
foreach ($ncd_clinics as $c_id => $c_name):
$is_active = ($curr_clinic === $c_id);
$sub_classes = $is_active
? $t('text-emerald-700 font-bold')
: $t('text-slate-500 hover:text-emerald-600');
?>
<a href="ncd_registry.php?clinic=<?= $c_id ?>" class="py-1.5 text-[14px] transition-colors <?= $sub_classes ?>">
<?= $c_name ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="mt-6 mb-2 border-t <?php echo $t('border-emerald-500/10'); ?> pt-6">
<span class="px-4 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-2 block">ช่วยเหลือ</span>
<?php
$is_manual = ($current_page == 'manual.php');
$manual_classes = $is_manual
? $t('bg-emerald-100 text-emerald-700 font-semibold shadow-sm')
: $t('text-slate-500 font-medium hover:bg-emerald-50/60 hover:text-emerald-600');
?>
<a href="manual.php" class="flex items-center gap-3 px-4 py-3 rounded-2xl transition-all duration-300 group <?php echo $manual_classes; ?>">
<svg class="w-5 h-5 transition-transform duration-300 group-hover:scale-125 group-hover:-rotate-12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
คู่มือการใช้งาน
</a>
</div>
<?php
// Only show settings to admins
$current_user = $_SESSION['account'] ?? '';
if (function_exists('is_admin') && is_admin($current_user)):
$is_settings = ($current_page == 'admin_settings.php');
$settings_classes = $is_settings
? $t('bg-emerald-100 text-emerald-700 font-semibold shadow-sm')
: $t('text-slate-500 font-medium hover:bg-emerald-50/60 hover:text-emerald-600');
?>
<div class="mt-6 mb-2 border-t <?php echo $t('border-emerald-500/10'); ?> pt-6">
<span class="px-4 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-2 block">สำหรับผู้ดูแลระบบ</span>
<a href="admin_settings.php" class="flex items-center gap-3 px-4 py-3 rounded-2xl transition-all duration-300 group <?php echo $settings_classes; ?>">
<svg class="w-5 h-5 transition-transform duration-500 group-hover:scale-110 group-hover:rotate-45" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
ตั้งค่าระบบ
</a>
</div>
<?php endif; ?>
<!-- Extensibility for other links if needed -->
</nav>
<div class="mt-auto p-4 border-t <?php echo $t('border-emerald-500/10'); ?>">
<a href="login.php?action=logout" class="flex items-center gap-3 px-4 py-3 rounded-2xl text-rose-500 font-medium hover:bg-rose-50/80 transition-all duration-300 mt-2 group">
<svg class="w-5 h-5 transition-transform duration-300 group-hover:scale-125 group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path></svg>
ออกจากระบบ
</a>
</div>
</aside>
<script>
// Sidebar Toggle Logic
document.addEventListener('DOMContentLoaded', () => {
const sidebar = document.getElementById('sidebar');
const toggleBtn = document.getElementById('sidebar-toggle');
const closeBtn = document.getElementById('sidebar-close');
const backdrop = document.getElementById('sidebar-backdrop');
function openSidebar() {
sidebar.classList.remove('-translate-x-full');
backdrop.classList.remove('hidden', 'pointer-events-none');
// Small delay to allow display:block to apply before animating opacity
setTimeout(() => backdrop.classList.remove('opacity-0'), 10);
document.body.style.overflow = 'hidden'; // Prevent background scrolling
}
function closeSidebar() {
sidebar.classList.add('-translate-x-full');
backdrop.classList.add('opacity-0', 'pointer-events-none');
setTimeout(() => backdrop.classList.add('hidden'), 300); // Wait for transition
document.body.style.overflow = ''; // Restore scrolling
}
if (toggleBtn) toggleBtn.addEventListener('click', openSidebar);
if (closeBtn) closeBtn.addEventListener('click', closeSidebar);
if (backdrop) backdrop.addEventListener('click', closeSidebar);
});
</script>
@@ -0,0 +1,33 @@
<?php
// components/layout_topbar.php
$current_page = basename($_SERVER['PHP_SELF']);
$is_settings_page = ($current_page === 'admin_settings.php');
$t = function($class_str) use ($is_settings_page) {
if ($is_settings_page) {
$class_str = str_replace('emerald', 'blue', $class_str);
$class_str = str_replace('teal', 'indigo', $class_str);
}
return $class_str;
};
?>
<header class="flex items-center justify-between px-4 lg:px-10 py-4 lg:py-5 bg-white/70 lg:bg-white/40 backdrop-blur-md border-b border-white/50 sticky top-0 z-20 shadow-sm lg:shadow-none">
<div class="flex items-center gap-2 lg:gap-4">
<button id="sidebar-toggle" class="lg:hidden p-2 text-slate-600 hover:bg-slate-100 rounded-lg transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
</button>
<span class="<?php echo $t('flex items-center gap-1 lg:gap-2 font-semibold text-emerald-800 bg-gradient-to-r from-emerald-50 to-teal-50 px-3 py-1.5 lg:px-4 lg:py-2 rounded-full shadow-sm text-xs lg:text-sm border border-emerald-200/60 backdrop-blur-sm transition-all hover:shadow-md'); ?>">
<svg class="<?php echo $t('w-4 h-4 text-emerald-600'); ?>" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<span id="live-clock" class="tracking-wide">กำลังโหลดเวลา...</span>
</span>
</div>
<div class="flex items-center gap-3 lg:gap-4">
<div class="flex flex-col items-end hidden sm:flex">
<span class="font-bold text-slate-700 text-sm lg:text-base"><?php echo htmlspecialchars($name); ?></span>
<span class="text-xs text-slate-500 font-medium"><?php echo htmlspecialchars($position); ?> (IP: <?php echo htmlspecialchars($ip); ?>)</span>
</div>
<div class="<?php echo $t('w-10 h-10 lg:w-12 lg:h-12 rounded-full bg-emerald-100 flex items-center justify-center text-emerald-600 font-bold text-lg lg:text-xl shadow-[0_4px_10px_rgba(16,185,129,0.2)] border-2 border-white ring-2 ring-emerald-50/50 cursor-pointer hover:scale-105 transition-transform'); ?>">
<?php echo strtoupper(mb_substr($name, 0, 1, 'UTF-8')); ?>
</div>
</div>
</header>