Initial commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
<div class="container mt-4">
|
||||
<div class="card modern-card shadow-sm border-0 rounded-4">
|
||||
<div class="card-body p-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<h5>Activity Log</h5>
|
||||
<button class="btn btn-secondary btn-sm" id="btnReload">
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="tableActivity" class="table modern-table align-middle w-100">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Waktu</th>
|
||||
<th>User</th>
|
||||
<th>Module</th>
|
||||
<th>Action</th>
|
||||
<th>Deskripsi</th>
|
||||
<th>Status</th>
|
||||
<th>IP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
let table = $('#tableActivity').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: {
|
||||
url: "<?= base_url('dashboard/get_activity_datatable'); ?>",
|
||||
type: "POST"
|
||||
},
|
||||
order: [[0, 'desc']],
|
||||
columns: [
|
||||
{ data: 'created_at' },
|
||||
{ data: 'nama' },
|
||||
{ data: 'module' },
|
||||
{ data: 'action' },
|
||||
{ data: 'description' },
|
||||
{ data: 'status' },
|
||||
{ data: 'ip_address' }
|
||||
],
|
||||
|
||||
columnDefs: [
|
||||
|
||||
|
||||
// ================= MODULE =================
|
||||
{
|
||||
targets: 2,
|
||||
render: function (data) {
|
||||
return `<span class="badge bg-light text-dark border">${data ?? '-'}</span>`;
|
||||
}
|
||||
},
|
||||
|
||||
// ================= ACTION =================
|
||||
{
|
||||
targets: 3,
|
||||
render: function (data) {
|
||||
return `<span class="text-muted">${data ?? '-'}</span>`;
|
||||
}
|
||||
},
|
||||
|
||||
// ================= STATUS =================
|
||||
{
|
||||
targets: 5,
|
||||
render: function (data) {
|
||||
|
||||
let cls = "text-muted";
|
||||
|
||||
if (data === "success") cls = "text-success";
|
||||
if (data === "error") cls = "text-danger";
|
||||
if (data === "warning") cls = "text-warning";
|
||||
|
||||
return `<span class="${cls}">${data ?? '-'}</span>`;
|
||||
}
|
||||
},
|
||||
|
||||
// ================= IP =================
|
||||
{
|
||||
targets: 6,
|
||||
render: function (data) {
|
||||
return `<small class="text-muted">${data ?? '-'}</small>`;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
$('#btnReload').click(function () {
|
||||
table.ajax.reload(null, false);
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,849 @@
|
||||
<?php $role = $this->session->userdata('role'); ?>
|
||||
|
||||
<style>
|
||||
/* ======================================================
|
||||
DASHBOARD CLEAN - Orange Theme (Minimal & Elegant)
|
||||
====================================================== */
|
||||
:root {
|
||||
/* Orange Theme Colors */
|
||||
--orange-primary: #ff8c00;
|
||||
--orange-light: #ffd4a3;
|
||||
--orange-dark: #e67700;
|
||||
--orange-gradient: linear-gradient(135deg, #ff8c00 0%, #ff7b00 50%, #e67700 100%);
|
||||
|
||||
/* Neutral Colors */
|
||||
--white: #ffffff;
|
||||
--light-bg: #f8fafc;
|
||||
--border-light: #e2e8f0;
|
||||
--text-dark: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
|
||||
/* Shadows - Subtle */
|
||||
--shadow-sm: 0 2px 8px rgba(0,0,0,0.06);
|
||||
--shadow-md: 0 8px 24px rgba(0,0,0,0.08);
|
||||
--shadow-lg: 0 16px 40px rgba(0,0,0,0.10);
|
||||
}
|
||||
|
||||
/* Modern Card - Clean & Minimal */
|
||||
.modern-card {
|
||||
background: var(--white);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modern-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: var(--orange-gradient);
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.modern-card:hover {
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.modern-card:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
SUMMARY CARDS - Orange Accents
|
||||
====================================================== */
|
||||
.summary-card {
|
||||
background: var(--white);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.summary-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
color: var(--white);
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 6px 20px rgba(255,140,0,0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Orange-based icon colors - subtle variations */
|
||||
.summary-card:nth-child(1) .summary-icon { background: var(--orange-gradient); }
|
||||
.summary-card:nth-child(2) .summary-icon {
|
||||
background: linear-gradient(135deg, var(--orange-light) 0%, var(--orange-primary) 100%);
|
||||
}
|
||||
.summary-card:nth-child(3) .summary-icon {
|
||||
background: linear-gradient(135deg, #ff9500 0%, var(--orange-primary) 100%);
|
||||
}
|
||||
.summary-card:nth-child(4) .summary-icon {
|
||||
background: linear-gradient(135deg, var(--orange-dark) 0%, #d66a00 100%);
|
||||
}
|
||||
.summary-card:nth-child(5) .summary-icon {
|
||||
background: linear-gradient(135deg, #ffb366 0%, var(--orange-primary) 100%);
|
||||
}
|
||||
.summary-card:nth-child(6) .summary-icon {
|
||||
background: linear-gradient(135deg, #ffcc80 0%, #ffad42 100%);
|
||||
}
|
||||
.summary-card:nth-child(7) .summary-icon {
|
||||
background: linear-gradient(135deg, #ffd180 0%, #ffab40 100%);
|
||||
}
|
||||
|
||||
/* Quick Action Cards */
|
||||
.quick-action .summary-card {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.quick-action .summary-card:hover .summary-icon {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 10px 30px rgba(255,140,0,0.4);
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
AI INSIGHT - Clean Cards
|
||||
====================================================== */
|
||||
.ai-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
max-height: 340px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ai-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.ai-box::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.ai-box::-webkit-scrollbar-thumb {
|
||||
background: var(--orange-light);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.ai-item {
|
||||
padding: 16px 20px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
border-left: 4px solid var(--orange-primary);
|
||||
background: #fef7ee;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid #fee2c7;
|
||||
}
|
||||
|
||||
.ai-item:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.ai-item i {
|
||||
font-size: 16px;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.9;
|
||||
color: var(--orange-primary);
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.ai-success {
|
||||
border-left-color: #10b981;
|
||||
background: #f0fdf4;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
|
||||
.ai-success i {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.ai-warning {
|
||||
border-left-color: #f59e0b;
|
||||
background: #fef3c7;
|
||||
border-color: #fed7aa;
|
||||
}
|
||||
|
||||
.ai-warning i {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.ai-info {
|
||||
border-left-color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
border-color: #bfdbfe;
|
||||
}
|
||||
|
||||
.ai-info i {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
TABLE CLEAN & MODERN
|
||||
====================================================== */
|
||||
.modern-table-dashboard {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
border: none;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.modern-table-dashboard thead th {
|
||||
background: linear-gradient(135deg, #fef7ee 0%, #fff8f0 100%);
|
||||
border: none;
|
||||
padding: 18px 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-dark);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.modern-table-dashboard tbody tr {
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modern-table-dashboard tbody tr:hover {
|
||||
background: rgba(255,140,0,0.04);
|
||||
}
|
||||
|
||||
.modern-table-dashboard td {
|
||||
padding: 18px 16px;
|
||||
border-color: #f8fafc;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Modern Badges */
|
||||
.badge {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
SUBTLE ANIMATIONS
|
||||
====================================================== */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.modern-card,
|
||||
.summary-card {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
.modern-card:nth-child(1) { animation-delay: 0.1s; }
|
||||
.modern-card:nth-child(2) { animation-delay: 0.15s; }
|
||||
.modern-card:nth-child(3) { animation-delay: 0.2s; }
|
||||
.modern-card:nth-child(4) { animation-delay: 0.25s; }
|
||||
|
||||
/* ======================================================
|
||||
RESPONSIVE - Clean Mobile
|
||||
====================================================== */
|
||||
@media (max-width: 768px) {
|
||||
.summary-card {
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ai-item {
|
||||
padding: 14px 16px;
|
||||
font-size: 13.5px;
|
||||
}
|
||||
|
||||
.modern-table-dashboard thead th,
|
||||
.modern-table-dashboard td {
|
||||
padding: 14px 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.modern-card,
|
||||
.summary-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Button enhancements */
|
||||
.btn-outline-primary {
|
||||
border-color: var(--orange-primary);
|
||||
color: var(--orange-primary);
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
background: var(--orange-primary);
|
||||
border-color: var(--orange-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Chart container */
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ───────────────────────── DASHBOARD CONTENT ───────────────────────── -->
|
||||
<div class="container mt-4 px-3 px-md-4">
|
||||
|
||||
<?php if($role == 'Admin'): ?>
|
||||
<!-- ================= SUMMARY CARDS ================= -->
|
||||
<div class="row g-4 mb-3">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="summary-card h-100">
|
||||
<div class="d-flex align-items-center h-100">
|
||||
<div class="summary-icon"><i class="bi bi-cash-stack"></i></div>
|
||||
<div class="ms-3 flex-grow-1">
|
||||
<div class="fw-bold fs-4 lh-1 text-dark">Rp <?= number_format($summary['total_revenue']); ?></div>
|
||||
<small class="text-muted fw-medium">Total Pemasukan</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="summary-card h-100">
|
||||
<div class="d-flex align-items-center h-100">
|
||||
<div class="summary-icon"><i class="bi bi-arrow-down-circle"></i></div>
|
||||
<div class="ms-3 flex-grow-1">
|
||||
<div class="fw-bold fs-4 lh-1 text-danger">Rp <?= number_format($summary['total_expense']); ?></div>
|
||||
<small class="text-muted fw-medium">Total Pengeluaran</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="summary-card h-100">
|
||||
<div class="d-flex align-items-center h-100">
|
||||
<div class="summary-icon"><i class="bi bi-graph-up-arrow"></i></div>
|
||||
<div class="ms-3 flex-grow-1">
|
||||
<div class="fw-bold fs-4 lh-1 text-success">Rp <?= number_format($summary['net_profit']); ?></div>
|
||||
<small class="text-muted fw-medium">Keuntungan Bersih</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ================= SECOND ROW ================= -->
|
||||
<div class="row g-4 mb-3">
|
||||
|
||||
<!-- ================= NERACA ================= -->
|
||||
<div class="col-12 col-md-6">
|
||||
<a href="<?= base_url('neraca'); ?>" class="text-decoration-none">
|
||||
<div class="summary-card h-100">
|
||||
<div class="d-flex align-items-center h-100">
|
||||
|
||||
<div class="summary-icon">
|
||||
<i class="bi bi-bar-chart-line-fill"></i>
|
||||
</div>
|
||||
|
||||
<div class="ms-3 flex-grow-1 position-relative">
|
||||
|
||||
<div class="fw-bold fs-4 lh-1 text-primary">
|
||||
Neraca
|
||||
</div>
|
||||
|
||||
<small class="text-muted fw-medium">
|
||||
Laporan posisi keuangan perusahaan
|
||||
</small>
|
||||
|
||||
<div class="position-absolute bottom-0 end-0 p-3">
|
||||
<i class="bi bi-arrow-right fs-5 text-secondary opacity-75"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- ================= LABA RUGI ================= -->
|
||||
<div class="col-12 col-md-6">
|
||||
<a href="<?= base_url('labarugi'); ?>" class="text-decoration-none">
|
||||
<div class="summary-card h-100">
|
||||
<div class="d-flex align-items-center h-100">
|
||||
|
||||
<div class="summary-icon">
|
||||
<i class="bi bi-graph-up-arrow"></i>
|
||||
</div>
|
||||
|
||||
<div class="ms-3 flex-grow-1 position-relative">
|
||||
|
||||
<div class="fw-bold fs-4 lh-1 text-success">
|
||||
Laba Rugi
|
||||
</div>
|
||||
|
||||
<small class="text-muted fw-medium">
|
||||
Analisis pendapatan dan beban usaha
|
||||
</small>
|
||||
|
||||
<div class="position-absolute bottom-0 end-0 p-3">
|
||||
<i class="bi bi-arrow-right fs-5 text-secondary opacity-75"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ================= QUICK ACTIONS ================= -->
|
||||
<div class="row g-4 mb-3 quick-action">
|
||||
|
||||
<!-- ================= INVOICE ================= -->
|
||||
<div class="col-md-6 col-sm-6">
|
||||
<a href="<?= base_url('invoices/draft'); ?>" class="text-decoration-none">
|
||||
<div class="summary-card h-100 text-center p-4 position-relative overflow-hidden">
|
||||
|
||||
<div class="summary-icon mx-auto mb-3 d-inline-block">
|
||||
<i class="bi bi-receipt"></i>
|
||||
</div>
|
||||
|
||||
<h6 class="mb-2 fw-bold text-dark">Invoice</h6>
|
||||
|
||||
<small class="text-muted fw-medium d-block">
|
||||
Kelola penjualan & penagihan pelanggan
|
||||
</small>
|
||||
|
||||
<div class="position-absolute bottom-0 end-0 p-3">
|
||||
<i class="bi bi-arrow-right fs-5 text-secondary opacity-75"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- ================= GUDANG / ITEMS ================= -->
|
||||
<div class="col-md-6 col-sm-6">
|
||||
<a href="<?= base_url('items'); ?>" class="text-decoration-none">
|
||||
<div class="summary-card h-100 text-center p-4 position-relative overflow-hidden">
|
||||
|
||||
<div class="summary-icon mx-auto mb-3 d-inline-block">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
</div>
|
||||
|
||||
<h6 class="mb-2 fw-bold text-dark">Persediaan</h6>
|
||||
|
||||
<small class="text-muted fw-medium d-block">
|
||||
Kelola stok & barang gudang
|
||||
</small>
|
||||
|
||||
<div class="position-absolute bottom-0 end-0 p-3">
|
||||
<i class="bi bi-arrow-right fs-5 text-secondary opacity-75"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- ================= MAIN CONTENT ================= -->
|
||||
<div class="row g-4">
|
||||
<!-- Chart -->
|
||||
<div class="col-xl-8">
|
||||
<div class="modern-card h-100">
|
||||
<div class="p-5">
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1 fw-bold text-dark">Ringkasan Keuangan</h4>
|
||||
<small class="text-muted">Performa keuangan</small>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<select id="yearFilter" class="form-select form-select-sm">
|
||||
<option value="">Semua Tahun</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="financeChart" height="140"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI INSIGHT -->
|
||||
<div class="col-xl-4">
|
||||
<div class="modern-card h-100 shadow-sm border-0 rounded-4">
|
||||
<div class="p-4">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<div class="bg-primary rounded-circle p-3 me-3 shadow-sm">
|
||||
<i class="bi bi-robot text-white fs-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="mb-0 fw-bold text-dark">AI Insight</h5>
|
||||
<small class="text-muted">Rekomendasi otomatis sistem</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div class="ai-box d-flex flex-column gap-2">
|
||||
|
||||
<?php if(empty($ai_insight)): ?>
|
||||
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="bi bi-lightbulb-off" style="font-size:28px;"></i>
|
||||
<div class="mt-2 small">Belum ada insight hari ini</div>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<?php foreach($ai_insight as $ai): ?>
|
||||
|
||||
<?php
|
||||
// =========================
|
||||
// mapping icon & color
|
||||
// =========================
|
||||
$icon = 'bi-info-circle-fill';
|
||||
$colorClass = 'ai-info';
|
||||
|
||||
if($ai->severity == 'critical'){
|
||||
$icon = 'bi-x-octagon-fill';
|
||||
$colorClass = 'ai-danger';
|
||||
} elseif($ai->severity == 'warning'){
|
||||
$icon = 'bi-exclamation-triangle-fill';
|
||||
$colorClass = 'ai-warning';
|
||||
} elseif($ai->severity == 'info'){
|
||||
$icon = 'bi-info-circle-fill';
|
||||
$colorClass = 'ai-info';
|
||||
} elseif($ai->type == 'stock'){
|
||||
$icon = 'bi-box-seam-fill';
|
||||
$colorClass = 'ai-stock';
|
||||
} elseif($ai->type == 'finance'){
|
||||
$icon = 'bi-cash-coin';
|
||||
$colorClass = 'ai-finance';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="ai-item <?= $colorClass ?>">
|
||||
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
|
||||
<i class="bi <?= $icon ?> fs-5 mt-1"></i>
|
||||
|
||||
<div class="flex-grow-1">
|
||||
<div class="small text-dark">
|
||||
<?= $ai->message; ?>
|
||||
</div>
|
||||
|
||||
<?php if(!empty($ai->meta)): ?>
|
||||
<div class="text-muted small mt-1">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
detail tambahan tersedia
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ================= ACTIVITY FEED ================= -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="modern-card">
|
||||
<div class="p-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1 fw-bold text-dark">Aktivitas Terbaru</h4>
|
||||
<small class="text-muted">Aktivitas terakhir di sistem</small>
|
||||
</div>
|
||||
<a href="<?= base_url('dashboard/log_activity') ?>" class="btn btn-outline-primary btn-sm px-4 rounded-pill">
|
||||
Lihat Semua <i class="bi bi-arrow-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table modern-table-dashboard table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="rounded-start">Waktu</th>
|
||||
<th>Modul</th>
|
||||
<th>Keterangan</th>
|
||||
<th>Pengguna</th>
|
||||
<th class="rounded-end">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="activityBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- End Container -->
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
let chartInstance = null;
|
||||
|
||||
// ================= INIT =================
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
loadYearOptions();
|
||||
loadChart(currentYear);
|
||||
|
||||
// set default dropdown setelah load
|
||||
setTimeout(() => {
|
||||
$('#yearFilter').val(currentYear);
|
||||
}, 500);
|
||||
|
||||
// ================= LOAD YEAR =================
|
||||
function loadYearOptions() {
|
||||
$.get("<?= base_url('dashboard/get_years'); ?>", function (res) {
|
||||
|
||||
let html = ``;
|
||||
|
||||
res.forEach(y => {
|
||||
html += `<option value="${y.year}">${y.year}</option>`;
|
||||
});
|
||||
|
||||
$('#yearFilter').html(html);
|
||||
|
||||
}, 'json');
|
||||
}
|
||||
|
||||
// ================= LOAD CHART =================
|
||||
function loadChart(year = '') {
|
||||
|
||||
$.get("<?= base_url('dashboard/get_chart'); ?>", { year: year }, function (res) {
|
||||
|
||||
const ctx = document.getElementById('financeChart');
|
||||
if (!ctx) return;
|
||||
|
||||
if (chartInstance) chartInstance.destroy();
|
||||
|
||||
const g1 = ctx.getContext('2d').createLinearGradient(0, 0, 0, 250);
|
||||
g1.addColorStop(0, 'rgba(25, 135, 84, 0.18)');
|
||||
g1.addColorStop(1, 'rgba(25, 135, 84, 0.02)');
|
||||
|
||||
const g2 = ctx.getContext('2d').createLinearGradient(0, 0, 0, 250);
|
||||
g2.addColorStop(0, 'rgba(220, 53, 69, 0.18)');
|
||||
g2.addColorStop(1, 'rgba(220, 53, 69, 0.02)');
|
||||
|
||||
chartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: res.labels || [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Revenue',
|
||||
data: res.revenue || [],
|
||||
borderColor: '#198754',
|
||||
backgroundColor: g1,
|
||||
borderWidth: 2,
|
||||
tension: 0.35,
|
||||
fill: true,
|
||||
pointRadius: 2,
|
||||
pointHoverRadius: 5
|
||||
},
|
||||
{
|
||||
label: 'Expense',
|
||||
data: res.expense || [],
|
||||
borderColor: '#dc3545',
|
||||
backgroundColor: g2,
|
||||
borderWidth: 2,
|
||||
tension: 0.35,
|
||||
fill: true,
|
||||
pointRadius: 2,
|
||||
pointHoverRadius: 5
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
},
|
||||
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
pointStyle: 'circle',
|
||||
padding: 18,
|
||||
font: {
|
||||
size: 12,
|
||||
weight: '500'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33,33,33,0.9)',
|
||||
padding: 10,
|
||||
displayColors: false
|
||||
}
|
||||
},
|
||||
|
||||
scales: {
|
||||
|
||||
x: {
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
font: {
|
||||
size: 11
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0,0,0,0.05)'
|
||||
},
|
||||
ticks: {
|
||||
font: {
|
||||
size: 11
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}, 'json');
|
||||
}
|
||||
|
||||
// ================= FILTER =================
|
||||
$('#yearFilter').on('change', function () {
|
||||
loadChart($(this).val());
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
loadActivity();
|
||||
});
|
||||
|
||||
function loadActivity() {
|
||||
fetch("<?= base_url('dashboard/get_activity') ?>")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
let html = "";
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
html = `
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted py-2">
|
||||
Tidak ada aktivitas
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
document.getElementById("activityBody").innerHTML = html;
|
||||
return;
|
||||
}
|
||||
|
||||
data.forEach(item => {
|
||||
|
||||
let time = new Date(item.created_at);
|
||||
let jam = time.getHours().toString().padStart(2,'0') + ":" +
|
||||
time.getMinutes().toString().padStart(2,'0');
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td class="fw-semibold text-primary">${jam}</td>
|
||||
<td>${item.module ?? '-'}</td>
|
||||
<td>${item.description ?? '-'}</td>
|
||||
<td class="fw-medium">${item.nama ?? 'System'}</td>
|
||||
<td>${getStatusBadge(item.status)}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById("activityBody").innerHTML = html;
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
}
|
||||
|
||||
|
||||
// ================= STATUS BADGE (SIMPLE CLEAN) =================
|
||||
function getStatusBadge(status) {
|
||||
|
||||
if (!status) status = 'success';
|
||||
|
||||
const map = {
|
||||
success: "success",
|
||||
failed: "danger",
|
||||
pending: "warning"
|
||||
};
|
||||
|
||||
let cls = map[status] || "secondary";
|
||||
|
||||
return `<span class="badge bg-${cls}">${status}</span>`;
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user