update user company UI CSS
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ $authNotice = trim((string) $this->session->flashdata('auth_notice'));
|
||||
background:
|
||||
radial-gradient(circle at 8% 12%, rgba(255, 138, 0, .13), transparent 28rem),
|
||||
linear-gradient(135deg, #f8fafc 0%, #fff8ef 100%);
|
||||
font-family: Inter, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
@@ -145,17 +146,17 @@ $authNotice = trim((string) $this->session->flashdata('auth_notice'));
|
||||
max-width: 470px;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-size: clamp(35px, 4.3vw, 54px);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -.045em;
|
||||
font-size: clamp(27px, 2.6vw, 32px);
|
||||
line-height: 1.2;
|
||||
letter-spacing: -.035em;
|
||||
}
|
||||
|
||||
.brand-copy p {
|
||||
max-width: 450px;
|
||||
margin: 22px 0 0;
|
||||
color: rgba(255, 255, 255, .72);
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.brand-footer {
|
||||
@@ -181,7 +182,7 @@ $authNotice = trim((string) $this->session->flashdata('auth_notice'));
|
||||
.form-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--navy-950);
|
||||
font-size: 31px;
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -.03em;
|
||||
}
|
||||
@@ -189,7 +190,7 @@ $authNotice = trim((string) $this->session->flashdata('auth_notice'));
|
||||
.form-heading p {
|
||||
margin: 10px 0 0;
|
||||
color: var(--slate-500);
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@@ -337,7 +338,7 @@ $authNotice = trim((string) $this->session->flashdata('auth_notice'));
|
||||
.auth-shell { width: 100%; min-height: 100vh; min-height: 100dvh; border: 0; border-radius: 0; box-shadow: none; }
|
||||
.form-panel { align-items: flex-start; padding: 30px 22px; }
|
||||
.mobile-logo { margin-bottom: 42px; }
|
||||
.form-heading h2 { font-size: 28px; }
|
||||
.form-heading h2 { font-size: 21px; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<link rel="stylesheet" href="<?=base_url('assets/css/inventory-professional.css')?>?v=<?=filemtime(FCPATH.'assets/css/inventory-professional.css')?>">
|
||||
<section class="inventory-hero mb-3">
|
||||
<div><span class="inventory-kicker">PERSEDIAAN TERINTEGRASI</span><h2 class="mb-1"><?=html_escape($inventory_title??'Persediaan')?></h2><p class="mb-0"><?=html_escape($inventory_subtitle??'Kontrol barang, gudang, dan nilai persediaan dalam satu alur.')?></p></div>
|
||||
<div><span class="inventory-kicker">PERSEDIAAN TERINTEGRASI</span><h2 class="mb-1 text-light"><?=html_escape($inventory_title??'Persediaan')?></h2><p class="mb-0"><?=html_escape($inventory_subtitle??'Kontrol barang, gudang, dan nilai persediaan dalam satu alur.')?></p></div>
|
||||
<a class="btn btn-light inventory-scan-link" href="<?=base_url('inventoryprofessional')?>#scanner"><i class="bi bi-qr-code-scan"></i> Scan Barcode</a>
|
||||
</section>
|
||||
<nav class="inventory-module-nav mb-4" aria-label="Menu Persediaan">
|
||||
|
||||
Reference in New Issue
Block a user