38394-vm/admin/financial_summary.php
2026-02-13 09:54:12 +00:00

501 lines
20 KiB
PHP

<?php
require_once 'auth.php';
require_once '../db/config.php';
require_login();
$user = get_user();
$pdo = db();
// Fetch Categories for filter
$stmt = $pdo->query("SELECT id, name_en FROM categories ORDER BY name_en ASC");
$categories = $stmt->fetchAll();
// Filters
$start_date = $_GET['start_date'] ?? '';
$end_date = $_GET['end_date'] ?? '';
$category_id = $_GET['category_id'] ?? '';
$status_filter = $_GET['status'] ?? '';
// Build base where clauses for donations table
$where_clauses = ["1=1"];
$params = [];
if (!empty($start_date)) {
$where_clauses[] = "d.created_at >= :start_date";
$params[':start_date'] = $start_date . " 00:00:00";
}
if (!empty($end_date)) {
$where_clauses[] = "d.created_at <= :end_date";
$params[':end_date'] = $end_date . " 23:59:59";
}
if (!empty($status_filter)) {
$where_clauses[] = "d.status = :status";
$params[':status'] = $status_filter;
}
// Category filter requires join with cases
$category_join = "";
if (!empty($category_id)) {
$category_join = " JOIN cases cs_filter ON d.case_id = cs_filter.id ";
$where_clauses[] = "cs_filter.category_id = :category_id";
$params[':category_id'] = $category_id;
}
$where_sql = implode(" AND ", $where_clauses);
// Basic Stats
$stats_stmt = $pdo->prepare("
SELECT
COUNT(*) as total_count,
SUM(CASE WHEN d.status = 'completed' THEN d.amount ELSE 0 END) as total_revenue,
AVG(CASE WHEN d.status = 'completed' THEN d.amount ELSE NULL END) as avg_donation,
SUM(CASE WHEN d.status = 'pending' THEN 1 ELSE 0 END) as pending_count,
SUM(CASE WHEN d.status = 'completed' THEN 1 ELSE 0 END) as completed_count
FROM donations d
$category_join
WHERE $where_sql
");
$stats_stmt->execute($params);
$stats = $stats_stmt->fetch();
// Revenue by Category
$cat_where = $where_sql;
$cat_params = $params;
if (empty($status_filter)) {
$cat_where .= " AND d.status = 'completed'";
}
$stmt = $pdo->prepare("
SELECT c.name_en, SUM(d.amount) as total
FROM categories c
JOIN cases cs ON cs.category_id = c.id
JOIN donations d ON d.case_id = cs.id
WHERE $cat_where
GROUP BY c.id
ORDER BY total DESC
");
$stmt->execute($cat_params);
$category_revenue = $stmt->fetchAll();
$cat_labels = [];
$cat_totals = [];
foreach ($category_revenue as $row) {
$cat_labels[] = $row['name_en'];
$cat_totals[] = (float)$row['total'];
}
// Monthly Revenue Trend
$trend_where = $where_sql;
$trend_params = $params;
if (empty($status_filter)) {
$trend_where .= " AND d.status = 'completed'";
}
if (empty($start_date) && empty($end_date)) {
$trend_where .= " AND d.created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)";
}
$stmt = $pdo->prepare("
SELECT
DATE_FORMAT(d.created_at, '%Y-%m') as month,
SUM(d.amount) as total
FROM donations d
$category_join
WHERE $trend_where
GROUP BY month
ORDER BY month ASC
");
$stmt->execute($trend_params);
$monthly_trend = $stmt->fetchAll();
$trend_labels = [];
$trend_totals = [];
foreach ($monthly_trend as $row) {
$trend_labels[] = date('M Y', strtotime($row['month'] . '-01'));
$trend_totals[] = (float)$row['total'];
}
// Top Cases by Revenue
$top_where = $where_sql;
$top_params = $params;
if (empty($status_filter)) {
$top_where .= " AND d.status = 'completed'";
}
$stmt = $pdo->prepare("
SELECT cs.title_en, SUM(d.amount) as total, cs.goal
FROM cases cs
JOIN donations d ON d.case_id = cs.id
WHERE $top_where
GROUP BY cs.id
ORDER BY total DESC
LIMIT 5
");
$stmt->execute($top_params);
$top_cases = $stmt->fetchAll();
// Gift vs Regular
$gift_where = $where_sql;
$gift_params = $params;
if (empty($status_filter)) {
$gift_where .= " AND d.status = 'completed'";
}
$stmt = $pdo->prepare("
SELECT
d.is_gift,
COUNT(*) as count,
SUM(d.amount) as total
FROM donations d
$category_join
WHERE $gift_where
GROUP BY d.is_gift
");
$stmt->execute($gift_params);
$gift_stats = $stmt->fetchAll();
$gift_labels = ['Regular', 'Gift'];
$gift_totals = [0, 0];
foreach ($gift_stats as $row) {
if ($row['is_gift']) {
$gift_totals[1] = (float)$row['total'];
} else {
$gift_totals[0] = (float)$row['total'];
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Financial Summary - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root { --sidebar-width: 260px; --primary-color: #059669; }
body { background-color: #f3f4f6; }
.sidebar { width: var(--sidebar-width); height: 100vh; position: fixed; left: 0; top: 0; background: #111827; color: #fff; padding: 1.5rem; }
.main-content { margin-left: var(--sidebar-width); padding: 2rem; }
.card { border: none; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.stat-card { padding: 1.5rem; border-left: 4px solid var(--primary-color); }
.chart-container { position: relative; height: 250px; }
.filter-section { background: #fff; padding: 1.25rem; border-radius: 12px; margin-bottom: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
@media print {
.sidebar, .filter-section, .no-print {
display: none !important;
}
.main-content {
margin-left: 0 !important;
padding: 0 !important;
}
.card {
box-shadow: none !important;
border: 1px solid #eee !important;
break-inside: avoid;
}
body {
background-color: #fff !important;
}
.stat-card {
border-left: 4px solid var(--primary-color) !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.progress-bar {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
</style>
</head>
<body>
<?php include "sidebar.php"; ?>
<div class="main-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="mb-0">Financial Summary Report</h2>
<p class="text-muted mb-0">Detailed analysis of donations and revenue streams.</p>
</div>
<div class="text-end">
<div class="fw-bold"><?= date('l, F j, Y') ?></div>
<div class="text-muted small no-print"><?= htmlspecialchars(get_org_name()) ?> Admin Panel</div>
</div>
</div>
<!-- Filters -->
<div class="filter-section">
<form method="GET" class="row g-3 align-items-end">
<div class="col-md-2">
<label class="form-label small fw-bold text-muted">Start Date</label>
<input type="date" name="start_date" class="form-control" value="<?= htmlspecialchars($start_date) ?>">
</div>
<div class="col-md-2">
<label class="form-label small fw-bold text-muted">End Date</label>
<input type="date" name="end_date" class="form-control" value="<?= htmlspecialchars($end_date) ?>">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold text-muted">Category</label>
<select name="category_id" class="form-select">
<option value="">All Categories</option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= $category_id == $cat['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name_en']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small fw-bold text-muted">Status</label>
<select name="status" class="form-select">
<option value="">All (Default: Completed)</option>
<option value="completed" <?= $status_filter == 'completed' ? 'selected' : '' ?>>Completed</option>
<option value="pending" <?= $status_filter == 'pending' ? 'selected' : '' ?>>Pending</option>
<option value="failed" <?= $status_filter == 'failed' ? 'selected' : '' ?>>Failed</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary px-3">
<i class="bi bi-filter"></i> Apply
</button>
<a href="financial_summary.php" class="btn btn-outline-secondary ms-1">Reset</a>
<button type="button" onclick="window.print()" class="btn btn-outline-danger ms-1">
<i class="bi bi-file-earmark-pdf"></i> Export PDF
</button>
</div>
</form>
<?php if (!empty($start_date) || !empty($end_date) || !empty($category_id) || !empty($status_filter)): ?>
<div class="mt-3">
<div class="d-flex flex-wrap gap-2">
<?php if (!empty($start_date) || !empty($end_date)): ?>
<span class="badge bg-light text-dark border p-2">
<i class="bi bi-calendar-check me-1"></i>
<?= $start_date ?: 'Beginning' ?> - <?= $end_date ?: 'Today' ?>
</span>
<?php endif; ?>
<?php if (!empty($category_id)):
$cat_name = "";
foreach($categories as $c) if($c['id'] == $category_id) $cat_name = $c['name_en'];
?>
<span class="badge bg-light text-dark border p-2">
<i class="bi bi-tag me-1"></i>
Category: <?= htmlspecialchars($cat_name) ?>
</span>
<?php endif; ?>
<?php if (!empty($status_filter)): ?>
<span class="badge bg-light text-dark border p-2">
<i class="bi bi-info-circle me-1"></i>
Status: <?= ucfirst(htmlspecialchars($status_filter)) ?>
</span>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
<!-- Print-only filter summary -->
<div class="d-none d-print-block mb-4 p-3 bg-light border rounded">
<h6 class="mb-2 fw-bold text-uppercase small text-muted">Report Filters</h6>
<div class="row">
<div class="col-3 small"><strong>Date Range:</strong> <?= ($start_date ?: 'All') . ' to ' . ($end_date ?: 'Today') ?></div>
<div class="col-3 small"><strong>Category:</strong> <?php
if($category_id) {
foreach($categories as $c) if($c['id'] == $category_id) echo htmlspecialchars($c['name_en']);
} else {
echo "All Categories";
}
?></div>
<div class="col-3 small"><strong>Status:</strong> <?= $status_filter ? ucfirst(htmlspecialchars($status_filter)) : 'Completed (Default)' ?></div>
<div class="col-3 small text-end text-muted">Generated by: <?= htmlspecialchars($user['name']) ?></div>
</div>
</div>
<!-- Summary Cards -->
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card stat-card">
<div class="text-muted small">Total Revenue</div>
<div class="h3 mb-0">OMR <?= number_format($stats['total_revenue'] ?? 0, 3) ?></div>
<div class="text-success small"><i class="bi bi-check-circle"></i> From <?= $stats['completed_count'] ?> donations</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card" style="border-left-color: #3b82f6;">
<div class="text-muted small">Avg. Donation</div>
<div class="h3 mb-0">OMR <?= number_format($stats['avg_donation'] ?? 0, 3) ?></div>
<div class="text-primary small">Per completed donation</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card" style="border-left-color: #f59e0b;">
<div class="text-muted small">Pending Count</div>
<div class="h3 mb-0"><?= $stats['pending_count'] ?></div>
<div class="text-warning small">Awaiting payment</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card" style="border-left-color: #ec4899;">
<div class="text-muted small">Total Donations</div>
<div class="h3 mb-0"><?= $stats['total_count'] ?></div>
<div class="text-danger small">Based on active filters</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<!-- Monthly Trend -->
<div class="col-lg-8">
<div class="card p-4 h-100">
<h5 class="mb-4">Revenue Trend <?= empty($start_date) && empty($end_date) ? '(Last 12 Months)' : '' ?></h5>
<div style="height: 300px;">
<canvas id="trendChart"></canvas>
</div>
</div>
</div>
<!-- Category Distribution -->
<div class="col-lg-4">
<div class="card p-4 h-100">
<h5 class="mb-4">Revenue by Category</h5>
<div class="chart-container">
<canvas id="categoryChart"></canvas>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<!-- Top Cases -->
<div class="col-lg-6">
<div class="card p-4 h-100">
<h5 class="mb-4">Top Performing Cases</h5>
<div class="list-group list-group-flush">
<?php if (empty($top_cases)): ?>
<p class="text-muted text-center py-5">No donations found for this selection.</p>
<?php else: ?>
<?php foreach ($top_cases as $case): ?>
<div class="list-group-item px-0 border-0 mb-3">
<div class="d-flex justify-content-between mb-1">
<span class="fw-bold"><?= htmlspecialchars($case['title_en']) ?></span>
<span>OMR <?= number_format($case['total'], 3) ?></span>
</div>
<?php
$percent = $case['goal'] > 0 ? ($case['total'] / $case['goal']) * 100 : 0;
?>
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-success" role="progressbar" style="width: <?= min(100, $percent) ?>%"></div>
</div>
<div class="text-muted small mt-1"><?= number_format($percent, 1) ?>% of OMR <?= number_format($case['goal'], 0) ?> goal</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
<!-- Gift vs Regular -->
<div class="col-lg-6">
<div class="card p-4 h-100">
<h5 class="mb-4">Regular vs Gift Donations (Revenue)</h5>
<div class="d-flex align-items-center justify-content-center" style="height: 250px;">
<canvas id="giftChart"></canvas>
</div>
<div class="mt-4 row text-center">
<div class="col-6">
<div class="text-muted small">Regular</div>
<div class="h5">OMR <?= number_format($gift_totals[0], 3) ?></div>
</div>
<div class="col-6">
<div class="text-muted small">Gift</div>
<div class="h5">OMR <?= number_format($gift_totals[1], 3) ?></div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Disable animations for cleaner printing
const isPrinting = window.matchMedia('print');
const animationConfig = {
duration: isPrinting.matches ? 0 : 1000
};
// Trend Chart
new Chart(document.getElementById('trendChart'), {
type: 'bar',
data: {
labels: <?= json_encode($trend_labels) ?>,
datasets: [{
label: 'Revenue (OMR)',
data: <?= json_encode($trend_totals) ?>,
backgroundColor: '#059669',
borderRadius: 6
}]
},
options: {
animation: animationConfig,
responsive: true,
maintainAspectRatio: false,
scales: {
y: { beginAtZero: true, grid: { color: '#f3f4f6' } },
x: { grid: { display: false } }
}
}
});
// Category Chart
new Chart(document.getElementById('categoryChart'), {
type: 'doughnut',
data: {
labels: <?= json_encode($cat_labels) ?>,
datasets: [{
data: <?= json_encode($cat_totals) ?>,
backgroundColor: ['#059669', '#3b82f6', '#f59e0b', '#ec4899', '#8b5cf6', '#06b6d4']
}]
},
options: {
animation: animationConfig,
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom', labels: { boxWidth: 12 } }
}
}
});
// Gift Chart
new Chart(document.getElementById('giftChart'), {
type: 'pie',
data: {
labels: <?= json_encode($gift_labels) ?>,
datasets: [{
data: <?= json_encode($gift_totals) ?>,
backgroundColor: ['#10b981', '#f472b6']
}]
},
options: {
animation: animationConfig,
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
}
}
});
// Listen for print event to disable animations if not already handled
window.onbeforeprint = () => {
Chart.instances.forEach(chart => {
chart.options.animation = false;
chart.update('none');
});
};
</script>
</body>
</html>