Files
2026-06-10 18:16:46 +07:00

156 lines
8.0 KiB
PHP

<!DOCTYPE html>
<html lang="id" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Log Aktivitas - WebGIS</title>
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
<style>
* { font-family: 'Plus Jakarta Sans', sans-serif; }
body { font-weight: 500; background-color: #f8fafc; }
h1, h2, h3, .btn { font-weight: 700 !important; letter-spacing: -0.01em; }
</style>
<link rel="stylesheet" href="{{ asset('css/genesis.css') }}">
</head>
<body class="min-h-screen app-body">
@include('partials.sidebar')
<main class="app-main">
<div class="max-w-screen-xl mx-auto px-4 py-8">
<div class="mb-8">
<h1 class="text-3xl font-bold flex items-center gap-3"><i class="fa-solid fa-clock-rotate-left text-primary"></i> Log Aktivitas</h1>
<p class="text-base-content/50 mt-1">200 aktivitas terakhir pengguna sistem.</p>
</div>
<!-- Stats cards -->
<div class="mt-2 grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="bg-white rounded-xl border border-base-200 p-4 shadow-sm">
<p class="text-xs text-base-content/50 uppercase font-bold">Login Hari Ini</p>
<p id="statLoginsToday" class="text-3xl font-bold text-primary mt-1">-</p>
</div>
<div class="bg-white rounded-xl border border-base-200 p-4 shadow-sm">
<p class="text-xs text-base-content/50 uppercase font-bold">Login Minggu Ini</p>
<p id="statLoginsWeek" class="text-3xl font-bold text-secondary mt-1">-</p>
</div>
<div class="bg-white rounded-xl border border-base-200 p-4 shadow-sm">
<p class="text-xs text-base-content/50 uppercase font-bold">Aksi Hari Ini</p>
<p id="statActionsToday" class="text-3xl font-bold text-accent mt-1">-</p>
</div>
<div class="bg-white rounded-xl border border-base-200 p-4 shadow-sm">
<p class="text-xs text-base-content/50 uppercase font-bold">Login Bulan Ini</p>
<p id="statLoginsMonth" class="text-3xl font-bold text-info mt-1">-</p>
</div>
</div>
<!-- Activity chart + top users -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="md:col-span-2 bg-white rounded-xl border border-base-200 p-4 shadow-sm">
<p class="text-xs font-bold uppercase opacity-50 mb-3">Aktivitas 14 Hari Terakhir</p>
<canvas id="chartDaily" height="80"></canvas>
</div>
<div class="bg-white rounded-xl border border-base-200 p-4 shadow-sm overflow-x-auto">
<p class="text-xs font-bold uppercase opacity-50 mb-3">Top Pengguna</p>
<table class="table table-xs w-full">
<thead><tr><th>Pengguna</th><th>Aksi</th></tr></thead>
<tbody id="bodyTopUsers"></tbody>
</table>
</div>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-base-200 overflow-x-auto">
<table class="table table-zebra table-sm w-full">
<thead>
<tr class="text-xs font-bold uppercase opacity-50">
<th>Waktu</th><th>Pengguna</th><th>Aksi</th><th>Deskripsi</th><th>IP</th>
</tr>
</thead>
<tbody id="bodyLogs"></tbody>
</table>
</div>
</div>
</main>
<div class="toast toast-end toast-bottom z-[9999]" id="toastBox"></div>
<script>
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
const actionBadge = {
create: 'badge-success', update: 'badge-warning', delete: 'badge-error',
bulk_delete: 'badge-error', import: 'badge-info', backup: 'badge-neutral',
login: 'badge-ghost', logout: 'badge-ghost',
};
document.addEventListener('DOMContentLoaded', () => { loadStats(); loadLogs(); });
let chartDaily = null;
async function loadStats() {
try {
const res = await fetch('{{ url('/api/admin/stats') }}', { headers: { 'X-CSRF-TOKEN': csrfToken } });
const s = await res.json();
document.getElementById('statLoginsToday').textContent = s.logins_today;
document.getElementById('statLoginsWeek').textContent = s.logins_week;
document.getElementById('statLoginsMonth').textContent = s.logins_month;
document.getElementById('statActionsToday').textContent = s.actions_today;
document.getElementById('bodyTopUsers').innerHTML = s.top_users.length
? s.top_users.map(u => `<tr><td class="font-semibold text-sm">${esc(u.user_name)}</td><td>${esc(String(u.total))}</td></tr>`).join('')
: '<tr><td colspan="2" class="text-center opacity-30 italic py-4">Belum ada data</td></tr>';
const labels = Object.keys(s.daily);
const values = Object.values(s.daily);
const ctx = document.getElementById('chartDaily').getContext('2d');
if (chartDaily) chartDaily.destroy();
chartDaily = new Chart(ctx, {
type: 'bar',
data: {
labels,
datasets: [{ label: 'Aktivitas', data: values, backgroundColor: '#6366f1', borderRadius: 4 }],
},
options: {
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } },
},
});
} catch (e) {
console.error('Gagal memuat statistik', e);
}
}
async function loadLogs() {
try {
const res = await fetch('{{ url('/api/admin/logs') }}', { headers: { 'X-CSRF-TOKEN': csrfToken } });
const logs = await res.json();
document.getElementById('bodyLogs').innerHTML = logs.length ? logs.map(l => `
<tr>
<td class="text-xs whitespace-nowrap opacity-70">${esc(l.created_at || '-')}</td>
<td class="font-semibold text-sm">${esc(l.user)}</td>
<td><span class="badge badge-sm ${actionBadge[l.action] || 'badge-ghost'} badge-outline">${esc(l.action)}</span></td>
<td class="text-sm">${esc(l.description)}</td>
<td class="font-mono text-xs opacity-50">${esc(l.ip || '-')}</td>
</tr>`).join('') : '<tr><td colspan="5" class="text-center py-10 opacity-30 italic">Belum ada aktivitas</td></tr>';
} catch (e) {
showToast('Gagal memuat log', 'error');
}
}
function showToast(msg, type = 'info') {
const t = document.createElement('div');
const cls = type === 'success' ? 'alert-success' : type === 'error' ? 'alert-error' : 'alert-info';
t.className = `alert ${cls} shadow-lg py-3 text-xs font-bold`;
t.innerHTML = `<span><i class="fa-solid fa-circle-info mr-2"></i>${esc(msg)}</span>`;
document.getElementById('toastBox').appendChild(t);
setTimeout(() => t.remove(), 3000);
}
</script>
</body>
</html>