228 lines
8.3 KiB
PHP
228 lines
8.3 KiB
PHP
<?php
|
|
require_once __DIR__ . '/includes/header.php';
|
|
|
|
if (!canView('expenses')) {
|
|
redirect('index.php');
|
|
}
|
|
|
|
// Helper to get totals
|
|
function getExpenseStats($month, $year) {
|
|
$db = db();
|
|
$start_date = "$year-$month-01";
|
|
$end_date = date("Y-m-t", strtotime($start_date));
|
|
|
|
// Total for month
|
|
$stmt = $db->prepare("SELECT SUM(amount) FROM expenses WHERE date BETWEEN ? AND ?");
|
|
$stmt->execute([$start_date, $end_date]);
|
|
$total = $stmt->fetchColumn() ?: 0;
|
|
|
|
// By Category
|
|
$stmt = $db->prepare("SELECT c.name, SUM(e.amount) as total
|
|
FROM expenses e
|
|
JOIN expense_categories c ON e.category_id = c.id
|
|
WHERE e.date BETWEEN ? AND ?
|
|
GROUP BY c.name
|
|
ORDER BY total DESC");
|
|
$stmt->execute([$start_date, $end_date]);
|
|
$by_category = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
return ['total' => $total, 'by_category' => $by_category];
|
|
}
|
|
|
|
// Current month stats
|
|
$current_month = date('m');
|
|
$current_year = date('Y');
|
|
$current_stats = getExpenseStats($current_month, $current_year);
|
|
|
|
// Last 6 months trend
|
|
$trend_data = [];
|
|
for ($i = 5; $i >= 0; $i--) {
|
|
$d = strtotime("-$i months");
|
|
$m = date('m', $d);
|
|
$y = date('Y', $d);
|
|
$s = getExpenseStats($m, $y);
|
|
$trend_data[] = [
|
|
'label' => date('M Y', $d), // English month names for Chart.js
|
|
'display_label' => date('m/Y', $d),
|
|
'total' => $s['total']
|
|
];
|
|
}
|
|
|
|
// Recent Expenses
|
|
$stmt = db()->query("SELECT e.*, c.name as category_name
|
|
FROM expenses e
|
|
JOIN expense_categories c ON e.category_id = c.id
|
|
ORDER BY e.date DESC, e.id DESC LIMIT 5");
|
|
$recent_expenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
?>
|
|
|
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">لوحة تحكم المصروفات</h1>
|
|
<div class="btn-toolbar mb-2 mb-md-0">
|
|
<a href="expenses.php" class="btn btn-sm btn-primary">
|
|
<i class="fas fa-list"></i> عرض السجل
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-body">
|
|
<h6 class="text-muted mb-2">إجمالي مصروفات هذا الشهر</h6>
|
|
<h3 class="fw-bold text-danger mb-0"><?= number_format($current_stats['total'], 2) ?> ر.س</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-body">
|
|
<h6 class="text-muted mb-2">أعلى تصنيف للصرف</h6>
|
|
<?php if (!empty($current_stats['by_category'])): ?>
|
|
<h3 class="fw-bold text-primary mb-0"><?= htmlspecialchars($current_stats['by_category'][0]['name']) ?></h3>
|
|
<small class="text-muted"><?= number_format($current_stats['by_category'][0]['total'], 2) ?> ر.س</small>
|
|
<?php else: ?>
|
|
<h3 class="fw-bold text-muted mb-0">-</h3>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-body">
|
|
<h6 class="text-muted mb-2">متوسط الصرف (آخر 6 أشهر)</h6>
|
|
<?php
|
|
$avg = array_sum(array_column($trend_data, 'total')) / count($trend_data);
|
|
?>
|
|
<h3 class="fw-bold text-info mb-0"><?= number_format($avg, 2) ?> ر.س</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<!-- Trend Chart -->
|
|
<div class="col-md-8 mb-4 mb-md-0">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header bg-transparent border-0 d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">اتجاه المصروفات (آخر 6 أشهر)</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="trendChart" height="120"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Pie Chart -->
|
|
<div class="col-md-4">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header bg-transparent border-0">
|
|
<h5 class="mb-0">توزيع المصروفات (<?= date('m/Y') ?>)</h5>
|
|
</div>
|
|
<div class="card-body position-relative">
|
|
<canvas id="categoryChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Expenses -->
|
|
<div class="card shadow-sm border-0">
|
|
<div class="card-header bg-transparent border-0">
|
|
<h5 class="mb-0">آخر المصروفات المسجلة</h5>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4">التاريخ</th>
|
|
<th>التصنيف</th>
|
|
<th>الوصف/المورد</th>
|
|
<th>المبلغ</th>
|
|
<th>طريقة الدفع</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($recent_expenses)): ?>
|
|
<tr>
|
|
<td colspan="5" class="text-center py-4 text-muted">لا توجد مصروفات مسجلة</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($recent_expenses as $exp): ?>
|
|
<tr>
|
|
<td class="ps-4"><?= $exp['date'] ?></td>
|
|
<td><span class="badge bg-secondary bg-opacity-10 text-secondary"><?= htmlspecialchars($exp['category_name']) ?></span></td>
|
|
<td>
|
|
<div class="fw-bold"><?= htmlspecialchars($exp['description'] ?: '-') ?></div>
|
|
<div class="small text-muted"><?= htmlspecialchars($exp['vendor'] ?: '') ?></div>
|
|
</td>
|
|
<td class="fw-bold text-danger"><?= number_format($exp['amount'], 2) ?></td>
|
|
<td><?= htmlspecialchars($exp['payment_method']) ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script>
|
|
// Trend Chart
|
|
const trendCtx = document.getElementById('trendChart').getContext('2d');
|
|
new Chart(trendCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: <?= json_encode(array_column($trend_data, 'display_label')) ?>,
|
|
datasets: [{
|
|
label: 'المصروفات',
|
|
data: <?= json_encode(array_column($trend_data, 'total')) ?>,
|
|
backgroundColor: 'rgba(232, 62, 140, 0.6)', // Pinkish
|
|
borderColor: 'rgba(232, 62, 140, 1)',
|
|
borderWidth: 1,
|
|
borderRadius: 4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: { display: false }
|
|
},
|
|
scales: {
|
|
y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' } },
|
|
x: { grid: { display: false } }
|
|
}
|
|
}
|
|
});
|
|
|
|
// Category Chart
|
|
const catCtx = document.getElementById('categoryChart').getContext('2d');
|
|
const catData = <?= json_encode($current_stats['by_category']) ?>;
|
|
|
|
new Chart(catCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: catData.map(d => d.name),
|
|
datasets: [{
|
|
data: catData.map(d => d.total),
|
|
backgroundColor: [
|
|
'#4e73df', '#1cc88a', '#36b9cc', '#f6c23e', '#e74a3b',
|
|
'#858796', '#5a5c69', '#fd7e14', '#20c997', '#6f42c1'
|
|
],
|
|
borderWidth: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
cutout: '70%',
|
|
plugins: {
|
|
legend: { position: 'bottom', labels: { boxWidth: 12 } }
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|