
Кольцова А.А. Разработка прототипа системы бронирования велопрокатной организации
.pdf
111
<form method="POST" action="catalog_edit.php" enctype="multipart/form-data"> <input type="hidden" name="action" value="add">
<label for="bike_model">Модель велосипеда:</label> <input type="text" name="bike_model" required> <label for="frame_size_id">Размер рамы:</label> <select name="frame_size_id" required>
<?php while ($frame_size = $frame_sizes_result->fetch_assoc()): ?>
<option value="<?php echo $frame_size['frame_size_id']; ?>"><?php echo htmlspecialchars($frame_size['frame_size'], ENT_QUOTES, 'UTF-8'); ?></option>
<?php endwhile; ?> </select>
<label for="age_limit_id">Возрастное ограничение:</label> <select name="age_limit_id" required>
<?php while ($age_limit = $age_limits_result->fetch_assoc()): ?>
<option value="<?php echo $age_limit['age_limit_id']; ?>"><?php echo htmlspecialchars($age_limit['age_limit'], ENT_QUOTES, 'UTF-8'); ?></option>
<?php endwhile; ?> </select>
<label for="serial_number">Серийный номер:</label> <input type="text" name="serial_number" required> <label for="availability_id">Доступность:</label> <select name="availability_id" required>
<?php while ($availability = $availability_result->fetch_assoc()): ?>
<option value="<?php echo $availability['availability_id']; ?>"><?php echo htmlspecialchars($availability['status'], ENT_QUOTES, 'UTF-8'); ?></option>
<?php endwhile; ?> </select>
<label for="category_id">Категория:</label> <select name="category_id" required>
<?php while ($category = $categories_result->fetch_assoc()): ?>
<option value="<?php echo $category['category_id']; ?>"><?php echo htmlspecialchars($category['category_name'], ENT_QUOTES, 'UTF-8'); ?></option>
<?php endwhile; ?> </select>
<label for="photo">Фото велосипеда:</label> <input type="file" name="photo" accept="image/*">
<button type="submit">Добавить велосипед</button> </form>
<form method="POST" action="download_catalog.php"> <button type="submit">Скачать каталог в Excel</button>
</form>
<h3>Список велосипедов</h3>
<input type="text" id="searchInput" onkeyup="filterTable()" placeholder="Поиск велосипедов по модели..." style="marginbottom: 20px; width: 400px;">
<table id="bikesTable"> <thead>
<tr>
<th>ID</th> <th>Модель</th>
<th>Размер рамы</th> <th>Возрастное ограничение</th> <th>Серийный номер</th> <th>Доступность</th>
<th>Категория</th> <th>Фото</th> <th>Изменить</th> <th>Удалить</th>
</tr>
</thead>
<tbody>
<?php while ($bike = $bikes_result->fetch_assoc()): ?> <tr>
<td><?php echo htmlspecialchars($bike['bike_id'], ENT_QUOTES, 'UTF-8'); ?></td> <td>
<form method="POST" action="catalog_edit.php" enctype="multipart/form-data"> <input type="hidden" name="bike_id" value="<?php echo $bike['bike_id']; ?>">
<input type="text" name="bike_model" value="<?php echo htmlspecialchars($bike['bike_model'], ENT_QUOTES, 'UTF-8'); ?>">

