Compare commits
5 Commits
feature/ca
...
feature/ev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32651ed433 | ||
|
|
f522b84fc1 | ||
|
|
2b136c4b06 | ||
|
|
7f0964009a | ||
|
|
5be946f78f |
@@ -77,12 +77,14 @@ RewriteRule ^view_indemnity$ src/pages/other/view_indemnity.php [L]
|
|||||||
RewriteRule ^admin_members$ src/admin/admin_members.php [L]
|
RewriteRule ^admin_members$ src/admin/admin_members.php [L]
|
||||||
RewriteRule ^admin_payments$ src/admin/admin_payments.php [L]
|
RewriteRule ^admin_payments$ src/admin/admin_payments.php [L]
|
||||||
RewriteRule ^admin_web_users$ src/admin/admin_web_users.php [L]
|
RewriteRule ^admin_web_users$ src/admin/admin_web_users.php [L]
|
||||||
|
RewriteRule ^admin_events$ src/admin/admin_events.php [L]
|
||||||
RewriteRule ^admin_course_bookings$ src/admin/admin_course_bookings.php [L]
|
RewriteRule ^admin_course_bookings$ src/admin/admin_course_bookings.php [L]
|
||||||
RewriteRule ^admin_camp_bookings$ src/admin/admin_camp_bookings.php [L]
|
RewriteRule ^admin_camp_bookings$ src/admin/admin_camp_bookings.php [L]
|
||||||
RewriteRule ^admin_trip_bookings$ src/admin/admin_trip_bookings.php [L]
|
RewriteRule ^admin_trip_bookings$ src/admin/admin_trip_bookings.php [L]
|
||||||
RewriteRule ^admin_visitors$ src/admin/admin_visitors.php [L]
|
RewriteRule ^admin_visitors$ src/admin/admin_visitors.php [L]
|
||||||
RewriteRule ^admin_efts$ src/admin/admin_efts.php [L]
|
RewriteRule ^admin_efts$ src/admin/admin_efts.php [L]
|
||||||
RewriteRule ^admin_trips$ src/admin/admin_trips.php [L]
|
RewriteRule ^admin_trips$ src/admin/admin_trips.php [L]
|
||||||
|
RewriteRule ^manage_events$ src/admin/manage_events.php [L]
|
||||||
RewriteRule ^manage_trips$ src/admin/manage_trips.php [L]
|
RewriteRule ^manage_trips$ src/admin/manage_trips.php [L]
|
||||||
|
|
||||||
# === API/AJAX ENDPOINTS ===
|
# === API/AJAX ENDPOINTS ===
|
||||||
@@ -114,8 +116,11 @@ RewriteRule ^upload_profile_picture$ src/processors/upload_profile_picture.php [
|
|||||||
RewriteRule ^send_reset_link$ src/processors/send_reset_link.php [L]
|
RewriteRule ^send_reset_link$ src/processors/send_reset_link.php [L]
|
||||||
RewriteRule ^logout$ src/processors/logout.php [L]
|
RewriteRule ^logout$ src/processors/logout.php [L]
|
||||||
RewriteRule ^process_trip$ src/processors/process_trip.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_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_trip$ src/processors/delete_trip.php [L]
|
||||||
|
RewriteRule ^delete_event$ src/admin/delete_event.php [L]
|
||||||
|
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
|
||||||
|
|||||||
176
docs/EVENTS_ADMIN_SYSTEM.md
Normal file
176
docs/EVENTS_ADMIN_SYSTEM.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# Events Management Admin System
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
A complete admin system for managing events on the 4WDCSA website, following the same patterns as the trip management system.
|
||||||
|
|
||||||
|
## Files Created
|
||||||
|
|
||||||
|
### 1. `/src/admin/manage_events.php`
|
||||||
|
**Purpose**: Form for creating and editing events
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Create new events form
|
||||||
|
- Edit existing events form
|
||||||
|
- Fields:
|
||||||
|
- Event Name (required)
|
||||||
|
- Event Type (required) - e.g., Workshop, Training, Rally
|
||||||
|
- Location (required)
|
||||||
|
- Date (required)
|
||||||
|
- Time (required)
|
||||||
|
- Feature/Category (required) - e.g., Off-Road Training, Social Event
|
||||||
|
- Description (required) - Full text description
|
||||||
|
- Event Image (required for new, optional for updates)
|
||||||
|
- Promotional Image (optional) - Displayed when users click "View Promo"
|
||||||
|
- Published Status (checkbox) - Controls visibility on website
|
||||||
|
|
||||||
|
**Technical Details**:
|
||||||
|
- AJAX form submission to `process_event` endpoint
|
||||||
|
- Image upload with validation
|
||||||
|
- CSRF token protection
|
||||||
|
- Responsive Bootstrap grid layout (col-md-6 fields)
|
||||||
|
- Success/error message display with auto-redirect
|
||||||
|
|
||||||
|
### 2. `/src/admin/process_event.php`
|
||||||
|
**Purpose**: Backend endpoint for handling event CRUD operations
|
||||||
|
|
||||||
|
**Endpoints**:
|
||||||
|
- `POST /process_event` - Create/Update event
|
||||||
|
- `GET /process_event?action=delete&event_id={id}` - Delete event
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Create new events with image uploads
|
||||||
|
- Update existing events with optional image replacement
|
||||||
|
- Delete events and associated image files
|
||||||
|
- CSRF token validation
|
||||||
|
- Image type validation (JPEG, PNG, GIF, WebP)
|
||||||
|
- File organization in `/assets/images/events/`
|
||||||
|
- Automatic timestamp management (created_at, updated_at)
|
||||||
|
- User tracking (created_by stores admin user_id)
|
||||||
|
|
||||||
|
**Image Handling**:
|
||||||
|
- Main event image: Stored with unique ID prefix
|
||||||
|
- Promo image: Stored with `_promo_` prefix
|
||||||
|
- Both uploaded to `/assets/images/events/`
|
||||||
|
|
||||||
|
### 3. `/src/admin/admin_events.php`
|
||||||
|
**Purpose**: Admin dashboard for managing all events
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- List all events with sortable columns
|
||||||
|
- Real-time search/filter across all columns
|
||||||
|
- Create new event button
|
||||||
|
- Edit event link for each row
|
||||||
|
- Delete event with confirmation dialog
|
||||||
|
- Status badges (Published/Draft)
|
||||||
|
- Responsive table with alternating row colors
|
||||||
|
- Rounded corners on even rows
|
||||||
|
|
||||||
|
**Sortable Columns**:
|
||||||
|
- Event Name
|
||||||
|
- Type
|
||||||
|
- Location
|
||||||
|
- Date
|
||||||
|
- Status
|
||||||
|
|
||||||
|
**Actions**:
|
||||||
|
- Edit - Redirects to manage_events.php with event_id
|
||||||
|
- Delete - Removes event and associated files
|
||||||
|
|
||||||
|
## Database Schema Changes
|
||||||
|
|
||||||
|
### Migration File: `/docs/migrations/001_add_events_tracking_columns.sql`
|
||||||
|
|
||||||
|
**Columns Added to events table**:
|
||||||
|
- `created_by` (int) - References user who created the event
|
||||||
|
- `published` (tinyint(1)) - Boolean flag for publication status (default 0/false)
|
||||||
|
- `created_at` (timestamp) - Automatic timestamp when event is created
|
||||||
|
- `updated_at` (timestamp) - Automatic timestamp updated on modification
|
||||||
|
|
||||||
|
**Indexes Added**:
|
||||||
|
- `idx_date` - For sorting and filtering by date
|
||||||
|
- `idx_published` - For filtering published/draft events
|
||||||
|
- `idx_created_by` - For tracking who created events
|
||||||
|
|
||||||
|
## Design Patterns
|
||||||
|
|
||||||
|
### Follows Trip Management System Architecture
|
||||||
|
- Same form layout and styling (`.comment-form.bgc-lighter`)
|
||||||
|
- Same table styling with sortable headers and filters
|
||||||
|
- Same image upload and validation patterns
|
||||||
|
- AJAX submission with success/error messaging
|
||||||
|
- Auto-redirect on successful operation
|
||||||
|
|
||||||
|
### Image Organization
|
||||||
|
```
|
||||||
|
/assets/images/events/
|
||||||
|
├── {unique_id}_{original_filename}.jpg (event images)
|
||||||
|
└── {unique_id}_promo_{original_filename}.jpg (promo images)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Front-end Integration
|
||||||
|
The existing `/src/pages/events/events.php` displays published events:
|
||||||
|
- Shows event image, name, location, date, time
|
||||||
|
- Feature description and full description
|
||||||
|
- "View Promo" button displays promotional image in modal
|
||||||
|
|
||||||
|
## Usage Workflow
|
||||||
|
|
||||||
|
### Creating an Event
|
||||||
|
1. Navigate to `/src/admin/manage_events.php`
|
||||||
|
2. Fill in all required fields
|
||||||
|
3. Upload event image
|
||||||
|
4. Optionally upload promotional image
|
||||||
|
5. Check "Publish Event" if ready to display
|
||||||
|
6. Submit form via AJAX
|
||||||
|
7. Redirected to admin_events.php list view
|
||||||
|
|
||||||
|
### Editing an Event
|
||||||
|
1. Click "Edit" button on admin_events.php
|
||||||
|
2. Modify any fields
|
||||||
|
3. Image upload is optional - existing image retained if not changed
|
||||||
|
4. Update timestamps and user tracking automatic
|
||||||
|
5. Submit form
|
||||||
|
6. Redirected back to list view
|
||||||
|
|
||||||
|
### Deleting an Event
|
||||||
|
1. Click "Delete" button on admin_events.php
|
||||||
|
2. Confirm deletion in dialog
|
||||||
|
3. Event and associated image files removed from server
|
||||||
|
4. Page automatically refreshes
|
||||||
|
|
||||||
|
### Publishing/Unpublishing
|
||||||
|
- Toggle "Publish Event" checkbox before saving
|
||||||
|
- Only published events appear on `/src/pages/events/events.php`
|
||||||
|
- Draft events hidden from public view
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
1. **CSRF Token Protection**: All forms include CSRF token validation
|
||||||
|
2. **Admin-only Access**: `checkAdmin()` function validates user permissions
|
||||||
|
3. **File Validation**: Image type checking (JPEG, PNG, GIF, WebP)
|
||||||
|
4. **SQL Injection Prevention**: Prepared statements with parameter binding
|
||||||
|
5. **XSS Prevention**: `htmlspecialchars()` used for output escaping
|
||||||
|
|
||||||
|
## Styling Classes
|
||||||
|
|
||||||
|
**Form Container**: `.comment-form.bgc-lighter.z-1.rel.mb-30.rmb-55`
|
||||||
|
**Action Buttons**: `.btn-edit`, `.btn-delete`
|
||||||
|
**Status Badges**: `.badge.badge-published`, `.badge.badge-draft`
|
||||||
|
**Tables**: Uses sortable header styling with visual sort indicators
|
||||||
|
|
||||||
|
## Browser Compatibility
|
||||||
|
|
||||||
|
- Modern browsers with AJAX/Fetch API support
|
||||||
|
- JavaScript enabled required for filtering and sorting
|
||||||
|
- File input accepts image MIME types
|
||||||
|
|
||||||
|
## Future Enhancement Opportunities
|
||||||
|
|
||||||
|
1. Bulk event operations (bulk delete, publish multiple)
|
||||||
|
2. Event categories/tags system
|
||||||
|
3. Event capacity limits with registrations
|
||||||
|
4. Email notifications for published events
|
||||||
|
5. Event calendar view
|
||||||
|
6. Event image gallery (multiple images per event)
|
||||||
|
7. Recurring events support
|
||||||
|
8. Event attendee tracking
|
||||||
14
docs/migrations/001_add_events_tracking_columns.sql
Normal file
14
docs/migrations/001_add_events_tracking_columns.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- Events Table Migration
|
||||||
|
-- Add missing columns to events table for proper tracking and publishing control
|
||||||
|
|
||||||
|
-- Add columns if they don't exist (using ALTER IGNORE for compatibility)
|
||||||
|
ALTER TABLE `events`
|
||||||
|
ADD COLUMN `created_by` int DEFAULT NULL AFTER `promo`,
|
||||||
|
ADD COLUMN `published` tinyint(1) DEFAULT 0 AFTER `created_by`,
|
||||||
|
ADD COLUMN `created_at` timestamp DEFAULT CURRENT_TIMESTAMP AFTER `published`,
|
||||||
|
ADD COLUMN `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `created_at`;
|
||||||
|
|
||||||
|
-- Add indexes for better query performance
|
||||||
|
ALTER TABLE `events` ADD INDEX `idx_date` (`date`);
|
||||||
|
ALTER TABLE `events` ADD INDEX `idx_published` (`published`);
|
||||||
|
ALTER TABLE `events` ADD INDEX `idx_created_by` (`created_by`);
|
||||||
@@ -283,6 +283,7 @@ if ($headerStyle === 'light') {
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="admin_web_users">Website Users</a></li>
|
<li><a href="admin_web_users">Website Users</a></li>
|
||||||
<li><a href="admin_members">4WDCSA Members</a></li>
|
<li><a href="admin_members">4WDCSA Members</a></li>
|
||||||
|
<li><a href="admin_events">Manage Events</a></li>
|
||||||
<li><a href="admin_trips">Manage Trips</a></li>
|
<li><a href="admin_trips">Manage Trips</a></li>
|
||||||
<li><a href="admin_trip_bookings">Trip Bookings</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_course_bookings">Course Bookings</a></li>
|
||||||
|
|||||||
357
src/admin/admin_events.php
Normal file
357
src/admin/admin_events.php
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
<?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',
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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'); ?>
|
||||||
46
src/admin/delete_event.php
Normal file
46
src/admin/delete_event.php
Normal 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();
|
||||||
173
src/admin/manage_events.php
Normal file
173
src/admin/manage_events.php
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<?php
|
||||||
|
$headerStyle = 'light';
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
include_once($rootPath . '/header.php');
|
||||||
|
checkAdmin();
|
||||||
|
|
||||||
|
$event_id = $_GET['event_id'] ?? null;
|
||||||
|
$event = null;
|
||||||
|
|
||||||
|
// If editing an existing event, fetch its data
|
||||||
|
if ($event_id) {
|
||||||
|
$stmt = $conn->prepare("SELECT * 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();
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$pageTitle = $event ? 'Edit Event' : 'Create New Event';
|
||||||
|
$breadcrumbs = [['Home' => 'index'], ['Admin' => 'admin_events'], [$pageTitle => '']];
|
||||||
|
require_once($rootPath . '/components/banner.php');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Event Manager Area start -->
|
||||||
|
<section class="event-manager-area py-100 rel z-1">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="comment-form bgc-lighter z-1 rel mb-30 rmb-55">
|
||||||
|
<form id="eventForm" enctype="multipart/form-data" method="POST" action="process_event">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
|
||||||
|
<?php if ($event): ?>
|
||||||
|
<input type="hidden" name="event_id" value="<?php echo $event['event_id']; ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="section-title py-20">
|
||||||
|
<h2><?php echo $event ? 'Edit Event: ' . htmlspecialchars($event['name']) : 'Create New Event'; ?></h2>
|
||||||
|
<div id="responseMessage"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Event Information -->
|
||||||
|
<div class="row mt-35">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Event Name *</label>
|
||||||
|
<input type="text" id="name" name="name" class="form-control" value="<?php echo $event ? htmlspecialchars($event['name']) : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="type">Event Type *</label>
|
||||||
|
<input type="text" id="type" name="type" class="form-control" value="<?php echo $event ? htmlspecialchars($event['type']) : ''; ?>" placeholder="e.g., Workshop, Training, Rally" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="location">Location *</label>
|
||||||
|
<input type="text" id="location" name="location" class="form-control" value="<?php echo $event ? htmlspecialchars($event['location']) : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="date">Date *</label>
|
||||||
|
<input type="date" id="date" name="date" class="form-control" value="<?php echo $event ? $event['date'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Time -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="time">Time *</label>
|
||||||
|
<input type="time" id="time" name="time" class="form-control" value="<?php echo $event ? $event['time'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature/Category -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="feature">Feature/Category *</label>
|
||||||
|
<input type="text" id="feature" name="feature" class="form-control" value="<?php echo $event ? htmlspecialchars($event['feature']) : ''; ?>" placeholder="e.g., Off-Road Training, Social Event" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descriptions -->
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Description *</label>
|
||||||
|
<textarea id="description" name="description" class="form-control" rows="6" required><?php echo $event ? htmlspecialchars($event['description']) : ''; ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Image Upload -->
|
||||||
|
<div class="col-md-12 mt-20">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="image">Event Image *</label>
|
||||||
|
<input type="file" id="image" name="image" class="form-control" accept="image/*" <?php echo !$event ? 'required' : ''; ?>>
|
||||||
|
<?php if ($event && $event['image']): ?>
|
||||||
|
<small class="text-info d-block mt-2">Current image: <img src="<?php echo $event['image']; ?>" alt="Event Image" style="max-width: 200px; margin-top: 10px;"></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Promo Image Upload -->
|
||||||
|
<div class="col-md-12 mt-20">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="promo">Promotional Image</label>
|
||||||
|
<input type="file" id="promo" name="promo" class="form-control" accept="image/*">
|
||||||
|
<small class="text-muted">This image will be displayed when users click "View Promo"</small>
|
||||||
|
<?php if ($event && $event['promo']): ?>
|
||||||
|
<small class="text-info d-block mt-2">Current promo: <img src="<?php echo $event['promo']; ?>" alt="Promo Image" style="max-width: 200px; margin-top: 10px;"></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</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%;">
|
||||||
|
<?php echo $event ? 'Update Event' : 'Create Event'; ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Event Manager Area end -->
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#eventForm').on('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(this);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'process_event',
|
||||||
|
type: 'POST',
|
||||||
|
data: formData,
|
||||||
|
contentType: false,
|
||||||
|
processData: false,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.status === 'success') {
|
||||||
|
$('#responseMessage').html('<div class="alert alert-success">' + response.message + '</div>');
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.href = 'admin_events';
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
$('#responseMessage').html('<div class="alert alert-danger">' + response.message + '</div>');
|
||||||
|
console.error('Server error:', response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.log('AJAX Error:', error);
|
||||||
|
console.log('Response:', xhr.responseText);
|
||||||
|
$('#responseMessage').html('<div class="alert alert-danger">Error creating/updating event: ' + error + '</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
|
||||||
193
src/admin/process_event.php
Normal file
193
src/admin/process_event.php
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
include_once($rootPath . '/header.php');
|
||||||
|
checkAdmin();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Handle delete action
|
||||||
|
if ($_GET['action'] ?? null === 'delete') {
|
||||||
|
$event_id = $_GET['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();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CSRF token
|
||||||
|
if (!isset($_POST['csrf_token']) || !verifyCsrfToken($_POST['csrf_token'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'CSRF token validation failed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event_id = $_POST['event_id'] ?? null;
|
||||||
|
$name = $_POST['name'] ?? null;
|
||||||
|
$type = $_POST['type'] ?? null;
|
||||||
|
$location = $_POST['location'] ?? null;
|
||||||
|
$date = $_POST['date'] ?? null;
|
||||||
|
$time = $_POST['time'] ?? null;
|
||||||
|
$feature = $_POST['feature'] ?? null;
|
||||||
|
$description = $_POST['description'] ?? null;
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!$name || !$type || !$location || !$date || !$time || !$feature || !$description) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'All required fields must be filled']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle image upload
|
||||||
|
$image_path = null;
|
||||||
|
if (!empty($_FILES['image']['name'])) {
|
||||||
|
$upload_dir = $rootPath . '/assets/images/events/';
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_name = uniqid() . '_' . basename($_FILES['image']['name']);
|
||||||
|
$target_file = $upload_dir . $file_name;
|
||||||
|
$file_type = mime_content_type($_FILES['image']['tmp_name']);
|
||||||
|
|
||||||
|
// Validate image file
|
||||||
|
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
|
if (!in_array($file_type, $allowed_types)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid image file type. Only JPEG, PNG, GIF, and WebP are allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move_uploaded_file($_FILES['image']['tmp_name'], $target_file)) {
|
||||||
|
$image_path = 'assets/images/events/' . $file_name;
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to upload image']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else if (!$event_id) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Image is required for new events']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle promo image upload
|
||||||
|
$promo_path = null;
|
||||||
|
if (!empty($_FILES['promo']['name'])) {
|
||||||
|
$upload_dir = $rootPath . '/assets/images/events/';
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_name = uniqid() . '_promo_' . basename($_FILES['promo']['name']);
|
||||||
|
$target_file = $upload_dir . $file_name;
|
||||||
|
$file_type = mime_content_type($_FILES['promo']['tmp_name']);
|
||||||
|
|
||||||
|
// Validate image file
|
||||||
|
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
|
if (!in_array($file_type, $allowed_types)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid promo image file type. Only JPEG, PNG, GIF, and WebP are allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move_uploaded_file($_FILES['promo']['tmp_name'], $target_file)) {
|
||||||
|
$promo_path = 'assets/images/events/' . $file_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($event_id) {
|
||||||
|
// Update existing event
|
||||||
|
$update_fields = [
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $type,
|
||||||
|
'location' => $location,
|
||||||
|
'date' => $date,
|
||||||
|
'time' => $time,
|
||||||
|
'feature' => $feature,
|
||||||
|
'description' => $description,
|
||||||
|
'updated_at' => date('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($image_path) {
|
||||||
|
$update_fields['image'] = $image_path;
|
||||||
|
}
|
||||||
|
if ($promo_path) {
|
||||||
|
$update_fields['promo'] = $promo_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$set_clause = implode(', ', array_map(function($key) {
|
||||||
|
return $key . ' = ?';
|
||||||
|
}, array_keys($update_fields)));
|
||||||
|
|
||||||
|
$values = array_values($update_fields);
|
||||||
|
$values[] = $event_id;
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE events SET $set_clause WHERE event_id = ?");
|
||||||
|
|
||||||
|
// Build type string for bind_param
|
||||||
|
$type_str = str_repeat('s', count($update_fields)) . 'i';
|
||||||
|
$stmt->bind_param($type_str, ...$values);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Event updated successfully']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to update event: ' . $stmt->error]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create new event
|
||||||
|
if (!$image_path) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Image is required for new events']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
");
|
||||||
|
|
||||||
|
$created_by = $_SESSION['user_id'] ?? 0;
|
||||||
|
|
||||||
|
$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']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to create event: ' . $stmt->error]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'An error occurred: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
61
src/admin/toggle_event_published.php
Normal file
61
src/admin/toggle_event_published.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?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) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Event ID is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get current published status
|
||||||
|
$stmt = $conn->prepare("SELECT published FROM events WHERE event_id = ?");
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception("Prepare failed: " . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("i", $event_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows === 0) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Event not found']);
|
||||||
|
$stmt->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = $result->fetch_assoc();
|
||||||
|
$new_status = $event['published'] == 1 ? 0 : 1;
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// Update published status
|
||||||
|
$update_stmt = $conn->prepare("UPDATE events SET published = ?, updated_at = NOW() WHERE event_id = ?");
|
||||||
|
if (!$update_stmt) {
|
||||||
|
throw new Exception("Prepare failed: " . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$update_stmt->bind_param("ii", $new_status, $event_id);
|
||||||
|
|
||||||
|
if ($update_stmt->execute()) {
|
||||||
|
http_response_code(200);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $new_status == 1 ? 'Event published' : 'Event unpublished',
|
||||||
|
'published' => $new_status
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
throw new Exception("Update failed: " . $update_stmt->error);
|
||||||
|
}
|
||||||
|
$update_stmt->close();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ include_once($rootPath . '/header.php');
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Query to retrieve upcoming events
|
// Query to retrieve upcoming published events only
|
||||||
$stmt = $conn->prepare("SELECT event_id, date, time, name, image, description, feature, location, type, promo FROM events WHERE date > CURDATE() ORDER BY date ASC");
|
$stmt = $conn->prepare("SELECT event_id, date, time, name, image, description, feature, location, type, promo FROM events WHERE date > CURDATE() AND published = 1 ORDER BY date ASC");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user