Sindbad~EG File Manager
<?php
require_once '../../config/config.php';
require_once '../../classes/MembershipCard.php';
checkLogin();
$pageTitle = "Upload Members - " . APP_NAME;
$db = Database::getInstance()->getConnection();
$successMessage = '';
$errorMessage = '';
$uploadResults = [];
// Get user access parameters
$accessLevel = $_SESSION['access_level'] ?? 'assembly';
$areaId = $_SESSION['area_id'] ?? null;
$districtId = $_SESSION['district_id'] ?? null;
$assemblyId = $_SESSION['assembly_id'] ?? null;
if (!function_exists('generateMembershipId')) {
function generateMembershipId() {
// Simple unique member ID: MEM + Year + random 6 digits
return 'MEM' . date('Y') . str_pad(mt_rand(1, 999999), 6, '0', STR_PAD_LEFT);
}
}
if (!function_exists('generateTrackingCode')) {
function generateTrackingCode($type = 'member') {
$prefix = $type === 'member' ? 'MEM' : 'USR';
return $prefix . date('Y') . str_pad(mt_rand(1, 999999), 6, '0', STR_PAD_LEFT);
}
}
if (!function_exists('generateBarcode')) {
function generateBarcode($code) {
$width = 200;
$height = 50;
$image = imagecreate($width, $height);
$white = imagecolorallocate($image, 255, 255, 255);
$black = imagecolorallocate($image, 0, 0, 0);
imagefill($image, 0, 0, $white);
$codeLength = strlen($code);
$x = 10;
for ($i = 0; $i < $codeLength; $i++) {
$char = ord($code[$i]);
$pattern = $char % 4;
for ($j = 0; $j < 3; $j++) {
$barWidth = ($pattern & (1 << $j)) ? 3 : 1;
imagefilledrectangle($image, $x, 5, $x + $barWidth, 35, $black);
$x += $barWidth + 1;
}
}
imagestring($image, 2, 10, 37, substr($code, 0, 20), $black);
ob_start();
imagepng($image);
$imageData = ob_get_contents();
ob_end_clean();
imagedestroy($image);
return "data:image/png;base64," . base64_encode($imageData);
}
}
if (!function_exists('generateQRCode')) {
function generateQRCode($code) {
$size = 100;
$image = imagecreate($size, $size);
$white = imagecolorallocate($image, 255, 255, 255);
$black = imagecolorallocate($image, 0, 0, 0);
imagefill($image, 0, 0, $white);
$codeHash = md5($code);
$blockSize = 5;
imagefilledrectangle($image, 5, 5, 30, 30, $black);
imagefilledrectangle($image, 70, 5, 95, 30, $black);
imagefilledrectangle($image, 5, 70, 30, 95, $black);
imagefilledrectangle($image, 10, 10, 25, 25, $white);
imagefilledrectangle($image, 75, 10, 90, 25, $white);
imagefilledrectangle($image, 10, 75, 25, 90, $white);
imagefilledrectangle($image, 15, 15, 20, 20, $black);
imagefilledrectangle($image, 80, 15, 85, 20, $black);
imagefilledrectangle($image, 15, 80, 20, 85, $black);
for ($i = 0; $i < 32; $i++) {
$hexChar = hexdec($codeHash[$i]);
for ($bit = 0; $bit < 4; $bit++) {
if ($hexChar & (1 << $bit)) {
$x = 35 + (($i * 2 + $bit) % 6) * $blockSize;
$y = 35 + floor(($i * 2 + $bit) / 6) * $blockSize;
if ($x < 95 && $y < 95) {
imagefilledrectangle($image, $x, $y, $x + $blockSize - 1, $y + $blockSize - 1, $black);
}
}
}
}
ob_start();
imagepng($image);
$imageData = ob_get_contents();
ob_end_clean();
imagedestroy($image);
return "data:image/png;base64," . base64_encode($imageData);
}
}
if (!function_exists('createMemberUserCode')) {
function createMemberUserCode(PDO $db, $memberId, $createdBy) {
try {
// Ensure we have a valid numeric member ID
$memberId = (int)$memberId;
if ($memberId <= 0) {
return;
}
// If a code already exists for this member, do not create another
$existingStmt = $db->prepare("SELECT id FROM memberuser_codes WHERE member_id = :member_id LIMIT 1");
$existingStmt->execute(['member_id' => $memberId]);
if ($existingStmt->fetch()) {
return;
}
// Generate unique tracking code
do {
$trackingCode = generateTrackingCode('member');
$checkStmt = $db->prepare("SELECT id FROM memberuser_codes WHERE tracking_code = :code");
$checkStmt->execute(['code' => $trackingCode]);
} while ($checkStmt->fetch());
$code = 'MC' . date('Ymd') . str_pad($memberId, 4, '0', STR_PAD_LEFT) . mt_rand(100, 999);
$barcode = generateBarcode($trackingCode);
$qrcode = generateQRCode($trackingCode);
// Get default Attendance event if available
$eventStmt = $db->prepare("SELECT id FROM events WHERE name = 'Attendance' AND is_active = 1 LIMIT 1");
$eventStmt->execute();
$defaultEvent = $eventStmt->fetch();
$eventId = $defaultEvent ? $defaultEvent['id'] : null;
// Insert member tracking code
$codeStmt = $db->prepare("
INSERT INTO memberuser_codes (
code, description, member_id, event_id, code_type, tracking_code,
barcode, qrcode, created_by, is_active
) VALUES (
:code, :description, :member_id, :event_id, 'member', :tracking_code,
:barcode, :qrcode, :created_by, 1
)
");
$codeStmt->execute([
'code' => $code,
'description' => 'Auto-generated member tracking code',
'member_id' => $memberId,
'event_id' => $eventId,
'tracking_code' => $trackingCode,
'barcode' => $barcode,
'qrcode' => $qrcode,
'created_by' => $createdBy
]);
} catch (Exception $e) {
// Do not break the upload if code generation fails
}
}
}
// Handle Reference IDs PDF export
if (isset($_GET['action']) && $_GET['action'] == 'export_reference_ids') {
require_once '../../vendor/autoload.php';
// Get all areas, districts, assemblies
$areasStmt = $db->query("SELECT id, area_name, area_code FROM areas WHERE is_active = 1 ORDER BY area_name");
$allAreas = $areasStmt->fetchAll(PDO::FETCH_ASSOC);
$districtsStmt = $db->query("SELECT d.id, d.district_name, d.district_code, a.area_name FROM districts d JOIN areas a ON d.area_id = a.id ORDER BY a.area_name, d.district_name");
$allDistricts = $districtsStmt->fetchAll(PDO::FETCH_ASSOC);
$assembliesStmt = $db->query("SELECT asm.id, asm.assembly_name, asm.assembly_code, d.district_name, a.area_name FROM assemblies asm JOIN districts d ON asm.district_id = d.id JOIN areas a ON asm.area_id = a.id ORDER BY a.area_name, d.district_name, asm.assembly_name");
$allAssemblies = $assembliesStmt->fetchAll(PDO::FETCH_ASSOC);
// Create PDF
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor(APP_NAME);
$pdf->SetTitle('Location Reference IDs');
$pdf->SetSubject('Complete list of Area, District, and Assembly IDs');
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->SetMargins(15, 15, 15);
$pdf->SetAutoPageBreak(TRUE, 15);
$pdf->AddPage();
// Title
$pdf->SetFont('helvetica', 'B', 16);
$pdf->Cell(0, 10, APP_NAME, 0, 1, 'C');
$pdf->SetFont('helvetica', 'B', 14);
$pdf->Cell(0, 10, 'Location Reference IDs', 0, 1, 'C');
$pdf->SetFont('helvetica', '', 10);
$pdf->Cell(0, 5, 'For Member Upload - Generated: ' . date('F d, Y - h:i A'), 0, 1, 'C');
$pdf->Ln(5);
// Areas Section
$pdf->SetFont('helvetica', 'B', 12);
$pdf->SetFillColor(30, 64, 175);
$pdf->SetTextColor(255, 255, 255);
$pdf->Cell(0, 8, 'AREAS (' . count($allAreas) . ')', 1, 1, 'L', true);
$pdf->SetFont('helvetica', 'B', 9);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetTextColor(0, 0, 0);
$pdf->Cell(20, 6, 'ID', 1, 0, 'C', true);
$pdf->Cell(35, 6, 'Area Code', 1, 0, 'C', true);
$pdf->Cell(125, 6, 'Area Name', 1, 1, 'L', true);
$pdf->SetFont('helvetica', '', 8);
foreach ($allAreas as $area) {
$pdf->Cell(20, 5, $area['id'], 1, 0, 'C');
$pdf->Cell(35, 5, $area['area_code'] ?? '-', 1, 0, 'C');
$pdf->Cell(125, 5, substr($area['area_name'], 0, 60), 1, 1, 'L');
}
$pdf->Ln(5);
// Districts Section
$pdf->SetFont('helvetica', 'B', 12);
$pdf->SetFillColor(247, 147, 22);
$pdf->SetTextColor(255, 255, 255);
$pdf->Cell(0, 8, 'DISTRICTS (' . count($allDistricts) . ')', 1, 1, 'L', true);
$pdf->SetFont('helvetica', 'B', 9);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetTextColor(0, 0, 0);
$pdf->Cell(20, 6, 'ID', 1, 0, 'C', true);
$pdf->Cell(35, 6, 'District Code', 1, 0, 'C', true);
$pdf->Cell(65, 6, 'District Name', 1, 0, 'L', true);
$pdf->Cell(60, 6, 'Area', 1, 1, 'L', true);
$pdf->SetFont('helvetica', '', 8);
foreach ($allDistricts as $district) {
$pdf->Cell(20, 5, $district['id'], 1, 0, 'C');
$pdf->Cell(35, 5, $district['district_code'] ?? '-', 1, 0, 'C');
$pdf->Cell(65, 5, substr($district['district_name'], 0, 35), 1, 0, 'L');
$pdf->Cell(60, 5, substr($district['area_name'], 0, 30), 1, 1, 'L');
}
$pdf->Ln(5);
// Assemblies Section
$pdf->SetFont('helvetica', 'B', 12);
$pdf->SetFillColor(147, 51, 234);
$pdf->SetTextColor(255, 255, 255);
$pdf->Cell(0, 8, 'ASSEMBLIES (' . count($allAssemblies) . ')', 1, 1, 'L', true);
$pdf->SetFont('helvetica', 'B', 9);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetTextColor(0, 0, 0);
$pdf->Cell(20, 6, 'ID', 1, 0, 'C', true);
$pdf->Cell(35, 6, 'Assembly Code', 1, 0, 'C', true);
$pdf->Cell(60, 6, 'Assembly Name', 1, 0, 'L', true);
$pdf->Cell(40, 6, 'District', 1, 0, 'L', true);
$pdf->Cell(25, 6, 'Area', 1, 1, 'L', true);
$pdf->SetFont('helvetica', '', 7);
foreach ($allAssemblies as $assembly) {
$pdf->Cell(20, 5, $assembly['id'], 1, 0, 'C');
$pdf->Cell(35, 5, $assembly['assembly_code'] ?? '-', 1, 0, 'C');
$pdf->Cell(60, 5, substr($assembly['assembly_name'], 0, 35), 1, 0, 'L');
$pdf->Cell(40, 5, substr($assembly['district_name'], 0, 22), 1, 0, 'L');
$pdf->Cell(25, 5, substr($assembly['area_name'], 0, 15), 1, 1, 'L');
}
$filename = 'reference_ids_' . date('Ymd_His') . '.pdf';
$pdf->Output($filename, 'D');
exit;
}
// Handle PDF export
if (isset($_GET['action']) && $_GET['action'] == 'export_pdf' && isset($_SESSION['upload_results'])) {
require_once '../../vendor/autoload.php';
$uploadResults = $_SESSION['upload_results'];
$uploadDate = $_SESSION['upload_date'] ?? date('Y-m-d H:i:s');
// Create new PDF document
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
// Set document information
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor(APP_NAME);
$pdf->SetTitle('Member Upload Results');
$pdf->SetSubject('Member Upload Results with Credentials');
// Remove default header/footer
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
// Set margins
$pdf->SetMargins(15, 15, 15);
$pdf->SetAutoPageBreak(TRUE, 15);
// Add a page
$pdf->AddPage();
// Set font
$pdf->SetFont('helvetica', 'B', 16);
// Title
$pdf->Cell(0, 10, APP_NAME, 0, 1, 'C');
$pdf->SetFont('helvetica', 'B', 14);
$pdf->Cell(0, 10, 'Member Upload Results', 0, 1, 'C');
$pdf->SetFont('helvetica', '', 10);
$pdf->Cell(0, 5, 'Upload Date: ' . date('F d, Y - h:i A', strtotime($uploadDate)), 0, 1, 'C');
$pdf->Ln(5);
// Summary
$successCount = count(array_filter($uploadResults, fn($r) => $r['status'] == 'success'));
$warningCount = count(array_filter($uploadResults, fn($r) => $r['status'] == 'warning'));
$errorCount = count(array_filter($uploadResults, fn($r) => $r['status'] == 'error'));
$pdf->SetFont('helvetica', 'B', 11);
$pdf->Cell(0, 7, 'Summary:', 0, 1);
$pdf->SetFont('helvetica', '', 10);
$pdf->Cell(0, 5, 'Total Records: ' . count($uploadResults), 0, 1);
$pdf->SetTextColor(0, 128, 0);
$pdf->Cell(0, 5, 'Successful: ' . $successCount, 0, 1);
$pdf->SetTextColor(255, 140, 0);
$pdf->Cell(0, 5, 'Warnings: ' . $warningCount, 0, 1);
$pdf->SetTextColor(255, 0, 0);
$pdf->Cell(0, 5, 'Errors: ' . $errorCount, 0, 1);
$pdf->SetTextColor(0, 0, 0);
$pdf->Ln(5);
// Table header
$pdf->SetFont('helvetica', 'B', 9);
$pdf->SetFillColor(30, 64, 175); // Blue color
$pdf->SetTextColor(255, 255, 255);
$pdf->Cell(15, 7, 'Row', 1, 0, 'C', true);
$pdf->Cell(40, 7, 'Member Name', 1, 0, 'C', true);
$pdf->Cell(20, 7, 'Status', 1, 0, 'C', true);
$pdf->Cell(30, 7, 'Card Number', 1, 0, 'C', true);
$pdf->Cell(75, 7, 'Message/Credentials', 1, 1, 'C', true);
// Table content
$pdf->SetFont('helvetica', '', 8);
$pdf->SetTextColor(0, 0, 0);
foreach ($uploadResults as $result) {
// Set fill color based on status
if ($result['status'] == 'success') {
$pdf->SetFillColor(240, 253, 244); // Light green
} elseif ($result['status'] == 'warning') {
$pdf->SetFillColor(254, 252, 232); // Light yellow
} else {
$pdf->SetFillColor(254, 242, 242); // Light red
}
$pdf->Cell(15, 6, $result['row'], 1, 0, 'C', true);
$pdf->Cell(40, 6, substr($result['name'], 0, 25), 1, 0, 'L', true);
$pdf->Cell(20, 6, ucfirst($result['status']), 1, 0, 'C', true);
$pdf->Cell(30, 6, $result['card_number'] ?? '-', 1, 0, 'C', true);
// Message might be long, use MultiCell equivalent
$message = $result['message'] ?? '-';
$pdf->Cell(75, 6, substr($message, 0, 50) . (strlen($message) > 50 ? '...' : ''), 1, 1, 'L', true);
}
// Output PDF
$filename = 'member_upload_results_' . date('Ymd_His') . '.pdf';
$pdf->Output($filename, 'D');
exit;
}
// Handle template download
if (isset($_GET['action']) && in_array($_GET['action'], ['download_template', 'download_excel'])) {
$isExcel = $_GET['action'] == 'download_excel';
if ($isExcel) {
// Excel template
require_once '../../vendor/autoload.php';
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Set headers with styling
$headers = [
'title', 'first_name', 'middle_name', 'last_name', 'gender', 'date_of_birth',
'place_of_birth', 'phone', 'email', 'member_type', 'marital_status',
'address_line1', 'gps_address', 'street_name', 'city', 'hometown',
'area_id', 'district_id', 'assembly_id', 'family_id',
'water_baptism', 'date_of_baptism', 'place_of_baptism', 'officiating_minister_baptism',
'officiating_ministers_district', 'holyghost_baptism', 'date_of_holyspirit_baptism',
'communicant', 'dedicated', 'dedication_date', 'name_of_officiating_minister',
'church_where_dedication_done', 'date_of_conversion', 'date_of_joining',
'occupation', 'level_of_education', 'parent_name', 'parent_relationship'
];
// Write headers
$col = 'A';
foreach ($headers as $header) {
$sheet->setCellValue($col . '1', $header);
$sheet->getStyle($col . '1')->getFont()->setBold(true);
$sheet->getStyle($col . '1')->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
->getStartColor()->setRGB('1E40AF');
$sheet->getStyle($col . '1')->getFont()->getColor()->setRGB('FFFFFF');
$col++;
}
// Add sample row
$sampleRow = [
'Mr', 'John', 'Paul', 'Doe', 'Male', '1990-01-15',
'Accra', '0244123456', 'john.doe@email.com', 'Full Member', 'Single',
'P.O. Box 123', 'GA-123-4567', 'Main Street', 'Accra', 'Cape Coast',
'1', '1', '1', '',
'1', '2020-01-15', 'Church Name', 'Minister Name',
'District', '1', '2020-02-15',
'1', '1', '2019-12-25', 'Minister Name',
'Church Name', '2019-01-01', '2019-06-15',
'Teacher', 'Tertiary', 'Parent Name', 'Father'
];
$col = 'A';
foreach ($sampleRow as $value) {
$sheet->setCellValue($col . '2', $value);
$sheet->getStyle($col . '2')->getFont()->setItalic(true);
$col++;
}
// Auto-size columns
foreach (range('A', $col) as $columnID) {
$sheet->getColumnDimension($columnID)->setAutoSize(true);
}
// Output Excel file
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="members_upload_template.xlsx"');
header('Cache-Control: max-age=0');
$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx');
$writer->save('php://output');
exit;
} else {
// CSV template
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=members_upload_template.csv');
$output = fopen('php://output', 'w');
// CSV Headers - matching members table structure
$headers = [
'title', 'first_name', 'middle_name', 'last_name', 'gender', 'date_of_birth',
'place_of_birth', 'phone', 'email', 'member_type', 'marital_status',
'address_line1', 'gps_address', 'street_name', 'city', 'hometown',
'area_id', 'district_id', 'assembly_id', 'family_id',
'water_baptism', 'date_of_baptism', 'place_of_baptism', 'officiating_minister_baptism',
'officiating_ministers_district', 'holyghost_baptism', 'date_of_holyspirit_baptism',
'communicant', 'dedicated', 'dedication_date', 'name_of_officiating_minister',
'church_where_dedication_done', 'date_of_conversion', 'date_of_joining',
'occupation', 'level_of_education', 'parent_name', 'parent_relationship'
];
fputcsv($output, $headers);
// Add sample row with instructions
$sampleRow = [
'Mr/Mrs/Miss/Dr/Rev/Pastor/Deacon/Deaconess/Elder/Evangelist/Prophet/Apostle',
'John', 'Paul', 'Doe', 'Male/Female', '1990-01-15',
'Accra', '0244123456', 'john.doe@email.com (leave empty to auto-generate)', 'Full Member/Associate Member/Youth/Children', 'Single/Married/Divorced/Widowed',
'P.O. Box 123', 'GA-123-4567', 'Main Street', 'Accra', 'Cape Coast',
'1 (Area ID)', '1 (District ID)', '1 (Assembly ID)', 'Optional',
'1/0 (Yes/No)', '2020-01-15', 'Church Name', 'Minister Name',
'District', '1/0', '2020-02-15',
'1/0', '1/0', '2019-12-25', 'Minister Name',
'Church Name', '2019-01-01', '2019-06-15',
'Teacher', 'Tertiary', 'Parent Name', 'Father/Mother/Guardian'
];
fputcsv($output, $sampleRow);
fclose($output);
exit;
}
}
// Handle file upload
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['member_file'])) {
try {
$file = $_FILES['member_file'];
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new Exception('File upload error');
}
$fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($fileExt, ['csv', 'xlsx', 'xls'])) {
throw new Exception('Please upload a CSV or Excel file');
}
// Process file based on type
if ($fileExt == 'csv') {
// Process CSV file
$handle = fopen($file['tmp_name'], 'r');
if ($handle === false) {
throw new Exception('Could not open file');
}
// Get headers
$headers = fgetcsv($handle);
if ($headers === false) {
throw new Exception('Could not read file headers');
}
$successCount = 0;
$errorCount = 0;
$row = 1;
// Start transaction
$db->beginTransaction();
// Membership card ID generator for member IDs
$memberIdCardGenerator = new MembershipCard();
try {
while (($data = fgetcsv($handle)) !== false) {
$row++;
// Skip completely empty rows
$isEmptyRow = true;
foreach ($data as $value) {
if (trim((string)$value) !== '') {
$isEmptyRow = false;
break;
}
}
if ($isEmptyRow) {
continue;
}
// Skip sample/template row based on title cell content
if (isset($data[0]) && stripos((string)$data[0], 'Mr/Mrs') !== false) {
continue;
}
// Map CSV data to array
$memberData = array_combine($headers, $data);
// Generate email if empty
if (empty($memberData['email']) || stripos($memberData['email'], 'leave empty') !== false) {
$firstName = strtolower(trim($memberData['first_name']));
$lastName = strtolower(trim($memberData['last_name']));
$memberData['email'] = $firstName . '.' . $lastName . '@church.auto';
}
// Use provided area/district/assembly or user's access level.
// Be flexible with values like "1 (Area ID)" by extracting the numeric portion.
$memberAreaId = null;
if (!empty($memberData['area_id'])) {
if (is_numeric($memberData['area_id'])) {
$memberAreaId = (int)$memberData['area_id'];
} elseif (preg_match('/(\d+)/', $memberData['area_id'], $m)) {
$memberAreaId = (int)$m[1];
}
} elseif (!empty($areaId)) {
$memberAreaId = $areaId;
}
$memberDistrictId = null;
if (!empty($memberData['district_id'])) {
if (is_numeric($memberData['district_id'])) {
$memberDistrictId = (int)$memberData['district_id'];
} elseif (preg_match('/(\d+)/', $memberData['district_id'], $m)) {
$memberDistrictId = (int)$m[1];
}
} elseif (!empty($districtId)) {
$memberDistrictId = $districtId;
}
$memberAssemblyId = null;
if (!empty($memberData['assembly_id'])) {
if (is_numeric($memberData['assembly_id'])) {
$memberAssemblyId = (int)$memberData['assembly_id'];
} elseif (preg_match('/(\d+)/', $memberData['assembly_id'], $m)) {
$memberAssemblyId = (int)$m[1];
}
} elseif (!empty($assemblyId)) {
$memberAssemblyId = $assemblyId;
}
if ($memberAreaId === null || $memberDistrictId === null || $memberAssemblyId === null) {
throw new Exception("Row {$row}: Missing or invalid Area/District/Assembly ID. Please provide numeric IDs in the file or set your default location.");
}
// Generate unique membershipcard_id (member ID) for this member
do {
$membershipCardId = $memberIdCardGenerator->generateCardNumber();
$checkStmt = $db->prepare("SELECT id FROM members WHERE membershipcard_id = :membershipcard_id");
$checkStmt->execute(['membershipcard_id' => $membershipCardId]);
} while ($checkStmt->fetch());
// Generate unique membership_id (member ID) for this member
do {
$membershipId = generateMembershipId();
$checkMidStmt = $db->prepare("SELECT id FROM members WHERE membership_id = :membership_id");
$checkMidStmt->execute(['membership_id' => $membershipId]);
} while ($checkMidStmt->fetch());
// Prepare insert statement including membershipcard_id and membership_id
$stmt = $db->prepare("
INSERT INTO members (
area_id, district_id, assembly_id, membershipcard_id, membership_id, family_id, title,
first_name, middle_name, last_name, gender, date_of_birth,
place_of_birth, phone, email, member_type, marital_status,
address_line1, gps_address, street_name, city, hometown,
water_baptism, date_of_baptism, place_of_baptism, officiating_minister_baptism,
officiating_ministers_district, holyghost_baptism, date_of_holyspirit_baptism,
communicant, dedicated, dedication_date, name_of_officiating_minister,
church_where_dedication_done, date_of_conversion, date_of_joining,
occupation, level_of_education, parent_name, parent_relationship,
created_by, is_active, created_at
) VALUES (
:area_id, :district_id, :assembly_id, :membershipcard_id, :membership_id, :family_id, :title,
:first_name, :middle_name, :last_name, :gender, :date_of_birth,
:place_of_birth, :phone, :email, :member_type, :marital_status,
:address_line1, :gps_address, :street_name, :city, :hometown,
:water_baptism, :date_of_baptism, :place_of_baptism, :officiating_minister_baptism,
:officiating_ministers_district, :holyghost_baptism, :date_of_holyspirit_baptism,
:communicant, :dedicated, :dedication_date, :name_of_officiating_minister,
:church_where_dedication_done, :date_of_conversion, :date_of_joining,
:occupation, :level_of_education, :parent_name, :parent_relationship,
:created_by, 1, NOW()
)
");
// Execute with data
$stmt->execute([
'area_id' => $memberAreaId,
'district_id' => $memberDistrictId,
'assembly_id' => $memberAssemblyId,
'membershipcard_id' => $membershipCardId,
'membership_id' => $membershipId,
'family_id' => !empty($memberData['family_id']) ? $memberData['family_id'] : null,
'title' => $memberData['title'] ?? null,
'first_name' => $memberData['first_name'],
'middle_name' => $memberData['middle_name'] ?? null,
'last_name' => $memberData['last_name'],
'gender' => $memberData['gender'],
'date_of_birth' => !empty($memberData['date_of_birth']) ? $memberData['date_of_birth'] : null,
'place_of_birth' => $memberData['place_of_birth'] ?? null,
'phone' => $memberData['phone'] ?? null,
'email' => $memberData['email'],
'member_type' => $memberData['member_type'] ?? 'Full Member',
'marital_status' => $memberData['marital_status'] ?? null,
'address_line1' => $memberData['address_line1'] ?? null,
'gps_address' => $memberData['gps_address'] ?? null,
'street_name' => $memberData['street_name'] ?? null,
'city' => $memberData['city'] ?? null,
'hometown' => $memberData['hometown'] ?? null,
'water_baptism' => ($memberData['water_baptism'] ?? '0') == '1' ? 1 : 0,
'date_of_baptism' => !empty($memberData['date_of_baptism']) ? $memberData['date_of_baptism'] : null,
'place_of_baptism' => $memberData['place_of_baptism'] ?? null,
'officiating_minister_baptism' => $memberData['officiating_minister_baptism'] ?? null,
'officiating_ministers_district' => $memberData['officiating_ministers_district'] ?? null,
'holyghost_baptism' => ($memberData['holyghost_baptism'] ?? '0') == '1' ? 1 : 0,
'date_of_holyspirit_baptism' => !empty($memberData['date_of_holyspirit_baptism']) ? $memberData['date_of_holyspirit_baptism'] : null,
'communicant' => ($memberData['communicant'] ?? '0') == '1' ? 1 : 0,
'dedicated' => ($memberData['dedicated'] ?? '0') == '1' ? 1 : 0,
'dedication_date' => !empty($memberData['dedication_date']) ? $memberData['dedication_date'] : null,
'name_of_officiating_minister' => $memberData['name_of_officiating_minister'] ?? null,
'church_where_dedication_done' => $memberData['church_where_dedication_done'] ?? null,
'date_of_conversion' => !empty($memberData['date_of_conversion']) ? $memberData['date_of_conversion'] : null,
'date_of_joining' => !empty($memberData['date_of_joining']) ? $memberData['date_of_joining'] : null,
'occupation' => $memberData['occupation'] ?? null,
'level_of_education' => $memberData['level_of_education'] ?? null,
'parent_name' => $memberData['parent_name'] ?? null,
'parent_relationship' => $memberData['parent_relationship'] ?? null,
'created_by' => $_SESSION['user_id']
]);
$newMemberId = $db->lastInsertId();
// Auto-generate tracking code for uploaded member (same as add member page)
createMemberUserCode($db, $newMemberId, $_SESSION['user_id']);
// Generate membership card
try {
$membershipCard = new MembershipCard();
$cardNumber = $membershipCard->generateCardNumber();
$membershipCard->createCard($newMemberId);
// Create member account
try {
$username = strtolower($memberData['first_name']) . '.' . strtolower($memberData['last_name']);
$defaultPassword = 'Member@' . date('Y');
$passwordHash = password_hash($defaultPassword, PASSWORD_DEFAULT);
$fullName = trim(($memberData['title'] ?? '') . ' ' . $memberData['first_name'] . ' ' . ($memberData['middle_name'] ?? '') . ' ' . $memberData['last_name']);
$accountStmt = $db->prepare("
INSERT INTO member_accounts (
member_id, username, email, password_hash, full_name, phone,
access_level, area_id, district_id, assembly_id, is_active
) VALUES (
:member_id, :username, :email, :password_hash, :full_name, :phone,
'member', :area_id, :district_id, :assembly_id, 1
)
");
$accountStmt->execute([
'member_id' => $newMemberId,
'username' => $username,
'email' => $memberData['email'],
'password_hash' => $passwordHash,
'full_name' => $fullName,
'phone' => $memberData['phone'] ?? null,
'area_id' => $memberAreaId,
'district_id' => $memberDistrictId,
'assembly_id' => $memberAssemblyId
]);
$uploadResults[] = [
'row' => $row,
'name' => $memberData['first_name'] . ' ' . $memberData['last_name'],
'status' => 'success',
'card_number' => $cardNumber,
'message' => 'Member, Card & Account created. Password: ' . $defaultPassword
];
} catch (Exception $accountError) {
$uploadResults[] = [
'row' => $row,
'name' => $memberData['first_name'] . ' ' . $memberData['last_name'],
'status' => 'warning',
'card_number' => $cardNumber,
'message' => 'Member & Card created, but account creation failed: ' . $accountError->getMessage()
];
}
$successCount++;
} catch (Exception $cardError) {
$uploadResults[] = [
'row' => $row,
'name' => $memberData['first_name'] . ' ' . $memberData['last_name'],
'status' => 'warning',
'message' => 'Member created but card generation failed: ' . $cardError->getMessage()
];
$successCount++;
}
}
// Commit transaction
$db->commit();
// Store upload results in session for PDF export
$_SESSION['upload_results'] = $uploadResults;
$_SESSION['upload_date'] = date('Y-m-d H:i:s');
$successMessage = "Successfully uploaded {$successCount} members with membership cards and accounts!";
if ($errorCount > 0) {
$errorMessage = "{$errorCount} rows had errors and were skipped.";
}
} catch (Exception $e) {
$db->rollBack();
throw $e;
}
fclose($handle);
} elseif (in_array($fileExt, ['xlsx', 'xls'])) {
// Process Excel file
require_once '../../vendor/autoload.php';
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file['tmp_name']);
$sheet = $spreadsheet->getActiveSheet();
$rows = $sheet->toArray();
if (empty($rows)) {
throw new Exception('Excel file is empty');
}
// Get headers (first row)
$headers = array_shift($rows);
$successCount = 0;
$errorCount = 0;
$rowNum = 1;
// Start transaction
$db->beginTransaction();
// Membership card ID generator for member IDs (Excel)
$memberIdCardGeneratorExcel = new MembershipCard();
try {
foreach ($rows as $data) {
$rowNum++;
// Skip completely empty rows
$isEmptyRow = true;
foreach ($data as $value) {
if (trim((string)$value) !== '') {
$isEmptyRow = false;
break;
}
}
if ($isEmptyRow) {
continue;
}
// Skip sample/template row based on title cell content
if (isset($data[0]) && (stripos((string)$data[0], 'Mr/Mrs') !== false || $data[0] == 'Mr')) {
continue;
}
// Map Excel data to array
$memberData = array_combine($headers, $data);
// Process member data (same as CSV processing)
if (empty($memberData['email']) || stripos($memberData['email'], 'leave empty') !== false || stripos($memberData['email'], '@email.com') !== false) {
$firstName = strtolower(trim($memberData['first_name']));
$lastName = strtolower(trim($memberData['last_name']));
$memberData['email'] = $firstName . '.' . $lastName . '@church.auto';
}
// Use provided area/district/assembly or user's access level.
// Be flexible with values like "1 (Area ID)" by extracting the numeric portion.
$memberAreaId = null;
if (!empty($memberData['area_id'])) {
if (is_numeric($memberData['area_id'])) {
$memberAreaId = (int)$memberData['area_id'];
} elseif (preg_match('/(\d+)/', $memberData['area_id'], $m)) {
$memberAreaId = (int)$m[1];
}
} elseif (!empty($areaId)) {
$memberAreaId = $areaId;
}
$memberDistrictId = null;
if (!empty($memberData['district_id'])) {
if (is_numeric($memberData['district_id'])) {
$memberDistrictId = (int)$memberData['district_id'];
} elseif (preg_match('/(\d+)/', $memberData['district_id'], $m)) {
$memberDistrictId = (int)$m[1];
}
} elseif (!empty($districtId)) {
$memberDistrictId = $districtId;
}
$memberAssemblyId = null;
if (!empty($memberData['assembly_id'])) {
if (is_numeric($memberData['assembly_id'])) {
$memberAssemblyId = (int)$memberData['assembly_id'];
} elseif (preg_match('/(\d+)/', $memberData['assembly_id'], $m)) {
$memberAssemblyId = (int)$m[1];
}
} elseif (!empty($assemblyId)) {
$memberAssemblyId = $assemblyId;
}
if ($memberAreaId === null || $memberDistrictId === null || $memberAssemblyId === null) {
throw new Exception("Row {$rowNum}: Missing or invalid Area/District/Assembly ID. Please provide numeric IDs in the file or set your default location.");
}
// Generate unique membershipcard_id (card ID) for this member (Excel)
do {
$membershipCardIdExcel = $memberIdCardGeneratorExcel->generateCardNumber();
$checkStmt = $db->prepare("SELECT id FROM members WHERE membershipcard_id = :membershipcard_id");
$checkStmt->execute(['membershipcard_id' => $membershipCardIdExcel]);
} while ($checkStmt->fetch());
// Generate unique membership_id (member ID) for this member (Excel)
do {
$membershipIdExcel = generateMembershipId();
$checkMidStmt = $db->prepare("SELECT id FROM members WHERE membership_id = :membership_id");
$checkMidStmt->execute(['membership_id' => $membershipIdExcel]);
} while ($checkMidStmt->fetch());
// Insert member (same query as CSV, including membershipcard_id)
$stmt = $db->prepare("
INSERT INTO members (
area_id, district_id, assembly_id, membershipcard_id, membership_id, family_id, title,
first_name, middle_name, last_name, gender, date_of_birth,
place_of_birth, phone, email, member_type, marital_status,
address_line1, gps_address, street_name, city, hometown,
water_baptism, date_of_baptism, place_of_baptism, officiating_minister_baptism,
officiating_ministers_district, holyghost_baptism, date_of_holyspirit_baptism,
communicant, dedicated, dedication_date, name_of_officiating_minister,
church_where_dedication_done, date_of_conversion, date_of_joining,
occupation, level_of_education, parent_name, parent_relationship,
created_by, is_active, created_at
) VALUES (
:area_id, :district_id, :assembly_id, :membershipcard_id, :membership_id, :family_id, :title,
:first_name, :middle_name, :last_name, :gender, :date_of_birth,
:place_of_birth, :phone, :email, :member_type, :marital_status,
:address_line1, :gps_address, :street_name, :city, :hometown,
:water_baptism, :date_of_baptism, :place_of_baptism, :officiating_minister_baptism,
:officiating_ministers_district, :holyghost_baptism, :date_of_holyspirit_baptism,
:communicant, :dedicated, :dedication_date, :name_of_officiating_minister,
:church_where_dedication_done, :date_of_conversion, :date_of_joining,
:occupation, :level_of_education, :parent_name, :parent_relationship,
:created_by, 1, NOW()
)
");
$stmt->execute([
'area_id' => $memberAreaId,
'district_id' => $memberDistrictId,
'assembly_id' => $memberAssemblyId,
'membershipcard_id' => $membershipCardIdExcel,
'family_id' => !empty($memberData['family_id']) ? $memberData['family_id'] : null,
'title' => $memberData['title'] ?? null,
'first_name' => $memberData['first_name'],
'middle_name' => $memberData['middle_name'] ?? null,
'last_name' => $memberData['last_name'],
'gender' => $memberData['gender'],
'date_of_birth' => !empty($memberData['date_of_birth']) ? $memberData['date_of_birth'] : null,
'place_of_birth' => $memberData['place_of_birth'] ?? null,
'phone' => $memberData['phone'] ?? null,
'email' => $memberData['email'],
'member_type' => $memberData['member_type'] ?? 'Full Member',
'marital_status' => $memberData['marital_status'] ?? null,
'address_line1' => $memberData['address_line1'] ?? null,
'gps_address' => $memberData['gps_address'] ?? null,
'street_name' => $memberData['street_name'] ?? null,
'city' => $memberData['city'] ?? null,
'hometown' => $memberData['hometown'] ?? null,
'water_baptism' => ($memberData['water_baptism'] ?? '0') == '1' ? 1 : 0,
'date_of_baptism' => !empty($memberData['date_of_baptism']) ? $memberData['date_of_baptism'] : null,
'place_of_baptism' => $memberData['place_of_baptism'] ?? null,
'officiating_minister_baptism' => $memberData['officiating_minister_baptism'] ?? null,
'officiating_ministers_district' => $memberData['officiating_ministers_district'] ?? null,
'holyghost_baptism' => ($memberData['holyghost_baptism'] ?? '0') == '1' ? 1 : 0,
'date_of_holyspirit_baptism' => !empty($memberData['date_of_holyspirit_baptism']) ? $memberData['date_of_holyspirit_baptism'] : null,
'communicant' => ($memberData['communicant'] ?? '0') == '1' ? 1 : 0,
'dedicated' => ($memberData['dedicated'] ?? '0') == '1' ? 1 : 0,
'dedication_date' => !empty($memberData['dedication_date']) ? $memberData['dedication_date'] : null,
'name_of_officiating_minister' => $memberData['name_of_officiating_minister'] ?? null,
'church_where_dedication_done' => $memberData['church_where_dedication_done'] ?? null,
'date_of_conversion' => !empty($memberData['date_of_conversion']) ? $memberData['date_of_conversion'] : null,
'date_of_joining' => !empty($memberData['date_of_joining']) ? $memberData['date_of_joining'] : null,
'occupation' => $memberData['occupation'] ?? null,
'level_of_education' => $memberData['level_of_education'] ?? null,
'parent_name' => $memberData['parent_name'] ?? null,
'parent_relationship' => $memberData['parent_relationship'] ?? null,
'created_by' => $_SESSION['user_id']
]);
$newMemberId = $db->lastInsertId();
// Auto-generate member tracking code for uploaded member
createMemberUserCode($db, $newMemberId, $_SESSION['user_id']);
// Generate membership card
try {
$membershipCard = new MembershipCard();
$cardNumber = $membershipCard->generateCardNumber();
$membershipCard->createCard($newMemberId);
// Create member account
try {
$username = strtolower($memberData['first_name']) . '.' . strtolower($memberData['last_name']);
$defaultPassword = 'Member@' . date('Y');
$passwordHash = password_hash($defaultPassword, PASSWORD_DEFAULT);
$fullName = trim(($memberData['title'] ?? '') . ' ' . $memberData['first_name'] . ' ' . ($memberData['middle_name'] ?? '') . ' ' . $memberData['last_name']);
$accountStmt = $db->prepare("
INSERT INTO member_accounts (
member_id, username, email, password_hash, full_name, phone,
access_level, area_id, district_id, assembly_id, is_active
) VALUES (
:member_id, :username, :email, :password_hash, :full_name, :phone,
'member', :area_id, :district_id, :assembly_id, 1
)
");
$accountStmt->execute([
'member_id' => $newMemberId,
'username' => $username,
'email' => $memberData['email'],
'password_hash' => $passwordHash,
'full_name' => $fullName,
'phone' => $memberData['phone'] ?? null,
'area_id' => $memberAreaId,
'district_id' => $memberDistrictId,
'assembly_id' => $memberAssemblyId
]);
$uploadResults[] = [
'row' => $rowNum,
'name' => $memberData['first_name'] . ' ' . $memberData['last_name'],
'status' => 'success',
'card_number' => $cardNumber,
'message' => 'Member, Card & Account created. Password: ' . $defaultPassword
];
} catch (Exception $accountError) {
$uploadResults[] = [
'row' => $rowNum,
'name' => $memberData['first_name'] . ' ' . $memberData['last_name'],
'status' => 'warning',
'card_number' => $cardNumber,
'message' => 'Member & Card created, but account creation failed: ' . $accountError->getMessage()
];
}
$successCount++;
} catch (Exception $cardError) {
$uploadResults[] = [
'row' => $rowNum,
'name' => $memberData['first_name'] . ' ' . $memberData['last_name'],
'status' => 'warning',
'message' => 'Member created but card generation failed: ' . $cardError->getMessage()
];
$successCount++;
}
}
// Commit transaction
$db->commit();
// Store upload results in session for PDF export
$_SESSION['upload_results'] = $uploadResults;
$_SESSION['upload_date'] = date('Y-m-d H:i:s');
$successMessage = "Successfully uploaded {$successCount} members with membership cards and accounts!";
if ($errorCount > 0) {
$errorMessage = "{$errorCount} rows had errors and were skipped.";
}
} catch (Exception $e) {
$db->rollBack();
throw $e;
}
}
} catch (Exception $e) {
$errorMessage = "Upload failed: " . $e->getMessage();
}
}
// Get areas, districts, assemblies for reference
$areasStmt = $db->query("SELECT id, area_name FROM areas WHERE is_active = 1 ORDER BY area_name");
$areas = $areasStmt->fetchAll(PDO::FETCH_ASSOC);
$districtsStmt = $db->query("SELECT id, district_name, area_id FROM districts ORDER BY district_name");
$districts = $districtsStmt->fetchAll(PDO::FETCH_ASSOC);
$assembliesStmt = $db->query("SELECT id, assembly_name, district_id FROM assemblies ORDER BY assembly_name");
$assemblies = $assembliesStmt->fetchAll(PDO::FETCH_ASSOC);
include '../../includes/header.php';
?>
<?php include '../../includes/sidebar.php'; ?>
<!-- Main Content -->
<main class="flex-1 md:ml-64 mt-16 min-h-screen bg-gray-50">
<div class="container mx-auto px-4 py-8">
<!-- Page Header -->
<div class="gradient-bg rounded-2xl shadow-2xl p-8 mb-8 text-white">
<div class="flex items-center justify-between flex-wrap gap-4">
<div>
<h1 class="text-4xl font-bold mb-2">
<i class="fas fa-upload mr-3"></i>Upload Members
</h1>
<p class="text-white/90 text-lg">Bulk import members from CSV or Excel file</p>
</div>
<div class="flex items-center gap-3">
<a href="special_upload.php" class="px-6 py-3 bg-white/20 hover:bg-white/30 text-white rounded-full font-semibold transition backdrop-blur-sm">
<i class="fas fa-magic mr-2"></i>Special Upload
</a>
<a href="index.php" class="px-6 py-3 bg-white/20 hover:bg-white/30 text-white rounded-full font-semibold transition backdrop-blur-sm">
<i class="fas fa-arrow-left mr-2"></i>Back to Members
</a>
</div>
</div>
</div>
<!-- Success Message -->
<?php if ($successMessage): ?>
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-6 rounded-lg mb-6 animate-fadeIn">
<div class="flex items-center">
<i class="fas fa-check-circle text-3xl mr-4"></i>
<div>
<p class="font-bold text-lg"><?php echo $successMessage; ?></p>
<?php if (!empty($uploadResults)): ?>
<p class="text-sm mt-2">View details below for individual results</p>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<!-- Error Message -->
<?php if ($errorMessage): ?>
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-6 rounded-lg mb-6 animate-fadeIn">
<div class="flex items-center">
<i class="fas fa-exclamation-circle text-3xl mr-4"></i>
<p class="font-bold text-lg"><?php echo $errorMessage; ?></p>
</div>
</div>
<?php endif; ?>
<!-- Upload Results -->
<?php if (!empty($uploadResults)): ?>
<div class="bg-white rounded-2xl shadow-lg p-6 mb-8">
<div class="flex items-center justify-between mb-4">
<h3 class="text-2xl font-bold text-gray-800">
<i class="fas fa-list-check mr-2 text-gradient"></i>Upload Results
</h3>
<a href="?action=export_pdf" class="btn-gradient text-white px-6 py-3 rounded-lg font-bold hover:shadow-xl transition inline-flex items-center">
<i class="fas fa-file-pdf mr-2"></i>Export to PDF
</a>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700">Row</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700">Member Name</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700">Status</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700">Card Number</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700">Message</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php foreach ($uploadResults as $result): ?>
<tr>
<td class="px-4 py-3 text-sm"><?php echo $result['row']; ?></td>
<td class="px-4 py-3 text-sm font-medium"><?php echo htmlspecialchars($result['name']); ?></td>
<td class="px-4 py-3 text-sm">
<?php if ($result['status'] == 'success'): ?>
<span class="px-3 py-1 rounded-full text-xs font-semibold bg-green-100 text-green-800">
<i class="fas fa-check mr-1"></i>Success
</span>
<?php elseif ($result['status'] == 'warning'): ?>
<span class="px-3 py-1 rounded-full text-xs font-semibold bg-yellow-100 text-yellow-800">
<i class="fas fa-exclamation-triangle mr-1"></i>Warning
</span>
<?php else: ?>
<span class="px-3 py-1 rounded-full text-xs font-semibold bg-red-100 text-red-800">
<i class="fas fa-times mr-1"></i>Error
</span>
<?php endif; ?>
</td>
<td class="px-4 py-3 text-sm font-mono"><?php echo $result['card_number'] ?? '-'; ?></td>
<td class="px-4 py-3 text-sm text-gray-600"><?php echo $result['message'] ?? '-'; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<!-- Instructions & Upload Form -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Instructions -->
<div class="bg-white rounded-2xl shadow-lg p-8">
<h2 class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-info-circle mr-2 text-blue-600"></i>Instructions
</h2>
<div class="space-y-4 text-gray-700">
<div class="flex items-start">
<div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center font-bold text-white mr-3" style="background: linear-gradient(135deg, #1E40AF 0%, #9333EA 100%);">1</div>
<div>
<p class="font-semibold mb-1">Download Template</p>
<p class="text-sm">Click the button below to download the CSV template with all required fields.</p>
</div>
</div>
<div class="flex items-start">
<div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center font-bold text-white mr-3" style="background: linear-gradient(135deg, #F97316 0%, #FBBF24 100%);">2</div>
<div>
<p class="font-semibold mb-1">Fill Member Data</p>
<p class="text-sm">Open the file in Excel or Google Sheets and fill in the member information. Delete the sample row.</p>
</div>
</div>
<div class="flex items-start">
<div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center font-bold text-white mr-3" style="background: linear-gradient(135deg, #9333EA 0%, #F97316 100%);">3</div>
<div>
<p class="font-semibold mb-1">Upload File</p>
<p class="text-sm">Save as CSV and upload the file. The system will automatically generate membership IDs and cards.</p>
</div>
</div>
</div>
<div class="mt-8 p-4 bg-blue-50 border-l-4 border-blue-500 rounded">
<p class="text-sm font-semibold text-blue-800 mb-2"><i class="fas fa-lightbulb mr-2"></i>Important Notes:</p>
<ul class="text-sm text-blue-700 space-y-1 list-disc list-inside">
<li><strong>Email:</strong> Leave empty to auto-generate (firstname.lastname@church.auto)</li>
<li><strong>Membership Card:</strong> Automatically created for each member</li>
<li><strong>Member Account:</strong> Automatically created (Username: firstname.lastname, Password: Member@2025)</li>
<li><strong>File Format:</strong> Download CSV or Excel template (both supported for upload)</li>
<li><strong>Required fields:</strong> first_name, last_name, gender</li>
<li><strong>Dates:</strong> Use format YYYY-MM-DD (e.g., 2024-01-15)</li>
<li><strong>Boolean fields:</strong> Use 1 for Yes, 0 for No</li>
</ul>
</div>
<div class="mt-6 flex gap-4 flex-wrap">
<a href="?action=download_template" class="btn-gradient-orange text-white px-8 py-4 rounded-full font-bold inline-flex items-center shadow-lg hover:shadow-xl transition">
<i class="fas fa-file-csv mr-2"></i>Download CSV Template
</a>
<a href="?action=download_excel" class="btn-gradient text-white px-8 py-4 rounded-full font-bold inline-flex items-center shadow-lg hover:shadow-xl transition">
<i class="fas fa-file-excel mr-2"></i>Download Excel Template
</a>
</div>
</div>
<!-- Upload Form -->
<div class="bg-white rounded-2xl shadow-lg p-8">
<h2 class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-cloud-upload-alt mr-2 text-green-600"></i>Upload File
</h2>
<form method="POST" enctype="multipart/form-data" class="space-y-6">
<div>
<label class="block text-sm font-bold text-gray-700 mb-3">Select CSV or Excel File</label>
<div class="border-2 border-dashed border-gray-300 rounded-xl p-8 text-center hover:border-blue-500 transition">
<i class="fas fa-file-upload text-5xl text-gray-400 mb-4"></i>
<input type="file"
id="member_file_input"
name="member_file"
accept=".csv,.xlsx,.xls"
required
class="block w-full text-sm text-gray-500 file:mr-4 file:py-3 file:px-6 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 cursor-pointer">
<p class="text-sm text-gray-500 mt-2">CSV, XLSX or XLS (Max 10MB)</p>
</div>
</div>
<!-- File Preview -->
<div id="file_preview_container" class="hidden bg-gray-50 border border-gray-200 rounded-xl p-4">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-semibold text-gray-800 flex items-center">
<i class="fas fa-eye mr-2 text-blue-600"></i>File Preview (first 10 rows)
</h3>
<span id="file_preview_filename" class="text-xs text-gray-500"></span>
</div>
<div id="file_preview_warning" class="text-xs text-yellow-700 bg-yellow-50 border border-yellow-200 rounded px-3 py-2 mb-2 hidden">
Preview is optimized for CSV files. Excel files may not show detailed row preview in the browser.
</div>
<div class="overflow-x-auto max-h-64">
<table class="min-w-full text-xs">
<thead id="file_preview_head" class="bg-gray-100 text-gray-700"></thead>
<tbody id="file_preview_body" class="divide-y divide-gray-200"></tbody>
</table>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="font-semibold text-gray-800 mb-3">Your Default Location</h3>
<div class="text-sm text-gray-600 space-y-1">
<?php if ($accessLevel == 'superuser'): ?>
<p><i class="fas fa-info-circle mr-2"></i>You can upload members to any location by specifying IDs in the file</p>
<?php elseif ($accessLevel == 'area'): ?>
<p><i class="fas fa-map mr-2"></i>Area: <?php
$areaStmt = $db->prepare("SELECT area_name FROM areas WHERE id = ?");
$areaStmt->execute([$areaId]);
echo htmlspecialchars($areaStmt->fetchColumn());
?></p>
<?php elseif ($accessLevel == 'district'): ?>
<p><i class="fas fa-map-marked mr-2"></i>District: <?php
$districtStmt = $db->prepare("SELECT district_name FROM districts WHERE id = ?");
$districtStmt->execute([$districtId]);
echo htmlspecialchars($districtStmt->fetchColumn());
?></p>
<?php elseif ($accessLevel == 'assembly'): ?>
<p><i class="fas fa-church mr-2"></i>Assembly: <?php
$assemblyStmt = $db->prepare("SELECT assembly_name FROM assemblies WHERE id = ?");
$assemblyStmt->execute([$assemblyId]);
echo htmlspecialchars($assemblyStmt->fetchColumn());
?></p>
<?php endif; ?>
<p class="text-xs mt-2"><i class="fas fa-lightbulb mr-1"></i>Members without location IDs will be assigned to your default location</p>
</div>
</div>
<button type="submit" class="btn-gradient w-full text-white px-8 py-4 rounded-full font-bold text-lg shadow-xl hover:shadow-2xl transition">
<i class="fas fa-upload mr-2"></i>Upload Members
</button>
</form>
<!-- Reference Tables -->
<div class="mt-8 p-4 bg-yellow-50 border-l-4 border-yellow-500 rounded">
<div class="flex items-center justify-between mb-3">
<p class="text-sm font-semibold text-yellow-800"><i class="fas fa-table mr-2"></i>Reference IDs</p>
<a href="?action=export_reference_ids" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg text-xs font-bold transition inline-flex items-center">
<i class="fas fa-file-pdf mr-2"></i>Export Full List
</a>
</div>
<div class="grid grid-cols-3 gap-4 text-xs">
<div>
<p class="font-semibold text-yellow-900 mb-1">Areas</p>
<?php foreach (array_slice($areas, 0, 3) as $area): ?>
<p class="text-yellow-700"><?php echo $area['id']; ?>: <?php echo htmlspecialchars($area['area_name']); ?></p>
<?php endforeach; ?>
<?php if (count($areas) > 3): ?>
<p class="text-yellow-600 italic">+<?php echo count($areas) - 3; ?> more...</p>
<?php endif; ?>
</div>
<div>
<p class="font-semibold text-yellow-900 mb-1">Districts</p>
<?php foreach (array_slice($districts, 0, 3) as $district): ?>
<p class="text-yellow-700"><?php echo $district['id']; ?>: <?php echo htmlspecialchars($district['district_name']); ?></p>
<?php endforeach; ?>
<?php if (count($districts) > 3): ?>
<p class="text-yellow-600 italic">+<?php echo count($districts) - 3; ?> more...</p>
<?php endif; ?>
</div>
<div>
<p class="font-semibold text-yellow-900 mb-1">Assemblies</p>
<?php foreach (array_slice($assemblies, 0, 3) as $assembly): ?>
<p class="text-yellow-700"><?php echo $assembly['id']; ?>: <?php echo htmlspecialchars($assembly['assembly_name']); ?></p>
<?php endforeach; ?>
<?php if (count($assemblies) > 3): ?>
<p class="text-yellow-600 italic">+<?php echo count($assemblies) - 3; ?> more...</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const fileInput = document.getElementById('member_file_input');
const previewContainer = document.getElementById('file_preview_container');
const previewHead = document.getElementById('file_preview_head');
const previewBody = document.getElementById('file_preview_body');
const previewFilename = document.getElementById('file_preview_filename');
const previewWarning = document.getElementById('file_preview_warning');
if (!fileInput) {
return;
}
fileInput.addEventListener('change', function (event) {
const file = event.target.files[0];
previewHead.innerHTML = '';
previewBody.innerHTML = '';
previewWarning.classList.add('hidden');
if (!file) {
previewContainer.classList.add('hidden');
previewFilename.textContent = '';
return;
}
const fileName = file.name || '';
previewFilename.textContent = fileName;
const lowerName = fileName.toLowerCase();
const maxRows = 10;
const reader = new FileReader();
// Handle CSV files
if (lowerName.endsWith('.csv')) {
reader.onload = function (e) {
const text = e.target.result || '';
if (!text) {
previewContainer.classList.add('hidden');
return;
}
const lines = text.split(/\r?\n/).filter(line => line.trim() !== '');
if (lines.length === 0) {
previewContainer.classList.add('hidden');
return;
}
const headerLine = lines[0];
const headers = headerLine.split(',');
// Render header
let headHtml = '<tr>';
headers.forEach(h => {
headHtml += '<th class="px-2 py-1 text-left font-semibold">' + h.replace(/</g, '<').replace(/>/g, '>') + '</th>';
});
headHtml += '</tr>';
previewHead.innerHTML = headHtml;
// Render body rows
let bodyHtml = '';
for (let i = 1; i < Math.min(lines.length, maxRows); i++) {
const row = lines[i].split(',');
if (row.every(cell => cell.trim() === '')) {
continue;
}
bodyHtml += '<tr>';
headers.forEach((_, idx) => {
const cell = (row[idx] || '').replace(/</g, '<').replace(/>/g, '>');
bodyHtml += '<td class="px-2 py-1 text-gray-700">' + cell + '</td>';
});
bodyHtml += '</tr>';
}
previewBody.innerHTML = bodyHtml || '<tr><td class="px-2 py-2 text-gray-500">No data rows found in file.</td></tr>';
previewContainer.classList.remove('hidden');
};
reader.onerror = function () {
previewContainer.classList.add('hidden');
};
reader.readAsText(file);
previewWarning.classList.add('hidden');
previewContainer.classList.remove('hidden');
return;
}
// Handle Excel files (.xlsx, .xls) using SheetJS
if (lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls')) {
reader.onload = function (e) {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[firstSheetName];
const sheetJson = XLSX.utils.sheet_to_json(sheet, { header: 1 }); // array of arrays
if (!sheetJson || sheetJson.length === 0) {
previewContainer.classList.add('hidden');
return;
}
const headers = sheetJson[0];
// Render header
let headHtml = '<tr>';
headers.forEach(h => {
const headerText = (h !== undefined && h !== null ? String(h) : '').replace(/</g, '<').replace(/>/g, '>');
headHtml += '<th class="px-2 py-1 text-left font-semibold">' + headerText + '</th>';
});
headHtml += '</tr>';
previewHead.innerHTML = headHtml;
// Render body rows
let bodyHtml = '';
for (let i = 1; i < Math.min(sheetJson.length, maxRows); i++) {
const row = sheetJson[i] || [];
if (row.length === 0 || row.every(cell => String(cell).trim() === '')) {
continue;
}
bodyHtml += '<tr>';
headers.forEach((_, idx) => {
const cellVal = row[idx] !== undefined && row[idx] !== null ? String(row[idx]) : '';
const cell = cellVal.replace(/</g, '<').replace(/>/g, '>');
bodyHtml += '<td class="px-2 py-1 text-gray-700">' + cell + '</td>';
});
bodyHtml += '</tr>';
}
previewBody.innerHTML = bodyHtml || '<tr><td class="px-2 py-2 text-gray-500">No data rows found in sheet.</td></tr>';
previewWarning.classList.add('hidden');
previewContainer.classList.remove('hidden');
} catch (err) {
previewWarning.textContent = 'Could not preview Excel file in browser.';
previewWarning.classList.remove('hidden');
previewContainer.classList.remove('hidden');
}
};
reader.onerror = function () {
previewWarning.textContent = 'Could not read Excel file for preview.';
previewWarning.classList.remove('hidden');
previewContainer.classList.remove('hidden');
};
reader.readAsArrayBuffer(file);
return;
}
// Unsupported extension
previewWarning.textContent = 'Preview is available for CSV and standard Excel files only.';
previewWarning.classList.remove('hidden');
previewContainer.classList.remove('hidden');
});
});
</script>
<?php include '../../includes/footer.php'; ?>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists