197 lines
12 KiB
PHP
197 lines
12 KiB
PHP
<?php
|
|
require_once 'includes/auth.php';
|
|
|
|
// Check if user is admin
|
|
if (!isAdmin()) {
|
|
header("Location: index.php");
|
|
exit();
|
|
}
|
|
|
|
$pageTitle = 'ประวัติการใช้งาน | ระบบจัดการข้อมูลยานพาหนะ โรงพยาบาลเกาะสมุย';
|
|
$bodyClass = 'bg-slate-50 dark:bg-slate-900 overflow-hidden';
|
|
require_once 'includes/header.php';
|
|
?>
|
|
<div class="flex h-screen overflow-hidden" x-data="logManagement()">
|
|
|
|
<?php require_once 'includes/sidebar.php'; ?>
|
|
|
|
<!-- Main Content -->
|
|
<main class="flex-1 flex flex-col min-w-0 overflow-hidden page-transition">
|
|
<?php require_once 'includes/navbar.php'; ?>
|
|
|
|
<!-- Content Area -->
|
|
<div class="flex-1 overflow-auto p-4 sm:p-6 lg:p-8 custom-scrollbar relative">
|
|
|
|
<!-- Background decoration -->
|
|
<div class="absolute top-[0%] right-[10%] w-[30vw] h-[30vw] rounded-full bg-blue-300/10 dark:bg-blue-900/10 blur-[100px] pointer-events-none"></div>
|
|
|
|
<div class="max-w-7xl mx-auto relative z-10">
|
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-8">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-slate-800 dark:text-white flex items-center gap-2 drop-shadow-sm">
|
|
<i class="fa-solid fa-list-check text-primary-600 dark:text-primary-400"></i>
|
|
ประวัติการใช้งานระบบ (Activity Logs)
|
|
</h1>
|
|
<p class="text-slate-500 dark:text-slate-400 mt-1">แสดงประวัติการใช้งานระบบ และการกระทำต่างๆ ของผู้ใช้</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search and Filter -->
|
|
<div class="glass-card p-4 mb-6 rounded-2xl flex flex-col sm:flex-row gap-4">
|
|
<div class="relative flex-1">
|
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
<i class="fa-solid fa-search text-slate-400"></i>
|
|
</div>
|
|
<input type="text" x-model="searchQuery" class="glass-input block w-full pl-10 py-2.5 rounded-xl text-sm" placeholder="ค้นหาจากชื่อผู้ใช้, การกระทำ, หรือ IP Address...">
|
|
</div>
|
|
<div class="w-full sm:w-48">
|
|
<input type="date" x-model="dateFilter" class="glass-input block w-full py-2.5 px-3 rounded-xl text-sm text-slate-700 dark:text-slate-300">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="glass-card rounded-2xl overflow-hidden">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700/50">
|
|
<thead class="bg-slate-50/50 dark:bg-slate-800/50">
|
|
<tr>
|
|
<th scope="col" class="px-6 py-4 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider">วัน/เวลา</th>
|
|
<th scope="col" class="px-6 py-4 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider">ผู้ใช้งาน</th>
|
|
<th scope="col" class="px-6 py-4 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider">การกระทำ</th>
|
|
<th scope="col" class="px-6 py-4 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider">ข้อมูลอ้างอิง</th>
|
|
<th scope="col" class="px-6 py-4 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider">IP Address</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-100 dark:divide-slate-700/50 bg-white/30 dark:bg-slate-800/30">
|
|
<template x-if="isLoading">
|
|
<tr>
|
|
<td colspan="5" class="px-6 py-8 text-center text-slate-500">
|
|
<i class="fa-solid fa-spinner fa-spin mr-2"></i> กำลังโหลดข้อมูล...
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
|
|
<template x-if="!isLoading && filteredLogs.length === 0">
|
|
<tr>
|
|
<td colspan="5" class="px-6 py-8 text-center text-slate-500">
|
|
<div class="flex flex-col items-center justify-center">
|
|
<i class="fa-solid fa-inbox text-4xl mb-3 text-slate-300 dark:text-slate-600"></i>
|
|
<p>ไม่พบข้อมูลประวัติการใช้งาน</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
|
|
<template x-for="log in filteredLogs" :key="log.id">
|
|
<tr class="hover:bg-primary-50/50 dark:hover:bg-slate-700/50 transition-colors">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400" x-text="formatDate(log.created_at)"></td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0 h-8 w-8 rounded-full bg-primary-100 dark:bg-primary-900/50 flex items-center justify-center text-primary-600 dark:text-primary-400">
|
|
<i class="fa-solid fa-user text-xs"></i>
|
|
</div>
|
|
<div class="ml-3">
|
|
<div class="text-sm font-medium text-slate-900 dark:text-white" x-text="log.user_name || 'System / Unknown'"></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="px-2.5 py-1 inline-flex text-xs leading-5 font-semibold rounded-full"
|
|
:class="getActionBadgeClass(log.action)"
|
|
x-text="log.action">
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400">
|
|
<span x-show="log.table_name" class="font-mono text-xs bg-slate-100 dark:bg-slate-800 px-2 py-0.5 rounded" x-text="log.table_name"></span>
|
|
<span x-show="log.record_id" class="ml-1 text-xs" x-text="'#' + log.record_id"></span>
|
|
<span x-show="!log.table_name">-</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400 font-mono" x-text="log.ip_address || '-'"></td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
function logManagement() {
|
|
return {
|
|
isExpanded: true,
|
|
toggleSidebar() { this.isExpanded = !this.isExpanded; },
|
|
logs: [],
|
|
searchQuery: '',
|
|
dateFilter: '',
|
|
isLoading: false,
|
|
|
|
init() {
|
|
this.fetchLogs();
|
|
},
|
|
|
|
fetchLogs() {
|
|
this.isLoading = true;
|
|
fetch('api/logs.php')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.logs) {
|
|
this.logs = data.logs;
|
|
}
|
|
this.isLoading = false;
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
this.isLoading = false;
|
|
});
|
|
},
|
|
|
|
get filteredLogs() {
|
|
let result = this.logs;
|
|
|
|
if (this.dateFilter) {
|
|
result = result.filter(log => {
|
|
if(!log.created_at) return false;
|
|
return log.created_at.startsWith(this.dateFilter);
|
|
});
|
|
}
|
|
|
|
if (this.searchQuery !== '') {
|
|
const q = this.searchQuery.toLowerCase();
|
|
result = result.filter(log =>
|
|
(log.user_name && log.user_name.toLowerCase().includes(q)) ||
|
|
(log.action && log.action.toLowerCase().includes(q)) ||
|
|
(log.ip_address && log.ip_address.includes(q))
|
|
);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
formatDate(dateStr) {
|
|
if(!dateStr) return '-';
|
|
const d = new Date(dateStr);
|
|
return d.toLocaleString('th-TH', {
|
|
year: 'numeric', month: 'short', day: 'numeric',
|
|
hour: '2-digit', minute:'2-digit'
|
|
});
|
|
},
|
|
|
|
getActionBadgeClass(action) {
|
|
const act = action.toLowerCase();
|
|
if (act.includes('login')) return 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400';
|
|
if (act.includes('logout')) return 'bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-300';
|
|
if (act.includes('create')) return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400';
|
|
if (act.includes('delete')) return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400';
|
|
if (act.includes('update')) return 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400';
|
|
return 'bg-primary-100 text-primary-800 dark:bg-primary-900/30 dark:text-primary-400';
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<?php require_once 'includes/footer.php'; ?>
|