38960-vm/includes/pages/reports.php
2026-03-21 14:56:14 +00:00

308 lines
11 KiB
PHP

<?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>