Calendar click

This commit is contained in:
Flatlogic Bot 2025-10-02 23:39:28 +00:00
parent c42199ed17
commit fa09637699
9 changed files with 470 additions and 104 deletions

186
assets/css/style.css Normal file
View File

@ -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;
}

111
assets/js/main.js Normal file
View File

@ -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();
}
});
}
});

27
create_booking.php Normal file
View File

@ -0,0 +1,27 @@
<?php
header('Content-Type: application/json');
require_once 'db/config.php';
$response = ['status' => '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);
?>

View File

@ -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
);

23
get_bookings.php Normal file
View File

@ -0,0 +1,23 @@
<?php
require_once 'db/config.php';
try {
$pdo = db();
// Insert sample data if the table is empty
$stmt = $pdo->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()]);
}

8
includes/footer.php Normal file
View File

@ -0,0 +1,8 @@
</main>
<footer>
<p>&copy; <?php echo date("Y"); ?> Shared Booking System</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

16
includes/header.php Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shared Booking System</title>
<link rel="stylesheet" href="assets/css/style.css?v=<?php echo time(); ?>">
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.css" rel="stylesheet" />
</head>
<body>
<header>
<nav>
<a href="index.php">Home</a>
</nav>
</header>
<main>

170
index.php
View File

@ -1,106 +1,68 @@
<?php <?php require_once 'includes/header.php'; ?>
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION; <div class="container">
$now = date('Y-m-d H:i:s'); <div class="booking-form">
?> <h2>Create a New Booking</h2>
<!doctype html> <form action="create_booking.php" method="POST">
<html lang="en"> <div class="form-group">
<head> <label for="sport">Sport</label>
<meta charset="utf-8" /> <select id="sport" name="sport" required>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <option value="tennis">Tennis</option>
<title>New Style</title> <option value="basketball">Basketball</option>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> <option value="volleyball">Volleyball</option>
<meta http-equiv="Pragma" content="no-cache" /> <option value="badminton">Badminton</option>
<meta http-equiv="Expires" content="0" /> </select>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #4B0082;
--bg-color-end: #8A2BE2;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head>
<body>
<main>
<div class="card">
<h1>Welcome!</h1>
<p>This is your new landing page.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div> </div>
</main> <div class="form-group">
<footer> <label for="court">Court</label>
Page updated: <?= htmlspecialchars($now) ?> (UTC) <select id="court" name="court" required>
</footer> <option value="1">Court 1</option>
</body> <option value="2">Court 2</option>
</html> <option value="3">Court 3</option>
</select>
</div>
<div class="form-group">
<label for="date">Date</label>
<input type="date" id="date" name="date" required>
</div>
<div class="form-group">
<label for="time">Start Time</label>
<input type="time" id="time" name="time" step="900" required>
</div>
<div class="form-group">
<label for="duration">Duration (in hours)</label>
<select id="duration" name="duration" required>
<option value="1">1 Hour</option>
<option value="2">2 Hours</option>
<option value="3">3 Hours</option>
</select>
</div>
<button type="submit">Create Booking</button>
</form>
</div>
<div class="calendar-container">
<h2>Booked Times</h2>
<div id="calendar"></div>
</div>
</div>
<div id="booking-modal" class="modal">
<div class="modal-content">
<span class="close-button">&times;</span>
<h2>New Booking</h2>
<form id="modal-booking-form">
<div class="form-group">
<label for="organizer-name">Organizer Name</label>
<input type="text" id="organizer-name" required>
</div>
<div class="form-group">
<label for="modal-sport">Sport</label>
<input type="text" id="modal-sport" required>
</div>
<button type="submit">Save Booking</button>
</form>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>

26
update_booking.php Normal file
View File

@ -0,0 +1,26 @@
<?php
header('Content-Type: application/json');
require_once 'db/config.php';
$response = ['status' => '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);
?>