Files
4WDCSA.co.za/src/admin/admin_events.php
twotalesanimation 9133b7bbc6 feat: improve campsites and events management UX
- Add map-based location picker with centered pin for campsites (two-step process)
- Hide edit buttons for campsites not owned by current user
- Allow numbers in campsite names (fix validateName function)
- Prepopulate edit form with existing campsite data
- Preserve country/province selection when confirming location
- Add real-time filter functionality to campsites table
- Fix events publish button error handling (use output buffering cleanup)
- Improve AJAX response handling with complete callback

Changes:
- src/pages/bookings/campsites.php: Location mode UI, filter, edit form improvements
- src/config/functions.php: Allow numbers in validateName regex
- src/admin/toggle_event_published.php: Clean output buffers before JSON response
- src/admin/admin_events.php: Use complete callback instead of success/error handlers
2025-12-05 09:20:48 +02:00

362 lines
11 KiB
PHP

<?php
$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>
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;
}
.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-sm {
padding: 4px 8px;
font-size: 12px;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.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;
}
.bg-success {
background-color: #28a745;
color: white;
}
.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).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', () => {
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));
});
});
// 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';
});
});
}
}
// 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',
complete: function(xhr, status) {
// Handle all response codes
try {
var response = JSON.parse(xhr.responseText);
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);
}
} catch (e) {
alert('Error updating event status. Response: ' + xhr.responseText);
}
}
});
});
// 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;
}
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: ' + response.message);
}
},
error: function() {
alert('Error deleting event');
}
});
});
});
</script>
<?php
$pageTitle = 'Manage Events';
$breadcrumbs = [['Home' => 'index'], [$pageTitle => '']];
require_once($rootPath . '/components/banner.php');
?>
<?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-30">
<div class="col-lg-12">
<a href="manage_events" class="theme-btn style-two">+ Create New Event</a>
</div>
</div>
<?php
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>
<th>Type</th>
<th>Location</th>
<th>Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>';
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>
<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>';
}
echo '</tbody></table>';
echo '</div>';
echo '</div>';
} else {
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'); ?>