feat: add usage statistics section to activity log page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user