MIF_E31231070/resources/views/admin/aktivitas.blade.php

1391 lines
43 KiB
PHP

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Log Aktivitas - Admin</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link href="https://cdn.jsdelivr.net/npm/fontsource-roboto@5.1.0/index.min.css" rel="stylesheet">
<style>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width-collapsed: 80px;
--sidebar-width-expanded: 250px;
--success-green: #2ecc71;
--warning-orange: #f39c12;
--danger-red: #e74c3c;
--purple: #9b59b6;
--teal: #1abc9c;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f0f2f5;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar */
.sidebar {
width: var(--sidebar-width-collapsed);
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
box-shadow: 5px 0 20px rgba(0,0,0,0.1);
transition: width 0.3s ease;
overflow: hidden;
}
.sidebar:hover { width: var(--sidebar-width-expanded); }
.sidebar-header {
padding: 25px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 15px;
white-space: nowrap;
overflow: hidden;
}
.logo {
width: 40px; height: 40px; background: white;
border-radius: 12px; display: flex; align-items: center;
justify-content: center; box-shadow: 0 4px 12px rgba(0,0,0,0.2);
flex-shrink: 0;
}
.logo i { font-size: 20px; color: var(--dark-blue); }
.company-name {
font-weight: 700; font-size: 0.85rem; line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.3); opacity: 0;
transition: opacity 0.3s ease;
}
.sidebar:hover .company-name { opacity: 1; }
.nav-menu { padding: 25px 0; flex-grow: 1; overflow-y: auto; }
.nav-menu a { text-decoration: none; color: inherit; display: block; }
.nav-item {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-left: 4px solid transparent;
white-space: nowrap;
overflow: hidden;
}
.nav-group {
display: flex;
flex-direction: column;
}
.nav-parent {
justify-content: space-between;
}
.arrow {
margin-left: auto;
transition: transform 0.3s ease;
}
.nav-submenu {
display: none;
flex-direction: column;
padding-left: 40px;
transition: all 0.2s ease;
}
/* optional arrow */
.nav-group.open .arrow {
transform: rotate(180deg);
}
.nav-submenu a {
padding: 10px 20px;
font-size: 0.9rem;
opacity: 0.85;
}
.nav-submenu a:hover {
opacity: 1;
}
/* open state */
.nav-group.open .nav-submenu {
display: flex;
}
.nav-item:hover { background: rgba(255,255,255,0.08); border-left-color: var(--primary-blue); }
.nav-item.active { background: rgba(255,255,255,0.1); border-left-color: var(--primary-blue); }
.nav-item i { font-size: 18px; width: 24px; text-align: center; flex-shrink: 0; }
.nav-item span {
font-weight: 500; font-size: 0.95rem; opacity: 0;
transition: opacity 0.3s ease; display: inline-block;
}
.sidebar:hover .nav-item span { opacity: 1; }
.logout-btn {
padding: 15px 20px; cursor: pointer; transition: all 0.3s ease;
display: flex; align-items: center; gap: 15px;
border-top: 1px solid rgba(255,255,255,0.1); margin-top: auto;
white-space: nowrap; overflow: hidden; color: #fff;
background: none; border: none; width: 100%; text-align: left;
}
.logout-btn:hover { background: rgba(255,255,255,0.08); }
.logout-btn i { font-size: 18px; width: 24px; text-align: center; flex-shrink: 0; }
.logout-btn span { opacity: 0; transition: opacity 0.3s ease; }
.sidebar:hover .logout-btn span { opacity: 1; }
/* Main Content */
.main-content {
flex: 1;
margin-left: var(--sidebar-width-collapsed);
overflow-y: auto;
padding: 25px 30px;
background: #f0f2f5;
transition: margin-left 0.3s ease;
height: 100vh;
}
.sidebar:hover + .main-content { margin-left: var(--sidebar-width-expanded); }
/* Page Header */
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 25px;
flex-wrap: wrap;
gap: 15px;
}
.page-title-section h1 {
font-size: 1.8rem; font-weight: 700; color: var(--dark-blue);
margin-bottom: 5px; display: flex; align-items: center; gap: 12px;
}
.page-title-section h1 i { color: var(--primary-blue); }
.page-title-section p { color: #64748b; font-size: 0.95rem; }
.header-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.btn {
display: inline-flex; align-items: center; gap: 8px;
padding: 10px 18px; border-radius: 10px; font-size: 0.9rem;
font-weight: 500; cursor: pointer; transition: all 0.3s ease;
border: none; text-decoration: none; font-family: inherit;
}
.btn-primary {
background: linear-gradient(135deg, var(--dark-blue), #2563eb);
color: white; box-shadow: 0 3px 10px rgba(30, 58, 95, 0.25);
}
.btn-primary:hover { box-shadow: 0 5px 15px rgba(30, 58, 95, 0.35); transform: translateY(-1px); }
.btn-outline {
background: white; color: var(--dark-blue); border: 2px solid #e2e8f0;
}
.btn-outline:hover { border-color: var(--primary-blue); color: var(--primary-blue); }
.btn-danger { background: linear-gradient(135deg, #c0392b, #e74c3c); color: white; }
.btn-danger:hover { transform: translateY(-1px); }
/* Stats Cards */
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
background: white; border-radius: 12px; padding: 18px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
display: flex; align-items: center; gap: 15px;
transition: all 0.3s;
}
.stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.stat-icon {
width: 48px; height: 48px; border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 1.2rem; flex-shrink: 0;
}
.stat-icon.login { background: rgba(122,178,211,0.15); color: var(--primary-blue); }
.stat-icon.transaction { background: rgba(46,204,113,0.15); color: var(--success-green); }
.stat-icon.account { background: rgba(155,89,182,0.15); color: var(--purple); }
.stat-icon.alert { background: rgba(231,76,60,0.15); color: var(--danger-red); }
.stat-info h4 { font-size: 0.75rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
.stat-value { font-size: 1.5rem; font-weight: 700; color: var(--dark-blue); }
/* Filter Bar */
.filter-bar {
background: white; border-radius: 14px; padding: 20px;
margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);
display: flex; gap: 15px; align-items: flex-end; flex-wrap: wrap;
}
.filter-group { flex: 1; min-width: 150px; }
.filter-label {
display: block; font-size: 0.8rem; font-weight: 600;
color: #64748b; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px;
}
.filter-input, .filter-select {
width: 100%; padding: 10px 12px; border: 2px solid #e2e8f0;
border-radius: 8px; font-size: 0.9rem; font-family: inherit;
color: #334155; background: white; transition: all 0.3s;
}
.filter-input:focus, .filter-select:focus {
outline: none; border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(122, 178, 211, 0.15);
}
/* Activity Layout */
.activity-layout {
display: grid;
grid-template-columns: 1fr 320px;
gap: 20px;
align-items: start;
}
@media (max-width: 1024px) {
.activity-layout { grid-template-columns: 1fr; }
}
/* Live Indicator */
.live-badge {
display: inline-flex; align-items: center; gap: 6px;
padding: 5px 12px; background: rgba(231,76,60,0.1);
border-radius: 20px; font-size: 0.8rem; color: #e74c3c; font-weight: 600;
}
.live-dot {
width: 8px; height: 8px; background: #e74c3c;
border-radius: 50%; animation: blink 1.5s infinite;
}
@keyframes blink { 0%,100% { opacity:1; } 50% { opacity:0.3; } }
/* Activity Feed */
.feed-card {
background: white; border-radius: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
overflow: hidden;
}
.feed-header {
padding: 18px 22px; border-bottom: 1px solid #f1f5f9;
display: flex; justify-content: space-between; align-items: center;
}
.feed-title {
font-size: 1.1rem; font-weight: 600; color: var(--dark-blue);
display: flex; align-items: center; gap: 10px;
}
.feed-body { padding: 0; max-height: calc(100vh - 280px); overflow-y: auto; }
.feed-body::-webkit-scrollbar { width: 6px; }
.feed-body::-webkit-scrollbar-track { background: #f1f5f9; }
.feed-body::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
/* Timeline */
.date-group {
padding: 10px 22px;
background: #f8fafc;
border-bottom: 1px solid #f1f5f9;
font-size: 0.8rem;
font-weight: 600;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.5px;
position: sticky;
top: 0;
z-index: 2;
}
.activity-item {
display: flex;
gap: 15px;
padding: 16px 22px;
border-bottom: 1px solid #f8fafc;
transition: background 0.2s;
cursor: pointer;
position: relative;
}
.activity-item:hover { background: #f8fafc; }
.activity-item:last-child { border-bottom: none; }
.activity-icon-wrap {
position: relative;
flex-shrink: 0;
}
.activity-icon {
width: 42px; height: 42px; border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 1rem;
}
.activity-icon.login { background: rgba(122,178,211,0.15); color: var(--primary-blue); }
.activity-icon.logout { background: rgba(148,163,184,0.2); color: #64748b; }
.activity-icon.booking { background: rgba(243,156,18,0.15); color: var(--warning-orange); }
.activity-icon.payment { background: rgba(46,204,113,0.15); color: var(--success-green); }
.activity-icon.register { background: rgba(155,89,182,0.15); color: var(--purple); }
.activity-icon.update { background: rgba(26,188,156,0.15); color: var(--teal); }
.activity-icon.delete { background: rgba(231,76,60,0.15); color: var(--danger-red); }
.activity-icon.document { background: rgba(52,152,219,0.15); color: #3498db; }
.activity-icon.alert-act { background: rgba(231,76,60,0.15); color: var(--danger-red); }
.activity-content { flex: 1; min-width: 0; }
.activity-user {
font-weight: 600; color: var(--dark-blue);
font-size: 0.95rem; margin-bottom: 3px;
display: flex; align-items: center; gap: 8px;
}
.activity-user .role-badge {
font-size: 0.65rem; padding: 2px 8px;
border-radius: 10px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.5px;
}
.role-badge.user { background: rgba(122,178,211,0.15); color: var(--primary-blue); }
.role-badge.admin { background: rgba(231,76,60,0.12); color: #e74c3c; }
.activity-desc {
font-size: 0.85rem; color: #64748b; line-height: 1.5;
}
.activity-desc strong { color: #334155; }
.activity-meta {
display: flex; align-items: center; gap: 15px;
margin-top: 8px; font-size: 0.78rem; color: #94a3b8;
}
.activity-meta span { display: flex; align-items: center; gap: 5px; }
.activity-meta i { font-size: 0.7rem; }
.activity-time {
flex-shrink: 0;
text-align: right;
display: flex;
flex-direction: column;
gap: 5px;
}
.time-value {
font-size: 0.85rem;
font-weight: 600;
color: var(--dark-blue);
font-variant-numeric: tabular-nums;
}
.time-ago {
font-size: 0.72rem;
color: #94a3b8;
background: #f1f5f9;
padding: 3px 8px;
border-radius: 10px;
}
/* Sidebar Widget */
.widget-card {
background: white; border-radius: 14px;
padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);
margin-bottom: 20px;
}
.widget-title {
font-size: 1rem; font-weight: 600; color: var(--dark-blue);
margin-bottom: 15px; display: flex; align-items: center; gap: 8px;
}
.widget-title i { color: var(--primary-blue); }
/* User Activity List */
.user-activity-item {
display: flex; align-items: center; gap: 12px;
padding: 12px 0; border-bottom: 1px solid #f1f5f9;
}
.user-activity-item:last-child { border-bottom: none; }
.user-avatar {
width: 40px; height: 40px; border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-weight: 700; color: white; font-size: 0.9rem;
flex-shrink: 0;
}
.user-info { flex: 1; }
.user-name { font-weight: 600; font-size: 0.9rem; color: var(--dark-blue); }
.user-last-action { font-size: 0.78rem; color: #94a3b8; }
.user-last-time { font-size: 0.75rem; color: #64748b; font-weight: 500; text-align: right; }
/* Activity Type Legend */
.legend-list { display: flex; flex-direction: column; gap: 10px; }
.legend-item {
display: flex; align-items: center; gap: 10px;
font-size: 0.85rem; color: #475569;
}
.legend-icon {
width: 30px; height: 30px; border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 0.75rem;
}
.legend-count {
margin-left: auto;
font-weight: 600;
color: var(--dark-blue);
background: #f1f5f9;
padding: 2px 8px;
border-radius: 10px;
font-size: 0.78rem;
}
/* Toast */
.toast-container { position: fixed; top: 20px; right: 20px; z-index: 2000; display: flex; flex-direction: column; gap: 10px; }
.toast {
display: flex; align-items: center; gap: 12px; padding: 14px 18px;
background: white; border-radius: 10px; box-shadow: 0 8px 25px rgba(0,0,0,0.15);
min-width: 280px; animation: slideInRight 0.3s ease;
border-left: 4px solid var(--success-green);
}
.toast.error { border-left-color: var(--danger-red); }
.toast.warning { border-left-color: var(--warning-orange); }
@keyframes slideInRight { from { transform: translateX(100%); opacity:0; } to { transform: translateX(0); opacity:1; } }
.toast-icon { font-size: 1.1rem; }
.toast-message { flex: 1; font-size: 0.85rem; color: #334155; font-weight: 500; }
.toast-close { background: none; border: none; color: #94a3b8; cursor: pointer; }
/* Modal */
.modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
z-index: 1000; display: none; align-items: center; justify-content: center; padding: 20px;
}
.modal-overlay.show { display: flex; animation: fadeIn 0.3s ease; }
@keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
.modal-content {
background: white; border-radius: 16px; max-width: 550px; width: 100%;
max-height: 90vh; overflow-y: auto; animation: slideUp 0.3s ease;
}
@keyframes slideUp { from { transform: translateY(20px); opacity:0; } to { transform: translateY(0); opacity:1; } }
.modal-header { padding: 22px; border-bottom: 1px solid #f1f5f9; display: flex; justify-content: space-between; align-items: center; }
.modal-title { font-size: 1.2rem; font-weight: 600; color: var(--dark-blue); }
.modal-close { width: 32px; height: 32px; border-radius: 8px; border: none; background: #f1f5f9; cursor: pointer; display: flex; align-items: center; justify-content: center; color: #64748b; transition: all 0.3s; }
.modal-close:hover { background: #fee2e2; color: #ef4444; }
.modal-body { padding: 22px; }
.modal-footer { padding: 18px 22px; border-top: 1px solid #f1f5f9; display: flex; gap: 10px; justify-content: flex-end; }
.detail-row { display: flex; padding: 8px 0; border-bottom: 1px solid #f8fafc; }
.detail-label { width: 140px; font-size: 0.85rem; color: #94a3b8; font-weight: 500; }
.detail-value { flex: 1; font-size: 0.9rem; color: #1e293b; font-weight: 500; }
/* Responsive */
@media (max-width: 768px) {
.main-content { padding: 15px; }
.page-title-section h1 { font-size: 1.4rem; }
.filter-bar { flex-direction: column; }
.filter-group { width: 100%; }
.activity-time { display: none; }
.stats-row { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
.stats-row { grid-template-columns: 1fr; }
}
/* Empty state */
.empty-state {
text-align: center; padding: 60px 20px;
}
.empty-state i { font-size: 3rem; color: #cbd5e1; margin-bottom: 15px; }
.empty-state h3 { color: #64748b; font-weight: 600; margin-bottom: 5px; }
.empty-state p { color: #94a3b8; font-size: 0.85rem; }
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Carani Bhanu Balakosa</div>
</div>
<div class="nav-menu" id="navMenu">
<a href="{{ route('admin.welcome') }}"
class="nav-item {{ request()->routeIs('admin.welcome') ? 'active' : '' }}">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</a>
<div class="nav-group" id="propertiMenu">
<div class="nav-item nav-parent" onclick="toggleMenu('propertiMenu')">
<i class="fas fa-house"></i>
<span>Properti</span>
<i class="fas fa-chevron-down arrow"></i>
</div>
<div class="nav-submenu">
<a href="{{ route('admin.data_rumah') }}" class="nav-subitem">
<span>Data Rumah</span>
</a>
<a href="{{ route('admin.perumahan') }}" class="nav-subitem">
<span>Perumahan</span>
</a>
</div>
</div>
<a href="{{ route('admin.halaman_verifikasi') }}"
class="nav-item {{ request()->routeIs('admin.halaman_verifikasi') ? 'active' : '' }}">
<i class="fas fa-check-circle"></i>
<span>Verifikasi Data</span>
</a>
<a href="{{ route('admin.monitoring-pemesanan') }}"
class="nav-item {{ request()->routeIs('admin.monitoring-pemesanan') ? 'active' : '' }}">
<i class="fas fa-chart-line"></i>
<span>Monitoring</span>
</a>
<a href="{{ route('admin.laporan_penjualan') }}"
class="nav-item {{ request()->routeIs('admin.laporan_penjualan') ? 'active' : '' }}">
<i class="fas fa-chart-bar"></i>
<span>Laporan Penjualan</span>
</a>
<a href="{{ route('admin.pesan_pelanggan') }}"
class="nav-item {{ request()->routeIs('admin.pesan_pelanggan') ? 'active' : '' }}">
<i class="fas fa-comments"></i>
<span>Pesan Pelanggan</span>
</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="logout-btn" style="width:100%; background:none; border:none; cursor:pointer; text-align:left;">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</button>
</form>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<div class="page-header">
<div class="page-title-section">
<h1><i class="fas fa-clock-rotate-left"></i> Log Aktivitas Pengguna</h1>
<p>Pantau seluruh aktivitas yang dilakukan oleh pengguna secara real-time</p>
</div>
<div class="header-actions">
<button class="btn btn-outline" onclick="refreshActivity()"><i class="fas fa-sync-alt"></i> Refresh</button>
<button class="btn btn-danger" onclick="confirmClearLogs()"><i class="fas fa-trash"></i> Bersihkan Log</button>
</div>
</div>
<!-- Stats -->
<!-- <div class="stats-row">
<div class="stat-card">
<div class="stat-icon login"><i class="fas fa-sign-in-alt"></i></div>
<div class="stat-info">
<h4>Login Hari Ini</h4>
<div class="stat-value" id="loginCount">24</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon transaction"><i class="fas fa-exchange-alt"></i></div>
<div class="stat-info">
<h4>Transaksi</h4>
<div class="stat-value" id="transCount">18</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon account"><i class="fas fa-user-plus"></i></div>
<div class="stat-info">
<h4>Pendaftaran Baru</h4>
<div class="stat-value" id="regCount">5</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon alert"><i class="fas fa-exclamation-triangle"></i></div>
<div class="stat-info">
<h4>Aktivitas Mencurigakan</h4>
<div class="stat-value" id="alertCount">2</div>
</div>
</div>
</div> -->
<!-- Filter Bar -->
<div class="filter-bar">
<div class="filter-group">
<label class="filter-label">Tanggal</label>
<input type="date" class="filter-input" id="filterDate">
</div>
<div class="filter-group">
<label class="filter-label">Tipe Aktivitas</label>
<select class="filter-select" id="filterType">
<option value="all">Semua Aktivitas</option>
<option value="login">Login</option>
<option value="logout">Logout</option>
<option value="register">Pendaftaran</option>
<option value="booking">Booking</option>
<option value="payment">Pembayaran</option>
<option value="document">Upload Dokumen</option>
<option value="update">Update Profil</option>
<option value="delete">Penghapusan</option>
<option value="alert">Peringatan</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">Cari Pengguna</label>
<input type="text" class="filter-input" placeholder="Nama atau email..." id="filterSearch">
</div>
<button class="btn btn-primary" onclick="applyFilters()" style="align-self:flex-end">
<i class="fas fa-filter"></i> Terapkan
</button>
</div>
<!-- Activity Layout -->
<div class="activity-layout">
<!-- Feed -->
<div class="feed-card">
<div class="feed-header">
<div class="feed-title">
<i class="fas fa-stream"></i> Timeline Aktivitas
</div>
<span style="font-size:0.85rem;color:#64748b"><strong id="totalFiltered">156</strong> aktivitas ditemukan</span>
</div>
<div class="feed-body" id="feedBody">
<!-- Activities will be populated by JS -->
</div>
<div id="paginationWrapper"></div>
</div>
<!-- Sidebar Widgets -->
<div>
<!-- Top Active Users -->
<div class="widget-card">
<div class="widget-title"><i class="fas fa-users"></i> Pengguna Aktif</div>
<div id="topUsers"></div>
</div>
</div>
</div>
</div>
<!-- Detail Modal -->
<div class="modal-overlay" id="detailModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">Detail Aktivitas</div>
<button class="modal-close" onclick="closeModal()"><i class="fas fa-times"></i></button>
</div>
<div class="modal-body" id="modalBody"></div>
<div class="modal-footer">
<button class="btn btn-outline" onclick="closeModal()">Tutup</button>
</div>
</div>
</div>
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<script>
const activityData = @json($recentActivities);
/**
* =========================================
* AUTO ICON BERDASARKAN TIPE AKTIVITAS
* =========================================
*/
function getActivityIcon(type) {
switch ((type || '').toLowerCase()) {
case 'login':
return 'fa-sign-in-alt';
case 'logout':
return 'fa-sign-out-alt';
case 'register':
return 'fa-user-plus';
case 'booking':
return 'fa-calendar-check';
case 'payment':
return 'fa-credit-card';
case 'document':
return 'fa-file-upload';
case 'chatbot':
return 'fa-robot';
case 'update':
return 'fa-user-edit';
case 'delete':
return 'fa-trash';
case 'alert':
return 'fa-triangle-exclamation';
default:
return 'fa-bell';
}
}
/**
* =========================================
* AUTO DETECT TYPE
* =========================================
*/
function inferType(text) {
text = (text || '').toLowerCase();
if (text.includes('login')) return 'login';
if (text.includes('logout')) return 'logout';
if (
text.includes('daftar') ||
text.includes('register')
) return 'register';
if (
text.includes('booking') ||
text.includes('pesan')
) return 'booking';
if (
text.includes('bayar') ||
text.includes('payment')
) return 'payment';
if (
text.includes('upload') ||
text.includes('dokumen')
) return 'document';
if (
text.includes('chatbot') ||
text.includes('chat') ||
text.includes('ai')
) return 'chatbot';
if (
text.includes('update') ||
text.includes('ubah')
) return 'update';
if (
text.includes('hapus') ||
text.includes('delete')
) return 'delete';
return 'alert';
}
/**
* =========================================
* FORMAT DATE ONLY
* =========================================
*/
function formatDateOnly(time) {
if (!time) return null;
const dt = new Date(time);
if (isNaN(dt.getTime())) return null;
const year = dt.getFullYear();
const month = String(dt.getMonth() + 1).padStart(2, '0');
const day = String(dt.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* =========================================
* FORMAT DATETIME
* =========================================
*/
function formatTime(time) {
if (!time) return '-';
const dt = new Date(time);
if (isNaN(dt.getTime())) {
console.log('INVALID DATE:', time);
return '-';
}
const now = new Date();
const diffMs = now - dt;
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHour / 24);
// Baru saja
if (diffSec < 60) {
return 'Baru saja';
}
// Menit
if (diffMin < 60) {
return `${diffMin} menit yang lalu`;
}
// Jam
if (diffHour < 24) {
return `${diffHour} jam yang lalu`;
}
// Kemarin
if (diffDay === 1) {
return 'Kemarin';
}
// Hari
if (diffDay < 7) {
return `${diffDay} hari yang lalu`;
}
// Fallback
return dt.toLocaleString('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
/**
* =========================================
* NORMALIZE DATA
* =========================================
*/
function normalize(data) {
return (data || []).map(item => {
const text =
item.text ??
item.aktivitas ??
item.desc ??
item.message ??
'';
const activityType =
item.type ??
item.tipe ??
inferType(text);
const createdAt =
item.created_at ??
item.time ??
null;
return {
id: item.id ?? null,
icon:
item.icon ??
getActivityIcon(activityType),
user:
item.user ??
item.name ??
'System',
role:
item.role ??
'user',
email:
item.email ??
'-',
text: text,
type:
(activityType || 'alert')
.toLowerCase(),
time: createdAt,
date: formatDateOnly(createdAt)
};
});
}
/**
* =========================================
* STATE
* =========================================
*/
let cleanedData = normalize(activityData);
let filteredData = [...cleanedData];
/**
* =========================================
* RENDER ACTIVITIES
* =========================================
*/
function renderActivities(data) {
const feedBody =
document.getElementById('feedBody');
feedBody.innerHTML = '';
document.getElementById('totalFiltered')
.textContent = data.length;
if (!data.length) {
feedBody.innerHTML = `
<div style="
padding:30px;
text-align:center;
color:#94a3b8;
">
Tidak ada aktivitas ditemukan
</div>
`;
return;
}
data.forEach(item => {
const div = document.createElement('div');
div.className = 'activity-item';
div.innerHTML = `
<div style="
display:flex;
gap:14px;
width:100%;
align-items:flex-start;
">
<!-- ICON -->
<div style="
width:42px;
height:42px;
border-radius:12px;
background:#eef2ff;
color:#4f46e5;
display:flex;
align-items:center;
justify-content:center;
flex-shrink:0;
">
<i class="fas ${item.icon}"></i>
</div>
<!-- CONTENT -->
<div style="flex:1">
<div style="
display:flex;
justify-content:space-between;
gap:10px;
align-items:flex-start;
">
<div>
<div style="
font-weight:600;
color:#0f172a;
">
${item.user}
</div>
<div style="
font-size:13px;
color:#64748b;
margin-top:3px;
">
${item.email}
</div>
</div>
<div style="
display:flex;
flex-direction:column;
align-items:flex-end;
gap:4px;
">
<span style="
font-size:12px;
color:#94a3b8;
white-space:nowrap;
">
${formatTime(item.time)}
</span>
<span style="
font-size:11px;
color:#cbd5e1;
">
${(() => {
if (!item.time) return '-';
const dt = new Date(item.time);
if (isNaN(dt.getTime())) return '-';
return dt.toLocaleTimeString('id-ID', {
hour: '2-digit',
minute: '2-digit'
});
})()}
</span>
</div>
</div>
<div style="
margin-top:10px;
color:#334155;
line-height:1.5;
">
${item.text}
</div>
<div style="margin-top:12px">
<span style="
display:inline-block;
padding:5px 12px;
border-radius:30px;
background:#eef2ff;
color:#4338ca;
font-size:11px;
font-weight:600;
text-transform:capitalize;
">
${item.type}
</span>
</div>
</div>
</div>
`;
feedBody.appendChild(div);
});
}
/**
* =========================================
* APPLY FILTERS
* =========================================
*/
function applyFilters() {
const selectedDate =
document.getElementById('filterDate')
.value;
const selectedType =
document.getElementById('filterType')
.value
.toLowerCase();
const search =
document.getElementById('filterSearch')
.value
.toLowerCase();
filteredData = cleanedData.filter(item => {
/**
* FILTER TYPE
*/
const matchType =
selectedType === 'all' ||
item.type === selectedType;
/**
* FILTER DATE
*/
const matchDate =
!selectedDate ||
item.date === selectedDate;
/**
* FILTER SEARCH
*/
const matchSearch =
!search ||
(item.user || '')
.toLowerCase()
.includes(search) ||
(item.email || '')
.toLowerCase()
.includes(search) ||
(item.text || '')
.toLowerCase()
.includes(search);
return (
matchType &&
matchDate &&
matchSearch
);
});
renderActivities(filteredData);
}
// RELOAD HALAMAN
function refreshActivity() {
location.reload();
}
// BERSIHKAN LOG
function confirmClearLogs() {
const confirmDelete = confirm(
'Yakin ingin menghapus semua log aktivitas?'
);
if (!confirmDelete) return;
fetch("{{ route('admin.clear_logs') }}", {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN':
'{{ csrf_token() }}',
'Accept':
'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(data.message);
location.reload();
} else {
alert('Gagal menghapus log.');
}
})
.catch(error => {
console.error(error);
alert('Terjadi kesalahan.');
});
}
/**
* =========================================
* TOP ACTIVE USERS
* =========================================
*/
function renderTopUsers() {
const container =
document.getElementById('topUsers');
if (!container) return;
const counts = {};
cleanedData.forEach(item => {
if (!counts[item.user]) {
counts[item.user] = {
total: 0,
email: item.email
};
}
counts[item.user].total++;
});
const sortedUsers =
Object.entries(counts)
.sort((a, b) => b[1].total - a[1].total)
.slice(0, 5);
container.innerHTML = '';
sortedUsers.forEach(([name, data]) => {
container.innerHTML += `
<div style="
display:flex;
justify-content:space-between;
align-items:center;
padding:12px 0;
border-bottom:1px solid #f1f5f9;
">
<div>
<div style="
font-weight:600;
color:#0f172a;
">
${name}
</div>
<div style="
font-size:12px;
color:#64748b;
margin-top:2px;
">
${data.email}
</div>
</div>
<div style="
background:#eef2ff;
color:#4338ca;
padding:4px 10px;
border-radius:20px;
font-size:12px;
font-weight:600;
">
${data.total}
</div>
</div>
`;
});
}
/**
* =========================================
* MODAL
* =========================================
*/
function closeModal() {
document
.getElementById('detailModal')
.classList
.remove('active');
}
/**
* =========================================
* INIT
* =========================================
*/
document.addEventListener('DOMContentLoaded', function () {
renderActivities(filteredData);
renderTopUsers();
/**
* AUTO FILTER
*/
document
.getElementById('filterDate')
.addEventListener('change', applyFilters);
document
.getElementById('filterType')
.addEventListener('change', applyFilters);
document
.getElementById('filterSearch')
.addEventListener('keyup', applyFilters);
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
if (!sidebar || !mainContent) {
console.error("sidebar / mainContent tidak ditemukan");
return;
}
const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
function closeAllDropdowns() {
document.querySelectorAll('.nav-group.open').forEach(el => {
el.classList.remove('open');
});
}
// INIT STATE
if (isCollapsed) {
sidebar.classList.add('collapsed');
mainContent.classList.add('expanded');
closeAllDropdowns();
}
// HOVER IN
sidebar.addEventListener('mouseenter', function () {
if (this.classList.contains('collapsed')) {
this.classList.add('hovering');
}
});
// HOVER OUT
sidebar.addEventListener('mouseleave', function () {
this.classList.remove('hovering');
closeAllDropdowns();
});
});
/* ===================================================
TOGGLE DROPDOWN (INI WAJIB GLOBAL BIAR onclick WORK)
=================================================== */
window.toggleMenu = function (id) {
const sidebar = document.getElementById('sidebar');
const el = document.getElementById(id);
if (!el || !sidebar) return;
// kalau sidebar collapsed DAN tidak hover → blok
const isBlocked =
sidebar.classList.contains('collapsed') &&
!sidebar.classList.contains('hovering');
if (isBlocked) return;
el.classList.toggle('open');
};
</script>
</body>
</html>