Sindbad~EG File Manager
<?php
require_once '../config/config.php';
// Check if user is logged in
if (!isLoggedIn()) {
redirect('login.php');
}
$db = new Database();
$conn = $db->getConnection();
// Handle export requests
if (isset($_GET['export'])) {
$export_type = $_GET['export'];
$format = $_GET['format'] ?? 'csv';
// Build query based on export type and user permissions
$location_filter = '';
$params = [];
if (hasRole('admin') && isset($_SESSION['location_id']) && $_SESSION['location_id']) {
$location_filter = " AND p.location_id = ?";
$params[] = $_SESSION['location_id'];
}
switch ($export_type) {
case 'attendance':
$query = "SELECT ar.full_name, ar.email, ar.telephone, ar.district_name, ar.assembly_name,
p.name as program_name, l.name as location_name, ar.submitted_at,
ar.latitude, ar.longitude, ar.location_accuracy, ar.location_address
FROM attendance_records ar
JOIN programs p ON ar.program_id = p.id
LEFT JOIN locations l ON p.location_id = l.id
WHERE 1=1 $location_filter
ORDER BY ar.submitted_at DESC";
$filename = 'attendance_records_' . date('Y-m-d');
break;
case 'programs':
$query = "SELECT p.name, p.description, p.start_date, p.end_date, l.name as location_name,
COUNT(ar.id) as total_attendance
FROM programs p
LEFT JOIN locations l ON p.location_id = l.id
LEFT JOIN attendance_records ar ON p.id = ar.program_id
WHERE p.is_active = 1 $location_filter
GROUP BY p.id
ORDER BY p.name";
$filename = 'programs_report_' . date('Y-m-d');
break;
default:
redirect('admin/reports.php');
}
$stmt = $conn->prepare($query);
$stmt->execute($params);
$data = $stmt->fetchAll();
// Handle empty data
if (empty($data)) {
$_SESSION['error_message'] = 'No data available to export.';
redirect('admin/reports.php');
}
if ($format === 'csv') {
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $filename . '.csv"');
$output = fopen('php://output', 'w');
if (!empty($data)) {
// Write headers
fputcsv($output, array_keys($data[0]));
// Write data
foreach ($data as $row) {
fputcsv($output, $row);
}
}
fclose($output);
exit;
} elseif ($format === 'excel') {
// Excel Export using HTML table format that Excel can read
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment; filename="' . $filename . '.xls"');
header('Pragma: no-cache');
header('Expires: 0');
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>' . $filename . '</title>
<style>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #000; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; font-weight: bold; }
.gps-coords { font-family: monospace; }
.number { mso-number-format: "0.000000"; }
</style>
</head>
<body>
<h1>Attendance Report</h1>
<p>Generated on ' . date('F j, Y g:i A') . '</p>
<table>';
if (!empty($data)) {
// Write headers
echo '<thead><tr>';
foreach (array_keys($data[0]) as $header) {
$display_header = ucwords(str_replace('_', ' ', $header));
echo '<th>' . htmlspecialchars($display_header) . '</th>';
}
echo '</tr></thead><tbody>';
// Write data
foreach ($data as $row) {
echo '<tr>';
foreach ($row as $key => $value) {
if ($key === 'latitude' || $key === 'longitude') {
if ($value) {
echo '<td class="gps-coords number">' . number_format($value, 6, '.', '') . '</td>';
} else {
echo '<td>-</td>';
}
} elseif ($key === 'location_accuracy') {
if ($value) {
echo '<td>' . round($value) . '</td>';
} else {
echo '<td>-</td>';
}
} elseif ($key === 'submitted_at') {
echo '<td>' . date('M j, Y g:i A', strtotime($value)) . '</td>';
} else {
echo '<td>' . htmlspecialchars($value ?? '') . '</td>';
}
}
echo '</tr>';
}
echo '</tbody>';
}
echo '</table>
</body>
</html>';
exit;
} elseif ($format === 'pdf') {
// PDF Export using HTML to PDF conversion via browser print
// This creates a print-friendly HTML page that can be saved as PDF
header('Content-Type: text/html; charset=utf-8');
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>' . $filename . '</title>
<style>
body { font-family: Arial, sans-serif; font-size: 12px; margin: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; font-weight: bold; }
.header { text-align: center; margin-bottom: 30px; }
.gps-coords { font-family: monospace; font-size: 10px; }
.no-location { color: #999; font-style: italic; }
@media print {
body { margin: 0; }
.no-print { display: none; }
}
</style>
<script>
window.onload = function() {
setTimeout(function() {
window.print();
}, 1000);
}
</script>
</head>
<body>
<div class="header">
<h1>Attendance Report</h1>
<p>Generated on ' . date('F j, Y g:i A') . '</p>
<p class="no-print">This page will automatically open the print dialog. Use "Save as PDF" in your browser.</p>
</div>
<table>';
if (!empty($data)) {
// Write headers
echo '<thead><tr>';
foreach (array_keys($data[0]) as $header) {
$display_header = ucwords(str_replace('_', ' ', $header));
echo '<th>' . htmlspecialchars($display_header) . '</th>';
}
echo '</tr></thead><tbody>';
// Write data
foreach ($data as $row) {
echo '<tr>';
foreach ($row as $key => $value) {
if ($key === 'latitude' || $key === 'longitude') {
if ($value) {
echo '<td class="gps-coords">' . number_format($value, 6) . '</td>';
} else {
echo '<td class="no-location">-</td>';
}
} elseif ($key === 'location_accuracy') {
if ($value) {
echo '<td>±' . round($value) . 'm</td>';
} else {
echo '<td class="no-location">-</td>';
}
} elseif ($key === 'location_address') {
if ($value) {
echo '<td>' . htmlspecialchars(substr($value, 0, 50)) . (strlen($value) > 50 ? '...' : '') . '</td>';
} else {
echo '<td class="no-location">No address</td>';
}
} elseif ($key === 'submitted_at') {
echo '<td>' . date('M j, Y g:i A', strtotime($value)) . '</td>';
} else {
echo '<td>' . htmlspecialchars($value ?? '') . '</td>';
}
}
echo '</tr>';
}
echo '</tbody>';
}
echo '</table>
</body>
</html>';
exit;
} else {
// Unknown format
$_SESSION['error_message'] = 'Unknown export format requested.';
redirect('admin/reports.php');
}
}
// Get report statistics
$stats = [];
// Total attendance by month (last 12 months)
$query = "SELECT DATE_FORMAT(submitted_at, '%Y-%m') as month, COUNT(*) as count
FROM attendance_records ar
JOIN programs p ON ar.program_id = p.id
WHERE ar.submitted_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)";
if (hasRole('admin') && isset($_SESSION['location_id']) && $_SESSION['location_id']) {
$query .= " AND p.location_id = ?";
$stmt = $conn->prepare($query . " GROUP BY month ORDER BY month");
$stmt->execute([$_SESSION['location_id']]);
} else {
$stmt = $conn->prepare($query . " GROUP BY month ORDER BY month");
$stmt->execute();
}
$monthly_stats = $stmt->fetchAll();
// Program attendance summary
$query = "SELECT p.name, COUNT(ar.id) as attendance_count, l.name as location_name
FROM programs p
LEFT JOIN attendance_records ar ON p.id = ar.program_id
LEFT JOIN locations l ON p.location_id = l.id
WHERE p.is_active = 1";
if (hasRole('admin') && isset($_SESSION['location_id']) && $_SESSION['location_id']) {
$query .= " AND p.location_id = ?";
$stmt = $conn->prepare($query . " GROUP BY p.id ORDER BY attendance_count DESC");
$stmt->execute([$_SESSION['location_id']]);
} else {
$stmt = $conn->prepare($query . " GROUP BY p.id ORDER BY attendance_count DESC");
$stmt->execute();
}
$program_summary = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reports - Admin Panel</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3B82F6',
secondary: '#F59E0B',
accent: '#6B7280'
}
}
}
}
</script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.gradient-bg {
background: linear-gradient(135deg, #3B82F6 0%, #F59E0B 50%, #6B7280 100%);
}
.spinner {
border: 2px solid #f3f3f3;
border-top: 2px solid #3B82F6;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Custom scrollbar styles */
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: #CBD5E1 #F1F5F9;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #F1F5F9;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #CBD5E1;
border-radius: 3px;
transition: background 0.2s ease;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #94A3B8;
}
/* Smooth scroll behavior */
.custom-scrollbar {
scroll-behavior: smooth;
}
/* Tab styling */
.report-tab {
transition: all 0.2s ease;
}
.report-tab:hover {
border-color: #93C5FD !important;
color: #3B82F6 !important;
}
/* Loading animation */
.loading-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: .5;
}
}
/* Chart container styles to prevent overflow issues */
.chart-container {
position: relative;
height: 320px;
width: 100%;
overflow: hidden;
}
.chart-container canvas {
position: absolute;
top: 0;
left: 0;
width: 100% !important;
height: 100% !important;
}
</style>
</head>
<body class="bg-gray-50">
<!-- Include Sidebar -->
<?php include 'includes/sidebar.php'; ?>
<!-- Main Content -->
<div class="md:ml-64">
<!-- Header -->
<header class="bg-white shadow-sm border-b">
<div class="px-6 py-4">
<h1 class="text-2xl font-bold text-gray-900">Reports & Analytics</h1>
</div>
</header>
<!-- Content -->
<main class="p-6">
<!-- Export Options -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center mb-4">
<div class="p-2 bg-blue-100 rounded-lg">
<i class="fas fa-users text-blue-600 text-xl"></i>
</div>
<h3 class="ml-3 text-lg font-semibold text-gray-900">Attendance Report</h3>
</div>
<p class="text-gray-600 mb-4">Export all attendance records with detailed information.</p>
<div class="flex space-x-2">
<a href="?export=attendance&format=csv"
class="bg-primary text-white px-4 py-2 rounded hover:bg-blue-700 transition duration-300 text-sm">
<i class="fas fa-download mr-1"></i>
CSV
</a>
<a href="?export=attendance&format=excel"
class="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 transition duration-300 text-sm">
<i class="fas fa-file-excel mr-1"></i>
Excel
</a>
<a href="?export=attendance&format=pdf"
class="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition duration-300 text-sm">
<i class="fas fa-file-pdf mr-1"></i>
PDF
</a>
</div>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center mb-4">
<div class="p-2 bg-green-100 rounded-lg">
<i class="fas fa-calendar text-green-600 text-xl"></i>
</div>
<h3 class="ml-3 text-lg font-semibold text-gray-900">Programs Report</h3>
</div>
<p class="text-gray-600 mb-4">Export program information with attendance statistics.</p>
<div class="flex space-x-2">
<a href="?export=programs&format=csv"
class="bg-primary text-white px-4 py-2 rounded hover:bg-blue-700 transition duration-300 text-sm">
<i class="fas fa-download mr-1"></i>
CSV
</a>
<a href="?export=programs&format=excel"
class="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 transition duration-300 text-sm">
<i class="fas fa-file-excel mr-1"></i>
Excel
</a>
<a href="?export=programs&format=pdf"
class="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition duration-300 text-sm">
<i class="fas fa-file-pdf mr-1"></i>
PDF
</a>
</div>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center mb-4">
<div class="p-2 bg-yellow-100 rounded-lg">
<i class="fas fa-chart-line text-yellow-600 text-xl"></i>
</div>
<h3 class="ml-3 text-lg font-semibold text-gray-900">Analytics Report</h3>
</div>
<p class="text-gray-600 mb-4">Comprehensive analytics with charts and trends.</p>
<div class="flex space-x-2">
<button onclick="generatePDFReport()"
class="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition duration-300 text-sm">
<i class="fas fa-file-pdf mr-1"></i>
PDF
</button>
</div>
</div>
</div>
<!-- Analytics Charts -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<!-- Monthly Attendance Trend -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">
<i class="fas fa-chart-line mr-2 text-primary"></i>
Monthly Attendance Trend
</h3>
<div class="chart-container">
<canvas id="monthlyChart"></canvas>
</div>
</div>
<!-- Program Attendance Distribution -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">
<i class="fas fa-chart-pie mr-2 text-primary"></i>
Program Distribution
</h3>
<div class="chart-container">
<canvas id="programChart"></canvas>
</div>
</div>
</div>
<!-- Report Tabs -->
<div class="bg-white rounded-lg shadow mb-8">
<div class="border-b border-gray-200">
<nav class="-mb-px flex space-x-8 px-6">
<button onclick="showReportTab('summary')" id="tab-summary"
class="report-tab py-4 px-1 border-b-2 border-primary text-primary font-medium text-sm">
<i class="fas fa-chart-bar mr-2"></i>Program Summary
</button>
<button onclick="showReportTab('attendance')" id="tab-attendance"
class="report-tab py-4 px-1 border-b-2 border-transparent text-gray-500 hover:text-gray-700 font-medium text-sm">
<i class="fas fa-users mr-2"></i>Detailed Attendance
</button>
</nav>
</div>
<!-- Program Summary Tab -->
<div id="content-summary" class="report-content">
<div class="px-6 py-4 border-b">
<h3 class="text-lg font-semibold text-gray-900">
<i class="fas fa-table mr-2 text-primary"></i>
Program Attendance Summary
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Program Name
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Location
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Total Attendance
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<?php foreach ($program_summary as $program): ?>
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
<?php echo htmlspecialchars($program['name']); ?>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">
<?php echo htmlspecialchars($program['location_name'] ?? 'All Locations'); ?>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
<?php echo number_format($program['attendance_count']); ?>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="attendance.php?program=<?php echo $program['name']; ?>"
class="text-primary hover:text-blue-700">
View Details
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Detailed Attendance Tab -->
<div id="content-attendance" class="report-content hidden">
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900">
<i class="fas fa-users mr-2 text-primary"></i>
Detailed Attendance Records
</h3>
<span class="text-sm text-gray-500">Complete submission history</span>
</div>
</div>
<div class="relative">
<div id="attendance-reports-container" class="h-96 overflow-y-auto custom-scrollbar">
<!-- Initial loading message -->
<div id="initial-loading" class="flex items-center justify-center h-full">
<div class="text-center">
<div class="inline-flex items-center text-gray-500">
<div class="spinner mr-3"></div>
<span>Loading attendance records...</span>
</div>
</div>
</div>
<!-- Records will be loaded here -->
<div id="attendance-records-list" class="divide-y divide-gray-100"></div>
<!-- Loading indicator -->
<div id="attendance-loading-indicator" class="hidden px-6 py-4 text-center border-t border-gray-100">
<div class="inline-flex items-center text-sm text-gray-500">
<div class="spinner mr-2"></div>
Loading more records...
</div>
</div>
<!-- Load More Button -->
<div id="attendance-load-more-btn" class="hidden px-6 py-4 text-center border-t border-gray-100">
<button onclick="loadMoreAttendanceRecords()"
class="inline-flex items-center px-4 py-2 text-sm font-medium text-primary bg-primary/10 rounded-lg hover:bg-primary/20 transition-colors duration-150">
<i class="fas fa-plus mr-2"></i>
Load More Records
</button>
</div>
<!-- End indicator -->
<div id="attendance-end-indicator" class="hidden px-6 py-4 text-center border-t border-gray-100">
<p class="text-sm text-gray-400">
<i class="fas fa-check-circle mr-2"></i>
All records loaded
</p>
</div>
</div>
<!-- Scroll indicator -->
<div class="absolute bottom-0 left-0 right-0 h-4 bg-gradient-to-t from-white to-transparent pointer-events-none"></div>
</div>
</div>
</div>
</main>
</div>
<script>
// Monthly attendance trend chart
const monthlyCtx = document.getElementById('monthlyChart').getContext('2d');
const monthlyData = <?php echo json_encode($monthly_stats); ?>;
new Chart(monthlyCtx, {
type: 'line',
data: {
labels: monthlyData.map(item => {
const date = new Date(item.month + '-01');
return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
}),
datasets: [{
label: 'Attendance',
data: monthlyData.map(item => item.count),
borderColor: 'rgba(59, 130, 246, 1)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
scales: {
x: {
display: true,
grid: {
display: false
}
},
y: {
beginAtZero: true,
display: true,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
},
ticks: {
stepSize: 1,
precision: 0
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: 'white',
bodyColor: 'white',
borderColor: 'rgba(59, 130, 246, 1)',
borderWidth: 1
}
},
layout: {
padding: {
top: 10,
bottom: 10
}
}
}
});
// Program distribution chart
const programCtx = document.getElementById('programChart').getContext('2d');
const programData = <?php echo json_encode($program_summary); ?>;
new Chart(programCtx, {
type: 'doughnut',
data: {
labels: programData.map(item => item.name),
datasets: [{
data: programData.map(item => item.attendance_count),
backgroundColor: [
'rgba(59, 130, 246, 0.8)',
'rgba(245, 158, 11, 0.8)',
'rgba(107, 114, 128, 0.8)',
'rgba(34, 197, 94, 0.8)',
'rgba(239, 68, 68, 0.8)',
'rgba(168, 85, 247, 0.8)'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'nearest'
},
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true,
font: {
size: 12
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: 'white',
bodyColor: 'white',
borderColor: 'rgba(59, 130, 246, 1)',
borderWidth: 1,
callbacks: {
label: function(context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((context.parsed / total) * 100).toFixed(1);
return `${context.label}: ${context.parsed} (${percentage}%)`;
}
}
}
},
layout: {
padding: {
top: 10,
bottom: 10
}
}
}
});
function generatePDFReport() {
alert('PDF report generation feature will be implemented with a PDF library like TCPDF or mPDF.');
}
// Reports Tab Management
function showReportTab(tabName) {
// Hide all content
document.querySelectorAll('.report-content').forEach(content => {
content.classList.add('hidden');
});
// Remove active class from all tabs
document.querySelectorAll('.report-tab').forEach(tab => {
tab.classList.remove('border-primary', 'text-primary');
tab.classList.add('border-transparent', 'text-gray-500');
});
// Show selected content
document.getElementById(`content-${tabName}`).classList.remove('hidden');
// Add active class to selected tab
const activeTab = document.getElementById(`tab-${tabName}`);
activeTab.classList.remove('border-transparent', 'text-gray-500');
activeTab.classList.add('border-primary', 'text-primary');
// Load attendance data when attendance tab is selected
if (tabName === 'attendance' && !window.attendanceLoaded) {
loadInitialAttendanceRecords();
}
}
// Attendance Records Infinite Scroll
let attendanceCurrentPage = 1;
let attendanceIsLoading = false;
let attendanceHasMore = true;
window.attendanceLoaded = false;
function initAttendanceScroll() {
const container = document.getElementById('attendance-reports-container');
if (!container) return;
// Add scroll listener
container.addEventListener('scroll', function() {
if (attendanceIsLoading || !attendanceHasMore) return;
const { scrollTop, scrollHeight, clientHeight } = container;
if (scrollTop + clientHeight >= scrollHeight - 20) {
loadMoreAttendanceRecords();
}
});
}
async function loadInitialAttendanceRecords() {
if (window.attendanceLoaded) return;
console.log('Loading initial attendance records...');
const initialLoading = document.getElementById('initial-loading');
const recordsList = document.getElementById('attendance-records-list');
try {
const response = await fetch(`api/get_reports_data.php?type=attendance&page=1&limit=20`);
const data = await response.json();
if (data.success && data.records) {
initialLoading.classList.add('hidden');
addAttendanceRecords(data.records);
attendanceHasMore = data.pagination.has_more;
attendanceCurrentPage = 2;
window.attendanceLoaded = true;
// Show load more button if there are more records
if (attendanceHasMore) {
document.getElementById('attendance-load-more-btn').classList.remove('hidden');
} else {
document.getElementById('attendance-end-indicator').classList.remove('hidden');
}
} else {
throw new Error(data.message || 'Failed to load records');
}
} catch (error) {
console.error('Error loading initial attendance records:', error);
initialLoading.innerHTML = `
<div class="text-center py-8">
<p class="text-red-500">
<i class="fas fa-exclamation-triangle mr-2"></i>
Failed to load attendance records: ${error.message}
</p>
<button onclick="loadInitialAttendanceRecords()"
class="mt-2 bg-primary text-white px-4 py-2 rounded text-sm">
Retry
</button>
</div>
`;
}
}
async function loadMoreAttendanceRecords() {
if (attendanceIsLoading || !attendanceHasMore) return;
console.log('Loading attendance page:', attendanceCurrentPage);
attendanceIsLoading = true;
const loadingIndicator = document.getElementById('attendance-loading-indicator');
const loadMoreBtn = document.getElementById('attendance-load-more-btn');
const endIndicator = document.getElementById('attendance-end-indicator');
loadingIndicator.classList.remove('hidden');
loadMoreBtn.classList.add('hidden');
try {
const response = await fetch(`api/get_reports_data.php?type=attendance&page=${attendanceCurrentPage}&limit=20`);
const data = await response.json();
if (data.success && data.records) {
addAttendanceRecords(data.records);
attendanceHasMore = data.pagination.has_more;
attendanceCurrentPage++;
if (!attendanceHasMore) {
endIndicator.classList.remove('hidden');
} else {
loadMoreBtn.classList.remove('hidden');
}
} else {
throw new Error(data.message || 'Failed to load records');
}
} catch (error) {
console.error('Error loading more attendance records:', error);
showAttendanceError('Failed to load more records: ' + error.message);
loadMoreBtn.classList.remove('hidden');
} finally {
attendanceIsLoading = false;
loadingIndicator.classList.add('hidden');
}
}
function addAttendanceRecords(records) {
const recordsList = document.getElementById('attendance-records-list');
records.forEach(record => {
const recordDiv = document.createElement('div');
recordDiv.className = 'px-6 py-4 hover:bg-gray-50 transition-colors duration-150';
const mainContainer = document.createElement('div');
mainContainer.className = 'flex items-start space-x-4';
// Avatar section
const avatarDiv = document.createElement('div');
avatarDiv.className = 'flex-shrink-0';
const avatar = document.createElement('div');
avatar.className = 'w-10 h-10 bg-gradient-to-br from-primary to-blue-600 rounded-full flex items-center justify-center';
const avatarText = document.createElement('span');
avatarText.className = 'text-white text-sm font-semibold';
avatarText.textContent = record.full_name.charAt(0).toUpperCase();
avatar.appendChild(avatarText);
avatarDiv.appendChild(avatar);
// Content section
const contentDiv = document.createElement('div');
contentDiv.className = 'flex-1 min-w-0';
// Header with name and date
const headerDiv = document.createElement('div');
headerDiv.className = 'flex items-center justify-between mb-1';
const nameP = document.createElement('p');
nameP.className = 'text-sm font-semibold text-gray-900 truncate';
nameP.textContent = record.full_name;
const dateP = document.createElement('p');
dateP.className = 'text-xs text-gray-500 flex-shrink-0 ml-2';
dateP.textContent = record.formatted_date;
headerDiv.appendChild(nameP);
headerDiv.appendChild(dateP);
// Program and location
const programP = document.createElement('p');
programP.className = 'text-sm text-primary font-medium mb-1';
programP.textContent = record.program_name;
// District and Assembly
const locationP = document.createElement('p');
locationP.className = 'text-xs text-gray-600 mb-2';
locationP.textContent = `${record.district_name || 'N/A'} • ${record.assembly_name || 'N/A'}`;
// Contact info
const contactDiv = document.createElement('div');
contactDiv.className = 'flex items-center space-x-4 text-xs text-gray-500';
if (record.email) {
const emailSpan = document.createElement('span');
emailSpan.className = 'flex items-center';
emailSpan.innerHTML = `<i class="fas fa-envelope mr-1"></i>${record.email}`;
contactDiv.appendChild(emailSpan);
}
if (record.telephone) {
const phoneSpan = document.createElement('span');
phoneSpan.className = 'flex items-center';
phoneSpan.innerHTML = `<i class="fas fa-phone mr-1"></i>${record.telephone}`;
contactDiv.appendChild(phoneSpan);
}
if (record.location_name) {
const locationSpan = document.createElement('span');
locationSpan.className = 'flex items-center';
locationSpan.innerHTML = `<i class="fas fa-map-marker-alt mr-1"></i>${record.location_name}`;
contactDiv.appendChild(locationSpan);
}
// Assemble content
contentDiv.appendChild(headerDiv);
contentDiv.appendChild(programP);
contentDiv.appendChild(locationP);
contentDiv.appendChild(contactDiv);
// Assemble main container
mainContainer.appendChild(avatarDiv);
mainContainer.appendChild(contentDiv);
recordDiv.appendChild(mainContainer);
recordsList.appendChild(recordDiv);
});
}
function showAttendanceError(message) {
const recordsList = document.getElementById('attendance-records-list');
const errorDiv = document.createElement('div');
errorDiv.className = 'text-center py-4';
errorDiv.innerHTML = `
<p class="text-red-500 text-sm">
<i class="fas fa-exclamation-triangle mr-1"></i>
${message}
</p>
`;
recordsList.appendChild(errorDiv);
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.remove();
}
}, 5000);
}
// Initialize attendance scroll when page loads
initAttendanceScroll();
// Mobile menu functionality is handled by sidebar.php
</script>
</main>
</div>
</body>
</html>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists