feat: create events management admin system

- Add manage_events.php form for creating/editing events
- Add process_event.php endpoint for CRUD operations with image uploads
- Add admin_events.php list view with sorting, filtering, and delete functionality
- Add database migration to add created_by, published, created_at, updated_at columns to events table
- Add event images directory structure
- All features follow same patterns as trip management system
This commit is contained in:
twotalesanimation
2025-12-04 20:25:48 +02:00
parent cb588d20ee
commit 5be946f78f
4 changed files with 667 additions and 0 deletions

275
src/admin/admin_events.php Normal file
View File

@@ -0,0 +1,275 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
?>
<style>
table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
margin: 10px 0;
}
thead th {
cursor: pointer;
text-align: left;
padding: 10px;
font-weight: bold;
position: relative;
}
thead th::after {
content: '\25B2';
/* Up arrow */
font-size: 0.8em;
position: absolute;
right: 10px;
opacity: 0;
transition: opacity 0.2s;
}
thead th.asc::after {
content: '\25B2';
/* Up arrow */
opacity: 1;
}
thead th.desc::after {
content: '\25BC';
/* Down arrow */
opacity: 1;
}
tbody tr:nth-child(odd) {
background-color: transparent;
}
tbody tr:nth-child(even) {
background-color: rgb(255, 255, 255);
border-radius: 10px;
}
tbody td {
padding: 10px;
}
tbody tr:nth-child(even) td:first-child {
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
}
tbody tr:nth-child(even) td:last-child {
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.filter-input {
width: 100%;
padding: 10px;
font-size: 16px;
background-color: rgb(255, 255, 255);
border-radius: 25px;
margin-bottom: 15px;
}
.event-list {
color: #484848;
background: #f9f9f7;
border: 1px solid #d8d8d8;
border-radius: 10px;
margin-top: 15px;
margin-bottom: 15px;
}
.event-header {
padding: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.event-header h4 {
margin: 0;
}
.action-buttons {
display: flex;
gap: 10px;
}
.action-buttons a, .action-buttons button {
padding: 6px 12px;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
border: none;
cursor: pointer;
}
.btn-edit {
background-color: #007bff;
color: white;
}
.btn-delete {
background-color: #dc3545;
color: white;
}
.badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.badge-published {
background-color: #28a745;
color: white;
}
.badge-draft {
background-color: #ffc107;
color: black;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
const tables = document.querySelectorAll("table");
tables.forEach((table) => {
const headers = table.querySelectorAll("thead th");
const rows = Array.from(table.querySelectorAll("tbody tr"));
const filterInput = table.previousElementSibling;
headers.forEach((header, index) => {
header.addEventListener("click", () => {
const sortedRows = rows.sort((a, b) => {
const aText = a.cells[index].textContent.trim().toLowerCase();
const bText = b.cells[index].textContent.trim().toLowerCase();
if (aText < bText) return -1;
if (aText > bText) return 1;
return 0;
});
if (header.classList.contains("asc")) {
header.classList.remove("asc");
header.classList.add("desc");
sortedRows.reverse();
} else {
headers.forEach(h => h.classList.remove("asc", "desc"));
header.classList.add("asc");
}
const tbody = table.querySelector("tbody");
tbody.innerHTML = "";
sortedRows.forEach(row => tbody.appendChild(row));
});
});
if (rows.length === 0) {
filterInput.style.display = "none";
} else {
filterInput.addEventListener("input", function() {
const filterValue = filterInput.value.trim().toLowerCase();
rows.forEach(row => {
const rowText = row.textContent.trim().toLowerCase();
row.style.display = rowText.includes(filterValue) ? "" : "none";
});
});
}
});
});
function deleteEvent(eventId) {
if (confirm('Are you sure you want to delete this event?')) {
fetch('process_event?action=delete&event_id=' + eventId, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
location.reload();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => console.error('Error:', error));
}
}
</script>
<?php
$pageTitle = 'Manage Events';
$breadcrumbs = [['Home' => 'index'], [$pageTitle => '']];
require_once($rootPath . '/components/banner.php');
?>
<section class="tour-list-page py-10 rel z-1">
<div class="container">
<div class="row mb-20">
<div class="col-lg-12">
<a href="manage_events" class="theme-btn style-two">+ Create New Event</a>
</div>
</div>
<?php
// Query to retrieve all events
$stmt = $conn->prepare("SELECT event_id, name, type, location, date, published FROM events ORDER BY date DESC");
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
echo '<input type="text" class="filter-input" placeholder="Search events...">';
echo '<table>
<thead>
<tr>
<th>Event Name</th>
<th>Type</th>
<th>Location</th>
<th>Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>';
while ($event = $result->fetch_assoc()) {
$event_id = $event['event_id'];
$name = htmlspecialchars($event['name']);
$type = htmlspecialchars($event['type']);
$location = htmlspecialchars($event['location']);
$date = convertDate($event['date']);
$status = $event['published'] ? '<span class="badge badge-published">Published</span>' : '<span class="badge badge-draft">Draft</span>';
echo "<tr>
<td>{$name}</td>
<td>{$type}</td>
<td>{$location}</td>
<td>{$date}</td>
<td>{$status}</td>
<td>
<div class='action-buttons'>
<a href='manage_events?event_id={$event_id}' class='btn-edit'>Edit</a>
<button class='btn-delete' onclick='deleteEvent({$event_id})'>Delete</button>
</div>
</td>
</tr>";
}
echo '</tbody></table>';
} else {
echo '<div class="alert alert-info">No events found. <a href="manage_events">Create the first event</a></div>';
}
?>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>