Sindbad~EG File Manager
<?php
/**
* Bulk Upload Locations (Areas, Districts, Assemblies)
* Supports CSV and Excel file uploads with templates
*/
require_once '../../config/config.php';
checkLogin();
if (!isSuperuser()) {
header('Location: ' . BASE_URL . 'dashboard.php');
exit();
}
$pageTitle = "Bulk Upload Locations - " . APP_NAME;
$db = Database::getInstance()->getConnection();
// Check if PhpSpreadsheet is available
$hasPhpSpreadsheet = class_exists('PhpOffice\PhpSpreadsheet\Spreadsheet');
// Handle template downloads
if (isset($_GET['download'])) {
$type = $_GET['download'];
$format = $_GET['format'] ?? 'csv';
if ($format === 'excel' && $hasPhpSpreadsheet) {
require_once '../../vendor/autoload.php';
downloadExcelTemplate($type);
} else {
downloadCSVTemplate($type);
}
exit();
}
// Handle file upload
$uploadResults = [];
$uploadErrors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload_file'])) {
$uploadType = $_POST['upload_type'] ?? '';
$file = $_FILES['upload_file'];
if ($file['error'] === UPLOAD_ERR_OK) {
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
try {
if (in_array($extension, ['xlsx', 'xls']) && $hasPhpSpreadsheet) {
require_once '../../vendor/autoload.php';
$uploadResults = processExcelUpload($file['tmp_name'], $uploadType, $db);
} elseif ($extension === 'csv') {
$uploadResults = processCSVUpload($file['tmp_name'], $uploadType, $db);
} else {
$uploadErrors[] = "Unsupported file format. Please upload CSV" . ($hasPhpSpreadsheet ? ', XLSX, or XLS' : '') . " files only.";
}
} catch (Exception $e) {
$uploadErrors[] = "Upload error: " . $e->getMessage();
}
} else {
$uploadErrors[] = "File upload failed. Please try again.";
}
}
include '../../includes/header.php';
include '../../includes/sidebar.php';
?>
<main class="flex-1 md:ml-64 mt-16 min-h-screen bg-gray-50">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="gradient-bg rounded-2xl shadow-2xl p-8 mb-8 text-white">
<div class="flex items-center justify-between">
<div>
<h1 class="text-4xl font-bold mb-2">
<i class="fas fa-upload mr-3"></i>Bulk Upload Locations
</h1>
<p class="text-white/90 text-lg">Upload multiple areas, districts, or assemblies at once</p>
</div>
<a href="index.php" class="bg-white/20 hover:bg-white/30 text-white px-6 py-3 rounded-full font-bold transition">
<i class="fas fa-arrow-left mr-2"></i>Back
</a>
</div>
</div>
<?php if (!empty($uploadResults)): ?>
<div class="bg-white rounded-2xl shadow-lg p-6 mb-6">
<h3 class="text-xl font-bold text-gray-800 mb-4">
<i class="fas fa-check-circle text-green-500 mr-2"></i>Upload Results
</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="bg-gray-100">
<th class="px-4 py-3 text-left">Row</th>
<th class="px-4 py-3 text-left">Name</th>
<th class="px-4 py-3 text-left">Code</th>
<th class="px-4 py-3 text-left">Status</th>
<th class="px-4 py-3 text-left">Message</th>
</tr>
</thead>
<tbody>
<?php foreach ($uploadResults as $result): ?>
<tr class="border-b hover:bg-gray-50">
<td class="px-4 py-3"><?php echo $result['row']; ?></td>
<td class="px-4 py-3"><?php echo htmlspecialchars($result['name']); ?></td>
<td class="px-4 py-3"><code class="bg-gray-100 px-2 py-1 rounded"><?php echo htmlspecialchars($result['code']); ?></code></td>
<td class="px-4 py-3">
<?php if ($result['success']): ?>
<span class="bg-green-100 text-green-800 px-3 py-1 rounded-full text-sm font-semibold">
<i class="fas fa-check mr-1"></i>Success
</span>
<?php else: ?>
<span class="bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-semibold">
<i class="fas fa-times mr-1"></i>Failed
</span>
<?php endif; ?>
</td>
<td class="px-4 py-3 text-sm text-gray-600"><?php echo htmlspecialchars($result['message']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="mt-4 text-sm text-gray-600">
Total: <?php echo count($uploadResults); ?> |
Success: <?php echo count(array_filter($uploadResults, fn($r) => $r['success'])); ?> |
Failed: <?php echo count(array_filter($uploadResults, fn($r) => !$r['success'])); ?>
</div>
</div>
<?php endif; ?>
<?php if (!empty($uploadErrors)): ?>
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-6 rounded-lg mb-6">
<ul class="list-disc list-inside">
<?php foreach ($uploadErrors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<!-- Upload Tabs -->
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
<div class="border-b border-gray-200">
<nav class="flex">
<button onclick="switchTab('areas')" id="tab-areas" class="upload-tab flex-1 px-6 py-4 text-center font-semibold border-b-4 border-blue-500 text-blue-600">
<i class="fas fa-map-marked-alt mr-2"></i>Areas
</button>
<button onclick="switchTab('districts')" id="tab-districts" class="upload-tab flex-1 px-6 py-4 text-center font-semibold border-b-4 border-transparent text-gray-500 hover:text-gray-700">
<i class="fas fa-map-marker-alt mr-2"></i>Districts
</button>
<button onclick="switchTab('assemblies')" id="tab-assemblies" class="upload-tab flex-1 px-6 py-4 text-center font-semibold border-b-4 border-transparent text-gray-500 hover:text-gray-700">
<i class="fas fa-church mr-2"></i>Assemblies
</button>
</nav>
</div>
<!-- Areas Upload -->
<div id="content-areas" class="upload-content p-8">
<h3 class="text-2xl font-bold text-gray-800 mb-4">Upload Areas</h3>
<p class="text-gray-600 mb-6">Download a template, fill in your area data, and upload the file.</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<a href="?download=areas&format=csv" class="btn-gradient-orange text-white px-6 py-4 rounded-lg text-center font-bold hover:shadow-xl transition">
<i class="fas fa-file-csv mr-2"></i>Download CSV Template
</a>
<?php if ($hasPhpSpreadsheet): ?>
<a href="?download=areas&format=excel" class="btn-gradient text-white px-6 py-4 rounded-lg text-center font-bold hover:shadow-xl transition">
<i class="fas fa-file-excel mr-2"></i>Download Excel Template
</a>
<?php endif; ?>
</div>
<form method="POST" enctype="multipart/form-data" class="space-y-4">
<input type="hidden" name="upload_type" value="areas">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Select File</label>
<input type="file" name="upload_file" required accept=".csv<?php echo $hasPhpSpreadsheet ? ',.xlsx,.xls' : ''; ?>"
class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<p class="text-sm text-gray-500 mt-2">Supported formats: CSV<?php echo $hasPhpSpreadsheet ? ', XLSX, XLS' : ''; ?></p>
</div>
<button type="submit" class="btn-gradient text-white px-8 py-3 rounded-lg font-bold hover:shadow-xl transition">
<i class="fas fa-upload mr-2"></i>Upload Areas
</button>
</form>
<div class="mt-6 p-4 bg-blue-50 rounded-lg">
<h4 class="font-bold text-blue-900 mb-2"><i class="fas fa-info-circle mr-2"></i>Required Fields:</h4>
<ul class="text-sm text-blue-800 space-y-1">
<li><strong>area_name</strong> - Name of the area (required)</li>
<li><strong>area_code</strong> - Unique code for the area (required)</li>
<li><strong>description</strong> - Brief description (optional)</li>
<li><strong>contact_person</strong> - Contact person name (optional)</li>
<li><strong>phone</strong> - Contact phone number (optional)</li>
<li><strong>email</strong> - Contact email (optional)</li>
<li><strong>address</strong> - Physical address (optional)</li>
</ul>
</div>
</div>
<!-- Districts Upload -->
<div id="content-districts" class="upload-content p-8 hidden">
<h3 class="text-2xl font-bold text-gray-800 mb-4">Upload Districts</h3>
<p class="text-gray-600 mb-6">Download a template, fill in your district data, and upload the file.</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<a href="?download=districts&format=csv" class="btn-gradient-orange text-white px-6 py-4 rounded-lg text-center font-bold hover:shadow-xl transition">
<i class="fas fa-file-csv mr-2"></i>Download CSV Template
</a>
<?php if ($hasPhpSpreadsheet): ?>
<a href="?download=districts&format=excel" class="btn-gradient text-white px-6 py-4 rounded-lg text-center font-bold hover:shadow-xl transition">
<i class="fas fa-file-excel mr-2"></i>Download Excel Template
</a>
<?php endif; ?>
</div>
<form method="POST" enctype="multipart/form-data" class="space-y-4">
<input type="hidden" name="upload_type" value="districts">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Select File</label>
<input type="file" name="upload_file" required accept=".csv<?php echo $hasPhpSpreadsheet ? ',.xlsx,.xls' : ''; ?>"
class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<p class="text-sm text-gray-500 mt-2">Supported formats: CSV<?php echo $hasPhpSpreadsheet ? ', XLSX, XLS' : ''; ?></p>
</div>
<button type="submit" class="btn-gradient text-white px-8 py-3 rounded-lg font-bold hover:shadow-xl transition">
<i class="fas fa-upload mr-2"></i>Upload Districts
</button>
</form>
<div class="mt-6 p-4 bg-orange-50 rounded-lg">
<h4 class="font-bold text-orange-900 mb-2"><i class="fas fa-info-circle mr-2"></i>Required Fields:</h4>
<ul class="text-sm text-orange-800 space-y-1">
<li><strong>area_code</strong> - Area code this district belongs to (required)</li>
<li><strong>district_name</strong> - Name of the district (required)</li>
<li><strong>district_code</strong> - Unique code for the district (required)</li>
<li><strong>description</strong> - Brief description (optional)</li>
<li><strong>contact_person</strong> - Contact person name (optional)</li>
<li><strong>phone</strong> - Contact phone number (optional)</li>
<li><strong>email</strong> - Contact email (optional)</li>
<li><strong>address</strong> - Physical address (optional)</li>
</ul>
</div>
</div>
<!-- Assemblies Upload -->
<div id="content-assemblies" class="upload-content p-8 hidden">
<h3 class="text-2xl font-bold text-gray-800 mb-4">Upload Assemblies</h3>
<p class="text-gray-600 mb-6">Download a template, fill in your assembly data, and upload the file.</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<a href="?download=assemblies&format=csv" class="btn-gradient-orange text-white px-6 py-4 rounded-lg text-center font-bold hover:shadow-xl transition">
<i class="fas fa-file-csv mr-2"></i>Download CSV Template
</a>
<?php if ($hasPhpSpreadsheet): ?>
<a href="?download=assemblies&format=excel" class="btn-gradient text-white px-6 py-4 rounded-lg text-center font-bold hover:shadow-xl transition">
<i class="fas fa-file-excel mr-2"></i>Download Excel Template
</a>
<?php endif; ?>
</div>
<form method="POST" enctype="multipart/form-data" class="space-y-4">
<input type="hidden" name="upload_type" value="assemblies">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Select File</label>
<input type="file" name="upload_file" required accept=".csv<?php echo $hasPhpSpreadsheet ? ',.xlsx,.xls' : ''; ?>"
class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<p class="text-sm text-gray-500 mt-2">Supported formats: CSV<?php echo $hasPhpSpreadsheet ? ', XLSX, XLS' : ''; ?></p>
</div>
<button type="submit" class="btn-gradient text-white px-8 py-3 rounded-lg font-bold hover:shadow-xl transition">
<i class="fas fa-upload mr-2"></i>Upload Assemblies
</button>
</form>
<div class="mt-6 p-4 bg-purple-50 rounded-lg">
<h4 class="font-bold text-purple-900 mb-2"><i class="fas fa-info-circle mr-2"></i>Required Fields:</h4>
<ul class="text-sm text-purple-800 space-y-1">
<li><strong>area_code</strong> - Area code this assembly belongs to (required)</li>
<li><strong>district_code</strong> - District code this assembly belongs to (required)</li>
<li><strong>assembly_name</strong> - Name of the assembly (required)</li>
<li><strong>assembly_code</strong> - Unique code for the assembly (required)</li>
<li><strong>description</strong> - Brief description (optional)</li>
<li><strong>pastor_name</strong> - Pastor's name (optional)</li>
<li><strong>phone</strong> - Contact phone number (optional)</li>
<li><strong>email</strong> - Contact email (optional)</li>
<li><strong>gps_address</strong> - GPS address (optional)</li>
<li><strong>city</strong> - City name (optional)</li>
<li><strong>address</strong> - Physical address (optional)</li>
</ul>
</div>
</div>
</div>
</div>
</main>
<script>
function switchTab(tab) {
// Hide all content
document.querySelectorAll('.upload-content').forEach(el => el.classList.add('hidden'));
// Reset all tabs
document.querySelectorAll('.upload-tab').forEach(el => {
el.classList.remove('border-blue-500', 'text-blue-600');
el.classList.add('border-transparent', 'text-gray-500');
});
// Show selected content
document.getElementById('content-' + tab).classList.remove('hidden');
// Highlight selected tab
const selectedTab = document.getElementById('tab-' + tab);
selectedTab.classList.remove('border-transparent', 'text-gray-500');
selectedTab.classList.add('border-blue-500', 'text-blue-600');
}
</script>
<?php include '../../includes/footer.php'; ?>
<?php
// Template download functions
function downloadCSVTemplate($type) {
$fields = getFieldsForType($type);
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $type . '_template.csv"');
$output = fopen('php://output', 'w');
fputcsv($output, $fields['headers']);
fputcsv($output, $fields['sample']);
fclose($output);
}
function downloadExcelTemplate($type) {
$fields = getFieldsForType($type);
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Set headers with styling
$col = 'A';
foreach ($fields['headers'] as $header) {
$sheet->setCellValue($col . '1', $header);
$sheet->getStyle($col . '1')->applyFromArray([
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
'fill' => ['fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, 'startColor' => ['rgb' => '1E40AF']]
]);
$sheet->getColumnDimension($col)->setAutoSize(true);
$col++;
}
// Add sample row
$col = 'A';
foreach ($fields['sample'] as $value) {
$sheet->setCellValue($col . '2', $value);
$sheet->getStyle($col . '2')->getFont()->setItalic(true);
$col++;
}
$filename = $type . '_template.xlsx';
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save('php://output');
}
function getFieldsForType($type) {
switch ($type) {
case 'areas':
return [
'headers' => ['area_name', 'area_code', 'description', 'contact_person', 'phone', 'email', 'address'],
'sample' => ['Greater Accra', 'GA', 'Greater Accra region', 'John Doe', '0201234567', 'accra@church.com', '123 Main St, Accra']
];
case 'districts':
return [
'headers' => ['area_code', 'district_name', 'district_code', 'description', 'contact_person', 'phone', 'email', 'address'],
'sample' => ['GA', 'Madina District', 'MAD', 'Madina area district', 'Jane Smith', '0301234567', 'madina@church.com', '456 District Rd']
];
case 'assemblies':
return [
'headers' => ['area_code', 'district_code', 'assembly_name', 'assembly_code', 'description', 'pastor_name', 'phone', 'email', 'gps_address', 'city', 'address'],
'sample' => ['GA', 'MAD', 'Madina Central Assembly', 'MCA001', 'Main assembly in Madina', 'Pastor Mensah', '0241234567', 'central@church.com', 'GA-123-4567', 'Accra', '789 Church Ave']
];
default:
return ['headers' => [], 'sample' => []];
}
}
function processCSVUpload($filePath, $type, $db) {
$results = [];
$row = 0;
if (($handle = fopen($filePath, 'r')) !== false) {
$headers = fgetcsv($handle);
while (($data = fgetcsv($handle)) !== false) {
$row++;
if (count($data) < 2) continue; // Skip empty rows
$rowData = array_combine($headers, $data);
$result = insertLocation($type, $rowData, $db, $row + 1);
$results[] = $result;
}
fclose($handle);
}
return $results;
}
function processExcelUpload($filePath, $type, $db) {
$results = [];
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($filePath);
$sheet = $spreadsheet->getActiveSheet();
$rows = $sheet->toArray();
$headers = array_shift($rows); // First row is headers
foreach ($rows as $index => $row) {
if (empty(array_filter($row))) continue; // Skip empty rows
$rowData = array_combine($headers, $row);
$result = insertLocation($type, $rowData, $db, $index + 2);
$results[] = $result;
}
return $results;
}
function insertLocation($type, $data, $db, $rowNum) {
try {
switch ($type) {
case 'areas':
return insertArea($data, $db, $rowNum);
case 'districts':
return insertDistrict($data, $db, $rowNum);
case 'assemblies':
return insertAssembly($data, $db, $rowNum);
default:
return [
'row' => $rowNum,
'name' => '',
'code' => '',
'success' => false,
'message' => 'Invalid upload type'
];
}
} catch (Exception $e) {
return [
'row' => $rowNum,
'name' => $data['area_name'] ?? $data['district_name'] ?? $data['assembly_name'] ?? '',
'code' => $data['area_code'] ?? $data['district_code'] ?? $data['assembly_code'] ?? '',
'success' => false,
'message' => $e->getMessage()
];
}
}
function insertArea($data, $db, $rowNum) {
// Check if area_code already exists
$checkStmt = $db->prepare("SELECT id, area_name FROM areas WHERE area_code = :code");
$checkStmt->execute(['code' => strtoupper($data['area_code'])]);
$existing = $checkStmt->fetch();
if ($existing) {
return [
'row' => $rowNum,
'name' => $data['area_name'],
'code' => $data['area_code'],
'success' => false,
'message' => 'Area code already exists: ' . $existing['area_name']
];
}
$stmt = $db->prepare("
INSERT INTO areas (area_name, area_code, description, contact_person, phone, email, address, is_active)
VALUES (:name, :code, :description, :contact, :phone, :email, :address, 1)
");
$stmt->execute([
'name' => $data['area_name'],
'code' => strtoupper($data['area_code']),
'description' => $data['description'] ?? null,
'contact' => $data['contact_person'] ?? null,
'phone' => $data['phone'] ?? null,
'email' => $data['email'] ?? null,
'address' => $data['address'] ?? null
]);
return [
'row' => $rowNum,
'name' => $data['area_name'],
'code' => $data['area_code'],
'success' => true,
'message' => 'Area created successfully'
];
}
function insertDistrict($data, $db, $rowNum) {
// Get area_id from area_code
$stmt = $db->prepare("SELECT id FROM areas WHERE area_code = :code");
$stmt->execute(['code' => $data['area_code']]);
$area = $stmt->fetch();
if (!$area) {
throw new Exception("Area with code '{$data['area_code']}' not found");
}
// Check if district_code already exists
$checkStmt = $db->prepare("SELECT id, district_name FROM districts WHERE district_code = :code");
$checkStmt->execute(['code' => strtoupper($data['district_code'])]);
$existing = $checkStmt->fetch();
if ($existing) {
return [
'row' => $rowNum,
'name' => $data['district_name'],
'code' => $data['district_code'],
'success' => false,
'message' => 'District code already exists: ' . $existing['district_name']
];
}
$stmt = $db->prepare("
INSERT INTO districts (area_id, district_name, district_code, description, contact_person, phone, email, address, is_active)
VALUES (:area_id, :name, :code, :description, :contact, :phone, :email, :address, 1)
");
$stmt->execute([
'area_id' => $area['id'],
'name' => $data['district_name'],
'code' => strtoupper($data['district_code']),
'description' => $data['description'] ?? null,
'contact' => $data['contact_person'] ?? null,
'phone' => $data['phone'] ?? null,
'email' => $data['email'] ?? null,
'address' => $data['address'] ?? null
]);
return [
'row' => $rowNum,
'name' => $data['district_name'],
'code' => $data['district_code'],
'success' => true,
'message' => 'District created successfully'
];
}
function insertAssembly($data, $db, $rowNum) {
// Get area_id from area_code
$stmt = $db->prepare("SELECT id FROM areas WHERE area_code = :code");
$stmt->execute(['code' => $data['area_code']]);
$area = $stmt->fetch();
if (!$area) {
throw new Exception("Area with code '{$data['area_code']}' not found");
}
// Get district_id from district_code
$stmt = $db->prepare("SELECT id FROM districts WHERE district_code = :code AND area_id = :area_id");
$stmt->execute(['code' => $data['district_code'], 'area_id' => $area['id']]);
$district = $stmt->fetch();
if (!$district) {
throw new Exception("District with code '{$data['district_code']}' not found in area '{$data['area_code']}'");
}
// Check if assembly_code already exists
$checkStmt = $db->prepare("SELECT id, assembly_name FROM assemblies WHERE assembly_code = :code");
$checkStmt->execute(['code' => strtoupper($data['assembly_code'])]);
$existing = $checkStmt->fetch();
if ($existing) {
return [
'row' => $rowNum,
'name' => $data['assembly_name'],
'code' => $data['assembly_code'],
'success' => false,
'message' => 'Assembly code already exists: ' . $existing['assembly_name']
];
}
$stmt = $db->prepare("
INSERT INTO assemblies (area_id, district_id, assembly_name, assembly_code, description, pastor_name, phone, email, gps_address, city, address, is_active)
VALUES (:area_id, :district_id, :name, :code, :description, :pastor, :phone, :email, :gps, :city, :address, 1)
");
$stmt->execute([
'area_id' => $area['id'],
'district_id' => $district['id'],
'name' => $data['assembly_name'],
'code' => strtoupper($data['assembly_code']),
'description' => $data['description'] ?? null,
'pastor' => $data['pastor_name'] ?? null,
'phone' => $data['phone'] ?? null,
'email' => $data['email'] ?? null,
'gps' => $data['gps_address'] ?? null,
'city' => $data['city'] ?? null,
'address' => $data['address'] ?? null
]);
return [
'row' => $rowNum,
'name' => $data['assembly_name'],
'code' => $data['assembly_code'],
'success' => true,
'message' => 'Assembly created successfully'
];
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists