refactor: align events admin pages with trips layout and add publish functionality

- Remove checkbox from manage_events.php form (publish via admin table instead)
- Redesign admin_events.php to match admin_trips.php layout exactly
- Add table-based actions with icon buttons (Edit, Publish/Unpublish, Delete)
- Change button styling to match trips (btn classes with colors)
- Add publish/unpublish toggle button with eye icon
- Create toggle_event_published.php endpoint for publish status switching
- Create delete_event.php endpoint for event deletion
- Add AJAX functionality for instant publish/delete without page reload
- Update .htaccess with new endpoint rewrite rules
- Badge styling updated to match trips (bg-success, bg-warning)
- Consistent sorting and filtering functionality
This commit is contained in:
twotalesanimation
2025-12-04 21:40:11 +02:00
parent 2b136c4b06
commit f522b84fc1
6 changed files with 290 additions and 128 deletions

View File

@@ -118,7 +118,9 @@ RewriteRule ^logout$ src/processors/logout.php [L]
RewriteRule ^process_trip$ src/processors/process_trip.php [L]
RewriteRule ^process_event$ src/admin/process_event.php [L]
RewriteRule ^toggle_trip_published$ src/processors/toggle_trip_published.php [L]
RewriteRule ^toggle_event_published$ src/admin/toggle_event_published.php [L]
RewriteRule ^delete_trip$ src/processors/delete_trip.php [L]
RewriteRule ^delete_event$ src/admin/delete_event.php [L]
</IfModule>

View File

