Sindbad~EG File Manager
<?php
require_once '../config/config.php';
header('Content-Type: application/json');
if (!isLoggedIn()) {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit();
}
$type = $_GET['type'] ?? '';
$db = Database::getInstance()->getConnection();
// Get access level parameters using safe helper functions
$accessLevel = getUserAccessLevel();
$areaId = getUserAreaId();
$districtId = getUserDistrictId();
$assemblyId = getUserAssemblyId();
$response = ['success' => true, 'title' => '', 'html' => ''];
switch ($type) {
case 'members':
$response['title'] = 'Members Report';
try {
// Check if members table exists
$db->query("SELECT 1 FROM members LIMIT 1");
// Build query based on access - with fallback for missing tables
$query = "SELECT m.*";
$joins = "";
$params = [];
// Check if location tables exist
try {
$db->query("SELECT 1 FROM areas LIMIT 1");
$query .= ", a.area_name";
$joins .= " LEFT JOIN areas a ON m.area_id = a.id";
} catch (Exception $e) {
$query .= ", 'N/A' as area_name";
}
try {
$db->query("SELECT 1 FROM districts LIMIT 1");
$query .= ", d.district_name";
$joins .= " LEFT JOIN districts d ON m.district_id = d.id";
} catch (Exception $e) {
$query .= ", 'N/A' as district_name";
}
try {
$db->query("SELECT 1 FROM assemblies LIMIT 1");
$query .= ", asm.assembly_name";
$joins .= " LEFT JOIN assemblies asm ON m.assembly_id = asm.id";
} catch (Exception $e) {
$query .= ", 'N/A' as assembly_name";
}
$query .= " FROM members m" . $joins . " WHERE 1=1";
// Apply access level filters
if ($accessLevel === 'assembly' && $assemblyId) {
$query .= " AND m.assembly_id = :assembly_id";
$params['assembly_id'] = $assemblyId;
} elseif ($accessLevel === 'district' && $districtId) {
$query .= " AND m.district_id = :district_id";
$params['district_id'] = $districtId;
} elseif ($accessLevel === 'area' && $areaId) {
$query .= " AND m.area_id = :area_id";
$params['area_id'] = $areaId;
}
$stmt = $db->prepare($query);
$stmt->execute($params);
$members = $stmt->fetchAll();
} catch (Exception $e) {
// Fallback: simple query without joins
$query = "SELECT * FROM members WHERE 1=1";
$params = [];
if ($accessLevel === 'assembly' && $assemblyId) {
$query .= " AND assembly_id = :assembly_id";
$params['assembly_id'] = $assemblyId;
} elseif ($accessLevel === 'district' && $districtId) {
$query .= " AND district_id = :district_id";
$params['district_id'] = $districtId;
} elseif ($accessLevel === 'area' && $areaId) {
$query .= " AND area_id = :area_id";
$params['area_id'] = $areaId;
}
$stmt = $db->prepare($query);
$stmt->execute($params);
$members = $stmt->fetchAll();
// Add default location names
foreach ($members as &$member) {
$member['area_name'] = $member['area_name'] ?? 'N/A';
$member['district_name'] = $member['district_name'] ?? 'N/A';
$member['assembly_name'] = $member['assembly_name'] ?? 'N/A';
}
}
$totalMembers = count($members);
$maleCount = count(array_filter($members, function($m) { return ($m['gender'] ?? '') === 'Male'; }));
$femaleCount = count(array_filter($members, function($m) { return ($m['gender'] ?? '') === 'Female'; }));
$communicants = count(array_filter($members, function($m) { return !empty($m['communicant']); }));
$html = '<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">';
$html .= '<div class="bg-blue-50 rounded-lg p-4"><h4 class="text-sm text-gray-600">Total Members</h4><p class="text-2xl font-bold text-blue-600">' . $totalMembers . '</p></div>';
$html .= '<div class="bg-green-50 rounded-lg p-4"><h4 class="text-sm text-gray-600">Male</h4><p class="text-2xl font-bold text-green-600">' . $maleCount . '</p></div>';
$html .= '<div class="bg-pink-50 rounded-lg p-4"><h4 class="text-sm text-gray-600">Female</h4><p class="text-2xl font-bold text-pink-600">' . $femaleCount . '</p></div>';
$html .= '<div class="bg-yellow-50 rounded-lg p-4"><h4 class="text-sm text-gray-600">Communicants</h4><p class="text-2xl font-bold text-yellow-600">' . $communicants . '</p></div>';
$html .= '</div>';
if ($totalMembers > 0) {
$html .= '<div class="overflow-x-auto"><table class="min-w-full divide-y divide-gray-200">';
$html .= '<thead class="bg-gray-50"><tr>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Member ID</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Gender</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Assembly</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>';
$html .= '</tr></thead><tbody class="bg-white divide-y divide-gray-200">';
foreach ($members as $member) {
$html .= '<tr>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($member['member_id'] ?? $member['id'] ?? 'N/A') . '</td>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars(($member['first_name'] ?? '') . ' ' . ($member['last_name'] ?? '')) . '</td>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($member['gender'] ?? 'N/A') . '</td>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($member['assembly_name'] ?? 'N/A') . '</td>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . (($member['is_active'] ?? 1) ? '<span class="text-green-600">Active</span>' : '<span class="text-red-600">Inactive</span>') . '</td>';
$html .= '</tr>';
}
$html .= '</tbody></table></div>';
} else {
$html .= '<div class="text-center py-12">';
$html .= '<i class="fas fa-users text-6xl text-gray-300 mb-4"></i>';
$html .= '<h3 class="text-xl font-bold text-gray-600 mb-2">No Members Found</h3>';
$html .= '<p class="text-gray-500">No members match the current filter criteria or no members have been added to the system yet.</p>';
$html .= '</div>';
}
$response['html'] = $html;
} catch (Exception $e) {
$response['success'] = false;
$response['message'] = 'Error generating members report: ' . $e->getMessage();
$response['html'] = '<div class="bg-red-50 rounded-lg p-4">';
$response['html'] .= '<h3 class="text-lg font-bold text-red-800 mb-2">Report Generation Error</h3>';
$response['html'] .= '<p class="text-red-600 mb-4">Unable to generate the members report due to the following error:</p>';
$response['html'] .= '<div class="bg-red-100 rounded p-3 font-mono text-sm text-red-800">' . htmlspecialchars($e->getMessage()) . '</div>';
if (strpos($e->getMessage(), 'members') !== false && strpos($e->getMessage(), "doesn't exist") !== false) {
$response['html'] .= '<div class="mt-4 p-3 bg-yellow-50 rounded">';
$response['html'] .= '<p class="text-yellow-800"><strong>Suggestion:</strong> The members table appears to be missing. Please ensure the database is properly set up with the members table.</p>';
$response['html'] .= '</div>';
}
$response['html'] .= '</div>';
}
break;
case 'baptism':
$response['title'] = 'Baptism Report';
try {
// Check which baptism columns exist
$columns = $db->query("SHOW COLUMNS FROM members")->fetchAll();
$columnNames = array_column($columns, 'Field');
$hasWaterBaptism = in_array('water_baptism', $columnNames);
$hasWaterBaptismDate = in_array('water_baptism_date', $columnNames);
$hasBaptized = in_array('baptized', $columnNames);
$hasBaptismDate = in_array('baptism_date', $columnNames);
// Build query based on available columns
$query = "SELECT m.*";
if ($hasWaterBaptism) {
$query .= ", CASE WHEN m.water_baptism = 1 THEN 'Yes' ELSE 'No' END as baptized_status";
$baptismColumn = 'water_baptism';
} elseif ($hasBaptized) {
$query .= ", CASE WHEN m.baptized = 1 THEN 'Yes' ELSE 'No' END as baptized_status";
$baptismColumn = 'baptized';
} else {
$query .= ", 'Unknown' as baptized_status";
$baptismColumn = null;
}
if ($hasWaterBaptismDate) {
$query .= ", m.water_baptism_date as baptism_date";
} elseif ($hasBaptismDate) {
$query .= ", m.baptism_date";
} else {
$query .= ", NULL as baptism_date";
}
$query .= " FROM members m WHERE 1=1";
$params = [];
// Apply access level filters
if ($accessLevel === 'assembly' && $assemblyId) {
$query .= " AND m.assembly_id = :assembly_id";
$params['assembly_id'] = $assemblyId;
} elseif ($accessLevel === 'district' && $districtId) {
$query .= " AND m.district_id = :district_id";
$params['district_id'] = $districtId;
} elseif ($accessLevel === 'area' && $areaId) {
$query .= " AND m.area_id = :area_id";
$params['area_id'] = $areaId;
}
$stmt = $db->prepare($query);
$stmt->execute($params);
$members = $stmt->fetchAll();
// Filter baptized members based on available column
if ($baptismColumn) {
$baptized = array_filter($members, fn($m) => $m[$baptismColumn] == 1);
$notBaptized = array_filter($members, fn($m) => $m[$baptismColumn] != 1);
} else {
// No baptism column available
$baptized = [];
$notBaptized = $members;
}
$html = '<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">';
$html .= '<div class="bg-blue-50 rounded-lg p-4"><h4 class="text-sm text-gray-600">Total Members</h4><p class="text-2xl font-bold text-blue-600">' . count($members) . '</p></div>';
$html .= '<div class="bg-green-50 rounded-lg p-4"><h4 class="text-sm text-gray-600">Baptized</h4><p class="text-2xl font-bold text-green-600">' . count($baptized) . '</p></div>';
$html .= '<div class="bg-red-50 rounded-lg p-4"><h4 class="text-sm text-gray-600">Not Baptized</h4><p class="text-2xl font-bold text-red-600">' . count($notBaptized) . '</p></div>';
$html .= '</div>';
if (!empty($baptized)) {
$html .= '<h3 class="text-lg font-bold mb-4">Baptized Members</h3>';
$html .= '<div class="overflow-x-auto"><table class="min-w-full divide-y divide-gray-200">';
$html .= '<thead class="bg-gray-50"><tr>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Gender</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Baptism Date</th>';
$html .= '</tr></thead><tbody class="bg-white divide-y divide-gray-200">';
foreach ($baptized as $member) {
$html .= '<tr>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($member['first_name'] . ' ' . $member['last_name']) . '</td>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($member['gender'] ?? 'N/A') . '</td>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($member['baptism_date'] ?? 'N/A') . '</td>';
$html .= '</tr>';
}
$html .= '</tbody></table></div>';
} elseif (!$baptismColumn) {
$html .= '<div class="bg-yellow-50 rounded-lg p-4 mt-4">';
$html .= '<p class="text-yellow-700"><i class="fas fa-exclamation-triangle mr-2"></i>Baptism tracking columns not found in the database. Please add "water_baptism" and "water_baptism_date" columns to the members table to track baptism records.</p>';
$html .= '</div>';
} else {
$html .= '<div class="text-center py-8">';
$html .= '<p class="text-gray-500">No baptized members found in the current selection.</p>';
$html .= '</div>';
}
} catch (Exception $e) {
$html = '<div class="bg-red-50 rounded-lg p-4"><p class="text-red-600">Error generating baptism report: ' . htmlspecialchars($e->getMessage()) . '</p></div>';
}
$response['html'] = $html;
break;
case 'demographics':
$response['title'] = 'Demographics Report';
try {
$query = "SELECT * FROM members WHERE 1=1";
$params = [];
// Apply access level filters
if ($accessLevel === 'assembly' && $assemblyId) {
$query .= " AND assembly_id = :assembly_id";
$params['assembly_id'] = $assemblyId;
} elseif ($accessLevel === 'district' && $districtId) {
$query .= " AND district_id = :district_id";
$params['district_id'] = $districtId;
} elseif ($accessLevel === 'area' && $areaId) {
$query .= " AND area_id = :area_id";
$params['area_id'] = $areaId;
}
$stmt = $db->prepare($query);
$stmt->execute($params);
$members = $stmt->fetchAll();
// Calculate demographics
$totalMembers = count($members);
$maleCount = count(array_filter($members, fn($m) => $m['gender'] === 'Male'));
$femaleCount = count(array_filter($members, fn($m) => $m['gender'] === 'Female'));
// Age groups
$children = 0; $youth = 0; $adults = 0; $seniors = 0;
foreach ($members as $member) {
if ($member['date_of_birth']) {
$age = date_diff(date_create($member['date_of_birth']), date_create('today'))->y;
if ($age < 13) $children++;
elseif ($age < 25) $youth++;
elseif ($age < 60) $adults++;
else $seniors++;
}
}
$html = '<div class="grid grid-cols-1 md:grid-cols-2 gap-6">';
// Gender Distribution
$html .= '<div class="bg-white rounded-lg p-6 border">';
$html .= '<h3 class="text-lg font-bold mb-4">Gender Distribution</h3>';
$html .= '<div class="space-y-3">';
$html .= '<div class="flex justify-between items-center"><span>Male</span><span class="font-bold text-blue-600">' . $maleCount . ' (' . ($totalMembers > 0 ? round(($maleCount/$totalMembers)*100, 1) : 0) . '%)</span></div>';
$html .= '<div class="flex justify-between items-center"><span>Female</span><span class="font-bold text-pink-600">' . $femaleCount . ' (' . ($totalMembers > 0 ? round(($femaleCount/$totalMembers)*100, 1) : 0) . '%)</span></div>';
$html .= '</div></div>';
// Age Groups
$html .= '<div class="bg-white rounded-lg p-6 border">';
$html .= '<h3 class="text-lg font-bold mb-4">Age Distribution</h3>';
$html .= '<div class="space-y-3">';
$html .= '<div class="flex justify-between items-center"><span>Children (0-12)</span><span class="font-bold text-green-600">' . $children . '</span></div>';
$html .= '<div class="flex justify-between items-center"><span>Youth (13-24)</span><span class="font-bold text-yellow-600">' . $youth . '</span></div>';
$html .= '<div class="flex justify-between items-center"><span>Adults (25-59)</span><span class="font-bold text-blue-600">' . $adults . '</span></div>';
$html .= '<div class="flex justify-between items-center"><span>Seniors (60+)</span><span class="font-bold text-purple-600">' . $seniors . '</span></div>';
$html .= '</div></div>';
$html .= '</div>';
} catch (Exception $e) {
$html = '<div class="bg-red-50 rounded-lg p-4"><p class="text-red-600">Error generating demographics report: ' . htmlspecialchars($e->getMessage()) . '</p></div>';
}
$response['html'] = $html;
break;
case 'location':
$response['title'] = 'Location Report';
try {
$query = "SELECT area_id, district_id, assembly_id FROM members WHERE 1=1";
$params = [];
// Apply access level filters
if ($accessLevel === 'assembly' && $assemblyId) {
$query .= " AND assembly_id = :assembly_id";
$params['assembly_id'] = $assemblyId;
} elseif ($accessLevel === 'district' && $districtId) {
$query .= " AND district_id = :district_id";
$params['district_id'] = $districtId;
} elseif ($accessLevel === 'area' && $areaId) {
$query .= " AND area_id = :area_id";
$params['area_id'] = $areaId;
}
$stmt = $db->prepare($query);
$stmt->execute($params);
$members = $stmt->fetchAll();
// Count by location
$areaStats = [];
$districtStats = [];
$assemblyStats = [];
foreach ($members as $member) {
$areaStats[$member['area_id']] = ($areaStats[$member['area_id']] ?? 0) + 1;
$districtStats[$member['district_id']] = ($districtStats[$member['district_id']] ?? 0) + 1;
$assemblyStats[$member['assembly_id']] = ($assemblyStats[$member['assembly_id']] ?? 0) + 1;
}
$html = '<div class="grid grid-cols-1 md:grid-cols-3 gap-6">';
$html .= '<div class="bg-white rounded-lg p-6 border">';
$html .= '<h3 class="text-lg font-bold mb-4">By Area</h3>';
$html .= '<div class="space-y-2">';
foreach ($areaStats as $areaId => $count) {
$html .= '<div class="flex justify-between"><span>Area ' . $areaId . '</span><span class="font-bold">' . $count . '</span></div>';
}
$html .= '</div></div>';
$html .= '<div class="bg-white rounded-lg p-6 border">';
$html .= '<h3 class="text-lg font-bold mb-4">By District</h3>';
$html .= '<div class="space-y-2">';
foreach ($districtStats as $districtId => $count) {
$html .= '<div class="flex justify-between"><span>District ' . $districtId . '</span><span class="font-bold">' . $count . '</span></div>';
}
$html .= '</div></div>';
$html .= '<div class="bg-white rounded-lg p-6 border">';
$html .= '<h3 class="text-lg font-bold mb-4">By Assembly</h3>';
$html .= '<div class="space-y-2">';
foreach ($assemblyStats as $assemblyId => $count) {
$html .= '<div class="flex justify-between"><span>Assembly ' . $assemblyId . '</span><span class="font-bold">' . $count . '</span></div>';
}
$html .= '</div></div>';
$html .= '</div>';
} catch (Exception $e) {
$html = '<div class="bg-red-50 rounded-lg p-4"><p class="text-red-600">Error generating location report: ' . htmlspecialchars($e->getMessage()) . '</p></div>';
}
$response['html'] = $html;
break;
case 'audit':
$response['title'] = 'Audit Trail Report';
try {
// Check if audit_logs table exists
$db->query("SELECT 1 FROM audit_logs LIMIT 1");
$query = "SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 100";
$stmt = $db->query($query);
$logs = $stmt->fetchAll();
$html = '<div class="bg-blue-50 rounded-lg p-4 mb-6">';
$html .= '<h3 class="text-lg font-bold">Recent System Activity (Last 100 entries)</h3>';
$html .= '</div>';
if (!empty($logs)) {
$html .= '<div class="overflow-x-auto"><table class="min-w-full divide-y divide-gray-200">';
$html .= '<thead class="bg-gray-50"><tr>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date/Time</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">User</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Action</th>';
$html .= '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Details</th>';
$html .= '</tr></thead><tbody class="bg-white divide-y divide-gray-200">';
foreach ($logs as $log) {
$html .= '<tr>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($log['created_at']) . '</td>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($log['user_id'] ?? 'System') . '</td>';
$html .= '<td class="px-6 py-4 whitespace-nowrap text-sm">' . htmlspecialchars($log['action'] ?? 'N/A') . '</td>';
$html .= '<td class="px-6 py-4 text-sm">' . htmlspecialchars($log['details'] ?? 'N/A') . '</td>';
$html .= '</tr>';
}
$html .= '</tbody></table></div>';
} else {
$html .= '<div class="text-center py-8"><p class="text-gray-500">No audit logs found</p></div>';
}
} catch (Exception $e) {
$html = '<div class="bg-yellow-50 rounded-lg p-4">';
$html .= '<p class="text-yellow-700">Audit logging is not yet configured. This feature tracks system activities and user actions.</p>';
$html .= '</div>';
}
$response['html'] = $html;
break;
case 'custom':
$response['title'] = 'Custom Report Builder';
$html = '<div class="bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg p-8">';
$html .= '<div class="text-center">';
$html .= '<i class="fas fa-tools text-6xl text-gray-400 mb-4"></i>';
$html .= '<h3 class="text-2xl font-bold text-gray-800 mb-4">Custom Report Builder</h3>';
$html .= '<p class="text-gray-600 mb-6">Build custom reports with advanced filtering and data selection options.</p>';
$html .= '<div class="bg-white rounded-lg p-6 max-w-md mx-auto">';
$html .= '<h4 class="font-bold mb-4">Coming Soon Features:</h4>';
$html .= '<ul class="text-left space-y-2 text-sm text-gray-600">';
$html .= '<li>• Drag & drop report builder</li>';
$html .= '<li>• Custom field selection</li>';
$html .= '<li>• Advanced filtering options</li>';
$html .= '<li>• Chart and graph generation</li>';
$html .= '<li>• Scheduled report delivery</li>';
$html .= '</ul>';
$html .= '</div>';
$html .= '</div>';
$html .= '</div>';
$response['html'] = $html;
break;
default:
$response['success'] = false;
$response['message'] = 'Invalid report type';
}
echo json_encode($response);
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists