![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/.cursor-server/data/User/History/-28a280e8/ |
<?php
session_start();
require_once 'config/database.php';
require_once 'includes/translations.php';
require_once 'includes/event_permissions.php';
if (!isset($_SESSION['user_id'])) {
header('Location: /auth/login.php');
exit;
}
$event_id = isset($_GET['event_id']) ? (int)$_GET['event_id'] : 0;
if ($event_id <= 0) {
header('Location: /events.php');
exit;
}
$pdo = getDBConnection();
ensureEventManagersTable($pdo);
$eventStmt = $pdo->prepare("
SELECT e.*, u.name as creator_name
FROM events e
JOIN users u ON e.creator_id = u.id
WHERE e.id = ?
");
$eventStmt->execute([$event_id]);
$event = $eventStmt->fetch(PDO::FETCH_ASSOC);
if (!$event) {
http_response_code(404);
echo 'Event not found';
exit;
}
if (!userCanManageEvent($pdo, $_SESSION['user_id'], $event_id)) {
http_response_code(403);
echo 'You do not have permission to manage this event.';
exit;
}
$ticketsStmt = $pdo->prepare("
SELECT
et.*,
usr.name as attendee_name,
usr.email as attendee_email
FROM event_tickets et
JOIN users usr ON et.user_id = usr.id
WHERE et.event_id = ?
ORDER BY et.purchase_date DESC
");
$ticketsStmt->execute([$event_id]);
$tickets = $ticketsStmt->fetchAll(PDO::FETCH_ASSOC);
$staffStmt = $pdo->prepare("
SELECT
em.user_id,
u.name,
u.email,
em.role,
CASE WHEN u.id = ? THEN 1 ELSE 0 END as is_creator
FROM event_managers em
JOIN users u ON em.user_id = u.id
WHERE em.event_id = ? AND em.status = 'active'
ORDER BY is_creator DESC, u.name ASC
");
$staffStmt->execute([$event['creator_id'], $event_id]);
$staff = $staffStmt->fetchAll(PDO::FETCH_ASSOC);
$ticketsSold = count($tickets);
$ticketsCheckedIn = count(array_filter($tickets, fn($ticket) => $ticket['status'] === 'used'));
$page_title = 'Manage Event - ' . htmlspecialchars($event['title']);
$current_page = 'events';
include 'includes/header.php';
?>
<style>
.console-container {
max-width: 1200px;
margin: 4rem auto;
padding: 0 1.5rem 4rem;
}
.console-hero {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.15), rgba(118, 75, 162, 0.15));
border: 1px solid rgba(102, 126, 234, 0.35);
border-radius: 24px;
padding: 2.5rem;
margin-bottom: 2rem;
color: #f7fafc;
box-shadow: 0 20px 45px rgba(15, 23, 42, 0.35);
}
.console-hero h1 {
margin: 0 0 0.5rem;
font-size: 2.2rem;
}
.console-hero p {
margin: 0;
color: #cbd5f5;
}
.console-metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.metric-card {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
padding: 1.3rem;
}
.metric-card h4 {
margin: 0;
color: #9ca3af;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.1em;
}
.metric-card strong {
display: block;
margin-top: 0.4rem;
font-size: 1.6rem;
color: #fff;
}
.panel {
background: rgba(15, 23, 42, 0.85);
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 20px;
padding: 2rem;
margin-top: 2rem;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.45);
color: #f8fafc;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
flex-wrap: wrap;
gap: 1rem;
}
.panel-header h2 {
margin: 0;
font-size: 1.4rem;
display: flex;
align-items: center;
gap: 0.6rem;
}
.panel-header h2 i {
color: #667eea;
}
.console-table {
width: 100%;
border-collapse: collapse;
color: #e2e8f0;
}
.console-table th,
.console-table td {
padding: 0.9rem 0.7rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
text-align: left;
font-size: 0.92rem;
}
.console-table th {
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.75rem;
color: #a0aec0;
}
.status-chip {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.2rem 0.8rem;
border-radius: 999px;
font-size: 0.8rem;
border: 1px solid transparent;
}
.status-chip.confirmed {
background: rgba(72, 187, 120, 0.2);
color: #68d391;
border-color: rgba(72, 187, 120, 0.3);
}
.status-chip.pending {
background: rgba(237, 137, 54, 0.15);
color: #fbd38d;
border-color: rgba(237, 137, 54, 0.3);
}
.status-chip.used {
background: rgba(236, 201, 75, 0.15);
color: #f6ad55;
border-color: rgba(236, 201, 75, 0.3);
}
.status-chip.cancelled {
background: rgba(245, 101, 101, 0.15);
color: #fc8181;
border-color: rgba(245, 101, 101, 0.3);
}
.ticket-actions {
display: flex;
gap: 0.4rem;
}
.ticket-actions button {
border: 1px solid rgba(255, 255, 255, 0.15);
background: transparent;
color: #cbd5f5;
border-radius: 10px;
padding: 0.35rem 0.8rem;
cursor: pointer;
font-size: 0.85rem;
}
.ticket-actions button:hover {
border-color: rgba(102, 126, 234, 0.6);
color: #fff;
}
.staff-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
}
.staff-card {
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 14px;
padding: 1rem;
background: rgba(255, 255, 255, 0.02);
}
.staff-card h4 {
margin: 0;
font-size: 1rem;
}
.staff-card span {
display: block;
color: #a0aec0;
font-size: 0.85rem;
margin-top: 0.2rem;
}
.staff-card button {
margin-top: 0.8rem;
width: 100%;
border-radius: 10px;
border: 1px solid rgba(255, 99, 99, 0.4);
background: rgba(255, 99, 99, 0.1);
color: #feb2b2;
padding: 0.4rem;
cursor: pointer;
}
.staff-form-inline {
margin-top: 1.5rem;
display: flex;
gap: 0.6rem;
}
.staff-form-inline input {
flex: 1;
padding: 0.75rem 1rem;
border-radius: 10px;
border: 1px solid rgba(148, 163, 184, 0.3);
background: rgba(255, 255, 255, 0.02);
color: #fff;
}
.staff-form-inline button {
padding: 0.75rem 1.2rem;
}
.empty-notice {
text-align: center;
padding: 2rem 1rem;
color: #a0aec0;
}
.empty-notice i {
font-size: 2rem;
margin-bottom: 0.5rem;
}
@media (max-width: 768px) {
.panel {
padding: 1.5rem;
}
.ticket-actions {
flex-direction: column;
}
.staff-form-inline {
flex-direction: column;
}
}
</style>
<div class="console-container">
<section class="console-hero">
<p><?= htmlspecialchars($event['event_type']) ?> · <?= date('M j, Y g:i A', strtotime($event['start_date'])) ?></p>
<h1><?= htmlspecialchars($event['title']) ?></h1>
<p>Managed by <?= htmlspecialchars($event['creator_name']) ?> · <a href="/events.php?event=<?= $event_id ?>" style="color: #90cdf4;">View public page</a></p>
<div class="console-metrics">
<div class="metric-card">
<h4>Tickets Sold</h4>
<strong><?= $ticketsSold ?><?= $event['max_attendees'] ? ' / ' . number_format($event['max_attendees']) : '' ?></strong>
</div>
<div class="metric-card">
<h4>Checked In</h4>
<strong><?= $ticketsCheckedIn ?></strong>
</div>
<div class="metric-card">
<h4>Pending</h4>
<strong><?= max($ticketsSold - $ticketsCheckedIn, 0) ?></strong>
</div>
</div>
</section>
<section class="panel" id="tickets-panel">
<div class="panel-header">
<h2><i class="fas fa-ticket-alt"></i> Tickets</h2>
<div>
<button class="btn btn-secondary" onclick="location.reload()">
<i class="fas fa-sync-alt"></i> Refresh
</button>
</div>
</div>
<?php if (empty($tickets)): ?>
<div class="empty-notice">
<i class="fas fa-ticket-alt"></i>
<p>No tickets issued yet.</p>
</div>
<?php else: ?>
<div class="table-wrapper" id="tickets">
<table class="console-table">
<thead>
<tr>
<th>Attendee</th>
<th>Email</th>
<th>Status</th>
<th>Purchased</th>
<th>Ticket Code</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($tickets as $ticket): ?>
<tr data-ticket="<?= htmlspecialchars($ticket['ticket_code']) ?>">
<td><?= htmlspecialchars($ticket['attendee_name']) ?></td>
<td><?= htmlspecialchars($ticket['attendee_email']) ?></td>
<td>
<span class="status-chip <?= htmlspecialchars($ticket['status']) ?>">
<?= ucfirst($ticket['status']) ?>
</span>
</td>
<td><?= date('M j, Y g:i A', strtotime($ticket['purchase_date'])) ?></td>
<td><?= htmlspecialchars($ticket['ticket_code']) ?></td>
<td>
<div class="ticket-actions">
<?php if ($ticket['status'] !== 'used'): ?>
<button onclick="performTicketAction('<?= $ticket['ticket_code'] ?>', 'check_in', this)">
<i class="fas fa-check"></i> Check In
</button>
<?php else: ?>
<button onclick="performTicketAction('<?= $ticket['ticket_code'] ?>', 'revert', this)">
<i class="fas fa-undo"></i> Undo
</button>
<?php endif; ?>
<a class="btn btn-secondary" href="/generate_ticket_qr.php?code=<?= urlencode($ticket['ticket_code']) ?>" target="_blank" rel="noopener">
QR
</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<section class="panel">
<div class="panel-header">
<h2><i class="fas fa-user-shield"></i> Staff Access</h2>
</div>
<?php if (empty($staff)): ?>
<div class="empty-notice">
<i class="fas fa-user-friends"></i>
<p>No staff members assigned.</p>
</div>
<?php else: ?>
<div class="staff-grid" id="staffGrid">
<?php foreach ($staff as $member): ?>
<div class="staff-card" data-user-id="<?= $member['user_id'] ?>">
<h4><?= htmlspecialchars($member['name']) ?></h4>
<span><?= htmlspecialchars($member['email']) ?></span>
<?php if ((int)$member['is_creator'] === 1): ?>
<span class="staff-role-chip">Owner</span>
<?php else: ?>
<button onclick="removeStaffInline(<?= $member['user_id'] ?>)">
<i class="fas fa-user-times"></i> Remove
</button>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<form class="staff-form-inline" id="staffConsoleForm">
<input type="hidden" name="event_id" value="<?= $event_id ?>">
<input type="email" id="staffConsoleEmail" placeholder="Invite by email" required>
<button type="submit" class="btn btn-primary">
<i class="fas fa-plus"></i> Add Staff
</button>
</form>
</section>
</div>
<script>
const eventId = <?= $event_id ?>;
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>"']/g, (char) => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[char] || char));
}
function updateTicketRow(ticketCode, payload) {
const row = document.querySelector(`[data-ticket="${ticketCode}"]`);
if (!row) return;
const statusCell = row.querySelector('.status-chip');
if (statusCell) {
statusCell.textContent = payload.status_label;
statusCell.className = `status-chip ${payload.status}`;
}
const actionsCell = row.querySelector('.ticket-actions');
if (actionsCell) {
if (payload.status === 'used') {
actionsCell.innerHTML = `
<button onclick="performTicketAction('${ticketCode}', 'revert', this)">
<i class="fas fa-undo"></i> Undo
</button>
<a class="btn btn-secondary" href="/generate_ticket_qr.php?code=${ticketCode}" target="_blank" rel="noopener">QR</a>
`;
} else {
actionsCell.innerHTML = `
<button onclick="performTicketAction('${ticketCode}', 'check_in', this)">
<i class="fas fa-check"></i> Check In
</button>
<a class="btn btn-secondary" href="/generate_ticket_qr.php?code=${ticketCode}" target="_blank" rel="noopener">QR</a>
`;
}
}
}
async function performTicketAction(ticketCode, action, button) {
button.disabled = true;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
try {
const response = await fetch('/api/ticket_checkin.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ticket_code: ticketCode, action })
});
const result = await response.json();
if (!response.ok || !result.success) {
throw new Error(result.error || 'Unable to update ticket');
}
updateTicketRow(ticketCode, result.ticket);
} catch (error) {
alert(error.message || 'Unable to update ticket right now.');
} finally {
button.disabled = false;
button.innerHTML = action === 'check_in' ? '<i class="fas fa-check"></i> Check In' : '<i class="fas fa-undo"></i> Undo';
}
}
async function removeStaffInline(userId) {
try {
const response = await fetch('/api/event_managers.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'remove', event_id: eventId, user_id: userId })
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error || 'Unable to remove staff');
}
renderStaffGrid(data.staff || []);
} catch (error) {
alert(error.message || 'Unable to remove staff member.');
}
}
function renderStaffGrid(staff) {
const grid = document.getElementById('staffGrid');
if (!grid) return;
if (!staff || staff.length === 0) {
grid.innerHTML = '<div class="empty-notice"><i class="fas fa-user-friends"></i><p>No staff members assigned.</p></div>';
return;
}
grid.innerHTML = staff.map(member => `
<div class="staff-card" data-user-id="${member.user_id}">
<h4>${escapeHtml(member.name ?? 'Member')}</h4>
<span>${escapeHtml(member.email ?? '')}</span>
${Number(member.is_creator) === 1 ? '<span class="staff-role-chip">Owner</span>' : `
<button onclick="removeStaffInline(${member.user_id})">
<i class="fas fa-user-times"></i> Remove
</button>`}
</div>
`).join('');
}
const staffConsoleForm = document.getElementById('staffConsoleForm');
if (staffConsoleForm) {
staffConsoleForm.addEventListener('submit', async (event) => {
event.preventDefault();
const emailInput = document.getElementById('staffConsoleEmail');
if (!emailInput) return;
const email = emailInput.value.trim();
if (!email) return;
emailInput.disabled = true;
try {
const response = await fetch('/api/event_managers.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'add', event_id: eventId, email })
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error || 'Unable to add staff');
}
emailInput.value = '';
renderStaffGrid(data.staff || []);
} catch (error) {
alert(error.message || 'Unable to add staff member.');
} finally {
emailInput.disabled = false;
emailInput.focus();
}
});
}
</script>
<?php include 'includes/footer.php'; ?>