112
</td>
<td>
<select name="frame_size_id"> <?php
$frame_sizes_result->data_seek(0); // Перемотка на начало результата while ($frame_size = $frame_sizes_result->fetch_assoc()): ?>
<option value="<?php echo $frame_size['frame_size_id']; ?>" <?php if ($frame_size['frame_size_id'] == $bike['frame_size_id']) echo 'selected'; ?>>
<?php echo htmlspecialchars($frame_size['frame_size'], ENT_QUOTES, 'UTF-8'); ?> </option>
<?php endwhile; ?> </select>
</td>
<td>
<select name="age_limit_id"> <?php
$age_limits_result->data_seek(0); // Перемотка на начало результата while ($age_limit = $age_limits_result->fetch_assoc()): ?>
<option value="<?php echo $age_limit['age_limit_id']; ?>" <?php if ($age_limit['age_limit_id'] == $bike['age_limit_id']) echo 'selected'; ?>>
<?php echo htmlspecialchars($age_limit['age_limit'], ENT_QUOTES, 'UTF-8'); ?> </option>
<?php endwhile; ?> </select>
</td>
<td><input type="text" name="serial_number" value="<?php echo htmlspecialchars($bike['serial_number'], ENT_QUOTES, 'UTF-8'); ?>"></td>
<td>
<select name="availability_id"> <?php
$availability_result->data_seek(0); // Перемотка на начало результата while ($availability = $availability_result->fetch_assoc()): ?>
<option value="<?php echo $availability['availability_id']; ?>" <?php if ($availability['availability_id'] == $bike['availability_id']) echo 'selected'; ?>>
<?php echo htmlspecialchars($availability['status'], ENT_QUOTES, 'UTF-8'); ?> </option>
<?php endwhile; ?> </select>
</td>
<td>
<select name="category_id"> <?php
$categories_result->data_seek(0); // Перемотка на начало результата while ($category = $categories_result->fetch_assoc()): ?>
<option value="<?php echo $category['category_id']; ?>" <?php if ($category['category_id'] == $bike['category_id']) echo 'selected'; ?>>
<?php echo htmlspecialchars($category['category_name'], ENT_QUOTES, 'UTF-8'); ?> </option>
<?php endwhile; ?> </select>
</td>
<td>
<?php if ($bike['photo']): ?>
<img src="data:image/jpeg;base64,<?php echo base64_encode($bike['photo']); ?>" alt="Фото велосипеда" style="max-width: 100px;">
<?php else: ?>
<span>Нет фото</span> <?php endif; ?>
<input type="file" name="photo" accept="image/*"> </td>
<td>
<button type="submit">Обновить</button> </form>
</td>
<td>
<form method="POST" action="catalog_edit.php" onsubmit="return confirm('Вы уверены, что хотите удалить этот велосипед?');">
<input type="hidden" name="bike_id" value="<?php echo $bike['bike_id']; ?>"> <input type="hidden" name="action" value="delete">
<button type="submit">Удалить</button>

