Autosave: 20260503-045416

This commit is contained in:
Flatlogic Bot 2026-05-03 04:54:16 +00:00
parent c53e944d52
commit abc4505db8
2 changed files with 304 additions and 84 deletions

283
index.php
View File

@ -197,6 +197,117 @@ if (!function_exists('db_insert_sql_for_existing_columns')) {
}
}
if (!function_exists('current_outlet_name')) {
function current_outlet_name(): string {
static $cache = [];
$oid = current_outlet_id();
if ($oid === -1 || $oid === 0) {
return __('All Outlets') ?: 'All Outlets';
}
if (array_key_exists($oid, $cache)) {
return $cache[$oid];
}
if (!db_table_exists('outlets')) {
$cache[$oid] = 'Outlet ' . $oid;
return $cache[$oid];
}
try {
$stmt = db()->prepare("SELECT name FROM outlets WHERE id = ? LIMIT 1");
$stmt->execute([$oid]);
$cache[$oid] = (string)($stmt->fetchColumn() ?: ('Outlet ' . $oid));
} catch (Throwable $e) {
$cache[$oid] = 'Outlet ' . $oid;
}
return $cache[$oid];
}
}
if (!function_exists('outlet_scope_sql')) {
function outlet_scope_sql(string $tableName, string $qualifiedColumn = 'outlet_id', bool $includeLegacyNull = true): array {
if (!db_column_exists($tableName, 'outlet_id')) {
return ['sql' => '1=1', 'params' => []];
}
$oid = current_outlet_id();
if ($oid === -1) {
return ['sql' => '1=1', 'params' => []];
}
$sql = $includeLegacyNull
? "({$qualifiedColumn} = ? OR {$qualifiedColumn} IS NULL)"
: "{$qualifiedColumn} = ?";
return ['sql' => $sql, 'params' => [$oid]];
}
}
if (!function_exists('dashboard_sales_series')) {
function dashboard_sales_series(string $period = 'month', int $limit = 12): array {
$period = strtolower($period) === 'year' ? 'year' : 'month';
$limit = max(1, (int)$limit);
$sources = [];
$params = [];
$db = db();
if (db_table_exists('invoices')) {
$invoiceDateColumn = db_first_existing_column('invoices', ['invoice_date', 'created_at']);
$invoiceTotalExpression = db_column_exists('invoices', 'total_with_vat')
? 'COALESCE(total_with_vat, 0)'
: (db_column_exists('invoices', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
if ($invoiceDateColumn !== null) {
$invoiceScope = outlet_scope_sql('invoices', 'outlet_id');
if ($period === 'year') {
$sources[] = "SELECT YEAR(`{$invoiceDateColumn}`) AS period_key, CAST(YEAR(`{$invoiceDateColumn}`) AS CHAR) AS label, {$invoiceTotalExpression} AS total FROM invoices WHERE `{$invoiceDateColumn}` IS NOT NULL AND {$invoiceScope['sql']}";
} else {
$sources[] = "SELECT DATE_FORMAT(`{$invoiceDateColumn}`, '%Y-%m') AS period_key, DATE_FORMAT(`{$invoiceDateColumn}`, '%b %Y') AS label, {$invoiceTotalExpression} AS total FROM invoices WHERE `{$invoiceDateColumn}` IS NOT NULL AND {$invoiceScope['sql']}";
}
$params = array_merge($params, $invoiceScope['params']);
}
}
if (db_table_exists('pos_transactions')) {
$posDateColumn = db_first_existing_column('pos_transactions', ['created_at', 'transaction_date', 'sale_date']);
$posTotalExpression = db_column_exists('pos_transactions', 'net_amount')
? 'COALESCE(net_amount, 0)'
: (db_column_exists('pos_transactions', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
if ($posDateColumn !== null) {
$posScope = outlet_scope_sql('pos_transactions', 'outlet_id');
$posStatusPredicate = db_column_exists('pos_transactions', 'status') ? "status = 'completed' AND " : '';
if ($period === 'year') {
$sources[] = "SELECT YEAR(`{$posDateColumn}`) AS period_key, CAST(YEAR(`{$posDateColumn}`) AS CHAR) AS label, {$posTotalExpression} AS total FROM pos_transactions WHERE {$posStatusPredicate}`{$posDateColumn}` IS NOT NULL AND {$posScope['sql']}";
} else {
$sources[] = "SELECT DATE_FORMAT(`{$posDateColumn}`, '%Y-%m') AS period_key, DATE_FORMAT(`{$posDateColumn}`, '%b %Y') AS label, {$posTotalExpression} AS total FROM pos_transactions WHERE {$posStatusPredicate}`{$posDateColumn}` IS NOT NULL AND {$posScope['sql']}";
}
$params = array_merge($params, $posScope['params']);
}
}
if ($sources === []) {
return [];
}
$sql = "SELECT period_key, label, SUM(total) AS total FROM (" . implode(' UNION ALL ', $sources) . ") dashboard_sales_rollup GROUP BY period_key, label ORDER BY period_key DESC LIMIT {$limit}";
$stmt = $db->prepare($sql);
$stmt->execute($params);
$rows = array_reverse($stmt->fetchAll(PDO::FETCH_ASSOC));
foreach ($rows as &$row) {
$row['label'] = (string)($row['label'] ?? '');
$row['total'] = (float)($row['total'] ?? 0);
}
unset($row);
return $rows;
}
}
if (!function_exists('sales_return_reference_column')) {
function sales_return_reference_column(): string {
return db_first_existing_column('sales_returns', ['invoice_id', 'sale_id']) ?? 'invoice_id';
@ -629,26 +740,41 @@ function getPurchaseAlerts() {
$db = db();
$hasSupplierJoin = db_table_exists('suppliers') && db_column_exists('purchases', 'supplier_id');
$dueDateExpression = db_column_exists('purchases', 'due_date') ? 'p.due_date' : 'NULL';
$statusPredicate = db_column_exists('purchases', 'status') ? "WHERE p.status != 'paid'" : 'WHERE 1=1';
$dueDatePredicate = db_column_exists('purchases', 'due_date')
? ' AND p.due_date IS NOT NULL AND p.due_date <= DATE_ADD(CURDATE(), INTERVAL 7 DAY)'
: '';
$totalExpression = db_column_exists('purchases', 'total_with_vat')
? 'p.total_with_vat'
? 'COALESCE(p.total_with_vat, 0)'
: (db_column_exists('purchases', 'total_amount') ? 'COALESCE(p.total_amount, 0)' : '0');
$supplierExpression = $hasSupplierJoin ? 's.name' : 'NULL';
$joinClause = $hasSupplierJoin ? ' LEFT JOIN suppliers s ON p.supplier_id = s.id' : '';
$orderBy = db_column_exists('purchases', 'due_date') ? ' ORDER BY p.due_date ASC' : ' ORDER BY p.id DESC';
$where = [];
$params = [];
if (db_column_exists('purchases', 'status')) {
$where[] = "p.status != 'paid'";
}
if (db_column_exists('purchases', 'due_date')) {
$where[] = 'p.due_date IS NOT NULL';
$where[] = 'p.due_date <= DATE_ADD(CURDATE(), INTERVAL 7 DAY)';
}
$outletScope = outlet_scope_sql('purchases', 'p.outlet_id');
if ($outletScope['sql'] !== '1=1') {
$where[] = $outletScope['sql'];
$params = array_merge($params, $outletScope['params']);
}
$whereSql = $where === [] ? '1=1' : implode(' AND ', $where);
$sql = "SELECT p.id, {$dueDateExpression} AS due_date, {$totalExpression} AS total_with_vat, {$supplierExpression} AS supplier_name"
. ' FROM purchases p'
. $joinClause
. ' '
. $statusPredicate
. $dueDatePredicate
. ' WHERE '
. $whereSql
. $orderBy;
$stmt = $db->query($sql);
$stmt = $db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
@ -3822,6 +3948,7 @@ $data = [
'cash_transactions' => [],
'monthly_sales' => [],
'yearly_sales' => [],
'dashboard_scope_label' => '',
'opening_balance' => 0,
'stats' => [
'expired_items' => 0,
@ -3831,6 +3958,10 @@ $data = [
'total_received' => 0,
'total_receivable' => 0,
'total_purchases' => 0,
'total_paid' => 0,
'total_payable' => 0,
'total_customers' => 0,
'total_items' => 0,
],
'settings' => [],
];
@ -4708,39 +4839,99 @@ switch ($page) {
break;
default:
if (can('dashboard_view')) {
$data['customers'] = db()->query("SELECT * FROM customers ORDER BY id DESC LIMIT 5")->fetchAll();
// Statistics with Outlet Filter
$current_oid = current_outlet_id();
$inv_cond = ($current_oid > 0) ? " WHERE outlet_id = $current_oid " : "";
$pos_cond = " WHERE status = 'completed' " . (($current_oid > 0) ? " AND outlet_id = $current_oid " : "");
$pay_inv_cond = ($current_oid > 0) ? " WHERE i.outlet_id = $current_oid " : "";
$pay_pos_cond = ($current_oid > 0) ? " WHERE t.outlet_id = $current_oid " : "";
$pur_cond = ($current_oid > 0) ? " WHERE outlet_id = $current_oid " : "";
$pay_pur_cond = ($current_oid > 0) ? " WHERE p.outlet_id = $current_oid " : "";
$db = db();
$scalar = static function (string $sql, array $params = []) use ($db) {
$stmt = $db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchColumn();
};
$low_stock_query = ($current_oid > 0)
? "SELECT COUNT(*) FROM stock_items WHERE outlet_id = $current_oid AND stock_quantity <= min_stock_level"
: "SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level";
$data['dashboard_scope_label'] = current_outlet_name();
$data['stats'] = [
'total_customers' => db()->query("SELECT COUNT(*) FROM customers")->fetchColumn(),
'total_items' => db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn(),
'total_sales' => (db()->query("SELECT SUM(total_with_vat) FROM invoices $inv_cond")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions $pos_cond")->fetchColumn() ?: 0),
'total_received' => (db()->query("SELECT SUM(p.amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id $pay_inv_cond")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(pp.amount) FROM pos_payments pp JOIN pos_transactions t ON pp.transaction_id = t.id $pay_pos_cond")->fetchColumn() ?: 0),
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM purchases $pur_cond")->fetchColumn() ?: 0,
'total_paid' => db()->query("SELECT SUM(pp.amount) FROM purchase_payments pp JOIN purchases p ON pp.purchase_id = p.id $pay_pur_cond")->fetchColumn() ?: 0,
'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE()")->fetchColumn(),
'near_expiry_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)")->fetchColumn(),
'low_stock_items_count' => db()->query($low_stock_query)->fetchColumn(),
];
$data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received'];
$data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid'];
$customerScope = outlet_scope_sql('customers', 'outlet_id');
if (db_table_exists('customers')) {
$customerStmt = $db->prepare("SELECT * FROM customers WHERE {$customerScope['sql']} ORDER BY id DESC LIMIT 5");
$customerStmt->execute($customerScope['params']);
$data['customers'] = $customerStmt->fetchAll();
$data['stats']['total_customers'] = (int)($scalar("SELECT COUNT(*) FROM customers WHERE {$customerScope['sql']}", $customerScope['params']) ?: 0);
}
// Sales Chart Data
$data['monthly_sales'] = db()->query("SELECT DATE_FORMAT(invoice_date, '%M %Y') as label, SUM(total_with_vat) as total FROM invoices GROUP BY DATE_FORMAT(invoice_date, '%Y-%m') ORDER BY invoice_date ASC LIMIT 12")->fetchAll(PDO::FETCH_ASSOC);
$data['yearly_sales'] = db()->query("SELECT YEAR(invoice_date) as label, SUM(total_with_vat) as total FROM invoices GROUP BY label ORDER BY label ASC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
$itemScope = outlet_scope_sql('stock_items', 'outlet_id');
if (db_table_exists('stock_items')) {
$data['stats']['total_items'] = (int)($scalar("SELECT COUNT(*) FROM stock_items WHERE {$itemScope['sql']}", $itemScope['params']) ?: 0);
if (db_column_exists('stock_items', 'expiry_date')) {
$data['stats']['expired_items'] = (int)($scalar("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE() AND {$itemScope['sql']}", $itemScope['params']) ?: 0);
$data['stats']['near_expiry_items'] = (int)($scalar("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY) AND {$itemScope['sql']}", $itemScope['params']) ?: 0);
}
if (db_column_exists('stock_items', 'stock_quantity') && db_column_exists('stock_items', 'min_stock_level')) {
$data['stats']['low_stock_items_count'] = (int)($scalar("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level AND {$itemScope['sql']}", $itemScope['params']) ?: 0);
}
}
if (db_table_exists('invoices')) {
$invoiceScope = outlet_scope_sql('invoices', 'outlet_id');
$invoiceTotalExpression = db_column_exists('invoices', 'total_with_vat') ? 'COALESCE(total_with_vat, 0)' : (db_column_exists('invoices', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
$data['stats']['total_sales'] += (float)($scalar("SELECT COALESCE(SUM({$invoiceTotalExpression}), 0) FROM invoices WHERE {$invoiceScope['sql']}", $invoiceScope['params']) ?: 0);
}
if (db_table_exists('pos_transactions')) {
$posScope = outlet_scope_sql('pos_transactions', 'outlet_id');
$posTotalExpression = db_column_exists('pos_transactions', 'net_amount') ? 'COALESCE(net_amount, 0)' : (db_column_exists('pos_transactions', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
$posWhere = [];
$posParams = [];
if (db_column_exists('pos_transactions', 'status')) {
$posWhere[] = 'status = ?';
$posParams[] = 'completed';
}
if ($posScope['sql'] !== '1=1') {
$posWhere[] = $posScope['sql'];
$posParams = array_merge($posParams, $posScope['params']);
}
$posWhereSql = $posWhere === [] ? '1=1' : implode(' AND ', $posWhere);
$data['stats']['total_sales'] += (float)($scalar("SELECT COALESCE(SUM({$posTotalExpression}), 0) FROM pos_transactions WHERE {$posWhereSql}", $posParams) ?: 0);
}
if (db_table_exists('payments') && db_table_exists('invoices') && db_column_exists('payments', 'invoice_id')) {
$paymentInvoiceScope = outlet_scope_sql('invoices', 'i.outlet_id');
$paymentInvoiceWhere = $paymentInvoiceScope['sql'] === '1=1' ? '1=1' : $paymentInvoiceScope['sql'];
$data['stats']['total_received'] += (float)($scalar("SELECT COALESCE(SUM(p.amount), 0) FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE {$paymentInvoiceWhere}", $paymentInvoiceScope['params']) ?: 0);
}
if (db_table_exists('pos_payments') && db_table_exists('pos_transactions') && db_column_exists('pos_payments', 'transaction_id')) {
$paymentPosScope = outlet_scope_sql('pos_transactions', 't.outlet_id');
$paymentPosWhere = [];
$paymentPosParams = [];
if (db_column_exists('pos_transactions', 'status')) {
$paymentPosWhere[] = 't.status = ?';
$paymentPosParams[] = 'completed';
}
if ($paymentPosScope['sql'] !== '1=1') {
$paymentPosWhere[] = $paymentPosScope['sql'];
$paymentPosParams = array_merge($paymentPosParams, $paymentPosScope['params']);
}
$paymentPosWhereSql = $paymentPosWhere === [] ? '1=1' : implode(' AND ', $paymentPosWhere);
$data['stats']['total_received'] += (float)($scalar("SELECT COALESCE(SUM(pp.amount), 0) FROM pos_payments pp JOIN pos_transactions t ON pp.transaction_id = t.id WHERE {$paymentPosWhereSql}", $paymentPosParams) ?: 0);
}
if (db_table_exists('purchases')) {
$purchaseScope = outlet_scope_sql('purchases', 'outlet_id');
$purchaseTotalExpression = db_column_exists('purchases', 'total_with_vat') ? 'COALESCE(total_with_vat, 0)' : (db_column_exists('purchases', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
$data['stats']['total_purchases'] = (float)($scalar("SELECT COALESCE(SUM({$purchaseTotalExpression}), 0) FROM purchases WHERE {$purchaseScope['sql']}", $purchaseScope['params']) ?: 0);
}
if (db_table_exists('purchase_payments') && db_table_exists('purchases') && db_column_exists('purchase_payments', 'purchase_id')) {
$purchasePaymentScope = outlet_scope_sql('purchases', 'p.outlet_id');
$purchasePaymentWhere = $purchasePaymentScope['sql'] === '1=1' ? '1=1' : $purchasePaymentScope['sql'];
$data['stats']['total_paid'] = (float)($scalar("SELECT COALESCE(SUM(pp.amount), 0) FROM purchase_payments pp JOIN purchases p ON pp.purchase_id = p.id WHERE {$purchasePaymentWhere}", $purchasePaymentScope['params']) ?: 0);
}
$data['stats']['total_receivable'] = max((float)$data['stats']['total_sales'] - (float)$data['stats']['total_received'], 0);
$data['stats']['total_payable'] = max((float)$data['stats']['total_purchases'] - (float)$data['stats']['total_paid'], 0);
$data['monthly_sales'] = dashboard_sales_series('month', 12);
$data['yearly_sales'] = dashboard_sales_series('year', 5);
}
break;
}
@ -5598,7 +5789,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
if (count($user_outlets_list) > 1 || $is_admin):
$current_oid = current_outlet_id();
$current_oname = $current_oid === -1 ? (__('All Outlets') ?: 'All Outlets') : (db()->query("SELECT name FROM outlets WHERE id = $current_oid")->fetchColumn() ?: 'Outlet ' . $current_oid);
$current_oname = current_outlet_name();
?>
<div class="dropdown d-inline-block me-3">
<button class="btn btn-outline-secondary dropdown-toggle btn-sm" type="button" data-bs-toggle="dropdown">
@ -5833,8 +6024,15 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<div class="row g-4 mb-4">
<div class="col-12">
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Sales Performance" data-ar="أداء المبيعات">Sales Performance</h5>
<?php $dashboardScopeLabel = (string)($data['dashboard_scope_label'] ?? current_outlet_name()); ?>
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
<div>
<h5 class="m-0" data-en="Sales Performance" data-ar="أداء المبيعات">Sales Performance</h5>
<div class="small text-muted mt-1">
<i class="fas fa-store me-1"></i>
<span data-en="<?= htmlspecialchars('Scope: ' . $dashboardScopeLabel, ENT_QUOTES) ?>" data-ar="<?= htmlspecialchars('النطاق: ' . $dashboardScopeLabel, ENT_QUOTES) ?>"><?= $lang === 'ar' ? 'النطاق: ' : 'Scope: ' ?><?= htmlspecialchars($dashboardScopeLabel) ?></span>
</div>
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary active" id="btnMonthly" data-en="Monthly" data-ar="شهري">Monthly</button>
<button type="button" class="btn btn-outline-primary" id="btnYearly" data-en="Yearly" data-ar="سنوي">Yearly</button>
@ -5843,6 +6041,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<div style="height: 300px;">
<canvas id="salesChart"></canvas>
</div>
<div id="salesChartEmptyState" class="small text-muted mt-3 d-none" data-en="No completed sales recorded for this outlet yet." data-ar="لا توجد مبيعات مكتملة مسجلة لهذا الفرع حتى الآن.">No completed sales recorded for this outlet yet.</div>
</div>
</div>
</div>

View File

@ -58,53 +58,74 @@ document.addEventListener('DOMContentLoaded', function() {
});
// -----------------------------
<?php if ($page === 'dashboard' && can('dashboard_view')): ?>
const monthlyData = <?= json_encode($data['monthly_sales']) ?>;
const yearlyData = <?= json_encode($data['yearly_sales']) ?>;
const normalizeSalesSeries = (series) => Array.isArray(series)
? series.map(point => ({
label: String(point.label ?? ''),
total: Number(point.total ?? 0)
}))
: [];
const ctx = document.getElementById('salesChart').getContext('2d');
let salesChart = new Chart(ctx, {
type: 'line',
data: {
labels: monthlyData.map(d => d.label),
datasets: [{
label: 'Sales (OMR)',
data: monthlyData.map(d => d.total),
borderColor: '#0d6efd',
backgroundColor: 'rgba(13, 110, 253, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
const monthlyData = normalizeSalesSeries(<?= json_encode($data['monthly_sales']) ?>);
const yearlyData = normalizeSalesSeries(<?= json_encode($data['yearly_sales']) ?>);
const salesChartCanvas = document.getElementById('salesChart');
const salesChartEmptyState = document.getElementById('salesChartEmptyState');
if (salesChartCanvas) {
const ctx = salesChartCanvas.getContext('2d');
let salesChart = new Chart(ctx, {
type: 'line',
data: {
labels: monthlyData.map(d => d.label),
datasets: [{
label: 'Sales (OMR)',
data: monthlyData.map(d => d.total),
borderColor: '#0d6efd',
backgroundColor: 'rgba(13, 110, 253, 0.1)',
fill: true,
tension: 0.4
}]
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) { return 'OMR ' + value.toFixed(3); }
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return 'OMR ' + Number(value).toFixed(3);
}
}
}
}
}
}
});
});
document.getElementById('btnMonthly').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('btnYearly').classList.remove('active');
salesChart.data.labels = monthlyData.map(d => d.label);
salesChart.data.datasets[0].data = monthlyData.map(d => d.total);
salesChart.update();
});
const renderSalesSeries = (series) => {
salesChart.data.labels = series.map(d => d.label);
salesChart.data.datasets[0].data = series.map(d => d.total);
salesChart.update();
document.getElementById('btnYearly').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('btnMonthly').classList.remove('active');
salesChart.data.labels = yearlyData.map(d => d.label);
salesChart.data.datasets[0].data = yearlyData.map(d => d.total);
salesChart.update();
});
if (salesChartEmptyState) {
salesChartEmptyState.classList.toggle('d-none', series.length > 0);
}
};
renderSalesSeries(monthlyData);
document.getElementById('btnMonthly')?.addEventListener('click', function() {
this.classList.add('active');
document.getElementById('btnYearly')?.classList.remove('active');
renderSalesSeries(monthlyData);
});
document.getElementById('btnYearly')?.addEventListener('click', function() {
this.classList.add('active');
document.getElementById('btnMonthly')?.classList.remove('active');
renderSalesSeries(yearlyData);
});
}
<?php endif; ?>