Initial commit

This commit is contained in:
root
2026-05-26 08:07:45 +00:00
commit a234e55e64
4263 changed files with 855927 additions and 0 deletions
@@ -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>
+849
View File
@@ -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>