diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..c123df5 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,186 @@ +/* Basic Reset & Body Styling */ +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.6; + margin: 0; + background-color: #f4f7f6; + color: #333; +} + +main { + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +/* Header & Navigation */ +header { + background-color: #fff; + padding: 1rem 2rem; + border-bottom: 1px solid #e0e0e0; + display: flex; + justify-content: space-between; + align-items: center; +} + +header nav a { + text-decoration: none; + color: #007bff; + font-weight: 500; + margin-left: 15px; +} + +header nav a:hover { + text-decoration: underline; +} + +/* Footer */ +footer { + text-align: center; + padding: 20px; + margin-top: 40px; + background-color: #fff; + border-top: 1px solid #e0e0e0; + color: #777; +} + +/* Forms */ +.booking-form { + background: #fff; + padding: 30px; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); + max-width: 500px; + margin: 40px auto; +} + +.booking-form h2 { + margin-top: 0; + margin-bottom: 20px; + color: #333; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #555; +} + +.form-group input, +.form-group select { + width: 100%; + padding: 12px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; /* Important */ +} + +.form-group input:focus, +.form-group select:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 2px rgba(0,123,255,0.25); +} + +button { + display: inline-block; + background-color: #007bff; + color: #fff; + padding: 12px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + width: 100%; + transition: background-color 0.2s; +} + +button:hover { + background-color: #0056b3; +} + +/* Container for the main content */ +.container { + padding: 2rem; +} + +/* Calendar Styling */ +.calendar-container { + background: #fff; + padding: 30px; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); + margin: 40px auto; +} + +.calendar-container h2 { + margin-top: 0; + margin-bottom: 20px; + color: #333; +} + +#calendar { + max-width: 1100px; + margin: 0 auto; +} + +/* Responsive container */ +.container { + display: flex; + flex-wrap: wrap; + gap: 20px; +} + +.booking-form { + flex: 1 1 400px; /* Flex-grow, flex-shrink, flex-basis */ +} + +.calendar-container { + flex: 2 1 600px; /* Takes up more space */ +} + +/* Modal Styling */ +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 1000; /* Sit on top */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ +} + +.modal-content { + background-color: #fefefe; + margin: 15% auto; /* 15% from the top and centered */ + padding: 20px; + border: 1px solid #888; + width: 80%; /* Could be more or less, depending on screen size */ + max-width: 500px; + border-radius: 8px; + position: relative; +} + +.close-button { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + position: absolute; + top: 10px; + right: 20px; +} + +.close-button:hover, +.close-button:focus { + color: black; + text-decoration: none; + cursor: pointer; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..a8c440a --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,111 @@ +document.addEventListener('DOMContentLoaded', function() { + var calendarEl = document.getElementById('calendar'); + var modal = document.getElementById('booking-modal'); + var closeButton = document.querySelector('.close-button'); + var bookingForm = document.getElementById('modal-booking-form'); + var calendar; + var currentSelectionInfo; + + if (calendarEl) { + calendar = new FullCalendar.Calendar(calendarEl, { + initialView: 'timeGridWeek', + headerToolbar: { + left: 'prev,next today', + center: 'title', + right: 'dayGridMonth,timeGridWeek,timeGridDay' + }, + events: 'get_bookings.php', + slotMinTime: '08:00:00', + slotMaxTime: '22:00:00', + allDaySlot: false, + slotDuration: '00:15:00', + selectable: true, + editable: true, + select: function(info) { + currentSelectionInfo = info; + modal.style.display = 'block'; + document.getElementById('organizer-name').focus(); + }, + eventDrop: function(info) { + updateBooking(info.event); + }, + eventResize: function(info) { + updateBooking(info.event); + } + }); + calendar.render(); + } + + if (closeButton) { + closeButton.onclick = function() { + modal.style.display = 'none'; + } + } + + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = 'none'; + } + } + + if (bookingForm) { + bookingForm.onsubmit = function(e) { + e.preventDefault(); + var title = document.getElementById('organizer-name').value; + var sport = document.getElementById('modal-sport').value; + + if (title && sport && currentSelectionInfo) { + var newEvent = { + title: title, + sport: sport, + start: currentSelectionInfo.startStr, + end: currentSelectionInfo.endStr + }; + + fetch('create_booking.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newEvent), + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + modal.style.display = 'none'; + bookingForm.reset(); + calendar.refetchEvents(); + } else { + alert('Error creating booking: ' + data.message); + } + }); + } + calendar.unselect(); + } + } + + function updateBooking(event) { + var eventData = { + id: event.id, + start: event.startStr, + end: event.endStr + }; + fetch('update_booking.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(eventData), + }) + .then(response => response.json()) + .then(data => { + if (data.status !== 'success') { + alert('Error updating booking: ' + data.message); + // Revert the event's position + // Note: The `revert()` function is not available on the `event` object directly in v5. + // Instead, you would refetch events or handle this state differently. + calendar.refetchEvents(); + } + }); + } +}); \ No newline at end of file diff --git a/create_booking.php b/create_booking.php new file mode 100644 index 0000000..9cce516 --- /dev/null +++ b/create_booking.php @@ -0,0 +1,27 @@ + 'error', 'message' => 'Invalid request']; + +$data = json_decode(file_get_contents('php://input'), true); + +if ($data) { + try { + $pdo = db(); + $stmt = $pdo->prepare("INSERT INTO bookings (title, sport, start_time, end_time) VALUES (?, ?, ?, ?)"); + $stmt->execute([ + $data['title'], + $data['sport'], + $data['start'], + $data['end'] + ]); + $response['status'] = 'success'; + $response['message'] = 'Booking created successfully'; + } catch (PDOException $e) { + $response['message'] = 'Database error: ' . $e->getMessage(); + } +} + +echo json_encode($response); +?> \ No newline at end of file diff --git a/db/migrations/001_create_bookings_table.sql b/db/migrations/001_create_bookings_table.sql new file mode 100644 index 0000000..66eb6ab --- /dev/null +++ b/db/migrations/001_create_bookings_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS bookings ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + start_time DATETIME NOT NULL, + end_time DATETIME NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/get_bookings.php b/get_bookings.php new file mode 100644 index 0000000..8b7d8d0 --- /dev/null +++ b/get_bookings.php @@ -0,0 +1,23 @@ +query('SELECT COUNT(*) FROM bookings'); + if ($stmt->fetchColumn() == 0) { + $pdo->exec("INSERT INTO bookings (title, start_time, end_time) VALUES ('Booking 1', '2025-10-02 10:00:00', '2025-10-02 11:00:00')"); + $pdo->exec("INSERT INTO bookings (title, start_time, end_time) VALUES ('Booking 2', '2025-10-02 14:00:00', '2025-10-02 15:00:00')"); + } + + $stmt = $pdo->query('SELECT title, start_time AS start, end_time AS end FROM bookings'); + $bookings = $stmt->fetchAll(PDO::FETCH_ASSOC); + + header('Content-Type: application/json'); + echo json_encode($bookings); + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => $e->getMessage()]); +} diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..0441230 --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,8 @@ + + + + + + diff --git a/includes/header.php b/includes/header.php new file mode 100644 index 0000000..af77deb --- /dev/null +++ b/includes/header.php @@ -0,0 +1,16 @@ + + + + + + Shared Booking System + + + + +
+ +
+
diff --git a/index.php b/index.php index 35b0bab..b38534d 100644 --- a/index.php +++ b/index.php @@ -1,106 +1,68 @@ - -$phpVersion = PHP_VERSION; -$now = date('Y-m-d H:i:s'); -?> - - - - - - New Style - - - - - - - - - -
-
-

Welcome!

-

This is your new landing page.

-

Runtime: PHP — UTC

+
+
+

Create a New Booking

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
-
- - - + +
+

Booked Times

+
+
+ + + + + \ No newline at end of file diff --git a/update_booking.php b/update_booking.php new file mode 100644 index 0000000..5b2c00c --- /dev/null +++ b/update_booking.php @@ -0,0 +1,26 @@ + 'error', 'message' => 'Invalid request']; + +$data = json_decode(file_get_contents('php://input'), true); + +if ($data && isset($data['id'], $data['start'], $data['end'])) { + try { + $pdo = db(); + $stmt = $pdo->prepare("UPDATE bookings SET start_time = ?, end_time = ? WHERE id = ?"); + $stmt->execute([ + $data['start'], + $data['end'], + $data['id'] + ]); + $response['status'] = 'success'; + $response['message'] = 'Booking updated successfully'; + } catch (PDOException $e) { + $response['message'] = 'Database error: ' . $e->getMessage(); + } +} + +echo json_encode($response); +?> \ No newline at end of file