Calendar click
This commit is contained in:
parent
c42199ed17
commit
fa09637699
186
assets/css/style.css
Normal file
186
assets/css/style.css
Normal 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
111
assets/js/main.js
Normal 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
27
create_booking.php
Normal 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);
|
||||
?>
|
||||
7
db/migrations/001_create_bookings_table.sql
Normal file
7
db/migrations/001_create_bookings_table.sql
Normal 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
23
get_bookings.php
Normal 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
8
includes/footer.php
Normal file
@ -0,0 +1,8 @@
|
||||
</main>
|
||||
<footer>
|
||||
<p>© <?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
16
includes/header.php
Normal 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
170
index.php
@ -1,106 +1,68 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<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 class="container">
|
||||
<div class="booking-form">
|
||||
<h2>Create a New Booking</h2>
|
||||
<form action="create_booking.php" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="sport">Sport</label>
|
||||
<select id="sport" name="sport" required>
|
||||
<option value="tennis">Tennis</option>
|
||||
<option value="basketball">Basketball</option>
|
||||
<option value="volleyball">Volleyball</option>
|
||||
<option value="badminton">Badminton</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="court">Court</label>
|
||||
<select id="court" name="court" required>
|
||||
<option value="1">Court 1</option>
|
||||
<option value="2">Court 2</option>
|
||||
<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>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<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">×</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
26
update_booking.php
Normal 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);
|
||||
?>
|
||||
Loading…
x
Reference in New Issue
Block a user