39728-vm/index.php
2026-04-23 03:09:14 +00:00

466 lines
20 KiB
PHP

<?php
require_once __DIR__ . '/includes/app.php';
$user = require_auth();
$pageTitle = tr('لوحة التحكم', 'Dashboard');
$activeNav = 'dashboard';
$dbError = null;
$metrics = ['today_sales' => 0, 'today_revenue' => 0.0, 'pos_count' => 0, 'normal_count' => 0, 'recent' => []];
$reportMetrics = ['branch_totals' => [], 'payment_totals' => [], 'product_totals' => []];
try {
$metrics = dashboard_metrics();
$reportMetrics = report_metrics();
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
$branchLabels = [];
$branchData = [];
foreach ($reportMetrics['branch_totals'] as $code => $total) {
$branchLabels[] = branch_label((string)$code);
$branchData[] = (float)$total;
}
$productLabels = [];
$productData = [];
$topProducts = array_slice($reportMetrics['product_totals'], 0, 5, true);
foreach ($topProducts as $sku => $qty) {
$productLabels[] = product_label((string)$sku);
$productData[] = (int)$qty;
}
$monthlyLabels = [];
$monthlyData = [];
if (isset($reportMetrics['monthly_totals'])) {
foreach ($reportMetrics['monthly_totals'] as $month => $total) {
// format month: "2026-04" -> "Apr 2026"
$time = strtotime($month . '-01');
$monthlyLabels[] = date('M Y', $time);
$monthlyData[] = (float)$total;
}
}
require __DIR__ . '/includes/header.php';
?>
<?php if ($dbError): ?>
<div class="alert alert-danger mb-4"><i class="bi bi-exclamation-triangle me-2"></i><?= h(tr('تعذر تحميل بيانات قاعدة البيانات حالياً:', 'Database data could not be loaded right now:')) ?> <?= h($dbError) ?></div>
<?php endif; ?>
<!-- Metrics Row -->
<div class="row g-4 mb-4">
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-primary h-100" style=" background: linear-gradient(135deg, #0d6efd 0%, #0043a8 100%);">
<div class="card-body d-flex flex-column justify-content-between">
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="text-white-50 text-uppercase mb-0 fw-bold"><?= h(tr('مبيعات اليوم', 'Today sales')) ?></h6>
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-receipt fs-5 text-white"></i></div>
</div>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['today_sales']) ?></h2>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-success h-100" style=" background: linear-gradient(135deg, #198754 0%, #0f5132 100%);">
<div class="card-body d-flex flex-column justify-content-between">
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="text-white-50 text-uppercase mb-0 fw-bold"><?= h(tr('إيراد اليوم', 'Today revenue')) ?></h6>
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-cash-stack fs-5 text-white"></i></div>
</div>
<h2 class="display-6 fw-bold mb-0"><?= h(currency((float) $metrics['today_revenue'])) ?></h2>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-info h-100" style=" background: linear-gradient(135deg, #0dcaf0 0%, #087990 100%);">
<div class="card-body d-flex flex-column justify-content-between">
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="text-white-50 text-uppercase mb-0 fw-bold">POS</h6>
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-cart fs-5 text-white"></i></div>
</div>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['pos_count']) ?></h2>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-warning h-100" style=" background: linear-gradient(135deg, #ffc107 0%, #b38600 100%);">
<div class="card-body d-flex flex-column justify-content-between">
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="text-white-50 text-uppercase mb-0 fw-bold"><?= h(tr('فاتورة', 'Invoice')) ?></h6>
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-basket fs-5 text-white"></i></div>
</div>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['normal_count']) ?></h2>
</div>
</div>
</div>
</div>
<!-- Charts Row -->
<div class="row g-4 mb-4">
<div class="col-xl-6">
<div class="card h-100">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-semibold"><i class="bi bi-bar-chart-line me-2 text-primary"></i><?= h(tr('أفضل الأصناف مبيعاً', 'Top Selling Products')) ?></h5>
</div>
<div class="card-body">
<?php if(empty($productLabels)): ?>
<div class="text-center text-muted py-5">
<i class="bi bi-graph-down text-secondary fs-1 d-block mb-3"></i>
<p><?= h(tr('لا توجد بيانات كافية لعرض الرسم البياني', 'Not enough data to show chart')) ?></p>
</div>
<?php else: ?>
<div style="position: relative; height:300px; width:100%">
<canvas id="topProductsChart"></canvas>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-xl-6">
<div class="card h-100">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-semibold"><i class="bi bi-pie-chart me-2 text-success"></i><?= h(tr('المبيعات حسب الفرع', 'Sales by Branch')) ?></h5>
</div>
<div class="card-body d-flex justify-content-center align-items-center">
<?php if(empty($branchLabels)): ?>
<div class="text-center text-muted py-5">
<i class="bi bi-pie-chart text-secondary fs-1 d-block mb-3"></i>
<p><?= h(tr('لا توجد بيانات كافية لعرض الرسم البياني', 'Not enough data to show chart')) ?></p>
</div>
<?php else: ?>
<div style="position: relative; height:300px; width:100%">
<canvas id="branchSalesChart"></canvas>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<!-- Recent Sales -->
<div class="col-xl-8">
<div class="card h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3 border-0">
<h5 class="mb-0 fw-semibold"><i class="bi bi-clock-history me-2 text-info"></i><?= h(tr('آخر المبيعات', 'Latest sales')) ?></h5>
<a class="btn btn-sm btn-outline-primary rounded-pill px-3" href="<?= h(url_for('sales.php')) ?>"><?= h(tr('عرض الكل', 'View all')) ?></a>
</div>
<div class="card-body p-0">
<?php if (!$metrics['recent']): ?>
<div class="text-center p-5 text-muted">
<i class="bi bi-inbox fs-1 mb-3 d-block text-secondary"></i>
<h5><?= h(tr('لا توجد مبيعات بعد', 'No sales yet')) ?></h5>
<p class="mb-3"><?= h(tr('ابدأ بأول فاتورة من صفحة نقاط البيع.', 'Start your first receipt from the POS page.')) ?></p>
<a class="btn btn-primary" href="<?= h(url_for('pos.php')) ?>"><?= h(tr('إنشاء بيع POS', 'Create POS sale')) ?></a>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0 text-center">
<thead class="bg-light text-muted">
<tr>
<th class="ps-3 border-0 py-3 fw-semibold"><?= h(tr('رقم الإيصال', 'Receipt No')) ?></th>
<th class="border-0 py-3 fw-semibold"><?= h(tr('الفرع', 'Branch')) ?></th>
<th class="border-0 py-3 fw-semibold"><?= h(tr('النوع', 'Type')) ?></th>
<th class="border-0 py-3 fw-semibold"><?= h(tr('التاريخ', 'Date')) ?></th>
<th class="text-end pe-3 border-0 py-3 fw-semibold"><?= h(tr('الإجمالي', 'Total')) ?></th>
</tr>
</thead>
<tbody class="border-top-0">
<?php foreach ($metrics['recent'] as $sale): ?>
<tr>
<td class="ps-3">
<a href="<?= h(url_for('sale.php', ['id' => $sale['id']])) ?>" class="fw-bold text-decoration-none text-primary">
#<?= h($sale['receipt_no']) ?>
</a>
</td>
<td>
<span class="badge bg-light text-dark border"><?= h(branch_label((string) $sale['branch_code'])) ?></span>
</td>
<td>
<?php if($sale['sale_mode'] === 'pos'): ?>
<span class="badge bg-info bg-opacity-10 text-info border border-info"><?= h(sale_mode_label((string) $sale['sale_mode'])) ?></span>
<?php else: ?>
<span class="badge bg-warning bg-opacity-10 text-warning border border-warning"><?= h(sale_mode_label((string) $sale['sale_mode'])) ?></span>
<?php endif; ?>
</td>
<td><small class="text-muted fw-medium"><?= h(date('M d, H:i', strtotime((string) $sale['sale_date']))) ?></small></td>
<td class="text-end pe-3 fw-bold text-success"><?= h(currency((float) $sale['total_amount'])) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Quick Actions / Status -->
<div class="col-xl-4">
<div class="card h-100">
<div class="card-header bg-white py-3 border-0">
<h5 class="mb-0 fw-semibold"><i class="bi bi-lightning-charge me-2 text-warning"></i><?= h(tr('إجراءات سريعة', 'Quick Actions')) ?></h5>
</div>
<div class="card-body">
<div class="d-grid gap-3">
<a href="<?= h(url_for('pos.php')) ?>" class="btn btn-primary btn-lg d-flex align-items-center justify-content-between" style="border-radius: 10px; box-shadow: 0 4px 6px rgba(13, 110, 253, 0.2);">
<span><i class="bi bi-cart-plus me-2"></i> <?= h(tr('نقطة بيع جديدة', 'New POS Sale')) ?></span>
<i class="bi bi-chevron-right"></i>
</a>
<a href="<?= h(url_for('normal_sale.php')) ?>" class="btn btn-outline-primary btn-lg d-flex align-items-center justify-content-between" style="border-radius: 10px;">
<span><i class="bi bi-receipt me-2"></i> <?= h(tr('فاتورة', 'Invoice')) ?></span>
<i class="bi bi-chevron-right"></i>
</a>
<button type="button" class="btn btn-light btn-lg d-flex align-items-center justify-content-between border" data-bs-toggle="modal" data-bs-target="#quickNoteModal" style="border-radius: 10px;">
<span class="text-dark"><i class="bi bi-pencil-square me-2"></i> <?= h(tr('ملاحظة سريعة', 'Quick Note')) ?></span>
<i class="bi bi-chevron-right text-dark"></i>
</button>
</div>
<div class="mt-4 pt-4 border-top">
<?php if ($user): ?>
<h6 class="text-muted text-uppercase small fw-bold mb-3"><?= h(tr('معلومات الحساب', 'Account Info')) ?></h6>
<div class="d-flex align-items-center mb-2 p-3 bg-light rounded-3 border">
<div class="bg-white p-2 rounded-circle shadow-sm me-3">
<i class="bi bi-person-badge fs-4 text-primary"></i>
</div>
<div>
<p class="mb-0 fw-bold"><?= h(current_lang() === 'ar' ? $user['name_ar'] : $user['name_en']) ?></p>
<small class="text-muted fw-medium"><?= h(role_label($user['role'])) ?> &bull; <?= h(branch_label($user['branch_code'])) ?></small>
</div>
</div>
<?php else: ?>
<div class="text-center p-3 bg-light rounded-3 border">
<p class="text-muted mb-3 small"><?= h(tr('جرّب المالك أو مدير الفرع أو الكاشير لرؤية اختلاف الصلاحيات.', 'Try owner, branch manager, or cashier to see different permissions.')) ?></p>
<a class="btn btn-dark w-100 rounded-pill" href="<?= h(url_for('login.php')) ?>"><?= h(tr('تسجيل الدخول للبدء', 'Sign in to start')) ?></a>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<!-- Monthly Sales Chart Row -->
<div class="row g-4 mb-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-semibold"><i class="bi bi-graph-up me-2 text-primary"></i><?= h(tr('مبيعات الأشهر', 'Monthly Sales')) ?></h5>
</div>
<div class="card-body">
<?php if(empty($monthlyLabels)): ?>
<div class="text-center text-muted py-5">
<i class="bi bi-graph-up text-secondary fs-1 d-block mb-3"></i>
<p><?= h(tr('لا توجد بيانات كافية لعرض الرسم البياني', 'Not enough data to show chart')) ?></p>
</div>
<?php else: ?>
<div style="position: relative; height:350px; width:100%">
<canvas id="monthlySalesChart"></canvas>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Modal Form Example -->
<div class="modal fade" id="quickNoteModal" tabindex="-1" aria-labelledby="quickNoteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius: 16px; border: 0; box-shadow: 0 10px 30px rgba(0,0,0,0.1);">
<form onsubmit="handleQuickNote(event)">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold" id="quickNoteModalLabel"><i class="bi bi-journal-text me-2 text-primary"></i><?= h(tr('إضافة ملاحظة سريعة', 'Add Quick Note')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="noteTitle" class="form-label fw-medium"><?= h(tr('العنوان', 'Title')) ?></label>
<input type="text" class="form-control form-control-lg bg-light border-0" id="noteTitle" required>
</div>
<div class="mb-3">
<label for="noteContent" class="form-label fw-medium"><?= h(tr('التفاصيل', 'Details')) ?></label>
<textarea class="form-control bg-light border-0" id="noteContent" rows="4" required></textarea>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
<button type="submit" class="btn btn-primary px-4 shadow-sm"><?= h(tr('حفظ الملاحظة', 'Save Note')) ?></button>
</div>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
<?php if(!empty($branchLabels)): ?>
const branchCtx = document.getElementById('branchSalesChart').getContext('2d');
new Chart(branchCtx, {
type: 'doughnut',
data: {
labels: <?= json_encode($branchLabels) ?>,
datasets: [{
data: <?= json_encode($branchData) ?>,
backgroundColor: [
'rgba(13, 110, 253, 0.8)',
'rgba(25, 135, 84, 0.8)',
'rgba(13, 202, 240, 0.8)',
'rgba(255, 193, 7, 0.8)',
'rgba(220, 53, 69, 0.8)'
],
borderWidth: 2,
borderColor: '#ffffff',
hoverOffset: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '65%',
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, padding: 20 } },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
padding: 12,
callbacks: {
label: function(context) {
let value = context.raw;
return ' ' + value.toFixed(3) + ' <?= tr("ر.ع", "OMR") ?>';
}
}
}
}
}
});
<?php endif; ?>
<?php if(!empty($productLabels)): ?>
const productsCtx = document.getElementById('topProductsChart').getContext('2d');
// Create gradient for bars
let gradient = productsCtx.createLinearGradient(0, 0, 0, 300);
gradient.addColorStop(0, 'rgba(13, 110, 253, 0.9)');
gradient.addColorStop(1, 'rgba(13, 110, 253, 0.2)');
new Chart(productsCtx, {
type: 'bar',
data: {
labels: <?= json_encode($productLabels) ?>,
datasets: [{
label: '<?= h(tr("الكمية المباعة", "Qty Sold")) ?>',
data: <?= json_encode($productData) ?>,
backgroundColor: gradient,
borderColor: '#0d6efd',
borderWidth: 1,
borderRadius: 6,
barPercentage: 0.6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
padding: 12
}
},
scales: {
y: {
beginAtZero: true,
ticks: { precision: 0 },
grid: { borderDash: [5, 5], color: '#f0f0f0' }
},
x: {
grid: { display: false }
}
}
}
});
<?php endif; ?>
<?php if(!empty($monthlyLabels)): ?>
const monthlyCtx = document.getElementById('monthlySalesChart').getContext('2d');
// Create gradient for line chart area
let gradientMonthly = monthlyCtx.createLinearGradient(0, 0, 0, 350);
gradientMonthly.addColorStop(0, 'rgba(25, 135, 84, 0.4)');
gradientMonthly.addColorStop(1, 'rgba(25, 135, 84, 0.0)');
new Chart(monthlyCtx, {
type: 'line',
data: {
labels: <?= json_encode($monthlyLabels) ?>,
datasets: [{
label: '<?= h(tr("إجمالي المبيعات", "Total Sales")) ?>',
data: <?= json_encode($monthlyData) ?>,
backgroundColor: gradientMonthly,
borderColor: '#198754',
borderWidth: 3,
pointBackgroundColor: '#ffffff',
pointBorderColor: '#198754',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
padding: 12,
callbacks: {
label: function(context) {
let value = context.raw;
return ' ' + value.toFixed(3) + ' <?= tr("ر.ع", "OMR") ?>';
}
}
}
},
scales: {
y: {
beginAtZero: true,
grid: { borderDash: [5, 5], color: '#f0f0f0' }
},
x: {
grid: { display: false }
}
}
}
});
<?php endif; ?>
});
function handleQuickNote(e) {
e.preventDefault();
var modalEl = document.getElementById('quickNoteModal');
var modal = bootstrap.Modal.getInstance(modalEl);
modal.hide();
Swal.fire({
title: '<?= h(tr('تم الحفظ!', 'Saved!')) ?>',
text: '<?= h(tr('تم حفظ الملاحظة بنجاح.', 'Note saved successfully.')) ?>',
icon: 'success',
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000
});
e.target.reset();
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>