113
</form>
</td>
</tr>
<?php endwhile; ?> </tbody>
</table>
</div>
</body>
</html>
<?php include 'footer.php'; ?>
7.Файл booking.php
Описание: Файл для оформления бронирования велосипедов. Он проверяет куки на наличие выбранных велосипедов, отображает форму для ввода данных бронирования и обрабатывает отправку формы, записывая данные о бронировании в базу данных.
<?php session_start(); include 'header.php';
include 'db.php'; // Подключение к базе данных
// Проверка, есть ли в куки информация о бронировании if (!isset($_COOKIE['bookings'])) {
header('Location: catalog.php'); exit();
}
$bookings = json_decode($_COOKIE['bookings'], true); if (empty($bookings)) {
header('Location: catalog.php'); exit();
}
// Получение информации о выбранных велосипедах из базы данных $bikeIds = implode(',', array_map('intval', $bookings));
$query = "SELECT bike_id, bike_model, serial_number FROM bikes WHERE bike_id IN ($bikeIds)"; $result = $conn->query($query);
$bikes = [];
while ($row = $result->fetch_assoc()) { $bikes[] = $row;
}
if ($_SERVER["REQUEST_METHOD"] == "POST") { // Обработка данных формы
if (!isset($_SESSION['user_id'])) {
header('Location: booking.php?error=Необходимо авторизоваться для оформления бронирования'); exit();
}
$user_id = $_SESSION['user_id'];
$start_date = $conn->real_escape_string($_POST['start_date']); $start_time = $conn->real_escape_string($_POST['start_time']); $end_date = $conn->real_escape_string($_POST['end_date']); $end_time = $conn->real_escape_string($_POST['end_time']);
$start_datetime = $start_date . ' ' . $start_time; $end_datetime = $end_date . ' ' . $end_time;
// Проверка доступности велосипедов из куки $availability_query = "SELECT BD.bike_id, BK.bike_model
FROM bookingdetails BD
JOIN bookings B ON BD.booking_id = B.booking_id JOIN bikes BK ON BD.bike_id = BK.bike_id WHERE BD.bike_id IN ($bikeIds)
AND B.status_id = 1
AND ((B.start_time <= '$start_datetime' AND B.end_time > '$start_datetime') OR (B.start_time < '$end_datetime' AND B.end_time >= '$end_datetime')

114
OR (B.start_time >= '$start_datetime' AND B.end_time <= '$end_datetime'))"; $availability_result = $conn->query($availability_query);
$unavailable_bikes = [];
while ($row = $availability_result->fetch_assoc()) { $unavailable_bikes[] = $row['bike_model'];
}
if (!empty($unavailable_bikes)) {
$error_message = "Следующие велосипеды недоступны на выбранные даты: " . implode(', ', $unavailable_bikes);
}else {
//Вставка данных о бронировании
$booking_query = "INSERT INTO bookings (user_id, start_time, end_time, status_id) VALUES ('$user_id', '$start_datetime', '$end_datetime', 1)";
if ($conn->query($booking_query) === TRUE) { $booking_id = $conn->insert_id;
//Вставка деталей бронирования foreach ($bikes as $bike) {
$bike_id = $bike['bike_id'];
$details_query = "INSERT INTO bookingdetails (booking_id, bike_id) VALUES ('$booking_id', '$bike_id')"; $conn->query($details_query);
}
//Обновление счетчика бронирований пользователя
$update_booking_count_query = "UPDATE users SET booking_count = booking_count + 1 WHERE user_id = '$user_id'"; $conn->query($update_booking_count_query);
// Очистка куки после бронирования setcookie('bookings', '', time() - 3600, '/'); header('Location: confirmation.php'); exit();
} else {
$error_message = "Ошибка при оформлении бронирования: " . $conn->error;
}
}
}
$conn->close(); ?>
<div class="container"> <h1>Оформление бронирования</h1>
<form class="form" action="booking.php" method="POST" id="booking-form"> <h3>Выбранные велосипеды</h3>
<ul id="bike-list">
<?php foreach ($bikes as $bike): ?>
<li class="bike-item" data-bike-id="<?php echo $bike['bike_id']; ?>">
<?php echo htmlspecialchars($bike['bike_model'], ENT_QUOTES, 'UTF-8'); ?> (Серийный номер: <?php echo htmlspecialchars($bike['serial_number'], ENT_QUOTES, 'UTF-8'); ?>)
<span class="remove-bike" onclick="removeBike(<?php echo $bike['bike_id']; ?>)">✖</span> </li>
<?php endforeach; ?> </ul>
<label for="start_date">Дата и время начала:</label>
<input type="date" id="start_date" name="start_date" required> <select id="start_time" name="start_time" required>
<?php for ($hour = 9; $hour <= 20; $hour++): ?>
<option value="<?php echo str_pad($hour, 2, '0', STR_PAD_LEFT) . ':00'; ?>"> <?php echo str_pad($hour, 2, '0', STR_PAD_LEFT) . ':00'; ?>
</option>
<option value="<?php echo str_pad($hour, 2, '0', STR_PAD_LEFT) . ':30'; ?>"> <?php echo str_pad($hour, 2, '0', STR_PAD_LEFT) . ':30'; ?>
</option> <?php endfor; ?>
</select>
<label for="end_date">Дата и время окончания:</label> <input type="date" id="end_date" name="end_date" required>

