consolodated admin pages

This commit is contained in:
twotalesanimation
2025-12-17 12:42:09 +02:00
parent 927f9f3fe1
commit d5feaacddf
14 changed files with 679 additions and 1525 deletions

View File

@@ -86,6 +86,8 @@ RewriteRule ^failure$ src/pages/payment/failure.php [L]
RewriteRule ^cancel$ src/pages/payment/cancel.php [L]
# === ADMIN PAGES ===
RewriteRule ^admin_trips_events_courses$ src/admin/admin_trips_events_courses.php [L]
RewriteRule ^admin_bookings$ src/admin/admin_bookings.php [L]
RewriteRule ^admin_members$ src/admin/admin_members.php [L]
RewriteRule ^admin_payments$ src/admin/admin_payments.php [L]
RewriteRule ^admin_web_users$ src/admin/admin_web_users.php [L]
@@ -147,7 +149,7 @@ RewriteRule ^link_membership_user$ src/processors/link_membership_user.php [L]
RewriteRule ^unlink_membership_user$ src/processors/unlink_membership_user.php [L]
# Blog routes
RewriteRule ^admin_blogs$ src/pages/blog/admin_blogs.php [L]
RewriteRule ^admin_blogs$ src/admin/admin_blogs.php [L]
RewriteRule ^user_blogs$ src/pages/blog/user_blogs.php [L]
RewriteRule ^blog_read$ src/pages/blog/blog_read.php [L]
RewriteRule ^blog_edit$ src/pages/blog/blog_edit.php [L]

View File

