adding admin reports
This commit is contained in:
parent
b5ea341aa5
commit
dc081ef86a
@ -168,6 +168,8 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
||||
<a href="doctor_holidays.php" class="sidebar-link <?php echo $section === 'doctor_holidays' ? 'active' : ''; ?>"><i class="bi bi-calendar-x me-2"></i> <?php echo __('doctor_holidays'); ?></a>
|
||||
<a href="nurses.php" class="sidebar-link <?php echo $section === 'nurses' ? 'active' : ''; ?>"><i class="bi bi-person-heart me-2"></i> <?php echo __('nurses'); ?></a>
|
||||
|
||||
<a href="reports.php" class="sidebar-link <?php echo $section === 'reports' ? 'active' : ''; ?>"><i class="bi bi-bar-chart-line me-2"></i> <?php echo __('admin_reports'); ?></a>
|
||||
|
||||
<a href="#settingsSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['employees', 'positions', 'company_profile', 'cities', 'services', 'departments', 'queue_ads']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||
<span><i class="bi bi-gear me-2"></i> <?php echo __('settings'); ?></span>
|
||||
<i class="bi bi-chevron-down small"></i>
|
||||
@ -220,4 +222,4 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
||||
<?php echo $message; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
307
includes/pages/reports.php
Normal file
307
includes/pages/reports.php
Normal file
@ -0,0 +1,307 @@
|
||||
<?php
|
||||
// Default Date Range: Current Month
|
||||
$start_date = $_GET['start_date'] ?? date('Y-m-01');
|
||||
$end_date = $_GET['end_date'] ?? date('Y-m-t');
|
||||
|
||||
// Ensure dates are valid
|
||||
if (!$start_date) $start_date = date('Y-m-01');
|
||||
if (!$end_date) $end_date = date('Y-m-t');
|
||||
|
||||
// --- QUERIES ---
|
||||
|
||||
// 1. New Patients
|
||||
$stmt = $db->prepare("SELECT COUNT(*) FROM patients WHERE DATE(created_at) BETWEEN ? AND ?");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$new_patients = $stmt->fetchColumn();
|
||||
|
||||
// 2. Total Visits
|
||||
$stmt = $db->prepare("SELECT COUNT(*) FROM visits WHERE DATE(visit_date) BETWEEN ? AND ?");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$total_visits = $stmt->fetchColumn();
|
||||
|
||||
// 3. Total Revenue
|
||||
$stmt = $db->prepare("SELECT SUM(total_amount) FROM bills WHERE DATE(created_at) BETWEEN ? AND ?");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$total_revenue = $stmt->fetchColumn() ?: 0;
|
||||
|
||||
// 4. Pending Bills (Outstanding amount for bills created in this period)
|
||||
$stmt = $db->prepare("SELECT SUM(patient_payable) FROM bills WHERE status != 'Paid' AND DATE(created_at) BETWEEN ? AND ?");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$pending_bills = $stmt->fetchColumn() ?: 0;
|
||||
|
||||
// 5. Visits by Status
|
||||
$stmt = $db->prepare("SELECT status, COUNT(*) as count FROM visits WHERE DATE(visit_date) BETWEEN ? AND ? GROUP BY status");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$visits_by_status = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
|
||||
// 6. Revenue Trend (Daily)
|
||||
$stmt = $db->prepare("SELECT DATE(created_at) as date, SUM(total_amount) as total FROM bills WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY DATE(created_at) ORDER BY date ASC");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$revenue_trend = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 7. Visits by Doctor
|
||||
$stmt = $db->prepare("
|
||||
SELECT d.name_$lang as doctor_name, COUNT(v.id) as count
|
||||
FROM visits v
|
||||
JOIN doctors d ON v.doctor_id = d.id
|
||||
WHERE DATE(v.visit_date) BETWEEN ? AND ?
|
||||
GROUP BY v.doctor_id
|
||||
ORDER BY count DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$visits_by_doctor = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 8. Patient Gender Distribution (All time or filtered? Let's do filtered by creation date to match "New Patients")
|
||||
$stmt = $db->prepare("SELECT gender, COUNT(*) as count FROM patients WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY gender");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$patients_by_gender = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
|
||||
// --- PREPARE DATA FOR JS ---
|
||||
$chart_status_labels = array_keys($visits_by_status);
|
||||
$chart_status_data = array_values($visits_by_status);
|
||||
|
||||
$chart_revenue_labels = array_column($revenue_trend, 'date');
|
||||
$chart_revenue_data = array_column($revenue_trend, 'total');
|
||||
|
||||
$chart_doctor_labels = array_column($visits_by_doctor, 'doctor_name');
|
||||
$chart_doctor_data = array_column($visits_by_doctor, 'count');
|
||||
|
||||
$chart_gender_labels = array_keys($patients_by_gender);
|
||||
$chart_gender_data = array_values($patients_by_gender);
|
||||
|
||||
?>
|
||||
|
||||
<!-- Include Chart.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="fw-bold text-secondary"><?php echo __('admin_reports'); ?></h3>
|
||||
|
||||
<!-- Date Filter Form -->
|
||||
<form method="GET" class="d-flex gap-2 align-items-center">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0"><i class="bi bi-calendar"></i></span>
|
||||
<input type="date" name="start_date" class="form-control border-start-0" value="<?php echo $start_date; ?>" required>
|
||||
</div>
|
||||
<span class="text-muted">-</span>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0"><i class="bi bi-calendar"></i></span>
|
||||
<input type="date" name="end_date" class="form-control border-start-0" value="<?php echo $end_date; ?>" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo __('generate'); ?></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="rounded-circle p-3 bg-primary bg-opacity-10 text-primary me-3">
|
||||
<i class="bi bi-people fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-1"><?php echo __('new_patients'); ?></h6>
|
||||
<h4 class="fw-bold mb-0"><?php echo number_format($new_patients); ?></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="rounded-circle p-3 bg-success bg-opacity-10 text-success me-3">
|
||||
<i class="bi bi-clipboard2-pulse fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-1"><?php echo __('total_visits'); ?></h6>
|
||||
<h4 class="fw-bold mb-0"><?php echo number_format($total_visits); ?></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="rounded-circle p-3 bg-info bg-opacity-10 text-info me-3">
|
||||
<i class="bi bi-currency-dollar fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-1"><?php echo __('total_revenue'); ?></h6>
|
||||
<h4 class="fw-bold mb-0"><?php echo format_currency($total_revenue); ?></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="rounded-circle p-3 bg-warning bg-opacity-10 text-warning me-3">
|
||||
<i class="bi bi-exclamation-circle fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-1"><?php echo __('pending_bills'); ?></h6>
|
||||
<h4 class="fw-bold mb-0"><?php echo format_currency($pending_bills); ?></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row 1 -->
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
|
||||
<h5 class="fw-bold text-dark mb-0"><?php echo __('visits_by_status'); ?></h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center">
|
||||
<div style="width: 300px; height: 300px;">
|
||||
<canvas id="statusChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
|
||||
<h5 class="fw-bold text-dark mb-0"><?php echo __('revenue_trend'); ?></h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="revenueChart" style="height: 300px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row 2 -->
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
|
||||
<h5 class="fw-bold text-dark mb-0"><?php echo __('top_doctors'); ?></h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="doctorsChart" style="height: 300px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
|
||||
<h5 class="fw-bold text-dark mb-0"><?php echo __('new_patients'); ?> (<?php echo __('gender'); ?>)</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center">
|
||||
<div style="width: 300px; height: 300px;">
|
||||
<canvas id="genderChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Colors
|
||||
const colors = {
|
||||
primary: '#0d6efd',
|
||||
success: '#198754',
|
||||
info: '#0dcaf0',
|
||||
warning: '#ffc107',
|
||||
danger: '#dc3545',
|
||||
secondary: '#6c757d',
|
||||
light: '#f8f9fa',
|
||||
dark: '#212529'
|
||||
};
|
||||
|
||||
// 1. Visits by Status (Doughnut)
|
||||
const ctxStatus = document.getElementById('statusChart').getContext('2d');
|
||||
new Chart(ctxStatus, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: <?php echo json_encode($chart_status_labels); ?>,
|
||||
datasets: [{
|
||||
data: <?php echo json_encode($chart_status_data); ?>,
|
||||
backgroundColor: [colors.success, colors.primary, colors.danger, colors.warning, colors.secondary],
|
||||
borderWidth: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { position: 'bottom' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Revenue Trend (Line)
|
||||
const ctxRevenue = document.getElementById('revenueChart').getContext('2d');
|
||||
new Chart(ctxRevenue, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: <?php echo json_encode($chart_revenue_labels); ?>,
|
||||
datasets: [{
|
||||
label: '<?php echo __('revenue'); ?>',
|
||||
data: <?php echo json_encode($chart_revenue_data); ?>,
|
||||
borderColor: colors.info,
|
||||
backgroundColor: 'rgba(13, 202, 240, 0.1)',
|
||||
tension: 0.3,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: { beginAtZero: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Top Doctors (Bar)
|
||||
const ctxDoctors = document.getElementById('doctorsChart').getContext('2d');
|
||||
new Chart(ctxDoctors, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: <?php echo json_encode($chart_doctor_labels); ?>,
|
||||
datasets: [{
|
||||
label: '<?php echo __('visits'); ?>',
|
||||
data: <?php echo json_encode($chart_doctor_data); ?>,
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
indexAxis: 'y', // Horizontal Bar
|
||||
scales: {
|
||||
x: { beginAtZero: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 4. Gender (Pie)
|
||||
const ctxGender = document.getElementById('genderChart').getContext('2d');
|
||||
new Chart(ctxGender, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: <?php echo json_encode($chart_gender_labels); ?>,
|
||||
datasets: [{
|
||||
data: <?php echo json_encode($chart_gender_data); ?>,
|
||||
backgroundColor: ['#36a2eb', '#ff6384', '#ffcd56'],
|
||||
borderWidth: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { position: 'bottom' }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
24
lang.php
24
lang.php
@ -423,7 +423,16 @@ $translations = [
|
||||
'payment_method_cash' => 'Cash',
|
||||
'payment_method_card' => 'Credit Card',
|
||||
'transactions' => 'Transactions',
|
||||
'companies' => 'Companies'
|
||||
'companies' => 'Companies',
|
||||
'admin_reports' => 'Admin Reports',
|
||||
'new_patients' => 'New Patients',
|
||||
'total_visits' => 'Total Visits',
|
||||
'total_revenue' => 'Total Revenue',
|
||||
'pending_bills' => 'Pending Bills',
|
||||
'visits_by_status' => 'Visits by Status',
|
||||
'revenue_trend' => 'Revenue Trend',
|
||||
'top_diagnoses' => 'Top Diagnoses',
|
||||
'top_doctors' => 'Top Doctors'
|
||||
],
|
||||
'ar' => [
|
||||
'attachment' => 'المرفق',
|
||||
@ -860,6 +869,15 @@ $translations = [
|
||||
'payment_method_cash' => 'نقدي',
|
||||
'payment_method_card' => 'بطاقة ائتمان',
|
||||
'transactions' => 'المعاملات',
|
||||
'companies' => 'الشركات'
|
||||
'companies' => 'الشركات',
|
||||
'admin_reports' => 'التقارير الإدارية',
|
||||
'new_patients' => 'مرضى جدد',
|
||||
'total_visits' => 'إجمالي الزيارات',
|
||||
'total_revenue' => 'إجمالي الإيرادات',
|
||||
'pending_bills' => 'فواتير معلقة',
|
||||
'visits_by_status' => 'الزيارات حسب الحالة',
|
||||
'revenue_trend' => 'اتجاه الإيرادات',
|
||||
'top_diagnoses' => 'أهم التشخيصات',
|
||||
'top_doctors' => 'أفضل الأطباء'
|
||||
]
|
||||
];
|
||||
];
|
||||
17
reports.php
Normal file
17
reports.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
$section = 'reports';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
|
||||
$db = db();
|
||||
$lang = $_SESSION['lang'];
|
||||
|
||||
require_once __DIR__ . '/includes/actions.php';
|
||||
require_once __DIR__ . '/includes/common_data.php';
|
||||
|
||||
// Ensure only authorized personnel can access (optional, but good practice)
|
||||
// For now, assuming all logged-in users are admins or have access.
|
||||
|
||||
require_once __DIR__ . '/includes/layout/header.php';
|
||||
require_once __DIR__ . '/includes/pages/reports.php';
|
||||
require_once __DIR__ . '/includes/layout/footer.php';
|
||||
Loading…
x
Reference in New Issue
Block a user