115
<select id="end_time" name="end_time" required> <?php for ($hour = 9; $hour <= 20; $hour++): ?>
<option value="<?php echo str_pad($hour, 2, '0', STR_PAD_LEFT) . ':00'; ?>"> <?php echo str_pad($hour, 2, '0', STR_PAD_LEFT) . ':00'; ?>
</option>
<option value="<?php echo str_pad($hour, 2, '0', STR_PAD_LEFT) . ':30'; ?>"> <?php echo str_pad($hour, 2, '0', STR_PAD_LEFT) . ':30'; ?>
</option> <?php endfor; ?>
</select>
<button type="submit" id="submit-button">Оформить бронирование</button> </form>
</div>
<script>
// Функция для удаления велосипеда из куки и DOM function removeBike(bikeId) {
let bookings = getCookie('bookings');
bookings = bookings ? JSON.parse(bookings) : []; bookings = bookings.filter(id => id !== bikeId);
//Если куки bookings пустые, удаляем их полностью if (bookings.length === 0) {
setCookie('bookings', '', -1); // Удаление куки } else {
setCookie('bookings', JSON.stringify(bookings), 7); // Сохранение на 7 дней
}
//Удаление элемента из DOM
document.querySelector(`li[data-bike-id="${bikeId}"]`).remove();
// Перезагрузка страницы, если все велосипеды удалены if (bookings.length === 0) {
location.reload();
}
}
//Функция для получения значения куки function getCookie(name) {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([.$?*|{}()[]\/+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
//Функция для установки куки
function setCookie(name, value, days) { let expires = "";
if (days) {
let date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = "; expires=" + date.toUTCString();
} else {
expires = "; expires=Thu, 01 Jan 1970 00:00:00 GMT";
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
// Ограничение выбора времени для начала и окончания бронирования function setDateTimeRestrictions() {
const now = new Date();
const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); const nextWeek = new Date(now); nextWeek.setDate(nextWeek.getDate() + 7);
const formatDate = (date) => {
return date.toISOString().slice(0, 10);
};

116
document.getElementById('start_date').setAttribute('min', formatDate(now)); document.getElementById('start_date').setAttribute('max', formatDate(nextWeek)); document.getElementById('end_date').setAttribute('min', formatDate(now)); document.getElementById('end_date').setAttribute('max', formatDate(nextWeek));
}
// Проверка доступности велосипедов function checkAvailability() {
const startDate = document.getElementById('start_date').value; const startTime = document.getElementById('start_time').value; const endDate = document.getElementById('end_date').value; const endTime = document.getElementById('end_time').value;
if (startDate && startTime && endDate && endTime) { const xhr = new XMLHttpRequest(); xhr.open('POST', 'check_availability.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
const errorDiv = document.querySelector('.alert.alert-error'); if (errorDiv) {
errorDiv.remove();
}
if (!response.available) { document.getElementById('submit-button').disabled = true; let newErrorDiv = document.createElement('div'); newErrorDiv.className = 'alert alert-error';
newErrorDiv.textContent = 'Следующие велосипеды недоступны на выбранные даты: ' + response.unavailable_bikes.join(', ');
document.querySelector('.container').insertBefore(newErrorDiv, document.querySelector('form')); } else {
document.getElementById('submit-button').disabled = false;
}
}
}
};
xhr.send(`start_date=${startDate}&start_time=${startTime}&end_date=${endDate}&end_time=${endTime}&bike_ids=${JSON
.stringify(<?php echo json_encode($bookings); ?>)}`);
}
}
// Проверка авторизации и установка ограничений времени document.addEventListener('DOMContentLoaded', function() {
setDateTimeRestrictions();
<?php if (!isset($_SESSION['user_id'])): ?> document.getElementById('submit-button').disabled = true; let errorDiv = document.createElement('div'); errorDiv.className = 'alert alert-error';
errorDiv.textContent = 'Необходимо авторизоваться для оформления бронирования'; document.querySelector('.container').insertBefore(errorDiv, document.querySelector('form'));
<?php endif; ?>
// Добавление обработчиков событий для полей даты и времени document.getElementById('start_date').addEventListener('change', checkAvailability); document.getElementById('start_time').addEventListener('change', checkAvailability); document.getElementById('end_date').addEventListener('change', checkAvailability); document.getElementById('end_time').addEventListener('change', checkAvailability);
});
</script>
</body>
</html>
8. Файл check_availability.php