@@ -285,12 +285,8 @@ if ($headerStyle === 'light') {
<ul>
<li><a href="admin_web_users">Website Users</a></li>
<li><a href="admin_members">4WDCSA Members</a></li>
<li><a href="admin_blogs">Manage Blogs</a></li>
<li><a href="admin_events">Manage Events</a></li>
<li><a href="admin_trips">Manage Trips</a></li>
<li><a href="admin_courses">Manage Courses</a></li>
<li><a href="admin_trip_bookings">Trip Bookings</a></li>
<li><a href="admin_course_bookings">Course Bookings</a></li>
<li><a href="admin_trips_events_courses">Trips, Events & Courses</a></li>
<li><a href="admin_bookings">Bookings</a></li>
<li><a href="admin_transactions">iKhokha Payment History</a></li>
<!-- <li><a href="process_payments">Process Payments</a></li> -->
<?php if ($role === 'superadmin') { ?>

View File

@@ -180,8 +180,6 @@ if (countUpcomingTrips() > 0) { ?>
}
?>
<!-- About Us Area start -->
<section class="about-us-area py-100 rpb-90 rel z-1">
<div class="container">
@@ -305,7 +303,6 @@ if (countUpcomingTrips() > 0) { ?>
</section>
<!-- Features Area end -->
<!-- Hotel Area start -->
<section class="hotel-area bgc-black py-100 rel z-1">
<div class="container-fluid">

View File

@@ -123,3 +123,6 @@
[2025-12-16 22:38:31] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
[2025-12-16 22:38:31] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
[2025-12-16 22:38:31] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
[2025-12-17 12:35:13] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
[2025-12-17 12:35:13] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
[2025-12-17 12:35:13] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature

View File

@@ -0,0 +1,230 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
// Ensure $conn is available; show a friendly message if DB is down
$conn = $conn ?? null;
if (!$conn) {
// Render a simple error notice inside the page layout and stop further DB queries
echo '<section class="tour-list-page py-10 rel z-1">\n <div class="container">\n <div class="alert alert-danger">Database connection unavailable. Please check your configuration and logs.</div>\n </div>\n</section>';
include_once($rootPath . '/components/insta_footer.php');
// Stop execution to prevent subsequent fatal errors from using $conn
exit;
}
?>
<style>
/* Shared table and tab styles */
.admin-tabs { text-align: center; display: flex; margin-bottom: 20px; margin-top: 20px; }
.admin-tab { padding: 10px 30px; margin-right: 20px; cursor: pointer; border: 2px solid; border-radius: 50px; background: none; font-size: 18px; color: #484848; }
.admin-tab.active { color: white; background: #63ab45; font-weight: bold; }
.tab-content { display: none; }
.tab-content.active { display: block; }
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'; font-size: 0.8em; position: absolute; right: 10px; opacity: 0; transition: opacity 0.2s; }
thead th.asc::after { content: '\25B2'; opacity: 1; }
thead th.desc::after { content: '\25BC'; 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: 5px; }
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: 5px; font-size: 16px; background-color: rgb(255, 255, 255); border-radius: 25px; }
.trip-booking { color: #484848; background: #f9f9f7; border: 1px solid #d8d8d8; border-radius: 10px; margin-top: 15px; margin-bottom: 15px; }
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Tab switching
const tabs = document.querySelectorAll('.admin-tab');
const contents = document.querySelectorAll('.tab-content');
tabs.forEach((tab, idx) => {
tab.addEventListener('click', function() {
tabs.forEach(t => t.classList.remove('active'));
contents.forEach(c => c.classList.remove('active'));
tab.classList.add('active');
contents[idx].classList.add('active');
});
});
// Table sorting/filtering (same as before)
document.querySelectorAll("table").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";
});
});
}
});
});
</script>
<?php
$pageTitle = 'Manage Bookings';
$breadcrumbs = [['Home' => 'index']];
require_once($rootPath . '/components/banner.php');
?>
<section class="tour-list-page py-10 rel z-1">
<div class="container">
<div class="admin-tabs">
<button class="admin-tab active">Trip Bookings</button>
<button class="admin-tab">Course Bookings</button>
<button class="admin-tab">Camp Bookings</button>
</div>
<div class="tab-content active">
<?php // Trip Bookings Tab
$tripsSql = "SELECT trip_id, trip_name FROM trips";
$tripsResult = $conn->query($tripsSql);
if ($tripsResult && $tripsResult->num_rows > 0) {
while ($trip = $tripsResult->fetch_assoc()) {
$tripId = $trip['trip_id'];
$tripName = htmlspecialchars($trip['trip_name']);
echo "<div class='trip-booking' data-aos='fade-up' data-aos-duration='1500' data-aos-offset='50'>";
echo "<div style='padding:10px;'>";
echo "<h4>{$tripName}</h4>";
$bookingsSql = "SELECT b.user_id, b.num_vehicles, b.num_adults, b.num_children, b.num_pensioners, b.radio, b.status, u.first_name, u.last_name, u.profile_pic, (b.total_amount - b.discount_amount) AS paid FROM bookings b INNER JOIN users u ON b.user_id = u.user_id WHERE b.trip_id = ?";
$stmt = $conn->prepare($bookingsSql);
$stmt->bind_param('i', $tripId);
$stmt->execute();
$bookingsResult = $stmt->get_result();
if ($bookingsResult->num_rows > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter results...">';
echo '<table><thead><tr><th></th><th>Name</th><th>Vehicles</th><th>Adults</th><th>Children</th><th>Pensioners</th><th>Radio</th><th>Status</th><th>Amount</th></tr></thead><tbody>';
while ($booking = $bookingsResult->fetch_assoc()) {
$userName = htmlspecialchars($booking['first_name'] . ' ' . $booking['last_name']);
$numVehicles = htmlspecialchars($booking['num_vehicles']);
$numAdults = htmlspecialchars($booking['num_adults']);
$numPensioners = htmlspecialchars($booking['num_pensioners']);
$numChildren = htmlspecialchars($booking['num_children']);
$radio = $booking['radio'] == 1 ? "YES" : "NO";
$status = htmlspecialchars($booking['status']);
$paid = "R " . number_format($booking['paid'], 2);
echo "<tr><td><img src='".$booking['profile_pic']."' alt='Profile Picture' class='profile-pic'></td><td>{$userName}</td><td>{$numVehicles}</td><td>{$numAdults}</td><td>{$numChildren}</td><td>{$numPensioners}</td><td>{$radio}</td><td>{$status}</td><td>{$paid}</td></tr>";
}
echo '</tbody></table>';
} else {
echo '<p>No bookings found for this trip.</p>';
}
echo "</div></div>";
}
} else {
echo '<p>No trips found.</p>';
}
?>
</div>
<div class="tab-content">
<?php // Course Bookings Tab
$courseSql = "SELECT date, course_id, course_type FROM courses";
$courseResult = $conn->query($courseSql);
if ($courseResult && $courseResult->num_rows > 0) {
while ($course = $courseResult->fetch_assoc()) {
$course_id = $course['course_id'];
$date = $course['date'];
$type = htmlspecialchars($course['course_type']);
if ($type === "driver_training") {
$course_name = "Basic 4X4 Driver Training Course ".$date;
} elseif ($type === "bush_mechanics") {
$course_name = "Bush Mechanics Course ".$date;
} elseif ($type === "rescue_recovery") {
$course_name = "Rescue & Recovery Training Course ".$date;
} else {
$course_name = "General Course ".$date;
}
echo "<div class='trip-booking' data-aos='fade-up' data-aos-duration='1500' data-aos-offset='50'>";
echo "<div style='padding:10px;'>";
echo "<h4>{$course_name}</h4>";
$bookingsSql = "SELECT b.user_id, b.num_adults, b.total_amount, b.status, b.course_non_members, u.first_name, u.last_name, u.profile_pic FROM bookings b INNER JOIN users u ON b.user_id = u.user_id WHERE b.course_id = ?";
if ($stmt = $conn->prepare($bookingsSql)) {
$stmt->bind_param('i', $course_id);
$stmt->execute();
$bookingsResult = $stmt->get_result();
} else {
echo "Error in prepared statement: " . $conn->error;
}
if ($bookingsResult && $bookingsResult->num_rows > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter results...">';
echo '<table><thead><tr><th></th><th>Name</th><th>Members</th><th>Non-Members</th><th>Status</th><th>Amount</th></tr></thead><tbody>';
while ($booking = $bookingsResult->fetch_assoc()) {
$userName = htmlspecialchars($booking['first_name'] . ' ' . $booking['last_name']);
$members = htmlspecialchars($booking['num_adults']);
$non_members = htmlspecialchars($booking['course_non_members']);
$status = htmlspecialchars($booking['status']);
$paid = "R " . number_format($booking['total_amount'], 2);
echo "<tr><td><img src='".$booking['profile_pic']."' alt='Profile Picture' class='profile-pic'></td><td>{$userName}</td><td>{$members}</td><td>{$non_members}</td><td>{$status}</td><td>{$paid}</td></tr>";
}
echo '</tbody></table>';
} else {
echo '<p>No bookings found for this course.</p>';
}
echo "</div></div>";
}
} else {
echo '<p>No courses found.</p>';
}
?>
</div>
<div class="tab-content">
<?php // Camp Bookings Tab
echo "<div class='trip-booking' data-aos='fade-up' data-aos-duration='1500' data-aos-offset='50'>";
echo "<div style='padding:10px;'>";
echo "<h4>BASE4 Camping</h4>";
$bookingsSql = "SELECT b.user_id, b.from_date, b.to_date, b.num_vehicles, b.num_adults, b.num_children, b.add_firewood, b.status, u.first_name, u.last_name, (b.total_amount - b.discount_amount) AS paid FROM bookings b INNER JOIN users u ON b.user_id = u.user_id WHERE b.booking_type = 'camping'";
$stmt = $conn->prepare($bookingsSql);
$stmt->execute();
$bookingsResult = $stmt->get_result();
if ($bookingsResult && $bookingsResult->num_rows > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter results...">';
echo '<table><thead><tr><th>Name</th><th>From</th><th>To</th><th>Vehicles</th><th>Adults</th><th>Children</th><th>Add Firewood</th><th>Status</th><th>Amount</th></tr></thead><tbody>';
while ($booking = $bookingsResult->fetch_assoc()) {
$userName = htmlspecialchars($booking['first_name'] . ' ' . $booking['last_name']);
$numVehicles = htmlspecialchars($booking['num_vehicles']);
$from = htmlspecialchars($booking['from_date']);
$to = htmlspecialchars($booking['to_date']);
$numAdults = htmlspecialchars($booking['num_adults']);
$numChildren = htmlspecialchars($booking['num_children']);
$radio = $booking['add_firewood'] == 1 ? "YES" : "NO";
$status = htmlspecialchars($booking['status']);
$paid = "R " . number_format($booking['paid'], 2);
echo "<tr><td>{$userName}</td><td>{$from}</td><td>{$to}</td><td>{$numVehicles}</td><td>{$numAdults}</td><td>{$numChildren}</td><td>{$radio}</td><td>{$status}</td><td>{$paid}</td></tr>";
}
echo '</tbody></table>';
} else {
echo '<p>No bookings found for this trip.</p>';
}
echo "</div></div>";
?>
</div>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -1,227 +0,0 @@
<?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: 5px;
}
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: 5px;
/* margin-bottom: 20px; */
font-size: 16px;
background-color: rgb(255, 255, 255);
border-radius: 25px;
}
.trip-booking {
color: #484848;
background: #f9f9f7;
border: 1px solid #d8d8d8;
border-radius: 10px;
margin-top: 15px;
margin-bottom: 15px;
}
</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";
});
});
}
});
});
</script>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white mb-50">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">4WDCSA Camping Bookings</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Camping Bookings</li>
</ol>
</nav>
</div>
</div>
</section>
<section class="tour-list-page py-10 rel z-1">
<div class="container">
<?php
echo "<div class='trip-booking' data-aos='fade-up' data-aos-duration='1500' data-aos-offset='50'>";
echo "<div style='padding:10px;'>";
echo "<h4>BASE4 Camping</h4>";
// Fetch bookings for the current trip
$bookingsSql = "SELECT b.user_id, b.from_date, b.to_date, b.num_vehicles, b.num_adults, b.num_children, b.add_firewood, b.status,
u.first_name, u.last_name,
(b.total_amount - b.discount_amount) AS paid
FROM bookings b
INNER JOIN users u ON b.user_id = u.user_id
WHERE b.booking_type = 'camping'";
$stmt = $conn->prepare($bookingsSql);
$stmt->execute();
$bookingsResult = $stmt->get_result();
if ($bookingsResult->num_rows > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter results...">';
echo '<table>
<thead>
<tr>
<th>Name</th>
<th>From</th>
<th>To</th>
<th>Vehicles</th>
<th>Adults</th>
<th>Children</th>
<th>Add Firewood</th>
<th>Status</th>
<th>Amount</th>
</tr>
</thead>
<tbody>';
while ($booking = $bookingsResult->fetch_assoc()) {
$userName = htmlspecialchars($booking['first_name'] . ' ' . $booking['last_name']);
$numVehicles = htmlspecialchars($booking['num_vehicles']);
$from = htmlspecialchars($booking['from_date']);
$to = htmlspecialchars($booking['to_date']);
$numAdults = htmlspecialchars($booking['num_adults']);
$numChildren = htmlspecialchars($booking['num_children']);
$radio = $booking['add_firewood'] == 1 ? "YES" : "NO";
$status = htmlspecialchars($booking['status']);
$paid = "R " . number_format($booking['paid'], 2);
echo "<tr>
<td>{$userName}</td>
<td>{$from}</td>
<td>{$to}</td>
<td>{$numVehicles}</td>
<td>{$numAdults}</td>
<td>{$numChildren}</td>
<td>{$radio}</td>
<td>{$status}</td>
<td>{$paid}</td>
</tr>";
}
echo '</tbody></table>';
} else {
echo '<p>No bookings found for this trip.</p>';
}
echo "</div>";
echo "</div>";
?>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -1,247 +0,0 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
// Fetch all trips
$courseSql = "SELECT date, course_id, course_type FROM courses";
$courseResult = $conn->query($courseSql);
if (!$courseResult) {
echo "Error in SQL query: " . $conn->error;
}
?>
<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: 5px;
}
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: 5px;
/* margin-bottom: 20px; */
font-size: 16px;
background-color: rgb(255, 255, 255);
border-radius: 25px;
}
.trip-booking {
color: #484848;
background: #f9f9f7;
border: 1px solid #d8d8d8;
border-radius: 10px;
margin-top: 15px;
margin-bottom: 15px;
}
</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";
});
});
}
});
});
</script>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white mb-50">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">4WDCSA Course Bookings</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Course Bookings</li>
</ol>
</nav>
</div>
</div>
</section>
<section class="tour-list-page py-10 rel z-1">
<div class="container">
<?php
if ($courseResult->num_rows > 0) {
while ($course = $courseResult->fetch_assoc()) {
$course_id = $course['course_id'];
$date = $course['date'];
$type = htmlspecialchars($course['course_type']);
if ($type === "driver_training") {
$course_name = "Basic 4X4 Driver Training Course ".$date;
} elseif ($type === "bush_mechanics") {
$course_name = "Bush Mechanics Course ".$date;
} elseif ($type === "rescue_recovery") {
$course_name = "Rescue & Recovery Training Course ".$date;
} else {
$course_name = "General Course ".$date; // Default fallback description
}
echo "<div class='trip-booking' data-aos='fade-up' data-aos-duration='1500' data-aos-offset='50'>";
echo "<div style='padding:10px;'>";
echo "<h4>{$course_name}</h4>";
// Fetch bookings for the current trip
$bookingsSql = "SELECT b.user_id, b.num_adults, b.total_amount, b.status, b.course_non_members,
u.first_name, u.last_name, u.profile_pic
FROM bookings b
INNER JOIN users u ON b.user_id = u.user_id
WHERE b.course_id = ?";
if ($stmt = $conn->prepare($bookingsSql)) {
$stmt->bind_param('i', $course_id);
$stmt->execute();
$bookingsResult = $stmt->get_result();
} else {
echo "Error in prepared statement: " . $conn->error;
}
if ($bookingsResult->num_rows > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter results...">';
echo '<table>
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Members</th>
<th>Non-Members</th>
<th>Status</th>
<th>Amount</th>
</tr>
</thead>
<tbody>';
while ($booking = $bookingsResult->fetch_assoc()) {
$userName = htmlspecialchars($booking['first_name'] . ' ' . $booking['last_name']);
$members = htmlspecialchars($booking['num_adults']);
$non_members = htmlspecialchars($booking['course_non_members']);
$status = htmlspecialchars($booking['status']);
$paid = "R " . number_format($booking['total_amount'], 2);
echo "<tr>
<td><img src=".$booking['profile_pic']." alt='Profile Picture' class='profile-pic'></td>
<td>{$userName}</td>
<td>{$members}</td>
<td>{$non_members}</td>
<td>{$status}</td>
<td>{$paid}</td>
</tr>";
}
echo '</tbody></table>';
} else {
echo '<p>No bookings found for this trip.</p>';
}
echo "</div>";
echo "</div>";
}
} else {
echo '<p>No courses found.</p>';
}
?>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -1,161 +0,0 @@
<?php
$rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . '/src/config/connection.php');
require_once($rootPath . '/src/config/functions.php');
require_once($rootPath . '/header.php');
checkAdmin();
checkUserSession();
$pageTitle = 'Manage Courses';
$breadcrumbs = [['Home' => 'index']];
require_once($rootPath . '/components/banner.php');
// Fetch all courses
$courses_query = "
SELECT
course_id, course_type, date, capacity, booked, cost_members, cost_nonmembers, instructor, instructor_email, code
FROM courses
ORDER BY date DESC
";
$result = $conn->query($courses_query);
$courses = [];
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$courses[] = $row;
}
}
?>
<!-- Courses Management Area start -->
<section class="blog-list-page py-100 rel z-1">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h2 style="margin: 0;">Manage Courses</h2>
<a href="manage_courses" class="theme-btn create-album-btn">
<i class="far fa-plus"></i> New Course
</a>
</div>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-warning message-box">
<?php echo $_SESSION['message']; ?>
<span class="close-btn" onclick="this.parentElement.style.display='none'">&times;</span>
</div>
<?php unset($_SESSION['message']);
endif;?>
<?php if (count($courses) > 0): ?>
<input type="text" class="filter-input" placeholder="Filter courses...">
<div class="courses-container" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<?php foreach ($courses as $course):
$available = intval($course['capacity']) - intval($course['booked']);
$type_label = strtoupper($course['course_type']);
if ($course['course_type'] == 'driver_training') {
$type_label = 'Driver Training';
} elseif ($course['course_type'] == 'bush_mechanics') {
$type_label = 'Bush Mechanics';
} elseif ($course['course_type'] == 'rescue_recovery') {
$type_label = 'Rescue & Recovery';
} elseif ($course['course_type'] == 'ladies_driver_training') {
$type_label = 'Ladies Driver Training';
}
?>
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
<div>
<h5 class="mb-0"><?php echo $type_label; ?></h5>
<small class="text-muted"><?php echo htmlspecialchars($course['course_type']); ?> — <?php echo date('M d, Y', strtotime($course['date'])); ?></small><br
<small class="text-muted"><?php echo $course['code'] ? 'Code: ' . htmlspecialchars($course['code']) : ''; ?></small>
</div>
</div>
<p style="margin: 10px 0;">
<strong>Instructor:</strong> <?php echo htmlspecialchars($course['instructor']); ?> (<?php echo htmlspecialchars($course['instructor_email']); ?>)<br>
<strong>Capacity:</strong> <?php echo intval($course['booked']); ?> / <?php echo intval($course['capacity']); ?> &nbsp; <strong>Available:</strong> <?php echo $available; ?><br>
<strong>Costs:</strong> Members: R <?php echo number_format($course['cost_members'],2); ?> | Non-Members: R <?php echo number_format($course['cost_nonmembers'],2); ?>
</p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="manage_courses?course_id=<?php echo $course['course_id']; ?>" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<button type="button" class="delete-course" data-course-id="<?php echo $course['course_id']; ?>" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">delete</span></button>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="no-courses">
<p>No courses found. <a href="manage_courses">Create one</a></p>
</div>
<?php endif; ?>
</div>
</div>
</div>
</section>
<!-- Courses Management Area end -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const filterInput = document.querySelector('.filter-input');
const cards = document.querySelectorAll('.destination-item');
if (cards.length === 0 && filterInput) {
filterInput.style.display = "none";
} else if (filterInput) {
filterInput.addEventListener("input", function() {
const filterValue = filterInput.value.trim().toLowerCase();
cards.forEach(card => {
const cardText = card.textContent.trim().toLowerCase();
card.style.display = cardText.includes(filterValue) ? "" : "none";
});
});
}
});
// Handle delete button clicks
document.querySelectorAll('.delete-course').forEach(btn => {
btn.addEventListener('click', function() {
if (!confirm('Are you sure you want to delete this course? This action cannot be undone.')) {
return false;
}
const courseId = this.dataset.courseId;
const card = this.closest('.destination-item');
const formData = new FormData();
formData.append('course_id', courseId);
fetch('delete_course', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('Course deleted successfully!');
card.remove();
if (document.querySelectorAll('.destination-item').length === 0) {
location.reload();
}
} else {
alert('Error: ' + data.message);
}
})
.catch(err => {
console.error('Error:', err);
alert('Delete failed due to network error.');
});
});
});
</script>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -1,230 +0,0 @@
<?php
$rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/header.php");
checkAdmin();
checkUserSession();
$pageTitle = 'Manage Events';
$breadcrumbs = [['Home' => 'index']];
require_once($rootPath . '/components/banner.php');
// Fetch all events
$events_query = "
SELECT
event_id, name, type, location, date, image, 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>
.image {
width: 300px;
/* Set your desired width */
height: 250px;
/* Set your desired height */
overflow: hidden;
/* Hide any overflow */
display: block;
/* Ensure proper block behavior */
border-radius: 8px;
}
.image img {
width: 100%;
/* Image scales to fill the container */
height: 100%;
/* Image scales to fill the container */
object-fit: cover;
/* Fills the container while maintaining aspect ratio */
object-position: center;
/* Aligns the center of the image with the center of the container */
display: block;
/* Prevents inline whitespace issues */
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const filterInput = document.querySelector('.filter-input');
const cards = document.querySelectorAll('.destination-item');
if (cards.length === 0 && filterInput) {
filterInput.style.display = "none";
} else if (filterInput) {
filterInput.addEventListener("input", function() {
const filterValue = filterInput.value.trim().toLowerCase();
cards.forEach(card => {
const cardText = card.textContent.trim().toLowerCase();
card.style.display = cardText.includes(filterValue) ? "" : "none";
});
});
}
});
</script>
<?php
$pageTitle = 'Manage Events';
$breadcrumbs = [['Home' => 'index']];
require_once($rootPath . '/components/banner.php');
?>
<!-- Events Management Area start -->
<section class="blog-list-page py-100 rel z-1">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h2 style="margin: 0;">Manage Events</h2>
<a href="manage_events" class="theme-btn create-album-btn">
<i class="far fa-plus"></i> New Event
</a>
</div>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-warning message-box">
<?php echo $_SESSION['message']; ?>
<span class="close-btn" onclick="this.parentElement.style.display='none'">&times;</span>
</div>
<?php unset($_SESSION['message']);
endif;
if (count($events) > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter events...">';
echo '<div class="events-container" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">';
foreach ($events as $event) {
$eventImagePath = $event['image'] ? htmlspecialchars($event['image']) : 'assets/images/placeholder.jpg';
$publishStatusBadge = $event['published'] == 1 ? 'PUBLISHED' : 'DRAFT';
echo '
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="image" style="width:300px;height:250px;">
<img src="' . $eventImagePath . '" alt="' . htmlspecialchars($event['name']) . '">
</div>
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
<div>
<span class="badge bg-dark mb-1">' . strtoupper($publishStatusBadge) . '</span>
<h5 class="mb-0">' . htmlspecialchars($event['name']) . '</h5>
<small class="text-muted">📍 ' . htmlspecialchars($event['location']) . '</small>
</div>
</div>
<p style="margin: 10px 0;">
<strong>Type:</strong> ' . htmlspecialchars($event['type']) . '<br>
<strong>Date:</strong> ' . convertDate($event['date']) . '
</p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="manage_events?event_id=' . $event['event_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<button type="button" class="toggle-publish" data-event-id="' . $event['event_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="' . ($event['published'] == 1 ? 'Unpublish' : 'Publish') . '" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">' . ($event['published'] == 1 ? 'cloud_off' : 'cloud_upload') . '</span></button>
<button type="button" class="delete-event" data-event-id="' . $event['event_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">delete</span></button>
</div>
</div>
</div>
</div>
';
}
echo '</div>';
} else {
echo '<div class="no-events">
<p>No events found. <a href="manage_events">Create one</a></p>
</div>';
}
?>
</div>
</div>
</div>
</section>
<!-- Events Management Area end -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipTriggerList.forEach(el => new bootstrap.Tooltip(el));
// Handle publish/unpublish button clicks
document.querySelectorAll('.toggle-publish').forEach(btn => {
btn.addEventListener('click', function() {
const eventId = this.dataset.eventId;
const currentIcon = this.querySelector('.material-icons').textContent;
const isPublished = currentIcon === 'cloud_off';
const action = isPublished ? 'Unpublish' : 'Publish';
const formData = new FormData();
formData.append('event_id', eventId);
fetch('toggle_event_published', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert(action + ' successful!');
location.reload();
} else {
alert(action + ' failed: ' + data.message);
}
})
.catch(err => {
console.error('Error:', err);
alert(action + ' failed due to network error.');
});
});
});
// Handle delete button clicks
document.querySelectorAll('.delete-event').forEach(btn => {
btn.addEventListener('click', function() {
if (!confirm('Are you sure you want to delete this event? This action cannot be undone.')) {
return false;
}
const eventId = this.dataset.eventId;
const card = this.closest('.destination-item');
const formData = new FormData();
formData.append('event_id', eventId);
fetch('delete_event', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
card.style.animation = 'fadeOut 0.3s ease-out';
setTimeout(() => {
card.remove();
if (document.querySelectorAll('.destination-item').length === 0) {
location.reload();
}
}, 300);
} else {
alert('Error: ' + data.message);
}
})
.catch(err => {
console.error('Error:', err);
alert('Delete failed due to network error.');
});
});
});
</script>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -1,240 +0,0 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
// Fetch all trips
$tripsSql = "SELECT trip_id, trip_name FROM trips";
$tripsResult = $conn->query($tripsSql);
?>
<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: 5px;
}
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: 5px;
/* margin-bottom: 20px; */
font-size: 16px;
background-color: rgb(255, 255, 255);
border-radius: 25px;
}
.trip-booking {
color: #484848;
background: #f9f9f7;
border: 1px solid #d8d8d8;
border-radius: 10px;
margin-top: 15px;
margin-bottom: 15px;
}
</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";
});
});
}
});
});
</script>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
if (!empty($bannerImages)) {
$randomBanner = $bannerImages[array_rand($bannerImages)];
}
?>
<section class="page-banner-area pt-50 pb-35 rel z-1 bgs-cover" style="background-image: url('<?php echo $randomBanner; ?>');">
<div class="banner-overlay"></div>
<div class="container">
<div class="banner-inner text-white mb-50">
<h2 class="page-title mb-10" data-aos="fade-left" data-aos-duration="1500" data-aos-offset="50">4WDCSA Trip Bookings</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-20" data-aos="fade-right" data-aos-delay="200" data-aos-duration="1500" data-aos-offset="50">
<li class="breadcrumb-item"><a href="index.php">Home</a></li>
<li class="breadcrumb-item active">Trip Bookings</li>
</ol>
</nav>
</div>
</div>
</section>
<section class="tour-list-page py-10 rel z-1">
<div class="container">
<?php
if ($tripsResult->num_rows > 0) {
while ($trip = $tripsResult->fetch_assoc()) {
$tripId = $trip['trip_id'];
$tripName = htmlspecialchars($trip['trip_name']);
echo "<div class='trip-booking' data-aos='fade-up' data-aos-duration='1500' data-aos-offset='50'>";
echo "<div style='padding:10px;'>";
echo "<h4>{$tripName}</h4>";
// Fetch bookings for the current trip
$bookingsSql = "SELECT b.user_id, b.num_vehicles, b.num_adults, b.num_children, b.num_pensioners, b.radio, b.status,
u.first_name, u.last_name, u.profile_pic,
(b.total_amount - b.discount_amount) AS paid
FROM bookings b
INNER JOIN users u ON b.user_id = u.user_id
WHERE b.trip_id = ?";
$stmt = $conn->prepare($bookingsSql);
$stmt->bind_param('i', $tripId);
$stmt->execute();
$bookingsResult = $stmt->get_result();
if ($bookingsResult->num_rows > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter results...">';
echo '<table>
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Vehicles</th>
<th>Adults</th>
<th>Children</th>
<th>Pensioners</th>
<th>Radio</th>
<th>Status</th>
<th>Amount</th>
</tr>
</thead>
<tbody>';
while ($booking = $bookingsResult->fetch_assoc()) {
$userName = htmlspecialchars($booking['first_name'] . ' ' . $booking['last_name']);
$numVehicles = htmlspecialchars($booking['num_vehicles']);
$numAdults = htmlspecialchars($booking['num_adults']);
$numPensioners = htmlspecialchars($booking['num_pensioners']);
$numChildren = htmlspecialchars($booking['num_children']);
$radio = $booking['radio'] == 1 ? "YES" : "NO";
$status = htmlspecialchars($booking['status']);
$paid = "R " . number_format($booking['paid'], 2);
echo "<tr>
<td><img src=".$booking['profile_pic']." alt='Profile Picture' class='profile-pic'></td>
<td>{$userName}</td>
<td>{$numVehicles}</td>
<td>{$numAdults}</td>
<td>{$numChildren}</td>
<td>{$numPensioners}</td>
<td>{$radio}</td>
<td>{$status}</td>
<td>{$paid}</td>
</tr>";
}
echo '</tbody></table>';
} else {
echo '<p>No bookings found for this trip.</p>';
}
echo "</div>";
echo "</div>";
}
} else {
echo '<p>No trips found.</p>';
}
?>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -1,244 +0,0 @@
<?php
$rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/header.php");
checkAdmin();
checkUserSession();
$pageTitle = 'Manage Trips';
$breadcrumbs = [['Home' => 'index']];
require_once($rootPath . '/components/banner.php');
// Fetch all trips with booking status
$trips_query = "
SELECT
trip_id, trip_name, location, start_date, end_date,
vehicle_capacity, places_booked, cost_members, cost_nonmembers,
cost_pensioner_member, cost_pensioner, published
FROM trips
ORDER BY start_date DESC
";
$result = $conn->query($trips_query);
$trips = [];
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$trips[] = $row;
}
}
?>
<style>
.image {
width: 200px;
/* Set your desired width */
height: 200px;
/* Set your desired height */
overflow: hidden;
/* Hide any overflow */
display: block;
/* Ensure proper block behavior */
border-radius: 8px;
}
.image img {
width: 100%;
/* Image scales to fill the container */
height: 100%;
/* Image scales to fill the container */
object-fit: cover;
/* Fills the container while maintaining aspect ratio */
object-position: center;
/* Aligns the center of the image with the center of the container */
display: block;
/* Prevents inline whitespace issues */
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
const filterInput = document.querySelector('.filter-input');
const cards = document.querySelectorAll('.destination-item');
if (cards.length === 0 && filterInput) {
filterInput.style.display = "none";
} else if (filterInput) {
filterInput.addEventListener("input", function() {
const filterValue = filterInput.value.trim().toLowerCase();
cards.forEach(card => {
const cardText = card.textContent.trim().toLowerCase();
card.style.display = cardText.includes(filterValue) ? "" : "none";
});
});
}
});
</script>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
?>
<!-- Trips Management Area start -->
<section class="blog-list-page py-100 rel z-1">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h2 style="margin: 0;">Manage Trips</h2>
<a href="manage_trips" class="theme-btn create-album-btn">
<i class="far fa-plus"></i> New Event
</a>
</div>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-warning message-box">
<?php echo $_SESSION['message']; ?>
<span class="close-btn" onclick="this.parentElement.style.display='none'">&times;</span>
</div>
<?php unset($_SESSION['message']);
endif;
if (count($trips) > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter trips...">';
echo '<div class="trips-container" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">';
foreach ($trips as $trip) {
$available = $trip['vehicle_capacity'] - $trip['places_booked'];
$publishStatus = $trip['published'] == 1 ? 'published' : 'draft';
$publishStatusBadge = $trip['published'] == 1 ? 'PUBLISHED' : 'DRAFT';
// Get trip image - look for assets/images/trips/$trip_id_{number}.jpg
$tripImagePath = '';
$tripImagesGlob = glob($rootPath . '/assets/images/trips/' . $trip['trip_id'] . '_*.jpg');
if (!empty($tripImagesGlob)) {
$tripImagePath = str_replace($rootPath, '', $tripImagesGlob[0]);
} else {
// Fallback to placeholder icon if no image found
$tripImagePath = 'assets/images/placeholder.jpg';
}
echo '
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="image" style="width:300px;height:250px;">
<img src="' . htmlspecialchars($tripImagePath) . '" alt="' . htmlspecialchars($trip['trip_name']) . '">
</div>
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
<div>
<span class="badge bg-dark mb-1">' . strtoupper($publishStatusBadge) . '</span>
<h5 class="mb-0">' . htmlspecialchars($trip['trip_name']) . '</h5>
<small class="text-muted">📍 ' . htmlspecialchars($trip['location']) . '</small>
</div>
</div>
<p style="margin: 10px 0;">
<strong>Dates:</strong> ' . date('M d', strtotime($trip['start_date'])) . ' - ' . date('M d, Y', strtotime($trip['end_date'])) . '<br>
<strong>Capacity:</strong> ' . $trip['places_booked'] . ' / ' . $trip['vehicle_capacity'] . '<br>
<strong>Costs:</strong> Members: R ' . number_format($trip['cost_members'], 2) . ' | Non-Members: R ' . number_format($trip['cost_nonmembers'], 2) . ' | Pensioner Members: R ' . number_format($trip['cost_pensioner_member'], 2) . ' | Pensioners: R ' . number_format($trip['cost_pensioner'], 2) . '
</p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="manage_trips?trip_id=' . $trip['trip_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<button type="button" class="toggle-publish" data-trip-id="' . $trip['trip_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="' . ($trip['published'] == 1 ? 'Unpublish' : 'Publish') . '" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">' . ($trip['published'] == 1 ? 'cloud_off' : 'cloud_upload') . '</span></button>
<button type="button" class="delete-trip" data-trip-id="' . $trip['trip_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">delete</span></button>
</div>
</div>
</div>
</div>
';
}
echo '</div>';
} else {
echo '<div class="no-trips">
<p>No trips found. <a href="manage_trips">Create one</a></p>
</div>';
}
?>
</div>
</div>
</div>
</section>
<!-- Trips Management Area end -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipTriggerList.forEach(el => new bootstrap.Tooltip(el));
// Handle publish/unpublish button clicks
document.querySelectorAll('.toggle-publish').forEach(btn => {
btn.addEventListener('click', function() {
const tripId = this.dataset.tripId;
const currentIcon = this.querySelector('.material-icons').textContent;
const isPublished = currentIcon === 'cloud_off';
const action = isPublished ? 'Unpublish' : 'Publish';
const formData = new FormData();
formData.append('trip_id', tripId);
fetch('toggle_trip_published', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert(action + ' successful!');
location.reload();
} else {
alert(action + ' failed: ' + data.message);
}
})
.catch(err => {
console.error('Error:', err);
alert(action + ' failed due to network error.');
});
});
});
// Handle delete button clicks
document.querySelectorAll('.delete-trip').forEach(btn => {
btn.addEventListener('click', function() {
if (!confirm('Are you sure you want to delete this trip? This action cannot be undone.')) {
return false;
}
const tripId = this.dataset.tripId;
const card = this.closest('.destination-item');
const formData = new FormData();
formData.append('trip_id', tripId);
fetch('delete_trip', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('Trip deleted successfully!');
card.style.animation = 'fadeOut 0.3s ease-out';
setTimeout(() => {
card.remove();
if (document.querySelectorAll('.destination-item').length === 0) {
location.reload();
}
}, 300);
} else {
alert('Error: ' + data.message);
}
})
.catch(err => {
console.error('Error:', err);
alert('Delete failed due to network error.');
});
});
});
</script>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -0,0 +1,441 @@
<?php
$headerStyle = 'light';
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
// Ensure $conn is available; show a friendly message if DB is down
$conn = $conn ?? null;
if (!$conn) {
// Render a simple error notice inside the page layout and stop further DB queries
echo '<section class="tour-list-page py-10 rel z-1">\n <div class="container">\n <div class="alert alert-danger">Database connection unavailable. Please check your configuration and logs.</div>\n </div>\n</section>';
include_once($rootPath . '/components/insta_footer.php');
// Stop execution to prevent subsequent fatal errors from using $conn
exit;
}
?>
<style>
/* Shared table and tab styles */
.admin-tabs { text-align: center; display: flex; margin-bottom: 20px; margin-top: 20px; }
.admin-tab { padding: 10px 30px; margin-right: 20px; cursor: pointer; border: 2px solid; border-radius: 50px; background: none; font-size: 18px; color: #484848; }
.admin-tab.active { color: white; background: #63ab45; font-weight: bold; }
.tab-content { display: none; }
.tab-content.active { display: block; }
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'; font-size: 0.8em; position: absolute; right: 10px; opacity: 0; transition: opacity 0.2s; }
thead th.asc::after { content: '\25B2'; opacity: 1; }
thead th.desc::after { content: '\25BC'; 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: 5px; }
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: 5px; font-size: 16px; background-color: rgb(255, 255, 255); border-radius: 25px; }
.trip-booking { color: #484848; background: #f9f9f7; border: 1px solid #d8d8d8; border-radius: 10px; margin-top: 15px; margin-bottom: 15px; }
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Tab switching
const tabs = document.querySelectorAll('.admin-tab');
const contents = document.querySelectorAll('.tab-content');
tabs.forEach((tab, idx) => {
tab.addEventListener('click', function() {
tabs.forEach(t => t.classList.remove('active'));
contents.forEach(c => c.classList.remove('active'));
tab.classList.add('active');
contents[idx].classList.add('active');
});
});
// Table sorting/filtering (same as before)
document.querySelectorAll("table").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";
});
});
}
});
});
</script>
<?php
$pageTitle = 'Manage Trips, Courses & Events';
$breadcrumbs = [['Home' => 'index']];
require_once($rootPath . '/components/banner.php');
?>
<section class="tour-list-page py-10 rel z-1">
<div class="container">
<div class="admin-tabs">
<button class="admin-tab active">Manage Trips</button>
<button class="admin-tab">Manage Events</button>
<button class="admin-tab">Manage Courses</button>
<button class="admin-tab">Manage Blogs</button>
</div>
<div class="tab-content active">
<?php
// Trips management content (adapted from admin_trips.php)
// Fetch trips (already available if connection present)
$trips_query = "
SELECT
trip_id, trip_name, location, start_date, end_date,
vehicle_capacity, places_booked, cost_members, cost_nonmembers,
cost_pensioner_member, cost_pensioner, published
FROM trips
ORDER BY start_date DESC
";
$result = $conn->query($trips_query);
$trips = [];
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$trips[] = $row;
}
}
?>
<div class="row">
<div class="col-lg-12">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h2 style="margin: 0;">Manage Trips</h2>
<a href="manage_trips" class="theme-btn create-album-btn">
<i class="far fa-plus"></i> New Trip
</a>
</div>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-warning message-box">
<?php echo $_SESSION['message']; ?>
<span class="close-btn" onclick="this.parentElement.style.display='none'">&times;</span>
</div>
<?php unset($_SESSION['message']);
endif;
if (count($trips) > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter trips...">';
echo '<div class="trips-container" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">';
foreach ($trips as $trip) {
$available = $trip['vehicle_capacity'] - $trip['places_booked'];
$publishStatusBadge = $trip['published'] == 1 ? 'PUBLISHED' : 'DRAFT';
$tripImagePath = '';
$tripImagesGlob = glob($rootPath . '/assets/images/trips/' . $trip['trip_id'] . '_*.jpg');
if (!empty($tripImagesGlob)) {
$tripImagePath = str_replace($rootPath, '', $tripImagesGlob[0]);
} else {
$tripImagePath = 'assets/images/placeholder.jpg';
}
echo '
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="image" style="width:300px;height:250px;">
<img src="' . htmlspecialchars($tripImagePath) . '" alt="' . htmlspecialchars($trip['trip_name']) . '">
</div>
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
<div>
<span class="badge bg-dark mb-1">' . strtoupper($publishStatusBadge) . '</span>
<h5 class="mb-0">' . htmlspecialchars($trip['trip_name']) . '</h5>
<small class="text-muted">📍 ' . htmlspecialchars($trip['location']) . '</small>
</div>
</div>
<p style="margin: 10px 0;">
<strong>Dates:</strong> ' . date('M d', strtotime($trip['start_date'])) . ' - ' . date('M d, Y', strtotime($trip['end_date'])) . '<br>
<strong>Capacity:</strong> ' . $trip['places_booked'] . ' / ' . $trip['vehicle_capacity'] . '<br>
<strong>Costs:</strong> Members: R ' . number_format($trip['cost_members'], 2) . ' | Non-Members: R ' . number_format($trip['cost_nonmembers'], 2) . ' | Pensioner Members: R ' . number_format($trip['cost_pensioner_member'], 2) . ' | Pensioners: R ' . number_format($trip['cost_pensioner'], 2) . '
</p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="manage_trips?trip_id=' . $trip['trip_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<button type="button" class="toggle-publish" data-trip-id="' . $trip['trip_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="' . ($trip['published'] == 1 ? 'Unpublish' : 'Publish') . '" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">' . ($trip['published'] == 1 ? 'cloud_off' : 'cloud_upload') . '</span></button>
<button type="button" class="delete-trip" data-trip-id="' . $trip['trip_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">delete</span></button>
</div>
</div>
</div>
</div>
';
}
echo '</div>';
} else {
echo "<div class=\"no-trips\"><p>No trips found. <a href=\"manage_trips\">Create one</a></p></div>";
}
?>
</div>
</div>
</div>
<div class="tab-content">
<?php
// Events management content (adapted from admin_events.php)
$events_query = "
SELECT
event_id, name, type, location, date, image, 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;
}
}
?>
<div class="row">
<div class="col-lg-12">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h2 style="margin: 0;">Manage Events</h2>
<a href="manage_events" class="theme-btn create-album-btn">
<i class="far fa-plus"></i> New Event
</a>
</div>
<?php if (count($events) > 0) {
echo '<input type="text" class="filter-input" placeholder="Filter events...">';
echo '<div class="events-container" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">';
foreach ($events as $event) {
$eventImagePath = $event['image'] ? htmlspecialchars($event['image']) : 'assets/images/placeholder.jpg';
$publishStatusBadge = $event['published'] == 1 ? 'PUBLISHED' : 'DRAFT';
echo '
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="image" style="width:300px;height:250px;">
<img src="' . $eventImagePath . '" alt="' . htmlspecialchars($event['name']) . '">
</div>
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
<div>
<span class="badge bg-dark mb-1">' . strtoupper($publishStatusBadge) . '</span>
<h5 class="mb-0">' . htmlspecialchars($event['name']) . '</h5>
<small class="text-muted">📍 ' . htmlspecialchars($event['location']) . '</small>
</div>
</div>
<p style="margin: 10px 0;">
<strong>Type:</strong> ' . htmlspecialchars($event['type']) . '<br>
<strong>Date:</strong> ' . convertDate($event['date']) . '
</p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="manage_events?event_id=' . $event['event_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<button type="button" class="toggle-publish" data-event-id="' . $event['event_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="' . ($event['published'] == 1 ? 'Unpublish' : 'Publish') . '" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">' . ($event['published'] == 1 ? 'cloud_off' : 'cloud_upload') . '</span></button>
<button type="button" class="delete-event" data-event-id="' . $event['event_id'] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">delete</span></button>
</div>
</div>
</div>
</div>
';
}
echo '</div>';
} else {
echo '<div class="no-events"><p>No events found. <a href="manage_events">Create one</a></p></div>';
}
?>
</div>
</div>
</div>
<div class="tab-content">
<?php
// Courses management content (adapted from admin_courses.php)
$courses_query = "
SELECT
course_id, course_type, date, capacity, booked, cost_members, cost_nonmembers, instructor, instructor_email, code
FROM courses
ORDER BY date DESC
";
$result = $conn->query($courses_query);
$courses = [];
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$courses[] = $row;
}
}
?>
<div class="row">
<div class="col-lg-12">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h2 style="margin: 0;">Manage Courses</h2>
<a href="manage_courses" class="theme-btn create-album-btn">
<i class="far fa-plus"></i> New Course
</a>
</div>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-warning message-box">
<?php echo $_SESSION['message']; ?>
<span class="close-btn" onclick="this.parentElement.style.display='none'">&times;</span>
</div>
<?php unset($_SESSION['message']);
endif; ?>
<?php if (count($courses) > 0): ?>
<input type="text" class="filter-input" placeholder="Filter courses...">
<div class="courses-container" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<?php foreach ($courses as $course):
$available = intval($course['capacity']) - intval($course['booked']);
$type_label = strtoupper($course['course_type']);
if ($course['course_type'] == 'driver_training') {
$type_label = 'Driver Training';
} elseif ($course['course_type'] == 'bush_mechanics') {
$type_label = 'Bush Mechanics';
} elseif ($course['course_type'] == 'rescue_recovery') {
$type_label = 'Rescue & Recovery';
} elseif ($course['course_type'] == 'ladies_driver_training') {
$type_label = 'Ladies Driver Training';
}
?>
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
<div>
<h5 class="mb-0"><?php echo $type_label; ?></h5>
<small class="text-muted"><?php echo htmlspecialchars($course['course_type']); ?> — <?php echo date('M d, Y', strtotime($course['date'])); ?></small><br>
<small class="text-muted"><?php echo $course['code'] ? 'Code: ' . htmlspecialchars($course['code']) : ''; ?></small>
</div>
</div>
<p style="margin: 10px 0;">
<strong>Instructor:</strong> <?php echo htmlspecialchars($course['instructor']); ?> (<?php echo htmlspecialchars($course['instructor_email']); ?>)<br>
<strong>Capacity:</strong> <?php echo intval($course['booked']); ?> / <?php echo intval($course['capacity']); ?> &nbsp; <strong>Available:</strong> <?php echo $available; ?><br>
<strong>Costs:</strong> Members: R <?php echo number_format($course['cost_members'],2); ?> | Non-Members: R <?php echo number_format($course['cost_nonmembers'],2); ?>
</p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="manage_courses?course_id=<?php echo $course['course_id']; ?>" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<button type="button" class="delete-course" data-course-id="<?php echo $course['course_id']; ?>" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">delete</span></button>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="no-courses">
<p>No courses found. <a href="manage_courses">Create one</a></p>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="tab-content">
<?php
// Blogs management content (adapted from src/admin/admin_blogs.php)
$posts = [];
if ($conn) {
$stmt = $conn->prepare("SELECT b.blog_id, b.title, b.description, b.status, b.date, b.image, CONCAT(u.first_name, ' ', u.last_name) AS author_name, u.email AS author_email, u.profile_pic FROM blogs b JOIN users u ON b.author = u.user_id WHERE b.status = 'published' ORDER BY b.date DESC");
if ($stmt) {
$stmt->execute();
$res = $stmt->get_result();
if ($res && $res->num_rows > 0) {
while ($r = $res->fetch_assoc()) $posts[] = $r;
}
}
}
?>
<div class="row">
<div class="col-lg-12">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:30px;">
<h2 style="margin:0;">Manage Blogs</h2>
<a href="blog_create" class="theme-btn create-album-btn"><i class="far fa-plus"></i> New Blog</a>
</div>
<?php if (count($posts) > 0): ?>
<input type="text" class="filter-input" placeholder="Filter blogs...">
<div class="blogs-container" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<?php foreach ($posts as $post):
$coverImage = $post['image'] ? $post['image'] : 'assets/images/placeholder.jpg';
?>
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="image" style="width:200px;height:200px;">
<img src="<?php echo htmlspecialchars($coverImage); ?>" alt="<?php echo htmlspecialchars($post['title']); ?>">
</div>
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
<img src="<?php echo htmlspecialchars($post['profile_pic'] ?? 'assets/images/placeholder.jpg'); ?>" alt="Author" class="rounded-circle border" width="80" height="80">
<div>
<span class="badge bg-dark mb-1"><?php echo $post['status'] == 1 || $post['status'] === 'published' ? 'PUBLISHED' : 'DRAFT'; ?></span>
<h5 class="mb-0"><?php echo htmlspecialchars($post['title']); ?></h5>
<small class="text-muted"><?php echo htmlspecialchars($post['author_name']); ?></small>
</div>
</div>
<p><?php echo htmlspecialchars($post['description']); ?></p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="blog_edit.php?token=<?php echo encryptData($post['blog_id'], $salt); ?>" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<a href="blog_read.php?token=<?php echo encryptData($post['blog_id'], $salt); ?>" data-bs-toggle="tooltip" data-bs-placement="top" title="Preview"><span class="material-icons">visibility</span></a>
<button type="button" class="publish-btn" data-blog-id="<?php echo $post['blog_id']; ?>" data-status="<?php echo htmlspecialchars($post['status']); ?>" data-bs-toggle="tooltip" data-bs-placement="top" title="Publish/Unpublish" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons"><?php echo ($post['status'] == 1 || $post['status'] === 'published') ? 'cloud_off' : 'cloud_upload'; ?></span></button>
<a href="blog_delete.php?token=<?php echo encryptData($post['blog_id'], $salt); ?>" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete"><span class="material-icons">delete</span></a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="no-blogs"><p>No blogs found. <a href="manage_blogs">Create one</a></p></div>
<?php endif; ?>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const filterInput = document.querySelector('.filter-input');
const cards = document.querySelectorAll('.destination-item');
if (cards.length === 0 && filterInput) filterInput.style.display = "none";
else if (filterInput) {
filterInput.addEventListener('input', function() {
const filterValue = filterInput.value.trim().toLowerCase();
cards.forEach(card => {
const cardText = card.textContent.trim().toLowerCase();
card.style.display = cardText.includes(filterValue) ? "" : "none";
});
});
}
// Tooltips
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipTriggerList.forEach(el => new bootstrap.Tooltip(el));
// Publish/unpublish
document.querySelectorAll('.publish-btn').forEach(btn => {
btn.addEventListener('click', function() {
const blogId = this.dataset.blogId;
const status = this.dataset.status;
const endpoint = (status == 1 || status === 'published') ? 'blog_unpublish' : 'publish_blog';
const formData = new FormData(); formData.append('id', blogId);
fetch(endpoint, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => { if (data.status === 'success') location.reload(); else alert('Action failed'); })
.catch(()=> alert('Network error'));
});
});
});
</script>
</div>
</div>
</div>
</section>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -1,165 +0,0 @@
<?php
$rootPath = dirname(dirname(dirname(__DIR__)));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/header.php");
checkAdmin();
checkUserSession();
$pageTitle = 'Manage Blog Posts';
$breadcrumbs = [['Home' => 'index']];
require_once($rootPath . '/components/banner.php');
$result = $conn->prepare("
SELECT
b.blog_id,
b.title,
b.description,
b.status,
b.date,
b.image,
CONCAT(u.first_name, ' ', u.last_name) AS author_name,
u.email AS author_email,
u.profile_pic
FROM blogs b
JOIN users u ON b.author = u.user_id
WHERE b.status = 'published'
ORDER BY b.date DESC
");
$result->execute();
$posts = $result->get_result();
?>
<style>
.image {
width: 400px;
/* Set your desired width */
height: 350px;
/* Set your desired height */
overflow: hidden;
/* Hide any overflow */
display: block;
/* Ensure proper block behavior */
}
.image img {
width: 100%;
/* Image scales to fill the container */
height: 100%;
/* Image scales to fill the container */
object-fit: cover;
/* Fills the container while maintaining aspect ratio */
object-position: top;
/* Aligns the top of the image with the top of the container */
display: block;
/* Prevents inline whitespace issues */
}
</style>
<?php
$bannerFolder = 'assets/images/banners/';
$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
?>
<!-- Blog List Area start -->
<section class="blog-list-page py-100 rel z-1">
<div class="container">
<div class="row">
<div class="col-lg-12">
<h2>Manage Blog Posts</h2>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-warning message-box">
<?php echo $_SESSION['message']; ?>
<span class="close-btn" onclick="this.parentElement.style.display='none'">&times;</span>
</div>
<?php unset($_SESSION['message']); ?>
<?php endif; ?>
<a href="blog_create.php">+ New Post</a>
<?php while ($post = $posts->fetch_assoc()):
// Determine cover image - use provided image or fallback placeholder
$coverImage = $post["image"] ? $post["image"] : 'assets/images/placeholder.jpg';
// Output the HTML structure with dynamic data
echo '
<div class="destination-item style-three bgc-lighter booking" data-aos="fade-up" data-aos-duration="1500" data-aos-offset="50">
<div class="image" style="width:200px;height:200px;">
<img src="' . htmlspecialchars($coverImage) . '" alt="' . htmlspecialchars($post["title"]) . '">
</div>
<div class="content" style="width:100%;">
<div class="destination-header d-flex align-items-start gap-3">
<img src="' . $post["profile_pic"] . '" alt="Author" class="rounded-circle border" width="80" height="80">
<div>
<span class="badge bg-dark mb-1">' . strtoupper($post["status"]) . '</span>
<h5 class="mb-0">' . $post["title"] . '</h5>
<small class="text-muted">' . $post["author_name"] . '</small>
</div>
</div>
<p>' . $post["description"] . '</p>
<div class="destination-footer">
<div class="btn-group" style="display:flex; justify-content:flex-end; gap:10px;">
<a href="blog_edit.php?token=' . encryptData($post["blog_id"], $salt) . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit"><span class="material-icons">edit</span></a>
<a href="blog_read.php?token=' . encryptData($post["blog_id"], $salt) . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Preview"><span class="material-icons">visibility</span></a>
<button type="button" class="publish-btn" data-blog-id="' . $post["blog_id"] . '" data-status="' . $post["status"] . '" data-bs-toggle="tooltip" data-bs-placement="top" title="' . ($post["status"] == "published" ? "Unpublish" : "Publish") . '" style="background:none; border:none; cursor:pointer; color:inherit;"><span class="material-icons">' . ($post["status"] == "published" ? "cloud_off" : "cloud_upload") . '</span></button>
<a href="blog_delete.php?token=' . encryptData($post["blog_id"], $salt) . '" data-bs-toggle="tooltip" data-bs-placement="top" title="Delete"><span class="material-icons">delete</span></a>
</div>
</div>
</div>
</div>
';
endwhile; ?>
</div>
</div>
</div>
</section>
<!-- Blog List Area end -->
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipTriggerList.forEach(el => new bootstrap.Tooltip(el));
// Handle publish/unpublish button clicks
document.querySelectorAll('.publish-btn').forEach(btn => {
btn.addEventListener('click', function() {
const blogId = this.dataset.blogId;
const status = this.dataset.status;
const action = status === 'published' ? 'unpublish' : 'publish';
const endpoint = status === 'published' ? 'blog_unpublish' : 'publish_blog';
const formData = new FormData();
formData.append('id', blogId);
fetch(endpoint, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
alert(action.charAt(0).toUpperCase() + action.slice(1) + ' successful!');
location.reload();
} else {
alert(action + ' failed.');
console.error('Error:', response.statusText);
}
})
.catch(err => {
console.error('Error:', err);
alert(action + ' failed due to network error.');
});
});
});
</script>
<?php include_once($rootPath . '/components/insta_footer.php'); ?>

View File

@@ -162,7 +162,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$resp = createIkhokhaPayment($payment_id, $payment_amount, $description, $publicRef);
// Send invoice and admin notification
// sendInvoice(getEmail($user_id), getFullName($user_id), $eft_id, formatCurrency($payment_amount), $description);
sendAdminNotification('New Trip Booking - '.getFullName($user_id), getFullName($user_id).' has booked for '.$description);
// Redirect to payment link if available