528 lines
20 KiB
PHP
528 lines
20 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. 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);
|
|
|
|
// 6. Visits by Doctor (Top 5)
|
|
$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);
|
|
|
|
// 7. Patient Gender Distribution
|
|
$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);
|
|
|
|
// 8. Department Revenue Report
|
|
$stmt = $db->prepare("
|
|
SELECT d.name_$lang as name,
|
|
COUNT(DISTINCT b.visit_id) as visit_count,
|
|
SUM(b.total_amount) as revenue
|
|
FROM bills b
|
|
JOIN visits v ON b.visit_id = v.id
|
|
JOIN doctors doc ON v.doctor_id = doc.id
|
|
JOIN departments d ON doc.department_id = d.id
|
|
WHERE b.status = 'Paid' AND DATE(b.created_at) BETWEEN ? AND ?
|
|
GROUP BY d.id
|
|
ORDER BY revenue DESC
|
|
");
|
|
$stmt->execute([$start_date, $end_date]);
|
|
$dept_revenue = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 9. Doctor Revenue Report
|
|
$stmt = $db->prepare("
|
|
SELECT doc.name_$lang as name,
|
|
d.name_$lang as department_name,
|
|
COUNT(DISTINCT b.visit_id) as visit_count,
|
|
SUM(b.total_amount) as revenue
|
|
FROM bills b
|
|
JOIN visits v ON b.visit_id = v.id
|
|
JOIN doctors doc ON v.doctor_id = doc.id
|
|
LEFT JOIN departments d ON doc.department_id = d.id
|
|
WHERE b.status = 'Paid' AND DATE(b.created_at) BETWEEN ? AND ?
|
|
GROUP BY doc.id
|
|
ORDER BY revenue DESC
|
|
");
|
|
$stmt->execute([$start_date, $end_date]);
|
|
$doctor_revenue = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
|
|
// --- PREPARE DATA FOR JS ---
|
|
$chart_dept_revenue_labels = array_column($dept_revenue, 'name');
|
|
$chart_dept_revenue_data = array_column($dept_revenue, 'revenue');
|
|
|
|
$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>
|
|
|
|
<style>
|
|
@media print {
|
|
/* Hide Non-Printable Elements */
|
|
#sidebar, .navbar, .btn, form, footer, .no-print {
|
|
display: none !important;
|
|
}
|
|
|
|
/* Layout Adjustments for Print */
|
|
.main-content {
|
|
margin-left: 0 !important;
|
|
padding: 0 !important;
|
|
width: 100% !important;
|
|
}
|
|
|
|
body {
|
|
background: white !important;
|
|
font-size: 12pt;
|
|
}
|
|
|
|
.card {
|
|
border: none !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.card-header {
|
|
background: none !important;
|
|
border-bottom: 2px solid #000 !important;
|
|
padding-left: 0 !important;
|
|
}
|
|
|
|
/* Ensure Charts are Visible */
|
|
canvas {
|
|
max-width: 100% !important;
|
|
height: auto !important;
|
|
}
|
|
|
|
/* Table Styling for Print */
|
|
.table {
|
|
width: 100% !important;
|
|
border-collapse: collapse !important;
|
|
}
|
|
|
|
.table th, .table td {
|
|
border: 1px solid #ddd !important;
|
|
padding: 8px !important;
|
|
}
|
|
|
|
/* Page Break */
|
|
.page-break {
|
|
page-break-before: always;
|
|
}
|
|
|
|
/* Header for Print */
|
|
.print-header {
|
|
display: block !important;
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
}
|
|
|
|
.print-header {
|
|
display: none;
|
|
}
|
|
</style>
|
|
|
|
<!-- Print Header -->
|
|
<div class="print-header">
|
|
<h2><?php echo __('admin_reports'); ?></h2>
|
|
<p class="text-muted">
|
|
<?php echo __('report_period'); ?>: <?php echo $start_date; ?> <?php echo __('to'); ?> <?php echo $end_date; ?><br>
|
|
<?php echo __('generated_on'); ?>: <?php echo date('Y-m-d H:i'); ?>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-4 no-print">
|
|
<h3 class="fw-bold text-secondary"><?php echo __('admin_reports'); ?></h3>
|
|
|
|
<div class="d-flex gap-2">
|
|
<!-- 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>
|
|
|
|
<button onclick="window.print()" class="btn btn-outline-dark">
|
|
<i class="bi bi-printer"></i> <?php echo __('print_report'); ?>
|
|
</button>
|
|
</div>
|
|
</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 page-break">
|
|
<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 __('department_revenue'); ?></h5>
|
|
</div>
|
|
<div class="card-body d-flex justify-content-center align-items-center" style="min-height: 350px;">
|
|
<div style="position: relative; width: 300px; height: 300px;">
|
|
<canvas id="deptRevenueChart"></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" style="min-height: 350px;">
|
|
<div style="position: relative; height: 300px; width: 100%;">
|
|
<canvas id="revenueChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Row 2 -->
|
|
<div class="row g-4 mb-5 page-break">
|
|
<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" style="min-height: 350px;">
|
|
<div style="position: relative; height: 300px; width: 100%;">
|
|
<canvas id="doctorsChart"></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 __('new_patients'); ?> (<?php echo __('gender'); ?>)</h5>
|
|
</div>
|
|
<div class="card-body d-flex justify-content-center align-items-center" style="min-height: 350px;">
|
|
<div style="position: relative; width: 300px; height: 300px;">
|
|
<canvas id="genderChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Detailed Tables -->
|
|
<div class="row g-4 mb-4 page-break">
|
|
<!-- Department Revenue -->
|
|
<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 d-flex justify-content-between align-items-center">
|
|
<h5 class="fw-bold text-dark mb-0"><?php echo __('department_revenue'); ?></h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="table-light text-secondary">
|
|
<tr>
|
|
<th class="px-4 py-3"><?php echo __('department'); ?></th>
|
|
<th class="py-3 text-center"><?php echo __('visits'); ?> (Paid)</th>
|
|
<th class="py-3 text-end px-4"><?php echo __('revenue'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($dept_revenue)): ?>
|
|
<tr><td colspan="3" class="text-center py-4 text-muted"><?php echo __('no_data_found'); ?></td></tr>
|
|
<?php else: ?>
|
|
<?php foreach ($dept_revenue as $d): ?>
|
|
<tr>
|
|
<td class="px-4 fw-semibold"><?php echo htmlspecialchars($d['name']); ?></td>
|
|
<td class="text-center"><?php echo number_format($d['visit_count']); ?></td>
|
|
<td class="text-end px-4 text-success fw-bold"><?php echo format_currency($d['revenue']); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Doctor Revenue -->
|
|
<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 d-flex justify-content-between align-items-center">
|
|
<h5 class="fw-bold text-dark mb-0"><?php echo __('doctor_revenue'); ?></h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="table-light text-secondary">
|
|
<tr>
|
|
<th class="px-4 py-3"><?php echo __('doctor'); ?></th>
|
|
<th class="py-3 text-center"><?php echo __('visits'); ?> (Paid)</th>
|
|
<th class="py-3 text-end px-4"><?php echo __('revenue'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($doctor_revenue)): ?>
|
|
<tr><td colspan="3" class="text-center py-4 text-muted"><?php echo __('no_data_found'); ?></td></tr>
|
|
<?php else: ?>
|
|
<?php foreach ($doctor_revenue as $d): ?>
|
|
<tr>
|
|
<td class="px-4">
|
|
<div class="fw-semibold text-dark"><?php echo htmlspecialchars($d['name']); ?></div>
|
|
<small class="text-muted"><?php echo htmlspecialchars($d['department_name'] ?? '-'); ?></small>
|
|
</td>
|
|
<td class="text-center"><?php echo number_format($d['visit_count']); ?></td>
|
|
<td class="text-end px-4 text-success fw-bold"><?php echo format_currency($d['revenue']); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</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',
|
|
purple: '#6f42c1',
|
|
indigo: '#6610f2',
|
|
pink: '#d63384',
|
|
teal: '#20c997'
|
|
};
|
|
|
|
// Palette for charts
|
|
const chartColors = [
|
|
colors.primary,
|
|
colors.success,
|
|
colors.info,
|
|
colors.warning,
|
|
colors.danger,
|
|
colors.purple,
|
|
colors.teal,
|
|
colors.pink,
|
|
colors.indigo,
|
|
colors.secondary
|
|
];
|
|
|
|
// 1. Department Revenue (Pie) - Replaces Visits by Status
|
|
const ctxDeptRevenue = document.getElementById('deptRevenueChart').getContext('2d');
|
|
new Chart(ctxDeptRevenue, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: <?php echo json_encode($chart_dept_revenue_labels); ?>,
|
|
datasets: [{
|
|
data: <?php echo json_encode($chart_dept_revenue_data); ?>,
|
|
backgroundColor: chartColors,
|
|
borderWidth: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { position: 'bottom' },
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
let label = context.label || '';
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
if (context.parsed !== null) {
|
|
label += new Intl.NumberFormat().format(context.parsed);
|
|
}
|
|
return label;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 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>
|