117
Описание: Этот файл обрабатывает AJAX-запросы для проверки доступности велосипедов на выбранные даты. Он используется в форме бронирования для проверки наличия велосипедов.
<?php
include 'db.php'; // Подключение к базе данных
$start_date = $conn->real_escape_string($_POST['start_date']); $start_time = $conn->real_escape_string($_POST['start_time']); $end_date = $conn->real_escape_string($_POST['end_date']); $end_time = $conn->real_escape_string($_POST['end_time']); $bike_ids = json_decode($_POST['bike_ids'], true);
$start_datetime = $start_date . ' ' . $start_time; $end_datetime = $end_date . ' ' . $end_time;
$bikeIds = implode(',', array_map('intval', $bike_ids));
// Проверка доступности велосипедов из куки, учитывая статус бронирования (status_id = 1) $availability_query = "SELECT BD.bike_id, BK.bike_model
FROM bookingdetails BD
JOIN bookings B ON BD.booking_id = B.booking_id JOIN bikes BK ON BD.bike_id = BK.bike_id WHERE BD.bike_id IN ($bikeIds)
AND B.status_id = 1
AND ((B.start_time <= '$start_datetime' AND B.end_time > '$start_datetime') OR (B.start_time < '$end_datetime' AND B.end_time >= '$end_datetime')
OR (B.start_time >= '$start_datetime' AND B.end_time <= '$end_datetime'))"; $availability_result = $conn->query($availability_query);
$unavailable_bikes = [];
while ($row = $availability_result->fetch_assoc()) { $unavailable_bikes[] = $row['bike_model'];
}
$response = [
'available' => empty($unavailable_bikes), 'unavailable_bikes' => $unavailable_bikes
];
echo json_encode($response);
$conn->close(); ?>
9. Файл login_process.php
Описание: Файл обрабатывает данные формы входа. Он проверяет наличие пользователя в базе данных, сверяет пароль и устанавливает сессию для авторизованного пользователя. В зависимости от роли пользователя, он перенаправляется на соответствующую страницу.
<?php
include 'db.php';
if ($_SERVER["REQUEST_METHOD"] == "POST") { $email = $conn->real_escape_string($_POST['email']);
$password = $conn->real_escape_string($_POST['password']);
$query = "SELECT * FROM Users WHERE email='$email'"; $result = $conn->query($query);
if ($result->num_rows == 1) { $user = $result->fetch_assoc();
if (password_verify($password, $user['password_hash'])) { session_start();
$_SESSION['user_id'] = $user['user_id'];

118
$_SESSION['username'] = $user['username']; $_SESSION['role_id'] = $user['role_id'];
switch ($user['role_id']) { case 1:
header("Location: admin_profile.php?success=Вы успешно вошли в систему."); break;
case 2:
header("Location: profile.php?success=Вы успешно вошли в систему."); break;
case 3:
header("Location: manager_profile.php?success=Вы успешно вошли в систему."); break;
default:
header("Location: login.php?error=Неизвестная роль пользователя.");
}
} else {
header("Location: login.php?error=Неверный пароль.");
}
} else {
header("Location: login.php?error=Пользователь с таким email не найден.");
}
}
$conn->close(); ?>
10.Файл registration.php
Описание: Этот файл отображает форму регистрации нового пользователя. В нем также
показываются оповещения об ошибках или успехах регистрации.
<?php
include 'header.php'; include 'db.php';
// Показ оповещений
if (isset($_GET['error'])) {
echo '<div class="alert alert-error">' . htmlspecialchars($_GET['error']) . '</div>';
}
if (isset($_GET['success'])) {
echo '<div class="alert alert-success">' . htmlspecialchars($_GET['success']) . '</div>';
}
?>
<div class="registration-container"> <h1>Регистрация</h1>
<form action="register_process.php" method="POST" class="form"> <label for="username">Имя пользователя:</label>
<input type="text" id="username" name="username" required>
<label for="email">Электронная почта:</label>
<input type="email" id="email" name="email" required>
<label for="phone_number">Номер телефона:</label>
<input type="text" id="phone_number" name="phone_number">
<label for="password">Пароль:</label>
<input type="password" id="password" name="password" required>
<label for="confirm_password">Подтвердите пароль:</label>
<input type="password" id="confirm_password" name="confirm_password" required>
<button type="submit">Зарегистрироваться</button> </form>
</div>
<?php include 'footer.php'; ?>

