update user company UI CSS

This commit is contained in:
Wian Drs
2026-09-11 23:35:24 +07:00
parent b011272f28
commit e43bda8d6b
6 changed files with 1026 additions and 1067 deletions
+290 -233
View File
@@ -1,92 +1,44 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Dashboard extends MY_Controller {
class Dashboard extends MY_Controller
{
public function __construct()
{
parent::__construct();
$this->load->library('session');
$this->load->library('PerformanceCacheService');
// Cek apakah user sudah login
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$companyId=(int)$this->companycontext->id();
$summary=$this->performancecacheservice->remember('dashboard:summary:'.$companyId,300,function()use($companyId){
$row=$this->db->query("SELECT COALESCE(SUM(IF(a.tipe='revenue',d.kredit-d.debit,0)),0) total_revenue,COALESCE(SUM(IF(a.tipe='expense',d.debit-d.kredit,0)),0) total_expense FROM journal_details d JOIN journals j ON j.id=d.journal_id JOIN accounts a ON a.id=d.account_id WHERE j.company_id=? AND j.status IN('posted','reversed') AND a.kategori='laba_rugi' AND a.is_active=1",array($companyId))->row_array();
$row['net_profit']=(float)$row['total_revenue']-(float)$row['total_expense'];return$row;
});
goto dashboard_summary_ready;
$this->db->select('
accounts.id,
accounts.kode_akun,
accounts.nama_akun,
accounts.tipe,
COALESCE(SUM(journal_details.debit),0) as debit,
COALESCE(SUM(journal_details.kredit),0) as kredit
');
$this->db->from('accounts');
$this->db->join('journal_details','accounts.id = journal_details.account_id','left');
$this->db->join('journals','journals.id = journal_details.journal_id','left');
$this->db->where("(journals.status IN ('posted','reversed') OR journal_details.id IS NULL)", null, false);
$this->db->where('accounts.kategori','laba_rugi');
$this->db->where('accounts.is_active',1);
$this->db->group_by('accounts.id');
$this->db->order_by('accounts.kode_akun','ASC');
$rows = $this->db->get()->result();
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
// 🔥 NORMALISASI SALDO BERDASARKAN POSISI
if ($r->tipe == 'revenue') {
$saldo = $r->kredit - $r->debit;
$revenue += $saldo;
$companyId = (int) $this->companycontext->id();
$year = (int) date('Y');
$summary = $this->performancecacheservice->remember(
'dashboard:summary:' . $companyId . ':' . $year,
300,
function () use ($companyId, $year) {
return $this->financialSummary($companyId, $year);
}
);
$previous = $this->financialSummary($companyId, $year - 1);
$overview = $this->operationalOverview($companyId);
if ($r->tipe == 'expense') {
$saldo = $r->debit - $r->kredit;
$expense += $saldo;
}
}
$summary = [
'total_revenue' => $revenue,
'total_expense' => $expense,
'net_profit' => $revenue - $expense
];
dashboard_summary_ready:
$today = date('Y-m');
$ai_insight = $this->db
->select('type, severity, message, meta, created_at')
->where('period', $today)
->order_by("
CASE severity
WHEN 'critical' THEN 1
WHEN 'warning' THEN 2
WHEN 'info' THEN 3
END
", '', false)
->order_by('created_at', 'DESC')
->limit(20)
->get('ai_insights')
->result();
$data = [
"active_menu" => "dashboard",
"summary" => $summary,
"ai_insight" => $ai_insight
];
$data = array(
'active_menu' => 'dashboard',
'summary' => $summary,
'comparison' => array(
'revenue' => $this->percentageChange($summary['total_revenue'], $previous['total_revenue']),
'expense' => $this->percentageChange($summary['total_expense'], $previous['total_expense']),
'profit' => $this->percentageChange($summary['net_profit'], $previous['net_profit']),
),
'overview' => $overview,
'ai_insight' => $this->insights($companyId),
'dashboard_year' => $year,
'quick_links' => $this->quickLinks(),
'show_financial' => is_master_admin_user()
|| check_permission('reports', 'can_view')
|| check_permission('jurnal', 'can_view'),
);
$this->load->view('partials/header', $data);
$this->load->view('dashboard/layout', $data);
@@ -95,197 +47,302 @@ class Dashboard extends MY_Controller {
public function get_activity()
{
$this->db->select('activity_logs.*, users.nama');
$this->db->from('activity_logs');
$this->db->join('users','users.id = activity_logs.user_id','left');
if ($this->session->userdata('role') != 'Admin') {
$this->db->where('user_id',$this->session->userdata('user_id'));
}
$this->db->order_by('created_at','DESC');
$this->db->limit(10);
$data = $this->db->get()->result();
echo json_encode($data);
$this->activityQuery();
$rows = $this->db->order_by('activity_logs.created_at', 'DESC')->limit(10)->get()->result();
return $this->json($rows);
}
public function log_activity()
{
$data = [
"active_menu" => "dashboard"
];
$data = array('active_menu' => 'dashboard');
$this->load->view('partials/header', $data);
$this->load->view('dashboard/activity_logs', $data);
$this->load->view('partials/footer');
}
public function get_activity_datatable()
{
$this->load->database();
$draw = (int) $this->input->post('draw');
$start = max(0, (int) $this->input->post('start'));
$length = (int) $this->input->post('length');
$searchInput = $this->input->post('search');
$search = is_array($searchInput) ? trim((string) ($searchInput['value'] ?? '')) : '';
$draw = $this->input->post('draw');
$start = $this->input->post('start');
$length = $this->input->post('length');
$search = $this->input->post('search')['value'] ?? '';
$this->activityQuery($search);
$recordsFiltered = (int) $this->db->count_all_results();
// =========================
// BASE QUERY
// =========================
$this->db->select('
activity_logs.*,
users.nama
');
$this->db->from('activity_logs');
$this->db->join('users', 'users.id = activity_logs.user_id', 'left');
// =========================
// SEARCH
// =========================
if (!empty($search)) {
$this->db->group_start();
$this->db->like('activity_logs.module', $search);
$this->db->or_like('activity_logs.action', $search);
$this->db->or_like('activity_logs.description', $search);
$this->db->or_like('activity_logs.status', $search);
$this->db->or_like('users.nama', $search);
$this->db->group_end();
}
// =========================
// COUNT FILTERED
// =========================
$filtered_query = clone $this->db;
$recordsFiltered = $filtered_query->count_all_results('', false);
// =========================
// ORDER
// =========================
$this->activityQuery($search);
$this->db->order_by('activity_logs.id', 'DESC');
// =========================
// LIMIT
// =========================
if ($length != -1) {
$this->db->limit($length, $start);
if ($length !== -1) {
$this->db->limit(max(1, $length), $start);
}
$rows = $this->db->get()->result();
$query = $this->db->get();
$data = $query->result();
// =========================
// TOTAL RECORD
// =========================
$recordsTotal = $this->db->count_all('activity_logs');
// =========================
// FORMAT OUTPUT
// =========================
$result = [];
foreach ($data as $row) {
$result[] = [
'created_at' => $row->created_at,
'nama' => $row->nama ?? 'System',
'module' => $row->module,
'action' => $row->action,
$this->activityQuery();
$recordsTotal = (int) $this->db->count_all_results();
$result = array();
foreach ($rows as $row) {
$result[] = array(
'created_at' => $row->created_at,
'nama' => $row->nama ?: 'System',
'module' => $row->module,
'action' => $row->action,
'description' => $row->description,
'status' => $row->status,
'ip_address' => $row->ip_address
];
'status' => $row->status,
'ip_address' => $row->ip_address,
);
}
echo json_encode([
"draw" => intval($draw),
"recordsTotal" => $recordsTotal,
"recordsFiltered" => $recordsFiltered,
"data" => $result
]);
return $this->json(array(
'draw' => $draw,
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $result,
));
}
public function get_chart()
{
$year = $this->input->get('year');
// 🔥 DEFAULT: tahun berjalan
if (empty($year)) {
$year = date('Y');
$year = (int) $this->input->get('year');
if ($year < 2000 || $year > 2200) {
$year = (int) date('Y');
}
$this->db->select('
journals.tanggal,
accounts.tipe,
SUM(journal_details.debit) as total_debit,
SUM(journal_details.kredit) as total_kredit
');
$this->db->from('journal_details');
$this->db->join('journals', 'journals.id = journal_details.journal_id', 'left');
$this->db->join('accounts', 'accounts.id = journal_details.account_id', 'left');
$this->db->where_in('journals.status', array('posted', 'reversed'));
$this->db->where('accounts.kategori', 'laba_rugi');
$this->db->where('accounts.is_active', 1);
$companyId = (int) $this->companycontext->id();
$rows = $this->db->query(
"SELECT MONTH(j.tanggal) month_no,
COALESCE(SUM(CASE WHEN a.tipe='revenue' THEN d.kredit-d.debit ELSE 0 END),0) revenue,
COALESCE(SUM(CASE WHEN a.tipe='expense' THEN d.debit-d.kredit ELSE 0 END),0) expense
FROM journal_details d
JOIN journals j ON j.id=d.journal_id
JOIN accounts a ON a.id=d.account_id
WHERE j.company_id=? AND j.status IN('posted','reversed')
AND a.kategori='laba_rugi' AND a.is_active=1 AND YEAR(j.tanggal)=?
GROUP BY MONTH(j.tanggal)
ORDER BY month_no",
array($companyId, $year)
)->result();
// 🔥 pakai tahun default / filter
$this->db->where('YEAR(journals.tanggal)', $year);
$this->db->group_by(['journals.tanggal', 'accounts.tipe']);
$this->db->order_by('journals.tanggal', 'ASC');
$rows = $this->db->get()->result();
$map = [];
foreach ($rows as $r) {
$date = $r->tanggal;
if (!isset($map[$date])) {
$map[$date] = [
'revenue' => 0,
'expense' => 0
];
}
if ($r->tipe == 'revenue') {
$map[$date]['revenue'] += ($r->total_kredit - $r->total_debit);
}
if ($r->tipe == 'expense') {
$map[$date]['expense'] += ($r->total_debit - $r->total_kredit);
$months = array('Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des');
$revenue = array_fill(0, 12, 0);
$expense = array_fill(0, 12, 0);
foreach ($rows as $row) {
$index = (int) $row->month_no - 1;
if ($index >= 0 && $index < 12) {
$revenue[$index] = (float) $row->revenue;
$expense[$index] = (float) $row->expense;
}
}
$labels = [];
$revenue = [];
$expense = [];
foreach ($map as $date => $val) {
$labels[] = $date;
$revenue[] = (float)$val['revenue'];
$expense[] = (float)$val['expense'];
}
echo json_encode([
'year' => $year, // optional biar debugging enak
'labels' => $labels,
return $this->json(array(
'year' => $year,
'labels' => $months,
'revenue' => $revenue,
'expense' => $expense
]);
'expense' => $expense,
));
}
public function get_years()
{
$this->db->select('DISTINCT YEAR(tanggal) as year');
$this->db->from('journals');
$this->db->where_in('status', array('posted', 'reversed'));
$this->db->order_by('year', 'DESC');
$data = $this->db->get()->result();
echo json_encode($data);
$this->db->select('DISTINCT YEAR(tanggal) year', false)
->from('journals')
->where('company_id', (int) $this->companycontext->id())
->where_in('status', array('posted', 'reversed'))
->order_by('year', 'DESC');
$years = $this->db->get()->result();
if (!$years) {
$years = array((object) array('year' => (int) date('Y')));
}
return $this->json($years);
}
private function financialSummary($companyId, $year)
{
$row = $this->db->query(
"SELECT
COALESCE(SUM(CASE WHEN a.tipe='revenue' THEN d.kredit-d.debit ELSE 0 END),0) total_revenue,
COALESCE(SUM(CASE WHEN a.tipe='expense' THEN d.debit-d.kredit ELSE 0 END),0) total_expense
FROM journal_details d
JOIN journals j ON j.id=d.journal_id
JOIN accounts a ON a.id=d.account_id
WHERE j.company_id=? AND j.status IN('posted','reversed')
AND a.kategori='laba_rugi' AND a.is_active=1 AND YEAR(j.tanggal)=?",
array((int) $companyId, (int) $year)
)->row_array();
$revenue = (float) ($row['total_revenue'] ?? 0);
$expense = (float) ($row['total_expense'] ?? 0);
return array(
'total_revenue' => $revenue,
'total_expense' => $expense,
'net_profit' => $revenue - $expense,
);
}
private function operationalOverview($companyId)
{
$result = array(
'receivable_amount' => 0,
'receivable_count' => 0,
'receivable_overdue' => 0,
'payable_amount' => 0,
'payable_count' => 0,
'payable_overdue' => 0,
'inventory_value' => 0,
'active_items' => 0,
'pending_items' => 0,
'pending_approvals' => 0,
);
if ($this->db->table_exists('invoices')) {
$whereDeleted = $this->db->field_exists('deleted_at', 'invoices') ? ' AND deleted_at IS NULL' : '';
$row = $this->db->query(
"SELECT COUNT(*) total,
COALESCE(SUM(sisa_piutang),0) amount,
SUM(CASE WHEN jatuh_tempo<CURDATE() THEN 1 ELSE 0 END) overdue
FROM invoices
WHERE company_id=? AND workflow_status='posted' AND sisa_piutang>0.001" . $whereDeleted,
array($companyId)
)->row();
if ($row) {
$result['receivable_count'] = (int) $row->total;
$result['receivable_amount'] = (float) $row->amount;
$result['receivable_overdue'] = (int) $row->overdue;
}
}
if ($this->db->table_exists('supplier_invoices')) {
$companyFilter = $this->db->field_exists('company_id', 'supplier_invoices')
? ' AND company_id=?'
: '';
$bindings = $companyFilter === '' ? array() : array($companyId);
$row = $this->db->query(
"SELECT COUNT(*) total,
COALESCE(SUM(balance),0) amount,
SUM(CASE WHEN due_date<=CURDATE() THEN 1 ELSE 0 END) overdue
FROM supplier_invoices
WHERE status='partial' AND balance>0.001" . $companyFilter,
$bindings
)->row();
if ($row) {
$result['payable_count'] = (int) $row->total;
$result['payable_amount'] = (float) $row->amount;
$result['payable_overdue'] = (int) $row->overdue;
}
}
if ($this->db->table_exists('items')) {
$result['active_items'] = (int) $this->db
->where(array('company_id' => $companyId, 'status' => 'active'))
->count_all_results('items');
if ($this->db->table_exists('item_barcodes')) {
$pending = $this->db->query(
"SELECT COUNT(DISTINCT i.id) total
FROM items i
LEFT JOIN item_barcodes b ON b.item_id=i.id AND b.status='pending'
WHERE i.company_id=? AND (i.status='draft' OR b.id IS NOT NULL)",
array($companyId)
)->row();
$result['pending_items'] = (int) ($pending ? $pending->total : 0);
} else {
$result['pending_items'] = (int) $this->db
->where(array('company_id' => $companyId, 'status' => 'draft'))
->count_all_results('items');
}
}
if ($this->db->table_exists('inventory_ledger') && $this->db->field_exists('value', 'inventory_ledger')) {
$inventory = $this->db->query(
"SELECT COALESCE(SUM(CASE WHEN l.direction='in' THEN l.value ELSE -l.value END),0) amount
FROM inventory_ledger l JOIN items i ON i.id=l.item_id WHERE i.company_id=?",
array($companyId)
)->row();
$result['inventory_value'] = (float) ($inventory ? $inventory->amount : 0);
}
if ($this->db->table_exists('approval_requests')
&& (is_master_admin_user() || check_permission('approvals', 'can_view'))) {
$result['pending_approvals'] = (int) $this->db
->where('status', 'pending')
->count_all_results('approval_requests');
}
return $result;
}
private function insights($companyId)
{
if (!$this->db->table_exists('ai_insights')) {
return array();
}
$this->db->select('type,severity,message,meta,created_at')
->where('period', date('Y-m'));
if ($this->db->field_exists('company_id', 'ai_insights')) {
$this->db->where('company_id', $companyId);
}
return $this->db
->order_by("CASE severity WHEN 'critical' THEN 1 WHEN 'warning' THEN 2 ELSE 3 END", '', false)
->order_by('created_at', 'DESC')
->limit(8)
->get('ai_insights')
->result();
}
private function quickLinks()
{
$definitions = array(
array('feature' => 'invoices', 'action' => 'can_create', 'label' => 'Buat Invoice', 'description' => 'Catat tagihan penjualan', 'icon' => 'bi-receipt-cutoff', 'url' => 'invoices/create'),
array('feature' => 'purchases', 'action' => 'can_create', 'label' => 'Purchase Workflow', 'description' => 'Buat permintaan pembelian', 'icon' => 'bi-cart-plus', 'url' => 'purchases'),
array('feature' => 'jurnal', 'action' => 'can_create', 'label' => 'Jurnal Umum', 'description' => 'Catat jurnal manual', 'icon' => 'bi-journal-plus', 'url' => 'jurnal'),
array('feature' => 'items', 'action' => 'can_view', 'label' => 'Scan Persediaan', 'description' => 'Periksa barcode dan stok', 'icon' => 'bi-qr-code-scan', 'url' => 'inventoryprofessional#scanner'),
);
$links = array();
foreach ($definitions as $definition) {
if (is_master_admin_user() || check_permission($definition['feature'], $definition['action'])) {
$links[] = $definition;
}
}
return $links;
}
private function activityQuery($search = '')
{
$this->db->select('activity_logs.*,users.nama')
->from('activity_logs')
->join('users', 'users.id=activity_logs.user_id', 'left');
if ($this->db->field_exists('company_id', 'activity_logs')) {
$this->db->where('activity_logs.company_id', (int) $this->companycontext->id());
} elseif (!is_master_admin_user()) {
$this->db->where('activity_logs.user_id', (int) $this->session->userdata('user_id'));
}
if ($search !== '') {
$this->db->group_start()
->like('activity_logs.module', $search)
->or_like('activity_logs.action', $search)
->or_like('activity_logs.description', $search)
->or_like('activity_logs.status', $search)
->or_like('users.nama', $search)
->group_end();
}
}
private function percentageChange($current, $previous)
{
$previous = (float) $previous;
if (abs($previous) < 0.01) {
return null;
}
return round((((float) $current - $previous) / abs($previous)) * 100, 1);
}
private function json($payload)
{
return $this->output
->set_content_type('application/json', 'utf-8')
->set_output(json_encode($payload, JSON_UNESCAPED_UNICODE));
}
}