@@ -3,6 +3,22 @@ $headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
// Fetch all events
$events_query = "
SELECT
event_id, name, type, location, date, published
FROM events
ORDER BY date DESC
";
$result = $conn->query($events_query);
$events = [];
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$events[] = $row;
}
}
?>
<style>
@@ -75,78 +91,89 @@ checkAdmin();
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 {
.btn {
display: inline-block;
padding: 6px 12px;
margin: 2px;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.btn-edit {
.btn-sm {
padding: 4px 8px;
font-size: 12px;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-delete {
.btn-primary:hover {
background-color: #0056b3;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-success:hover {
background-color: #218838;
}
.btn-warning {
background-color: #ffc107;
color: black;
}
.btn-warning:hover {
background-color: #e0a800;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover {
background-color: #c82333;
}
.badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.badge-published {
.bg-success {
background-color: #28a745;
color: white;
}
.badge-draft {
.bg-warning {
background-color: #ffc107;
color: black;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<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;
$(document).ready(function() {
// Sorting functionality
const table = document.querySelector('table');
if (table) {
const headers = table.querySelectorAll('thead th');
const rows = Array.from(table.querySelectorAll('tbody tr'));
headers.forEach((header, index) => {
header.addEventListener("click", () => {
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();
@@ -156,54 +183,102 @@ checkAdmin();
return 0;
});
if (header.classList.contains("asc")) {
header.classList.remove("asc");
header.classList.add("desc");
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");
headers.forEach(h => h.classList.remove('asc', 'desc'));
header.classList.add('asc');
}
const tbody = table.querySelector("tbody");
tbody.innerHTML = "";
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() {
// Filter functionality
const filterInput = document.querySelector('.filter-input');
if (filterInput) {
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";
row.style.display = rowText.includes(filterValue) ? '' : 'none';
});
});
}
}
// Publish/Unpublish toggle
$('.toggle-publish').on('click', function() {
var eventId = $(this).data('event-id');
var button = $(this);
var row = button.closest('tr');
$.ajax({
url: 'toggle_event_published',
type: 'POST',
data: {
event_id: eventId
},
dataType: 'json',
success: function(response) {
if (response.status === 'success') {
if (response.published == 1) {
button.removeClass('btn-success').addClass('btn-warning');
button.find('i').removeClass('fa-eye').addClass('fa-eye-slash');
button.attr('title', 'Unpublish');
row.find('td:nth-child(5)').html('<span class="badge bg-success">Published</span>');
} else {
button.removeClass('btn-warning').addClass('btn-success');
button.find('i').removeClass('fa-eye-slash').addClass('fa-eye');
button.attr('title', 'Publish');
row.find('td:nth-child(5)').html('<span class="badge bg-warning">Draft</span>');
}
} else {
alert('Error: ' + response.message);
}
},
error: function() {
alert('Error updating event status');
}
});
});
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',
// Delete event
$('.delete-event').on('click', function() {
if (!confirm('Are you sure you want to delete this event? This action cannot be undone.')) {
return false;
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
location.reload();
var eventId = $(this).data('event-id');
var button = $(this);
var row = button.closest('tr');
$.ajax({
url: 'delete_event',
type: 'POST',
data: {
event_id: eventId
},
dataType: 'json',
success: function(response) {
if (response.status === 'success') {
row.fadeOut(300, function() {
$(this).remove();
});
} else {
alert('Error: ' + data.message);
}
})
.catch(error => console.error('Error:', error));
alert('Error: ' + response.message);
}
},
error: function() {
alert('Error deleting event');
}
});
});
});
</script>
<?php
@@ -212,23 +287,29 @@ checkAdmin();
require_once($rootPath . '/components/banner.php');
?>
<section class="tour-list-page py-10 rel z-1">
<?php
$pageTitle = 'Manage Events';
$breadcrumbs = [['Home' => 'index'], [$pageTitle => '']];
require_once($rootPath . '/components/banner.php');
?>
<!-- Events Management Area start -->
<section class="events-management-area py-100 rel z-1">
<div class="container">
<div class="row mb-20">
<div class="row mb-30">
<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>
if (!empty($events)) {
echo '<div class="row">
<div class="col-lg-12">
<div class="form-group mb-20">
<input type="text" class="filter-input" placeholder="Search events...">
</div>
<table>
<thead>
<tr>
<th>Event Name</th>
@@ -240,36 +321,37 @@ checkAdmin();
</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>
foreach ($events as $event) {
$publishButtonText = $event['published'] == 1 ? 'Unpublish' : 'Publish';
$publishButtonClass = $event['published'] == 1 ? 'btn-warning' : 'btn-success';
echo '<tr>
<td><strong>' . htmlspecialchars($event['name']) . '</strong></td>
<td>' . htmlspecialchars($event['type']) . '</td>
<td>' . htmlspecialchars($event['location']) . '</td>
<td>' . convertDate($event['date']) . '</td>
<td>' . ($event['published'] == 1 ? '<span class="badge bg-success">Published</span>' : '<span class="badge bg-warning">Draft</span>') . '</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>
<a href="manage_events?event_id=' . $event['event_id'] . '" class="btn btn-sm btn-primary" title="Edit">
<i class="far fa-edit"></i>
</a>
<button class="btn btn-sm ' . $publishButtonClass . ' toggle-publish" data-event-id="' . $event['event_id'] . '" title="' . $publishButtonText . '">
<i class="far fa-' . ($event['published'] == 1 ? 'eye-slash' : 'eye') . '"></i>
</button>
<button class="btn btn-sm btn-danger delete-event" data-event-id="' . $event['event_id'] . '" title="Delete">
<i class="far fa-trash"></i>
</button>
</td>
</tr>";
</tr>';
}
echo '</tbody></table>';
echo '</div>';
echo '</div>';
} else {
echo '<div class="alert alert-info">No events found. <a href="manage_events">Create the first event</a></div>';
echo '<p>No events found. <a href="manage_events">Create one</a></p>';
}
?>
</div>
</section>
<!-- Events Management Area end -->
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -0,0 +1,46 @@
<?php
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
header('Content-Type: application/json');
$event_id = $_POST['event_id'] ?? null;
if (!$event_id) {
echo json_encode(['status' => 'error', 'message' => 'Event ID is required']);
exit;
}
// Get event details to delete associated files
$stmt = $conn->prepare("SELECT image, promo FROM events WHERE event_id = ?");
$stmt->bind_param("i", $event_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
$event = $result->fetch_assoc();
// Delete image files
if ($event['image'] && file_exists($rootPath . '/' . $event['image'])) {
unlink($rootPath . '/' . $event['image']);
}
if ($event['promo'] && file_exists($rootPath . '/' . $event['promo'])) {
unlink($rootPath . '/' . $event['promo']);
}
// Delete from database
$delete_stmt = $conn->prepare("DELETE FROM events WHERE event_id = ?");
$delete_stmt->bind_param("i", $event_id);
if ($delete_stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Event deleted successfully']);
} else {
echo json_encode(['status' => 'error', 'message' => 'Failed to delete event']);
}
$delete_stmt->close();
} else {
echo json_encode(['status' => 'error', 'message' => 'Event not found']);
}
$stmt->close();

View File

@@ -118,16 +118,6 @@ if ($event_id) {
</div>
</div>
<!-- Published Status -->
<div class="col-md-6 mt-20">
<div class="form-group">
<label for="published">
<input type="checkbox" id="published" name="published" value="1" <?php echo ($event && $event['published']) ? 'checked' : ''; ?>>
Publish Event (visible on website)
</label>
</div>
</div>
<div class="col-md-12 mt-20">
<div class="form-group mb-0">
<button type="submit" class="theme-btn style-two" style="width:100%;">

View File

@@ -62,7 +62,6 @@ $date = $_POST['date'] ?? null;
$time = $_POST['time'] ?? null;
$feature = $_POST['feature'] ?? null;
$description = $_POST['description'] ?? null;
$published = isset($_POST['published']) ? 1 : 0;
// Validate required fields
if (!$name || !$type || !$location || !$date || !$time || !$feature || !$description) {
@@ -135,7 +134,6 @@ try {
'time' => $time,
'feature' => $feature,
'description' => $description,
'published' => $published,
'updated_at' => date('Y-m-d H:i:s')
];
@@ -174,13 +172,13 @@ try {
$promo_path = $promo_path ?? 'assets/images/events/default-promo.jpg';
$stmt = $conn->prepare("
INSERT INTO events (name, type, location, date, time, feature, description, image, promo, published, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO events (name, type, location, date, time, feature, description, image, promo, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$created_by = $_SESSION['user_id'] ?? 0;
$stmt->bind_param('ssssssssiii', $name, $type, $location, $date, $time, $feature, $description, $image_path, $promo_path, $published, $created_by);
$stmt->bind_param('sssssssssi', $name, $type, $location, $date, $time, $feature, $description, $image_path, $promo_path, $created_by);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Event created successfully']);

View File

@@ -0,0 +1,44 @@
<?php
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
header('Content-Type: application/json');
$event_id = $_POST['event_id'] ?? null;
if (!$event_id) {
echo json_encode(['status' => 'error', 'message' => 'Event ID is required']);
exit;
}
// Get current published status
$stmt = $conn->prepare("SELECT published FROM events WHERE event_id = ?");
$stmt->bind_param("i", $event_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
echo json_encode(['status' => 'error', 'message' => 'Event not found']);
exit;
}
$event = $result->fetch_assoc();
$new_status = $event['published'] == 1 ? 0 : 1;
// Update published status
$update_stmt = $conn->prepare("UPDATE events SET published = ? WHERE event_id = ?");
$update_stmt->bind_param("ii", $new_status, $event_id);
if ($update_stmt->execute()) {
echo json_encode([
'status' => 'success',
'message' => $new_status == 1 ? 'Event published' : 'Event unpublished',
'published' => $new_status
]);
} else {
echo json_encode(['status' => 'error', 'message' => 'Failed to update event status']);
}
$stmt->close();
$update_stmt->close();