119
</body>
</html>
11.Файл register_process.php
Описание: Файл обрабатывает данные формы регистрации. Он проверяет корректность введенных данных, хеширует пароль и записывает данные нового пользователя в базу данных.
<?php
include 'header.php'; include 'db.php';
// Показ оповещений
if (isset($_GET['error'])) {
echo '<div class="alert alert-error">' . htmlspecialchars($_GET['error']) . '</div>';
}
if (isset($_GET['success'])) {
echo '<div class="alert alert-success">' . htmlspecialchars($_GET['success']) . '</div>';
}
?>
<div class="registration-container"> <h1>Регистрация</h1>
<form action="register_process.php" method="POST" class="form"> <label for="username">Имя пользователя:</label>
<input type="text" id="username" name="username" required>
<label for="email">Электронная почта:</label>
<input type="email" id="email" name="email" required>
<label for="phone_number">Номер телефона:</label>
<input type="text" id="phone_number" name="phone_number">
<label for="password">Пароль:</label>
<input type="password" id="password" name="password" required>
<label for="confirm_password">Подтвердите пароль:</label>
<input type="password" id="confirm_password" name="confirm_password" required>
<button type="submit">Зарегистрироваться</button> </form>
</div>
<?php include 'footer.php'; ?> </body>
</html>
12.Файл statistics_manager.php
Описание: Файл предназначен для менеджеров и администраторов. Он позволяет формировать отчеты по бронированиям за выбранный период и, при необходимости,
скачивать эти отчеты в формате Excel.
<?php
include 'header_adm.php'; include 'db.php';
//Проверка роли пользователя if ($_SESSION['role_id'] != 3) { header("Location: login.php");
exit();
}
//Инициализация переменных для сообщений $message = '';
$error = '';

120
// Обработка запроса на формирование отчета $report_data = [];
if ($_SERVER["REQUEST_METHOD"] == "POST" && (isset($_POST['generate_report']) || isset($_POST['download_excel'])))
{
$report_type = $_POST['report_type']; $start_date = $_POST['start_date']; $end_date = $_POST['end_date']; $user_id = $_POST['user_id']; $status_id = $_POST['status_id'];
// Проверка корректности введенных данных if (empty($start_date) || empty($end_date)) {
$error = 'Пожалуйста, укажите оба периода для отчета.';
} elseif ($start_date > $end_date) {
$error = 'Дата начала не может быть позже даты окончания.';
} else { $conditions = [];
$conditions[] = "B.start_time BETWEEN '$start_date' AND '$end_date'"; if ($user_id) {
$conditions[] = "B.user_id = $user_id";
}
if ($status_id) {
$conditions[] = "B.status_id = $status_id";
}
$where_clause = implode(' AND ', $conditions);
if ($report_type == 'bookings') {
$report_query = "SELECT B.booking_id, B.start_time, B.end_time, U.username, BS.status_name, GROUP_CONCAT(DISTINCT BK.bike_model SEPARATOR ', ') AS bike_models
FROM bookings B
LEFT JOIN users U ON B.user_id = U.user_id
LEFT JOIN bookingstatus BS ON B.status_id = BS.status_id LEFT JOIN bookingdetails BD ON B.booking_id = BD.booking_id LEFT JOIN bikes BK ON BD.bike_id = BK.bike_id
WHERE $where_clause GROUP BY B.booking_id";
$result = $conn->query($report_query); while ($row = $result->fetch_assoc()) {
$report_data[] = $row;
}
if (empty($report_data)) {
$message = 'Нет данных о бронированиях за указанный период.';
}elseif (isset($_POST['download_excel'])) {
//Создание Excel документа
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment; filename="booking_report_' . date('Ymd') . '.xls"');
echo "ID бронирования\tНачало\tКонец\tПользователь\tСтатус\tВелосипеды\n"; foreach ($report_data as $data) {
echo $data['booking_id'] . "\t" . $data['start_time'] . "\t" . $data['end_time'] . "\t" . $data['username'] . "\t" . $data['status_name'] . "\t" . $data['bike_models'] . "\n";
}
exit();
}
}
}
}
// Получение списка пользователей для фильтра $users_query = "SELECT user_id, username FROM users"; $users_result = $conn->query($users_query);
// Получение списка статусов бронирования для фильтра $status_query = "SELECT status_id, status_name FROM bookingstatus"; $status_result = $conn->query($status_query);
$conn->close(); ?>
<!DOCTYPE html>