feat: add usage statistics section to activity log page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
GuavaPopper
2026-06-04 14:47:39 +07:00
parent 5ad1ba36b8
commit 1535c75138
3 changed files with 109 additions and 2 deletions
@@ -18,4 +18,38 @@ class ActivityLogController extends Controller
]);
return response()->json($logs);
}
public function stats()
{
$today = now()->startOfDay();
$week = now()->startOfWeek();
$month = now()->startOfMonth();
$byType = ActivityLog::selectRaw('action, COUNT(*) as total')
->groupBy('action')
->pluck('total', 'action');
$topUsers = ActivityLog::selectRaw('user_name, COUNT(*) as total')
->whereNotNull('user_name')
->groupBy('user_name')
->orderByDesc('total')
->limit(5)
->get();
$daily = ActivityLog::selectRaw('DATE(created_at) as day, COUNT(*) as total')
->where('created_at', '>=', now()->subDays(13)->startOfDay())
->groupBy('day')
->orderBy('day')
->pluck('total', 'day');
return response()->json([
'logins_today' => ActivityLog::where('action', 'login')->where('created_at', '>=', $today)->count(),
'logins_week' => ActivityLog::where('action', 'login')->where('created_at', '>=', $week)->count(),
'logins_month' => ActivityLog::where('action', 'login')->where('created_at', '>=', $month)->count(),
'actions_today' => ActivityLog::where('created_at', '>=', $today)->count(),
'by_type' => $byType,
'top_users' => $topUsers,
'daily' => $daily,
]);
}
}
+73 -1
View File
@@ -7,6 +7,7 @@
<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>
@@ -49,6 +50,41 @@
<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>
@@ -73,7 +109,43 @@
login: 'badge-ghost', logout: 'badge-ghost',
};
document.addEventListener('DOMContentLoaded', loadLogs);
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 {
+2 -1
View File
@@ -70,7 +70,8 @@ Route::middleware('auth')->group(function () {
Route::get('/backup', [BackupController::class, 'download'])->name('admin.backup');
Route::get('/logs', [ActivityLogController::class, 'index']);
Route::get('/logs', [ActivityLogController::class, 'index']);
Route::get('/stats', [ActivityLogController::class, 'stats']);
});
});
});