add financial summery
This commit is contained in:
parent
a4dccb579a
commit
699f776eee
80
admin/audit_logs.php
Normal file
80
admin/audit_logs.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
require_once 'auth.php';
|
||||
require_once '../db/config.php';
|
||||
require_login();
|
||||
|
||||
$user = get_user();
|
||||
$pdo = db();
|
||||
|
||||
// Fetch audit logs with user info
|
||||
$logs = $pdo->query("
|
||||
SELECT l.*, u.email as user_email
|
||||
FROM audit_logs l
|
||||
LEFT JOIN users u ON l.user_id = u.id
|
||||
ORDER BY l.created_at DESC
|
||||
LIMIT 100
|
||||
")->fetchAll();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Audit Logs - CharityHub 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">
|
||||
<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; }
|
||||
.nav-link { color: #9ca3af; margin-bottom: 0.5rem; border-radius: 8px; }
|
||||
.nav-link:hover, .nav-link.active { color: #fff; background: #1f2937; }
|
||||
.nav-link.active { background: var(--primary-color); }
|
||||
.card { border: none; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php include "sidebar.php"; ?>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="mb-0">Activity Audit Logs</h2>
|
||||
</div>
|
||||
|
||||
<div class="card p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="ps-4">User</th>
|
||||
<th>Action</th>
|
||||
<th>Details</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($logs as $log): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<strong><?= htmlspecialchars($log['user_email'] ?? 'System/Unknown') ?></strong>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary rounded-pill"><?= htmlspecialchars($log['action']) ?></span>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($log['details']) ?></td>
|
||||
<td><?= date('Y-m-d H:i:s', strtotime($log['created_at'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($logs)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-4 text-muted">No logs found.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -43,4 +43,16 @@ function get_user() {
|
||||
function is_super_admin() {
|
||||
$user = get_user();
|
||||
return $user && $user['role'] === 'super_admin';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an administrative action to the audit_logs table
|
||||
*/
|
||||
function log_action($action, $details = null) {
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
$userId = $_SESSION['user_id'] ?? null;
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO audit_logs (user_id, action, details) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$userId, $action, $details]);
|
||||
}
|
||||
|
||||
@ -6,10 +6,41 @@ require_login();
|
||||
$pdo = db();
|
||||
$msg = '';
|
||||
|
||||
/**
|
||||
* Basic Image Optimization
|
||||
*/
|
||||
function optimize_image($source, $destination, $quality = 80) {
|
||||
$info = getimagesize($source);
|
||||
if ($info['mime'] == 'image/jpeg') {
|
||||
$image = imagecreatefromjpeg($source);
|
||||
imagejpeg($image, $destination, $quality);
|
||||
} elseif ($info['mime'] == 'image/png') {
|
||||
$image = imagecreatefrompng($source);
|
||||
imagepalettetotruecolor($image);
|
||||
imagealphablending($image, false);
|
||||
imagesavealpha($image, true);
|
||||
imagepng($image, $destination, round(9 * ($quality/100)));
|
||||
} elseif ($info['mime'] == 'image/webp') {
|
||||
$image = imagecreatefromwebp($source);
|
||||
imagewebp($image, $destination, $quality);
|
||||
}
|
||||
return $destination;
|
||||
}
|
||||
|
||||
// Handle Delete
|
||||
if (isset($_GET['delete'])) {
|
||||
$id = (int)$_GET['delete'];
|
||||
$pdo->prepare("DELETE FROM cases WHERE id = ?")->execute([$id]);
|
||||
|
||||
// Fetch title for logging before deleting
|
||||
$stmt = $pdo->prepare("SELECT title_en FROM cases WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$case_to_delete = $stmt->fetch();
|
||||
|
||||
if ($case_to_delete) {
|
||||
$pdo->prepare("DELETE FROM cases WHERE id = ?")->execute([$id]);
|
||||
log_action('delete_case', "Deleted case: " . $case_to_delete['title_en'] . " (ID: $id)");
|
||||
}
|
||||
|
||||
header('Location: cases.php?success=deleted');
|
||||
exit;
|
||||
}
|
||||
@ -40,7 +71,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$upload_dir = '../assets/images/cases/';
|
||||
if (!is_dir($upload_dir)) mkdir($upload_dir, 0775, true);
|
||||
|
||||
if (move_uploaded_file($_FILES['image']['tmp_name'], $upload_dir . $filename)) {
|
||||
$temp_path = $_FILES['image']['tmp_name'];
|
||||
$target_path = $upload_dir . $filename;
|
||||
|
||||
if (move_uploaded_file($temp_path, $target_path)) {
|
||||
// Optimize the uploaded image
|
||||
if ($ext !== 'gif') {
|
||||
optimize_image($target_path, $target_path, 75);
|
||||
}
|
||||
$image_url = 'assets/images/cases/' . $filename;
|
||||
}
|
||||
}
|
||||
@ -49,9 +87,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($id) {
|
||||
$stmt = $pdo->prepare("UPDATE cases SET category_id=?, title_en=?, title_ar=?, desc_en=?, desc_ar=?, goal=?, image_url=?, importance=?, status=? WHERE id=?");
|
||||
$stmt->execute([$category_id, $title_en, $title_ar, $desc_en, $desc_ar, $goal, $image_url, $importance, $status, $id]);
|
||||
log_action('edit_case', "Updated case: $title_en (ID: $id)");
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO cases (category_id, title_en, title_ar, desc_en, desc_ar, goal, raised, image_url, importance, status) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)");
|
||||
$stmt->execute([$category_id, $title_en, $title_ar, $desc_en, $desc_ar, $goal, $image_url, $importance, $status]);
|
||||
$new_id = $pdo->lastInsertId();
|
||||
log_action('create_case', "Created new case: $title_en (ID: $new_id)");
|
||||
}
|
||||
header('Location: cases.php?success=saved');
|
||||
exit;
|
||||
|
||||
@ -40,7 +40,10 @@ $donations = $pdo->query("SELECT d.*, c.title_en as case_title, cat.name_en as c
|
||||
|
||||
<div class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Donations History</h2>
|
||||
<h2 class="mb-0">Donations History</h2>
|
||||
<a href="export_donations.php" class="btn btn-outline-success">
|
||||
<i class="bi bi-file-earmark-excel me-2"></i>Export to CSV
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card p-0">
|
||||
@ -61,6 +64,9 @@ $donations = $pdo->query("SELECT d.*, c.title_en as case_title, cat.name_en as c
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<strong><?= htmlspecialchars($don['donor_name'] ?: 'Anonymous') ?></strong>
|
||||
<?php if ($don['is_gift']): ?>
|
||||
<span class="badge bg-info-subtle text-info rounded-pill ms-2" style="font-size: 0.7rem;">Gift</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small"><?= htmlspecialchars($don['donor_email']) ?></div>
|
||||
|
||||
88
admin/donors.php
Normal file
88
admin/donors.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
require_once 'auth.php';
|
||||
require_once '../db/config.php';
|
||||
require_login();
|
||||
|
||||
$user = get_user();
|
||||
$pdo = db();
|
||||
|
||||
// Fetch unique donors based on email, with total amount and count
|
||||
$donors = $pdo->query("
|
||||
SELECT
|
||||
donor_email,
|
||||
donor_name,
|
||||
donor_phone,
|
||||
SUM(amount) as total_contributed,
|
||||
COUNT(*) as donation_count,
|
||||
MAX(created_at) as last_donation
|
||||
FROM donations
|
||||
WHERE status = 'completed'
|
||||
GROUP BY donor_email
|
||||
ORDER BY total_contributed DESC
|
||||
")->fetchAll();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donors CRM - CharityHub 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">
|
||||
<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; }
|
||||
.nav-link { color: #9ca3af; margin-bottom: 0.5rem; border-radius: 8px; }
|
||||
.nav-link:hover, .nav-link.active { color: #fff; background: #1f2937; }
|
||||
.nav-link.active { background: var(--primary-color); }
|
||||
.card { border: none; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php include "sidebar.php"; ?>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="mb-0">Donor CRM</h2>
|
||||
</div>
|
||||
|
||||
<div class="card p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="ps-4">Donor Name</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Donations</th>
|
||||
<th>Total Amount</th>
|
||||
<th>Last Donation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($donors as $donor): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<strong><?= htmlspecialchars($donor['donor_name'] ?: 'Anonymous') ?></strong>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($donor['donor_email']) ?></td>
|
||||
<td><?= htmlspecialchars($donor['donor_phone'] ?? 'N/A') ?></td>
|
||||
<td><span class="badge bg-primary rounded-pill"><?= $donor['donation_count'] ?></span></td>
|
||||
<td><strong>OMR <?= number_format($donor['total_contributed'], 3) ?></strong></td>
|
||||
<td class="small text-muted"><?= date('M j, Y', strtotime($donor['last_donation'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($donors)): ?>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4 text-muted">No donors found with completed payments.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
47
admin/export_donations.php
Normal file
47
admin/export_donations.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
require_once 'auth.php';
|
||||
require_once '../db/config.php';
|
||||
require_login();
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Fetch all donations with details
|
||||
$stmt = $pdo->query("
|
||||
SELECT d.id, d.donor_name, d.donor_email, d.donor_phone, c.title_en as case_title, d.amount, d.status, d.transaction_id, d.created_at, d.is_gift, d.gift_recipient_name, d.gift_recipient_phone
|
||||
FROM donations d
|
||||
JOIN cases c ON d.case_id = c.id
|
||||
ORDER BY d.created_at DESC
|
||||
");
|
||||
|
||||
$filename = "donations_" . date('Y-m-d') . ".csv";
|
||||
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename=' . $filename);
|
||||
|
||||
$output = fopen('php://output', 'w');
|
||||
|
||||
// Set CSV headers
|
||||
fputcsv($output, [
|
||||
'ID',
|
||||
'Donor Name',
|
||||
'Donor Email',
|
||||
'Donor Phone',
|
||||
'Case',
|
||||
'Amount (OMR)',
|
||||
'Status',
|
||||
'Transaction ID',
|
||||
'Date',
|
||||
'Is Gift',
|
||||
'Recipient Name',
|
||||
'Recipient Phone'
|
||||
]);
|
||||
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
// Clean up status for CSV
|
||||
$row['status'] = ucfirst($row['status']);
|
||||
$row['is_gift'] = $row['is_gift'] ? 'Yes' : 'No';
|
||||
fputcsv($output, $row);
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
exit;
|
||||
281
admin/financial_summary.php
Normal file
281
admin/financial_summary.php
Normal file
@ -0,0 +1,281 @@
|
||||
<?php
|
||||
require_once 'auth.php';
|
||||
require_once '../db/config.php';
|
||||
require_login();
|
||||
|
||||
$user = get_user();
|
||||
$pdo = db();
|
||||
|
||||
// Basic Stats
|
||||
$stats = $pdo->query("
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END) as total_revenue,
|
||||
AVG(CASE WHEN status = 'completed' THEN amount ELSE NULL END) as avg_donation,
|
||||
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count,
|
||||
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count
|
||||
FROM donations
|
||||
")->fetch();
|
||||
|
||||
// Revenue by Category
|
||||
$category_revenue = $pdo->query("
|
||||
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 d.status = 'completed'
|
||||
GROUP BY c.id
|
||||
ORDER BY total DESC
|
||||
")->fetchAll();
|
||||
|
||||
$cat_labels = [];
|
||||
$cat_totals = [];
|
||||
foreach ($category_revenue as $row) {
|
||||
$cat_labels[] = $row['name_en'];
|
||||
$cat_totals[] = (float)$row['total'];
|
||||
}
|
||||
|
||||
// Monthly Revenue Trend (Last 12 Months)
|
||||
$monthly_trend = $pdo->query("
|
||||
SELECT
|
||||
DATE_FORMAT(created_at, '%Y-%m') as month,
|
||||
SUM(amount) as total
|
||||
FROM donations
|
||||
WHERE status = 'completed' AND created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
|
||||
GROUP BY month
|
||||
ORDER BY month ASC
|
||||
")->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_cases = $pdo->query("
|
||||
SELECT cs.title_en, SUM(d.amount) as total, cs.goal
|
||||
FROM cases cs
|
||||
JOIN donations d ON d.case_id = cs.id
|
||||
WHERE d.status = 'completed'
|
||||
GROUP BY cs.id
|
||||
ORDER BY total DESC
|
||||
LIMIT 5
|
||||
")->fetchAll();
|
||||
|
||||
// Gift vs Regular
|
||||
$gift_stats = $pdo->query("
|
||||
SELECT
|
||||
is_gift,
|
||||
COUNT(*) as count,
|
||||
SUM(amount) as total
|
||||
FROM donations
|
||||
WHERE status = 'completed'
|
||||
GROUP BY is_gift
|
||||
")->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 - CharityHub 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; }
|
||||
</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</h2>
|
||||
<p class="text-muted mb-0">Detailed analysis of donations and revenue streams.</p>
|
||||
</div>
|
||||
<div class="text-muted"><?= date('l, F j, Y') ?></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 Revenue</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">All statuses included</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">Monthly Revenue Trend (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 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; ?>
|
||||
</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>
|
||||
// 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: {
|
||||
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: {
|
||||
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: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -11,6 +11,22 @@ $total_categories = $pdo->query("SELECT COUNT(*) FROM categories")->fetchColumn(
|
||||
$total_cases = $pdo->query("SELECT COUNT(*) FROM cases")->fetchColumn();
|
||||
$total_donations = $pdo->query("SELECT SUM(amount) FROM donations WHERE status = 'completed'")->fetchColumn() ?: 0;
|
||||
|
||||
// Fetch chart data (last 30 days)
|
||||
$chart_data = $pdo->query("
|
||||
SELECT DATE(created_at) as date, SUM(amount) as total
|
||||
FROM donations
|
||||
WHERE status = 'completed' AND created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date ASC
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$labels = [];
|
||||
$totals = [];
|
||||
foreach ($chart_data as $row) {
|
||||
$labels[] = date('M j', strtotime($row['date']));
|
||||
$totals[] = (float)$row['total'];
|
||||
}
|
||||
|
||||
// Fetch recent donations
|
||||
$recent_donations = $pdo->query("
|
||||
SELECT d.*, c.title_en as case_title
|
||||
@ -28,6 +44,7 @@ $recent_donations = $pdo->query("
|
||||
<title>Dashboard - CharityHub 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; }
|
||||
@ -42,6 +59,7 @@ $recent_donations = $pdo->query("
|
||||
.badge-pending { background-color: #fef3c7; color: #92400e; }
|
||||
.badge-completed { background-color: #d1fae5; color: #065f46; }
|
||||
.badge-failed { background-color: #fee2e2; color: #991b1b; }
|
||||
.chart-container { height: 300px; position: relative; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -92,6 +110,27 @@ $recent_donations = $pdo->query("
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="card p-4">
|
||||
<h5 class="mb-4">Donation Trends (Last 30 Days)</h5>
|
||||
<div class="chart-container">
|
||||
<canvas id="donationsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card p-4 h-100">
|
||||
<h5 class="mb-4">Quick Links</h5>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="cases.php" class="btn btn-outline-primary text-start"><i class="bi bi-plus-circle me-2"></i> Create New Case</a>
|
||||
<a href="donations.php" class="btn btn-outline-success text-start"><i class="bi bi-download me-2"></i> Export Donations</a>
|
||||
<a href="settings.php" class="btn btn-outline-secondary text-start"><i class="bi bi-gear me-2"></i> System Settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h5 class="mb-0">Recent Donations</h5>
|
||||
@ -136,5 +175,42 @@ $recent_donations = $pdo->query("
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ctx = document.getElementById('donationsChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: <?= json_encode($labels) ?>,
|
||||
datasets: [{
|
||||
label: 'Donations (OMR)',
|
||||
data: <?= json_encode($totals) ?>,
|
||||
borderColor: '#059669',
|
||||
backgroundColor: 'rgba(5, 150, 105, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
borderWidth: 3,
|
||||
pointRadius: 4,
|
||||
pointBackgroundColor: '#059669'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: '#f3f4f6' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -33,6 +33,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Regenerate ID for security
|
||||
session_regenerate_id(true);
|
||||
|
||||
// Log the successful login
|
||||
log_action('login', "User $email logged in successfully.");
|
||||
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
@ -10,7 +10,10 @@ $current_page = basename($_SERVER['PHP_SELF']);
|
||||
<a href="categories.php" class="nav-link <?= $current_page == 'categories.php' ? 'active' : '' ?>"><i class="bi bi-tags me-2"></i> Categories</a>
|
||||
<a href="cases.php" class="nav-link <?= $current_page == 'cases.php' ? 'active' : '' ?>"><i class="bi bi-grid me-2"></i> Cases</a>
|
||||
<a href="donations.php" class="nav-link <?= $current_page == 'donations.php' ? 'active' : '' ?>"><i class="bi bi-cash-stack me-2"></i> Donations</a>
|
||||
<a href="financial_summary.php" class="nav-link <?= $current_page == 'financial_summary.php' ? 'active' : '' ?>"><i class="bi bi-graph-up-arrow me-2"></i> Financial Summary</a>
|
||||
<a href="donors.php" class="nav-link <?= $current_page == 'donors.php' ? 'active' : '' ?>"><i class="bi bi-people me-2"></i> Donors (CRM)</a>
|
||||
<a href="audit_logs.php" class="nav-link <?= $current_page == 'audit_logs.php' ? 'active' : '' ?>"><i class="bi bi-journal-text me-2"></i> Audit Logs</a>
|
||||
<hr>
|
||||
<a href="logout.php" class="nav-link"><i class="bi bi-box-arrow-right me-2"></i> Logout</a>
|
||||
<a href="logout.php" class="nav-link text-danger"><i class="bi bi-box-arrow-right me-2"></i> Logout</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
106
certificate.php
Normal file
106
certificate.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
$donation_id = $_GET['id'] ?? null;
|
||||
if (!$donation_id) exit('Invalid donation ID');
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT d.*, c.title_en as case_title, c.title_ar as case_title_ar
|
||||
FROM donations d
|
||||
JOIN cases c ON d.case_id = c.id
|
||||
WHERE d.id = ? AND d.status = 'completed'
|
||||
");
|
||||
$stmt->execute([$donation_id]);
|
||||
$don = $stmt->fetch();
|
||||
|
||||
if (!$don) exit('Donation not found or not completed.');
|
||||
|
||||
$org = $pdo->query("SELECT * FROM org_profile LIMIT 1")->fetch();
|
||||
|
||||
// This is a simple HTML certificate that can be printed or saved as PDF by the user
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donation Certificate - CharityHub</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background: #f3f4f6; padding: 40px 20px; font-family: 'Inter', sans-serif; }
|
||||
.certificate-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
padding: 60px;
|
||||
border: 20px solid #059669;
|
||||
position: relative;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
.certificate-container::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 10px; left: 10px; right: 10px; bottom: 10px;
|
||||
border: 2px solid #059669;
|
||||
pointer-events: none;
|
||||
}
|
||||
.cert-header { font-family: 'Cinzel', serif; font-size: 3rem; color: #111827; margin-bottom: 10px; }
|
||||
.cert-sub { font-size: 1.25rem; color: #059669; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 40px; }
|
||||
.cert-body { font-size: 1.2rem; color: #4b5563; line-height: 1.8; margin-bottom: 40px; }
|
||||
.cert-name { font-family: 'Cinzel', serif; font-size: 2.5rem; color: #059669; margin: 20px 0; border-bottom: 2px solid #e5e7eb; display: inline-block; padding: 0 40px; }
|
||||
.cert-footer { margin-top: 60px; display: flex; justify-content: space-between; align-items: flex-end; }
|
||||
.signature { border-top: 1px solid #9ca3af; padding-top: 10px; width: 200px; font-style: italic; color: #6b7280; }
|
||||
.stamp { width: 120px; height: 120px; border: 4px double #059669; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #059669; font-weight: 800; transform: rotate(-15deg); font-size: 0.8rem; margin: 0 auto; }
|
||||
|
||||
@media print {
|
||||
body { background: #fff; padding: 0; }
|
||||
.certificate-container { box-shadow: none; border-width: 15px; }
|
||||
.no-print { display: none; }
|
||||
}
|
||||
|
||||
.no-print { margin-top: 30px; text-align: center; }
|
||||
.btn-print { background: #059669; color: #fff; border: none; padding: 12px 30px; border-radius: 9999px; font-weight: 600; cursor: pointer; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="certificate-container">
|
||||
<div class="cert-header">Certificate</div>
|
||||
<div class="cert-sub">of Appreciation</div>
|
||||
|
||||
<div class="cert-body">
|
||||
This certificate is proudly presented to
|
||||
<br>
|
||||
<div class="cert-name"><?= htmlspecialchars($don['donor_name'] ?: 'Valued Donor') ?></div>
|
||||
<br>
|
||||
In recognition of their generous donation of <strong>OMR <?= number_format($don['amount'], 3) ?></strong>
|
||||
<br>
|
||||
towards the cause: <strong><?= htmlspecialchars($don['case_title']) ?></strong>
|
||||
</div>
|
||||
|
||||
<div class="stamp">
|
||||
OFFICIAL<br>SEAL<br>CHARITYHUB
|
||||
</div>
|
||||
|
||||
<div class="cert-footer">
|
||||
<div class="signature">
|
||||
Date: <?= date('M j, Y', strtotime($don['created_at'])) ?>
|
||||
</div>
|
||||
<div class="signature">
|
||||
Authorized Signature<br>
|
||||
<?= htmlspecialchars($org['name_en'] ?? 'CharityHub') ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-print">
|
||||
<button onclick="window.print()" class="btn-print">Print or Save as PDF</button>
|
||||
<a href="index.php" style="margin-left: 15px; color: #6b7280; text-decoration: none;">Back to Home</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
21
success.php
21
success.php
@ -66,12 +66,15 @@ if ($donation) {
|
||||
}
|
||||
|
||||
$success = true;
|
||||
$final_donation_id = $fullDonation['id'];
|
||||
} else {
|
||||
// Check if it was already completed (user refreshed page)
|
||||
$stmt = $pdo->prepare("SELECT * FROM donations transaction_id = ? AND status = 'completed'");
|
||||
$stmt = $pdo->prepare("SELECT * FROM donations WHERE transaction_id = ? AND status = 'completed'");
|
||||
$stmt->execute([$session_id]);
|
||||
if ($stmt->fetch()) {
|
||||
$existing = $stmt->fetch();
|
||||
if ($existing) {
|
||||
$success = true;
|
||||
$final_donation_id = $existing['id'];
|
||||
}
|
||||
}
|
||||
?>
|
||||
@ -82,12 +85,15 @@ if ($donation) {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donation Successful - CharityHub</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">
|
||||
<style>
|
||||
body { background-color: #f8fafc; font-family: 'Inter', sans-serif; }
|
||||
.success-card { max-width: 600px; border-radius: 24px; border: none; overflow: hidden; }
|
||||
.success-icon { background: #ecfdf5; color: #10b981; width: 100px; height: 100px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto; }
|
||||
.btn-home { background: #059669; color: white; border: none; padding: 12px 40px; border-radius: 12px; font-weight: 600; transition: all 0.3s; }
|
||||
.btn-home:hover { background: #047857; color: white; transform: translateY(-2px); }
|
||||
.btn-cert { background: #fff; color: #059669; border: 2px solid #059669; padding: 12px 40px; border-radius: 12px; font-weight: 600; transition: all 0.3s; text-decoration: none; display: inline-block; }
|
||||
.btn-cert:hover { background: #ecfdf5; color: #047857; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -103,6 +109,14 @@ if ($donation) {
|
||||
</div>
|
||||
<h1 class="fw-bold mb-3">Thank You!</h1>
|
||||
<p class="text-muted fs-5 mb-4">Your donation has been successfully processed. Your generosity helps us continue our mission.</p>
|
||||
|
||||
<div class="d-flex flex-column flex-sm-row gap-3 justify-content-center mb-5">
|
||||
<a href="certificate.php?id=<?= $final_donation_id ?>" target="_blank" class="btn-cert">
|
||||
<i class="bi bi-patch-check me-2"></i>Download Certificate
|
||||
</a>
|
||||
<a href="index.php" class="btn btn-home">Return to Home</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-light p-4 rounded-4 mb-4 text-start">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Transaction ID</span>
|
||||
@ -113,8 +127,7 @@ if ($donation) {
|
||||
<span class="badge bg-success rounded-pill px-3">Completed</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small text-muted mb-4">Confirmation messages have been sent to the relevant parties.</p>
|
||||
<a href="index.php" class="btn btn-home">Return to Home</a>
|
||||
<p class="small text-muted mb-0">Confirmation messages have been sent to the relevant parties.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="card border-0 shadow-lg p-5 rounded-4">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user