Sindbad~EG File Manager
<?php
require_once '../../config/config.php';
checkLogin();
// Only superusers and area/district/assembly level users can access
if (!isSuperuser() && !in_array(getAccessLevel(), ['area', 'district', 'assembly'])) {
redirect('../../dashboard.php');
}
$pageTitle = "Member Accounts - " . APP_NAME;
$db = Database::getInstance()->getConnection();
$success = '';
$error = '';
require_once '../../classes/MemberAuth.php';
$memberAuth = new MemberAuth();
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['create_new_account'])) {
try {
$memberId = $_POST['member_id'];
// Get member details
$stmt = $db->prepare("SELECT * FROM members WHERE id = :id");
$stmt->execute(['id' => $memberId]);
$member = $stmt->fetch();
if (!$member) {
throw new Exception("Member not found");
}
// Check if account already exists
$existingAccount = $memberAuth->getMemberAccountByMemberId($memberId);
if ($existingAccount) {
throw new Exception("Member already has an account");
}
// Custom account creation with admin-provided details
$username = $_POST['username'];
$password = $_POST['password'];
$email = $_POST['email'];
$phone = $_POST['phone'] ?: $member['phone'];
$accessLevel = $_POST['access_level'] ?? 'member';
// Validate email is provided
if (empty($email)) {
throw new Exception("Email address is required to create an account");
}
// Validate username uniqueness
$checkStmt = $db->prepare("SELECT id FROM member_accounts WHERE username = :username");
$checkStmt->execute(['username' => $username]);
if ($checkStmt->fetch()) {
throw new Exception("Username already exists");
}
// Update member's email in members table if it's empty
if (empty($member['email'])) {
$updateStmt = $db->prepare("UPDATE members SET email = :email WHERE id = :id");
$updateStmt->execute(['email' => $email, 'id' => $memberId]);
}
// Create account
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $db->prepare("
INSERT INTO member_accounts (
member_id, username, email, password, full_name, phone,
access_level, area_id, district_id, assembly_id, is_active
) VALUES (
:member_id, :username, :email, :password, :full_name, :phone,
:access_level, :area_id, :district_id, :assembly_id, 1
)
");
$stmt->execute([
'member_id' => $memberId,
'username' => $username,
'email' => $email,
'password' => $passwordHash,
'full_name' => $member['first_name'] . ' ' . $member['last_name'],
'phone' => $phone,
'access_level' => $accessLevel,
'area_id' => $member['area_id'],
'district_id' => $member['district_id'],
'assembly_id' => $member['assembly_id']
]);
$success = "Account created successfully! Member email updated.<br>Username: <strong>" . htmlspecialchars($username) . "</strong><br>Password: <strong>" . htmlspecialchars($password) . "</strong><br>Email: <strong>" . htmlspecialchars($email) . "</strong>";
} catch (Exception $e) {
$error = "Error creating account: " . $e->getMessage();
}
}
if (isset($_POST['create_account'])) {
try {
$memberId = $_POST['member_id'];
$providedEmail = $_POST['email'] ?? '';
// Get member details
$stmt = $db->prepare("SELECT * FROM members WHERE id = :id");
$stmt->execute(['id' => $memberId]);
$member = $stmt->fetch();
if (!$member) {
throw new Exception("Member not found");
}
// Use provided email or member's existing email
$emailToUse = !empty($providedEmail) ? $providedEmail : $member['email'];
// If still no email, throw error
if (empty($emailToUse)) {
throw new Exception("Email address is required. Please provide an email address to create the account.");
}
// Update member's email if provided and different from current
if (!empty($providedEmail) && $providedEmail !== $member['email']) {
$updateStmt = $db->prepare("UPDATE members SET email = :email WHERE id = :id");
$updateStmt->execute(['email' => $providedEmail, 'id' => $memberId]);
}
// Check if account already exists
$existingAccount = $memberAuth->getMemberAccountByMemberId($memberId);
if ($existingAccount) {
throw new Exception("Member already has an account");
}
$result = $memberAuth->createMemberAccount([
'member_id' => $memberId,
'first_name' => $member['first_name'],
'last_name' => $member['last_name'],
'email' => $emailToUse,
'phone' => $member['phone'],
'area_id' => $member['area_id'],
'district_id' => $member['district_id'],
'assembly_id' => $member['assembly_id']
]);
if ($result['success']) {
$success = "Account created successfully!<br>Username: <strong>{$result['username']}</strong><br>Password: <strong>{$result['password']}</strong><br>Email: <strong>" . htmlspecialchars($emailToUse) . "</strong>";
} else {
$error = $result['message'];
}
} catch (Exception $e) {
$error = "Error creating account: " . $e->getMessage();
}
}
if (isset($_POST['update_account'])) {
try {
$accountId = $_POST['account_id'];
$updateData = [];
if (!empty($_POST['email'])) {
$updateData['email'] = $_POST['email'];
}
if (!empty($_POST['phone'])) {
$updateData['phone'] = $_POST['phone'];
}
if (!empty($_POST['new_password'])) {
$updateData['password'] = $_POST['new_password'];
}
if (!empty($updateData)) {
$result = $memberAuth->updateMemberAccount($accountId, $updateData);
if ($result['success']) {
$success = $result['message'];
} else {
$error = $result['message'];
}
}
} catch (Exception $e) {
$error = "Error updating account: " . $e->getMessage();
}
}
if (isset($_POST['toggle_status'])) {
try {
$accountId = $_POST['account_id'];
$newStatus = $_POST['new_status'];
$stmt = $db->prepare("UPDATE member_accounts SET is_active = :status WHERE id = :id");
$stmt->execute(['status' => $newStatus, 'id' => $accountId]);
$success = "Account status updated successfully!";
} catch (Exception $e) {
$error = "Error updating status: " . $e->getMessage();
}
}
// Toggle account status (new handler)
if (isset($_POST['toggle_account_status'])) {
try {
$accountId = $_POST['account_id'];
$activate = $_POST['activate'] === 'true' ? 1 : 0;
$stmt = $db->prepare("UPDATE member_accounts SET is_active = :status WHERE id = :id");
$stmt->execute(['status' => $activate, 'id' => $accountId]);
$action = $activate ? 'activated' : 'deactivated';
$success = "Account {$action} successfully!";
} catch (Exception $e) {
$error = "Error updating account: " . $e->getMessage();
}
}
// Reset account password
if (isset($_POST['reset_account_password'])) {
try {
$accountId = $_POST['account_id'];
$newPassword = $_POST['new_password'];
$passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $db->prepare("UPDATE member_accounts SET password = :password WHERE id = :id");
$stmt->execute(['password' => $passwordHash, 'id' => $accountId]);
$success = "Password reset successfully! New password: <strong>" . htmlspecialchars($newPassword) . "</strong>";
} catch (Exception $e) {
$error = "Error resetting password: " . $e->getMessage();
}
}
// Delete account
if (isset($_POST['delete_account'])) {
try {
$accountId = $_POST['account_id'];
$stmt = $db->prepare("DELETE FROM member_accounts WHERE id = :id");
$stmt->execute(['id' => $accountId]);
$success = "Account deleted successfully!";
} catch (Exception $e) {
$error = "Error deleting account: " . $e->getMessage();
}
}
}
// Get members without emails (so admin can add emails and create accounts)
$membersWithoutEmails = [];
try {
$stmt = $db->prepare("
SELECT m.id, m.first_name, m.last_name, m.email, m.phone,
m.area_id, m.district_id, m.assembly_id
FROM members m
WHERE (m.email IS NULL OR m.email = '')
AND m.is_active = 1
ORDER BY m.first_name, m.last_name
");
$stmt->execute();
$membersWithoutEmails = $stmt->fetchAll();
} catch (Exception $e) {
$error = "Error fetching members: " . $e->getMessage();
}
// Get filter parameters
$filterDistrict = $_GET['filter_district'] ?? '';
$filterAssembly = $_GET['filter_assembly'] ?? '';
// Build query with filters
$query = "
SELECT ma.*, m.first_name, m.last_name, m.phone as member_phone, m.email as member_email,
a.area_name, d.district_name, ass.assembly_name
FROM member_accounts ma
LEFT JOIN members m ON ma.member_id = m.id
LEFT JOIN areas a ON ma.area_id = a.id
LEFT JOIN districts d ON ma.district_id = d.id
LEFT JOIN assemblies ass ON ma.assembly_id = ass.id
WHERE 1=1
";
$params = [];
if (!empty($filterDistrict)) {
$query .= " AND ma.district_id = :district_id";
$params['district_id'] = $filterDistrict;
}
if (!empty($filterAssembly)) {
$query .= " AND ma.assembly_id = :assembly_id";
$params['assembly_id'] = $filterAssembly;
}
$query .= " ORDER BY ma.created_at DESC";
$stmt = $db->prepare($query);
$stmt->execute($params);
$memberAccounts = $stmt->fetchAll();
// Get districts and assemblies for filter dropdowns
$districts = $db->query("SELECT * FROM districts WHERE is_active = 1 ORDER BY district_name")->fetchAll();
$assemblies = $db->query("SELECT id, assembly_name, district_id FROM assemblies WHERE is_active = 1 ORDER BY assembly_name")->fetchAll();
include '../../includes/header.php';
?>
<?php include '../../includes/sidebar.php'; ?>
<!-- Main Content -->
<main class="flex-1 md:ml-64 mt-16">
<div class="container mx-auto px-4 py-8">
<div class="max-w-6xl mx-auto">
<div class="mb-6">
<h1 class="text-3xl font-bold text-gray-800">
<i class="fas fa-users-cog mr-2 text-blue-500"></i>Member Accounts
</h1>
<p class="text-gray-600 mt-2">Manage member login accounts and access</p>
</div>
<?php if ($success): ?>
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
<i class="fas fa-check-circle mr-2"></i><?php echo $success; ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
<i class="fas fa-exclamation-circle mr-2"></i><?php echo $error; ?>
</div>
<?php endif; ?>
<!-- Statistics -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-white rounded-xl shadow-lg p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
<i class="fas fa-users text-xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600">Total Accounts</p>
<p class="text-2xl font-bold text-gray-900"><?php echo count($memberAccounts); ?></p>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-lg p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100 text-green-600">
<i class="fas fa-user-check text-xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600">Active Accounts</p>
<p class="text-2xl font-bold text-gray-900"><?php echo count(array_filter($memberAccounts, fn($acc) => $acc['is_active'])); ?></p>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-lg p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-100 text-yellow-600">
<i class="fas fa-user-plus text-xl"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-600">Members Needing Emails</p>
<p class="text-2xl font-bold text-gray-900"><?php echo count($membersWithoutEmails); ?></p>
</div>
</div>
</div>
</div>
<!-- Tabs -->
<div class="bg-white rounded-xl shadow-lg">
<div class="border-b border-gray-200">
<nav class="flex">
<button onclick="switchAccountTab('create')" class="account-tab px-6 py-4 font-medium border-b-2 border-blue-500 text-blue-600">
<i class="fas fa-user-plus mr-2"></i>Create Account
</button>
<button onclick="switchAccountTab('new')" class="account-tab px-6 py-4 font-medium text-gray-600 hover:text-blue-600 border-b-2 border-transparent">
<i class="fas fa-user-shield mr-2"></i>New Account
</button>
<button onclick="switchAccountTab('manage')" class="account-tab px-6 py-4 font-medium text-gray-600 hover:text-blue-600 border-b-2 border-transparent">
<i class="fas fa-users-cog mr-2"></i>Manage Accounts
</button>
</nav>
</div>
<!-- Create Account Tab -->
<div id="createTab" class="account-tab-content p-8">
<div class="max-w-2xl mx-auto">
<h3 class="text-lg font-semibold text-gray-800 mb-6">Create Member Account - Quick Creation</h3>
<p class="text-sm text-gray-600 mb-6">Select a member, update/add email if needed, and create their account</p>
<?php if (empty($membersWithoutEmails)): ?>
<div class="text-center py-8">
<i class="fas fa-check-circle text-4xl text-green-500 mb-4"></i>
<p class="text-gray-600">All members have email addresses!</p>
</div>
<?php else: ?>
<form method="POST" class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Search & Select Member</label>
<input type="text" id="memberSearch"
placeholder="Type to search member by name or email..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 mb-2">
<select name="member_id" id="memberSelect" onchange="loadMemberEmail(this)" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" required>
<option value="">Choose a member...</option>
<?php foreach ($membersWithoutEmails as $member): ?>
<option value="<?php echo $member['id']; ?>"
data-name="<?php echo htmlspecialchars(strtolower($member['first_name'] . ' ' . $member['last_name'])); ?>"
data-email="<?php echo htmlspecialchars($member['email'] ?? ''); ?>">
<?php echo htmlspecialchars($member['first_name'] . ' ' . $member['last_name']); ?>
<?php if (empty($member['email'])): ?>
<span class="text-red-500"> (No email)</span>
<?php endif; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Email Address <span class="text-red-500">*</span>
<span class="text-xs text-gray-500">(Update if needed)</span>
</label>
<input type="email" name="email" id="memberEmail" required
placeholder="Enter or update email address"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">If member has no email, you must provide one. If email exists, you can update it here.</p>
</div>
<div class="bg-blue-50 rounded-lg p-4">
<h4 class="font-semibold text-blue-800 mb-2">
<i class="fas fa-info-circle mr-2"></i>Account Creation Details
</h4>
<ul class="text-sm text-blue-700 space-y-1">
<li>• Username will be generated automatically (first initial + last name)</li>
<li>• A random password will be generated</li>
<li>• Email will be updated in member profile if changed</li>
<li>• Member will receive login credentials</li>
<li>• Account will be active immediately</li>
</ul>
</div>
<div class="flex justify-center">
<button type="submit" name="create_account" class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition">
<i class="fas fa-user-plus mr-2"></i>Create Account
</button>
</div>
</form>
<?php endif; ?>
</div>
</div>
<!-- New Account Tab -->
<div id="newTab" class="account-tab-content hidden p-8">
<div class="mb-6 flex justify-between items-center">
<h3 class="text-2xl font-bold text-gray-800">
<i class="fas fa-users-cog mr-2 text-blue-500"></i>Member Account Management
</h3>
<button onclick="openCreateAccountModal()" class="bg-gradient-to-r from-blue-500 to-blue-600 text-white px-6 py-3 rounded-lg hover:shadow-lg transition">
<i class="fas fa-user-plus mr-2"></i>Create New Account
</button>
</div>
<!-- Search and Filter -->
<div class="bg-white rounded-lg shadow p-4 mb-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">Search Accounts</label>
<input type="text" id="accountSearch"
placeholder="Search by member name, username, or email..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Filter by Status</label>
<select id="statusFilter" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="all">All Accounts</option>
<option value="active">Active Only</option>
<option value="inactive">Inactive Only</option>
</select>
</div>
</div>
</div>
<!-- Accounts Table -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Member</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Username</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Access Level</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="accountsTableBody">
<?php
// Get all member accounts with member details
$accountsQuery = "SELECT ma.*, m.first_name, m.last_name, m.membershipcard_id
FROM member_accounts ma
LEFT JOIN members m ON ma.member_id = m.id
ORDER BY ma.created_at DESC";
$accountsStmt = $db->query($accountsQuery);
$accounts = $accountsStmt->fetchAll();
?>
<?php foreach ($accounts as $account): ?>
<tr class="hover:bg-gray-50 account-row"
data-name="<?php echo htmlspecialchars(strtolower(($account['first_name'] ?? '') . ' ' . ($account['last_name'] ?? ''))); ?>"
data-username="<?php echo htmlspecialchars(strtolower($account['username'])); ?>"
data-email="<?php echo htmlspecialchars(strtolower($account['email'] ?? '')); ?>"
data-status="<?php echo $account['is_active'] ? 'active' : 'inactive'; ?>">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="h-10 w-10 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center text-white font-bold">
<?php echo strtoupper(substr($account['first_name'] ?? 'U', 0, 1) . substr($account['last_name'] ?? 'N', 0, 1)); ?>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
<?php echo htmlspecialchars(($account['first_name'] ?? 'Unknown') . ' ' . ($account['last_name'] ?? '')); ?>
</div>
<div class="text-sm text-gray-500">
Card: <?php echo htmlspecialchars($account['membershipcard_id'] ?? 'N/A'); ?>
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<?php echo htmlspecialchars($account['username']); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<?php echo htmlspecialchars($account['email'] ?? 'N/A'); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
<?php
$badgeClass = '';
switch($account['access_level']) {
case 'admin': $badgeClass = 'bg-red-100 text-red-800'; break;
case 'area': $badgeClass = 'bg-purple-100 text-purple-800'; break;
case 'district': $badgeClass = 'bg-blue-100 text-blue-800'; break;
case 'assembly': $badgeClass = 'bg-green-100 text-green-800'; break;
default: $badgeClass = 'bg-gray-100 text-gray-800';
}
echo $badgeClass;
?>">
<?php echo ucfirst($account['access_level']); ?>
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
<?php echo $account['is_active'] ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'; ?>">
<?php echo $account['is_active'] ? 'Active' : 'Inactive'; ?>
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div class="flex gap-2">
<button onclick="editAccount(<?php echo $account['id']; ?>)"
class="text-blue-600 hover:text-blue-900" title="Edit">
<i class="fas fa-edit"></i>
</button>
<button onclick="toggleAccountStatus(<?php echo $account['id']; ?>, <?php echo $account['is_active'] ? 'false' : 'true'; ?>)"
class="<?php echo $account['is_active'] ? 'text-orange-600 hover:text-orange-900' : 'text-green-600 hover:text-green-900'; ?>"
title="<?php echo $account['is_active'] ? 'Deactivate' : 'Activate'; ?>">
<i class="fas fa-<?php echo $account['is_active'] ? 'ban' : 'check-circle'; ?>"></i>
</button>
<button onclick="resetPassword(<?php echo $account['id']; ?>)"
class="text-purple-600 hover:text-purple-900" title="Reset Password">
<i class="fas fa-key"></i>
</button>
<button onclick="deleteAccount(<?php echo $account['id']; ?>)"
class="text-red-600 hover:text-red-900" title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Create Account Modal -->
<div id="createAccountModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 overflow-y-auto">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="bg-white rounded-lg max-w-2xl w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-800">Create New Account</h3>
<button onclick="closeCreateAccountModal()" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times text-2xl"></i>
</button>
</div>
<form method="POST" class="space-y-6">
<!-- Member Selection -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6">
<h4 class="font-semibold text-blue-800 mb-4">
<i class="fas fa-user mr-2"></i>Select Member
</h4>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Search & Select Member</label>
<input type="text" id="newMemberSearch"
placeholder="Type to search member by name or email..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 mb-2">
<select name="member_id" id="newMemberSelect"
onchange="loadMemberDetails(this)"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" required>
<option value="">Choose a member...</option>
<?php foreach ($membersWithoutEmails as $member): ?>
<option value="<?php echo $member['id']; ?>"
data-name="<?php echo htmlspecialchars(strtolower($member['first_name'] . ' ' . $member['last_name'])); ?>"
data-email=""
data-fname="<?php echo htmlspecialchars($member['first_name']); ?>"
data-lname="<?php echo htmlspecialchars($member['last_name']); ?>"
data-member-email=""
data-member-phone="<?php echo htmlspecialchars($member['phone'] ?? ''); ?>">
<?php echo htmlspecialchars($member['first_name'] . ' ' . $member['last_name'] . ' (No email)'); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<!-- Account Details -->
<div class="bg-gray-50 border border-gray-200 rounded-lg p-6">
<h4 class="font-semibold text-gray-800 mb-4">
<i class="fas fa-key mr-2"></i>Account Details
</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Username*</label>
<input type="text" name="username" id="newUsername" required
placeholder="e.g., john.doe"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">Letters, numbers, dots, underscores only</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Password*</label>
<input type="password" name="password" id="newPassword" required
placeholder="Enter password"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">Min 8 characters</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Email* <span class="text-red-500">(Required)</span></label>
<input type="email" name="email" id="newEmail" required
placeholder="email@example.com"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">This email will be saved to member profile</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Phone</label>
<input type="tel" name="phone" id="newPhone"
placeholder="Phone number"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">Access Level*</label>
<select name="access_level" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" required>
<option value="member" selected>Member (Basic Access)</option>
<option value="viewer">Viewer (Read Only)</option>
<option value="assembly">Assembly Admin</option>
<?php if (isSuperuser() || in_array(getUserAccessLevel(), ['area', 'district'])): ?>
<option value="district">District Admin</option>
<option value="area">Area Admin</option>
<?php endif; ?>
<?php if (isSuperuser()): ?>
<option value="admin">System Admin</option>
<?php endif; ?>
</select>
</div>
</div>
</div>
<!-- Info Box -->
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<h4 class="font-semibold text-yellow-800 mb-2">
<i class="fas fa-info-circle mr-2"></i>Important Notes
</h4>
<ul class="text-sm text-yellow-700 space-y-1">
<li>• These members don't have email addresses in their profiles</li>
<li>• <strong>You must provide an email address</strong> - it will be saved to the member's profile</li>
<li>• Username must be unique across all accounts</li>
<li>• Password will be shown after account creation (share with member)</li>
<li>• Account will be active immediately</li>
</ul>
</div>
<div class="flex justify-center">
<button type="submit" name="create_new_account" class="bg-gradient-to-r from-blue-500 to-blue-600 text-white px-8 py-3 rounded-lg hover:shadow-lg transition">
<i class="fas fa-user-plus mr-2"></i>Create Account
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Manage Accounts Tab -->
<div id="manageTab" class="account-tab-content hidden p-8">
<!-- Filters -->
<div class="mb-6">
<form method="GET" class="flex flex-wrap gap-4 items-end">
<div class="flex-1 min-w-[200px]">
<label class="block text-sm font-medium text-gray-700 mb-2">Filter by District</label>
<select name="filter_district" id="filterDistrict" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">All Districts</option>
<?php foreach ($districts as $district): ?>
<option value="<?php echo $district['id']; ?>" <?php echo $filterDistrict == $district['id'] ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($district['district_name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="flex-1 min-w-[200px]">
<label class="block text-sm font-medium text-gray-700 mb-2">Filter by Assembly</label>
<select name="filter_assembly" id="filterAssembly" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">All Assemblies</option>
<?php foreach ($assemblies as $assembly): ?>
<option value="<?php echo $assembly['id']; ?>"
data-district-id="<?php echo $assembly['district_id']; ?>"
<?php echo $filterAssembly == $assembly['id'] ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($assembly['assembly_name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<button type="submit" class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition">
<i class="fas fa-filter mr-2"></i>Apply Filters
</button>
<a href="?" class="ml-2 text-gray-600 hover:text-gray-800 px-4 py-2 inline-block">
<i class="fas fa-times mr-2"></i>Clear
</a>
</div>
</form>
</div>
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200 rounded-lg">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Member</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Username</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Login</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php if (empty($memberAccounts)): ?>
<tr>
<td colspan="6" class="px-6 py-8 text-center text-gray-500">
<i class="fas fa-users text-3xl mb-2"></i>
<p>No member accounts found</p>
</td>
</tr>
<?php else: ?>
<?php foreach ($memberAccounts as $account): ?>
<tr>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<?php if ($account['profile_photo']): ?>
<img src="../../<?php echo htmlspecialchars($account['profile_photo']); ?>"
alt="Profile" class="w-8 h-8 rounded-full mr-3 object-cover">
<?php else: ?>
<div class="w-8 h-8 rounded-full mr-3 bg-gray-300 flex items-center justify-center">
<i class="fas fa-user text-xs text-gray-600"></i>
</div>
<?php endif; ?>
<div>
<div class="text-sm font-medium text-gray-900">
<?php echo htmlspecialchars($account['first_name'] . ' ' . $account['last_name']); ?>
</div>
<div class="text-sm text-gray-500">
<?php echo htmlspecialchars($account['assembly_name'] ?? 'N/A'); ?>
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<?php echo htmlspecialchars($account['username']); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<?php echo htmlspecialchars($account['email']); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<?php if ($account['is_active']): ?>
<span class="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800">Active</span>
<?php else: ?>
<span class="px-2 py-1 text-xs font-medium rounded-full bg-red-100 text-red-800">Inactive</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<?php echo $account['last_login'] ? date('M j, Y H:i', strtotime($account['last_login'])) : 'Never'; ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button onclick="editAccount(<?php echo $account['id']; ?>)" class="text-blue-600 hover:text-blue-900 mr-3">
<i class="fas fa-edit"></i>
</button>
<form method="POST" class="inline">
<input type="hidden" name="account_id" value="<?php echo $account['id']; ?>">
<input type="hidden" name="new_status" value="<?php echo $account['is_active'] ? 0 : 1; ?>">
<button type="submit" name="toggle_status"
class="<?php echo $account['is_active'] ? 'text-red-600 hover:text-red-900' : 'text-green-600 hover:text-green-900'; ?>">
<i class="fas fa-<?php echo $account['is_active'] ? 'ban' : 'check'; ?>"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Edit Account Modal -->
<div id="editAccountModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="bg-white rounded-lg max-w-md w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Edit Account</h3>
<button onclick="closeEditModal()" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
<form method="POST" id="editAccountForm">
<input type="hidden" name="account_id" id="editAccountId">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Email</label>
<input type="email" name="email" id="editEmail"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Phone</label>
<input type="tel" name="phone" id="editPhone"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">New Password</label>
<input type="password" name="new_password"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">Leave blank to keep current password</p>
</div>
</div>
<div class="flex justify-end space-x-3 mt-6">
<button type="button" onclick="closeEditModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">
Cancel
</button>
<button type="submit" name="update_account" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
Update Account
</button>
</div>
</form>
</div>
</div>
</div>
<script>
function switchAccountTab(tabName) {
// Hide all tab contents
document.querySelectorAll('.account-tab-content').forEach(content => {
content.classList.add('hidden');
});
// Reset all tabs to inactive state
document.querySelectorAll('.account-tab').forEach(tab => {
tab.classList.remove('border-blue-500', 'text-blue-600');
tab.classList.add('border-transparent', 'text-gray-600', 'hover:text-blue-600');
});
// Show selected tab content
document.getElementById(tabName + 'Tab').classList.remove('hidden');
// Activate selected tab
event.target.classList.remove('border-transparent', 'text-gray-600', 'hover:text-blue-600');
event.target.classList.add('border-blue-500', 'text-blue-600');
}
function editAccount(accountId) {
// Get account data from the table row
const accounts = <?php echo json_encode($memberAccounts); ?>;
const account = accounts.find(acc => acc.id == accountId);
if (account) {
document.getElementById('editAccountId').value = accountId;
document.getElementById('editEmail').value = account.email;
document.getElementById('editPhone').value = account.phone || '';
document.getElementById('editAccountModal').classList.remove('hidden');
}
}
function closeEditModal() {
document.getElementById('editAccountModal').classList.add('hidden');
}
// Member search functionality
const memberSearch = document.getElementById('memberSearch');
const memberSelect = document.getElementById('memberSelect');
if (memberSearch && memberSelect) {
memberSearch.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const options = memberSelect.querySelectorAll('option');
options.forEach(option => {
if (option.value === '') return; // Skip the placeholder
const name = option.getAttribute('data-name') || '';
const email = option.getAttribute('data-email') || '';
if (name.includes(searchTerm) || email.includes(searchTerm)) {
option.style.display = '';
} else {
option.style.display = 'none';
}
});
// Auto-select if only one match
const visibleOptions = Array.from(options).filter(opt => opt.value !== '' && opt.style.display !== 'none');
if (visibleOptions.length === 1) {
memberSelect.value = visibleOptions[0].value;
}
});
}
// Cascading filter: Assembly based on District
const filterDistrict = document.getElementById('filterDistrict');
const filterAssembly = document.getElementById('filterAssembly');
if (filterDistrict && filterAssembly) {
filterDistrict.addEventListener('change', function() {
const selectedDistrict = this.value;
const assemblyOptions = filterAssembly.querySelectorAll('option');
assemblyOptions.forEach(option => {
if (option.value === '') {
option.style.display = ''; // Always show "All Assemblies"
return;
}
const districtId = option.getAttribute('data-district-id');
if (selectedDistrict === '' || districtId === selectedDistrict) {
option.style.display = '';
} else {
option.style.display = 'none';
}
});
// Reset assembly selection if current selection is hidden
const currentAssembly = filterAssembly.value;
if (currentAssembly) {
const currentOption = filterAssembly.querySelector(`option[value="${currentAssembly}"]`);
if (currentOption && currentOption.style.display === 'none') {
filterAssembly.value = ''; // Reset to "All Assemblies"
}
}
});
}
// New account tab: Member search functionality
const newMemberSearch = document.getElementById('newMemberSearch');
const newMemberSelect = document.getElementById('newMemberSelect');
if (newMemberSearch && newMemberSelect) {
newMemberSearch.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const options = newMemberSelect.querySelectorAll('option');
options.forEach(option => {
if (option.value === '') return;
const name = option.getAttribute('data-name') || '';
const email = option.getAttribute('data-email') || '';
if (name.includes(searchTerm) || email.includes(searchTerm)) {
option.style.display = '';
} else {
option.style.display = 'none';
}
});
const visibleOptions = Array.from(options).filter(opt => opt.value !== '' && opt.style.display !== 'none');
if (visibleOptions.length === 1) {
newMemberSelect.value = visibleOptions[0].value;
loadMemberDetails(newMemberSelect);
}
});
}
// Load member details into form fields
function loadMemberDetails(selectElement) {
const selectedOption = selectElement.options[selectElement.selectedIndex];
if (!selectedOption || !selectedOption.value) return;
const fname = selectedOption.getAttribute('data-fname') || '';
const lname = selectedOption.getAttribute('data-lname') || '';
const email = selectedOption.getAttribute('data-member-email') || '';
const phone = selectedOption.getAttribute('data-member-phone') || '';
// Auto-fill username (firstname.lastname)
const username = (fname + '.' + lname).toLowerCase().replace(/[^a-z0-9._]/g, '');
document.getElementById('newUsername').value = username;
// Clear email field (member doesn't have one) and focus on it
document.getElementById('newEmail').value = '';
// Auto-fill phone if available
if (phone) document.getElementById('newPhone').value = phone;
// Suggest a default password
const year = new Date().getFullYear();
document.getElementById('newPassword').value = 'Member@' + year;
}
// Load member email in Create Account tab
function loadMemberEmail(selectElement) {
const selectedOption = selectElement.options[selectElement.selectedIndex];
if (!selectedOption || !selectedOption.value) {
document.getElementById('memberEmail').value = '';
return;
}
const email = selectedOption.getAttribute('data-email') || '';
document.getElementById('memberEmail').value = email;
// If no email, focus on email field for admin to enter
if (!email) {
document.getElementById('memberEmail').focus();
}
}
// Create Account Modal functions
function openCreateAccountModal() {
document.getElementById('createAccountModal').classList.remove('hidden');
}
function closeCreateAccountModal() {
document.getElementById('createAccountModal').classList.add('hidden');
}
// Account Search and Filter
const accountSearch = document.getElementById('accountSearch');
const statusFilter = document.getElementById('statusFilter');
if (accountSearch) {
accountSearch.addEventListener('input', filterAccounts);
}
if (statusFilter) {
statusFilter.addEventListener('change', filterAccounts);
}
function filterAccounts() {
const searchTerm = accountSearch ? accountSearch.value.toLowerCase() : '';
const status = statusFilter ? statusFilter.value : 'all';
const rows = document.querySelectorAll('.account-row');
rows.forEach(row => {
const name = row.getAttribute('data-name') || '';
const username = row.getAttribute('data-username') || '';
const email = row.getAttribute('data-email') || '';
const rowStatus = row.getAttribute('data-status') || '';
const matchesSearch = !searchTerm ||
name.includes(searchTerm) ||
username.includes(searchTerm) ||
email.includes(searchTerm);
const matchesStatus = status === 'all' || rowStatus === status;
if (matchesSearch && matchesStatus) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
// CRUD Functions
function editAccount(accountId) {
window.location.href = '?edit_account=' + accountId;
}
function toggleAccountStatus(accountId, activate) {
const action = activate ? 'activate' : 'deactivate';
if (confirm(`Are you sure you want to ${action} this account?`)) {
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="toggle_account_status" value="1">
<input type="hidden" name="account_id" value="${accountId}">
<input type="hidden" name="activate" value="${activate}">
`;
document.body.appendChild(form);
form.submit();
}
}
function resetPassword(accountId) {
const newPassword = prompt('Enter new password for this account:');
if (newPassword && newPassword.length >= 8) {
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="reset_account_password" value="1">
<input type="hidden" name="account_id" value="${accountId}">
<input type="hidden" name="new_password" value="${newPassword}">
`;
document.body.appendChild(form);
form.submit();
} else if (newPassword) {
alert('Password must be at least 8 characters long');
}
}
function deleteAccount(accountId) {
if (confirm('Are you sure you want to DELETE this account? This action cannot be undone!')) {
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="delete_account" value="1">
<input type="hidden" name="account_id" value="${accountId}">
`;
document.body.appendChild(form);
form.submit();
}
}
</script>
<?php include '../../includes/footer.php'; ?>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists