Стек: PHP (без фреймворков), MySQL (PDO), HTML, немного JavaScript.
Что умеет система:
- Регистрация и вход по телефону + пароль
- Отдельный вход для администратора (логин admin, пароль по умолчанию dar123456)
- Запрос расписания к внешнему API по дате и номеру телефона
- Диагностический просмотр по коду (можно посмотреть расписание по чужому телефону)
- Rate limiting — ограничение частоты запросов (настраивается)
- Админ-панель: просмотр/редактирование/удаление пользователей, сброс пароля
- Настройка URL и токена API через веб-интерфейс (без правки кода)
- Автоматическое создание таблиц в БД при первом запуске
Структура базы данных
Скрипт сам создаёт три таблицы:
- users — id, phone, password, full_name, is_admin, created_at, last_login
- settings — setting_key, setting_value (api_url, api_token, лимиты)
- rate_limits — id, user_id, request_time (логи для антифлуда)
Как работает антифлуд (Rate Limiting)
- Считает количество запросов от пользователя за заданный период
- Проверяет минимальный интервал между запросами
- При превышении лимита возвращает сообщение с ожиданием
- Старые записи автоматически очищаются раз в час
Админ-панель
- Просмотр всех пользователей с их ролями
- Редактирование телефона и имени пользователя
- Сброс пароля на dar123456
- Удаление пользователей (кроме самого себя)
- Настройка API URL и токена через форму
- Настройка параметров rate limiting (макс запросов, период, кулдаун)
Работа с внешним API
Запросы к API расписания выполняются через cURL с параметрами:
?token=...&DatRasp=YYYYMMDD&telephone=...
SSL-проверка отключена для совместимости с серверами на самоподписных сертификатах.
Пользовательская часть
- Вход по телефону (11 цифр) и паролю
- Регистрация с проверкой уникальности телефона
- Выбор даты и просмотр расписания в таблице
- Подсветка строк с услугой "Перерыв"
- Диагностическая ссылка — просмотр расписания по чужому коду
Особенности безопасности
- Пароли хранятся в хешированном виде (password_hash)
- Подготовленные запросы PDO защищают от SQL-инъекций
- Входные данные фильтруются (preg_replace для телефона и даты)
- Сессии для авторизации
Минусы и что можно улучшить
- Отключена проверка SSL-сертификатов (можно исправить, добавив CURLOPT_CAINFO)
- Вся логика в одном файле — для большого проекта лучше разделить
- Нет CSRF-защиты форм
- Пароль администратора по умолчанию жёстко задан — лучше вынести в настройки
Итог
Отличное решение для небольшого корпоративного сервиса или учебного проекта. Всё работает "из коробки": создал БД, накидал таблицы, настроил подключение — и можно пользоваться. Админка позволяет править всё на лету без доступа к коду и FTP.
PHP
<div class="container">
<?php
session_start();
// Подключение к MySQL
$db_host = 'localhost';
$db_name = 'baza';
$db_user = 'nameuser';
$db_pass = 'parol';
try {
$db = new PDO("mysql:host=$db_host;dbname=$db_name;charset=utf8mb4", $db_user, $db_pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Создание таблицы для настроек
$db->exec("CREATE TABLE IF NOT EXISTS settings (
setting_key VARCHAR(100) PRIMARY KEY,
setting_value TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
// Создание таблицы для rate limiting
$db->exec("CREATE TABLE IF NOT EXISTS rate_limits (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
request_time DATETIME NOT NULL,
INDEX idx_user_time (user_id, request_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
// Функция получения настроек
function getSetting($db, $key, $default = '') {
$stmt = $db->prepare("SELECT setting_value FROM settings WHERE setting_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ? $result['setting_value'] : $default;
}
// Функция обновления настроек
function updateSetting($db, $key, $value) {
$stmt = $db->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?)
ON DUPLICATE KEY UPDATE setting_value = ?");
return $stmt->execute([$key, $value, $value]);
}
// Инициализация настроек по умолчанию, если их нет
if (!getSetting($db, 'api_url')) {
updateSetting($db, 'api_url', 'https://38.22.15.96:88/logobaza/ru_RU/hs/RaspSite/');
}
if (!getSetting($db, 'api_token')) {
updateSetting($db, 'api_token', '68421395:AAasdfasdfasFdYeFmqLs_s');
}
if (!getSetting($db, 'rate_limit_requests')) {
updateSetting($db, 'rate_limit_requests', '6');
}
if (!getSetting($db, 'rate_limit_time')) {
updateSetting($db, 'rate_limit_time', '60');
}
if (!getSetting($db, 'rate_limit_cooldown')) {
updateSetting($db, 'rate_limit_cooldown', '10');
}
} catch (PDOException $e) {
die("Ошибка базы данных: " . $e->getMessage());
}
// Получаем текущие настройки
$apiUrlBase = getSetting($db, 'api_url');
$defaultToken = getSetting($db, 'api_token');
$RATE_LIMIT_REQUESTS = (int)getSetting($db, 'rate_limit_requests');
$RATE_LIMIT_TIME = (int)getSetting($db, 'rate_limit_time');
$RATE_LIMIT_COOLDOWN = (int)getSetting($db, 'rate_limit_cooldown');
// Функция проверки rate limit
function checkRateLimit($db, $userId) {
global $RATE_LIMIT_REQUESTS, $RATE_LIMIT_TIME, $RATE_LIMIT_COOLDOWN;
if (!$userId) return true;
try {
$now = new DateTime();
// Проверка минимального интервала между запросами
$stmt = $db->prepare("SELECT request_time FROM rate_limits
WHERE user_id = ?
ORDER BY request_time DESC LIMIT 1");
$stmt->execute([$userId]);
$lastRequest = $stmt->fetch(PDO::FETCH_ASSOC);
if ($lastRequest) {
$lastTime = new DateTime($lastRequest['request_time']);
$interval = $now->getTimestamp() - $lastTime->getTimestamp();
if ($interval < $RATE_LIMIT_COOLDOWN) {
return [
'allowed' => false,
'message' => "Подождите " . ($RATE_LIMIT_COOLDOWN - $interval) . " секунд перед следующим запросом",
'wait' => $RATE_LIMIT_COOLDOWN - $interval
];
}
}
// Проверка количества запросов за период
$periodStart = new DateTime();
$periodStart->modify('-' . $RATE_LIMIT_TIME . ' seconds');
$stmt = $db->prepare("SELECT COUNT(*) as count FROM rate_limits
WHERE user_id = ? AND request_time > ?");
$stmt->execute([$userId, $periodStart->format('Y-m-d H:i:s')]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result['count'] >= $RATE_LIMIT_REQUESTS) {
$oldestRequest = new DateTime();
$oldestRequest->modify('-' . $RATE_LIMIT_TIME . ' seconds');
$stmt = $db->prepare("SELECT request_time FROM rate_limits
WHERE user_id = ? AND request_time > ?
ORDER BY request_time ASC LIMIT 1");
$stmt->execute([$userId, $oldestRequest->format('Y-m-d H:i:s')]);
$oldest = $stmt->fetch(PDO::FETCH_ASSOC);
if ($oldest) {
$oldTime = new DateTime($oldest['request_time']);
$waitTime = $RATE_LIMIT_TIME - ($now->getTimestamp() - $oldTime->getTimestamp());
return [
'allowed' => false,
'message' => "Лимит запросов: " . $RATE_LIMIT_REQUESTS . " запросов в " . $RATE_LIMIT_TIME . " секунд. Подождите " . ceil($waitTime) . " секунд",
'wait' => ceil($waitTime)
];
}
}
// Записываем запрос
$stmt = $db->prepare("INSERT INTO rate_limits (user_id, request_time) VALUES (?, ?)");
$stmt->execute([$userId, $now->format('Y-m-d H:i:s')]);
// Очищаем старые записи
$cleanupTime = new DateTime();
$cleanupTime->modify('-1 hour');
$stmt = $db->prepare("DELETE FROM rate_limits WHERE request_time < ?");
$stmt->execute([$cleanupTime->format('Y-m-d H:i:s')]);
return ['allowed' => true, 'message' => null];
} catch (PDOException $e) {
error_log("Rate limit error: " . $e->getMessage());
return ['allowed' => true, 'message' => null];
}
}
// Создаем администратора если его нет
$adminPasswordHash = password_hash('qwert12345', PASSWORD_DEFAULT);
$stmt = $db->prepare("SELECT id FROM users WHERE phone = 'admin'");
$stmt->execute();
if (!$stmt->fetch()) {
$stmt = $db->prepare("INSERT INTO users (phone, password, full_name, is_admin) VALUES (?, ?, ?, 1)");
$stmt->execute(['admin', $adminPasswordHash, 'Администратор']);
}
// Обработка админских действий
if (isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1) {
// Обновление настроек API
if (isset($_POST['update_api_settings'])) {
$new_api_url = trim($_POST['api_url']);
$new_api_token = trim($_POST['api_token']);
$new_rate_requests = (int)$_POST['rate_limit_requests'];
$new_rate_time = (int)$_POST['rate_limit_time'];
$new_rate_cooldown = (int)$_POST['rate_limit_cooldown'];
if ($new_api_url && $new_api_token) {
updateSetting($db, 'api_url', $new_api_url);
updateSetting($db, 'api_token', $new_api_token);
updateSetting($db, 'rate_limit_requests', $new_rate_requests);
updateSetting($db, 'rate_limit_time', $new_rate_time);
updateSetting($db, 'rate_limit_cooldown', $new_rate_cooldown);
// Обновляем глобальные переменные
$apiUrlBase = $new_api_url;
$defaultToken = $new_api_token;
$RATE_LIMIT_REQUESTS = $new_rate_requests;
$RATE_LIMIT_TIME = $new_rate_time;
$RATE_LIMIT_COOLDOWN = $new_rate_cooldown;
$_SESSION['admin_message'] = "Настройки API успешно обновлены!";
} else {
$_SESSION['admin_error'] = "Заполните все поля настроек";
}
header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
exit;
}
// Сброс пароля
if (isset($_POST['reset_password'])) {
$user_id = $_POST['user_id'];
$new_password = 'qwerty';
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $db->prepare("UPDATE users SET password = ? WHERE id = ?");
if ($stmt->execute([$hashed_password, $user_id])) {
$_SESSION['admin_message'] = "Пароль успешно сброшен на: qwerty";
} else {
$_SESSION['admin_error'] = "Ошибка сброса пароля";
}
header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
exit;
}
// Удаление пользователя
if (isset($_POST['delete_user'])) {
$user_id = $_POST['user_id'];
if ($user_id == $_SESSION['user_id']) {
$_SESSION['admin_error'] = "Нельзя удалить самого себя!";
} else {
$stmt = $db->prepare("DELETE FROM users WHERE id = ?");
if ($stmt->execute([$user_id])) {
$_SESSION['admin_message'] = "Пользователь успешно удален";
} else {
$_SESSION['admin_error'] = "Ошибка удаления";
}
}
header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
exit;
}
// Редактирование пользователя
if (isset($_POST['edit_user'])) {
$user_id = $_POST['user_id'];
$full_name = $_POST['full_name'];
$phone = preg_replace('/[^0-9]/', '', $_POST['phone']);
$stmt = $db->prepare("UPDATE users SET full_name = ?, phone = ? WHERE id = ?");
if ($stmt->execute([$full_name, $phone, $user_id])) {
$_SESSION['admin_message'] = "Данные пользователя обновлены";
} else {
$_SESSION['admin_error'] = "Ошибка обновления";
}
header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
exit;
}
}
// Обработка действий пользователя
$action = isset($_GET['action']) ? $_GET['action'] : 'login';
$error = '';
$success = '';
// Регистрация
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['register'])) {
$phone = preg_replace('/[^0-9]/', '', $_POST['phone']);
$password = $_POST['password'];
$confirm_password = $_POST['confirm_password'];
$full_name = trim($_POST['full_name'] ?? '');
if (empty($phone) || empty($password)) {
$error = "Заполните все обязательные поля";
} elseif (strlen($phone) != 11) {
$error = "Телефон должен содержать 11 цифр (например, 79262852676)";
} elseif (strlen($password) < 4) {
$error = "Пароль должен быть не менее 4 символов";
} elseif ($password != $confirm_password) {
$error = "Пароли не совпадают";
} else {
$stmt = $db->prepare("SELECT id FROM users WHERE phone = ?");
$stmt->execute([$phone]);
if ($stmt->fetch()) {
$error = "Пользователь с таким номером уже существует";
} else {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = $db->prepare("INSERT INTO users (phone, password, full_name, is_admin) VALUES (?, ?, ?, 0)");
if ($stmt->execute([$phone, $hashed_password, $full_name, 0])) {
$success = "Регистрация успешна! Теперь вы можете войти.";
$action = 'login';
} else {
$error = "Ошибка при регистрации";
}
}
}
}
// Авторизация
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['login'])) {
$phone = preg_replace('/[^0-9]/', '', $_POST['phone']);
$password = $_POST['password'];
if (empty($phone) || empty($password)) {
$error = "Введите телефон и пароль";
} else {
$stmt = $db->prepare("SELECT id, phone, password, full_name, is_admin FROM users WHERE phone = ?");
$stmt->execute([$phone]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_phone'] = $user['phone'];
$_SESSION['user_name'] = $user['full_name'];
$_SESSION['is_admin'] = isset($user['is_admin']) ? $user['is_admin'] : 0;
$stmt = $db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?");
$stmt->execute([$user['id']]);
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
} else {
$error = "Неверный телефон или пароль";
}
}
}
// Админ-логин
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['admin_login'])) {
$login = $_POST['admin_login_name'];
$password = $_POST['admin_password'];
if ($login == 'admin' && password_verify($password, $adminPasswordHash)) {
$_SESSION['admin_logged_in'] = true;
$_SESSION['is_admin'] = 1;
$_SESSION['user_id'] = 1;
$_SESSION['user_phone'] = 'admin';
$_SESSION['user_name'] = 'Администратор';
header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
exit;
} else {
$admin_error = "Неверный логин или пароль";
}
}
// Выход
if (isset($_GET['logout'])) {
session_destroy();
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
// Проверка авторизации
$isAuthenticated = isset($_SESSION['user_id']);
$userPhone = $isAuthenticated ? $_SESSION['user_phone'] : '';
// Админ-панель
$showAdminPanel = isset($_GET['admin_panel']) && isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1;
// Получаем список пользователей для админки
$users = [];
if ($showAdminPanel) {
$stmt = $db->query("SELECT id, phone, full_name, is_admin, created_at, last_login FROM users ORDER BY id DESC");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Получаем данные расписания
$schedule = [];
$error_api = null;
$submitted = false;
$date = isset($_GET['date']) ? preg_replace('/[^0-9]/', '', $_GET['date']) : date('Ymd');
$diagnosticCode = isset($_GET['diagnostic']) ? preg_replace('/[^0-9]/', '', $_GET['diagnostic']) : null;
$rateLimitError = null;
if ($isAuthenticated && isset($_GET['show_schedule']) && !$showAdminPanel) {
// Проверяем rate limit перед запросом
$rateLimitCheck = checkRateLimit($db, $_SESSION['user_id']);
if (!$rateLimitCheck['allowed']) {
$error_api = $rateLimitCheck['message'];
} else {
$submitted = true;
$phone = $diagnosticCode ? $diagnosticCode : $userPhone;
$url = $apiUrlBase . "?token=" . urlencode($defaultToken) . "&DatRasp=" . $date . "&telephone=" . urlencode($phone);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false) {
$error_api = "Ошибка соединения с сервером";
} elseif ($httpCode != 200) {
$error_api = "Ошибка сервера: " . $httpCode;
} else {
$bom = pack('H*','EFBBBF');
$response = preg_replace('/^' . $bom . '/', '', $response);
$data = json_decode($response, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
$schedule = $data;
} else {
$error_api = "Ошибка обработки данных";
}
}
}
}
// Получаем сообщения из сессии
$admin_message = isset($_SESSION['admin_message']) ? $_SESSION['admin_message'] : null;
$admin_error = isset($_SESSION['admin_error']) ? $_SESSION['admin_error'] : null;
unset($_SESSION['admin_message'], $_SESSION['admin_error']);
?>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
.page-title {padding: 0px !important;
}
.auth-card, .schedule-card, .admin-card { background: white; border-radius: 24px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow: hidden; }
.auth-header, .schedule-header, .admin-header { padding: 32px; text-align: center; color: white; }
.auth-header, .schedule-header { background:#612f8c; }
.admin-header { background: linear-gradient(135deg, #e53e3e 0%, #c53030 100%); }
.auth-header h1, .schedule-header h1, .admin-header h1 { font-size: 1.8rem; margin-bottom: 8px; }
.auth-body, .schedule-body, .admin-body { padding: 32px; background: #d7cce1}
.form-group { margin-bottom: 20px; }
.form-group label { display: block; font-size: 0.85rem; font-weight: 600; color: #4a5568; margin-bottom: 8px; }
.form-group input, .form-group textarea { width: 100%; padding: 12px 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 1rem; font-family: monospace; }
.form-group textarea { font-family: monospace; resize: vertical; }
.btn {background: #FFFFFF}
.btn-danger { background: linear-gradient(135deg, #e53e3e 0%, #c53030 100%); }
.btn-warning { background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%); }
.btn-sm { padding: 8px 16px; font-size: 0.85rem; width: auto; }
.btn-success { background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); }
.link { text-align: center; margin-top: 20px; color: #718096; }
.link a { color: #667eea; text-decoration: none; font-weight: 600; }
.alert { padding: 12px 16px; border-radius: 12px; margin-bottom: 20px; }
.alert-error { background: #fed7d7; color: #c53030; }
.alert-success { background: #c6f6d5; color: #22543d; }
.alert-info { background: #edf2f7; text-align: center; }
.user-info { background: #edf2f7; padding: 16px; border-radius: 12px; margin-bottom: 24px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px; align-items: center; }
.users-table, .schedule-table { width: 100%; border-collapse: collapse; overflow-x: auto; display: block; }
.users-table th, .users-table td, .schedule-table th, .schedule-table td { padding: 12px; text-align: left; border-bottom: 1px solid #e2e8f0; }
.users-table th, .schedule-table th { background: #f7fafc; font-weight: 600; }
.badge-admin { background: #e53e3e; color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.7rem; display: inline-block; }
.badge-user { background: #48bb78; color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.7rem; display: inline-block; }
.action-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
.diagnostic-link { display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 12px; border-radius: 20px; font-size: 0.8rem; text-decoration: none; }
.break-row { background: #fff5f0; }
.settings-section { background: #f7fafc; border-radius: 16px; padding: 20px; margin-bottom: 30px; border: 2px solid #e2e8f0; }
.settings-section h3 { margin-bottom: 15px; color: #2d3748; }
.settings-section .form-group { margin-bottom: 15px; }
hr { margin: 20px 0; border-color: #e2e8f0; }
@media (max-width: 768px) { .container { padding: 20px; } .action-buttons { flex-direction: column; } }
</style>
<?php if ($showAdminPanel): ?>
<div class="admin-card">
<div class="admin-header"><h1>🔧 Админ-панель</h1><p>Управление пользователями и настройками системы</p></div>
<div class="admin-body">
<?php if ($admin_message): ?><div class="alert alert-success">✅ <?php echo htmlspecialchars($admin_message); ?></div><?php endif; ?>
<?php if ($admin_error): ?><div class="alert alert-error">⚠️ <?php echo htmlspecialchars($admin_error); ?></div><?php endif; ?>
<div style="display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap;">
<a href="<?php echo $_SERVER['PHP_SELF']; ?>" style="padding: 10px 20px; background: #edf2f7; color: #4a5568; text-decoration: none; border-radius: 40px;">📅 Основная страница</a>
<a href="?logout" style="padding: 10px 20px; background: #e53e3e; color: white; text-decoration: none; border-radius: 40px;">🚪 Выйти</a>
</div>
<!-- Настройки API -->
<div class="settings-section">
<h3>⚙️ Настройки API расписания</h3>
<form method="POST">
<div class="form-group">
<label>🔗 URL API:</label>
<input type="text" name="api_url" value="<?php echo htmlspecialchars(getSetting($db, 'api_url')); ?>" required>
</div>
<div class="form-group">
<label>🔑 Токен доступа:</label>
<textarea name="api_token" rows="2" required><?php echo htmlspecialchars(getSetting($db, 'api_token')); ?></textarea>
</div>
<h4 style="margin: 20px 0 15px 0;">🚦 Настройки ограничения запросов</h4>
<div class="form-group">
<label>📊 Максимум запросов:</label>
<input type="number" name="rate_limit_requests" value="<?php echo htmlspecialchars(getSetting($db, 'rate_limit_requests')); ?>" min="1" max="60" required>
<small style="color:#718096;">Количество запросов за период</small>
</div>
<div class="form-group">
<label>⏱️ Период (секунд):</label>
<input type="number" name="rate_limit_time" value="<?php echo htmlspecialchars(getSetting($db, 'rate_limit_time')); ?>" min="10" max="3600" required>
<small style="color:#718096;">За сколько секунд считаются запросы</small>
</div>
<div class="form-group">
<label>🕐 Минимальный интервал (секунд):</label>
<input type="number" name="rate_limit_cooldown" value="<?php echo htmlspecialchars(getSetting($db, 'rate_limit_cooldown')); ?>" min="1" max="60" required>
<small style="color:#718096;">Минимальное время между запросами</small>
</div>
<button type="submit" name="update_api_settings" class="btn btn-success">💾 Сохранить настройки</button>
</form>
</div>
<hr>
<h3>👥 Список пользователей</h3>
<div style="overflow-x: auto; margin-top: 20px;">
<table class="users-table">
<thead>
<tr><th>ID</th><th>Телефон</th><th>Имя</th><th>Роль</th><th>Действия</th>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo $user['id']; ?></td>
<td><?php echo htmlspecialchars($user['phone']); ?></td>
<td><?php echo htmlspecialchars($user['full_name'] ?: '—'); ?></td>
<td><?php echo ($user['is_admin'] == 1) ? '<span class="badge-admin">👑 Админ</span>' : '<span class="badge-user">👤 Пользователь</span>'; ?></td>
<td class="action-buttons">
<button onclick='editUser(<?php echo $user['id']; ?>, "<?php echo htmlspecialchars($user['phone']); ?>", "<?php echo htmlspecialchars($user['full_name']); ?>")' class="btn btn-sm" style="background:#4299e1;">✏️ Ред.</button>
<button onclick='resetPassword(<?php echo $user['id']; ?>)' class="btn btn-sm btn-warning">🔑 Сброс</button>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<button onclick='deleteUser(<?php echo $user['id']; ?>)' class="btn btn-sm btn-danger">🗑️ Удалить</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div id="editModal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); align-items:center; justify-content:center;">
<div style="background:white; padding:30px; border-radius:24px; max-width:500px;">
<h3>Редактировать пользователя</h3>
<form method="POST">
<input type="hidden" name="user_id" id="edit_user_id">
<div class="form-group"><label>Телефон</label><input type="tel" name="phone" id="edit_phone" required></div>
<div class="form-group"><label>Имя</label><input type="text" name="full_name" id="edit_full_name"></div>
<div style="display:flex; gap:10px;">
<button type="submit" name="edit_user" class="btn">Сохранить</button>
<button type="button" onclick="closeModal()" class="btn" style="background:#a0aec0;">Отмена</button>
</div>
</form>
</div>
</div>
<form method="POST" id="resetForm" style="display:none;"><input type="hidden" name="user_id" id="reset_user_id"><input type="hidden" name="reset_password" value="1"></form>
<form method="POST" id="deleteForm" style="display:none;"><input type="hidden" name="user_id" id="delete_user_id"><input type="hidden" name="delete_user" value="1"></form>
</div>
</div>
<script>
function editUser(id,phone,name){
document.getElementById('edit_user_id').value=id;
document.getElementById('edit_phone').value=phone;
document.getElementById('edit_full_name').value=name;
document.getElementById('editModal').style.display='flex';
}
function closeModal(){
document.getElementById('editModal').style.display='none';
}
function resetPassword(id){
if(confirm('Сбросить пароль на qwerty?')){
document.getElementById('reset_user_id').value=id;
document.getElementById('resetForm').submit();
}
}
function deleteUser(id){
if(confirm('Удалить пользователя?')){
document.getElementById('delete_user_id').value=id;
document.getElementById('deleteForm').submit();
}
}
</script>
<?php elseif (!$isAuthenticated): ?>
<div class="auth-card"><div class="auth-header"><p>Вход в систему</p></div>
<div class="auth-body">
<?php if ($error): ?><div class="alert alert-error"><?php echo htmlspecialchars($error); ?></div><?php endif; ?>
<?php if ($success): ?><div class="alert alert-success"><?php echo htmlspecialchars($success); ?></div><?php endif; ?>
<?php if ($action == 'login'): ?>
<form method="POST"><div class="form-group"><label>📞 Телефон</label><input type="tel" name="phone" required></div>
<div class="form-group"><label>🔒 Пароль</label><input type="password" name="password" required></div>
<button type="submit" name="login" class="btn">Войти</button><div class="link">Нет аккаунта? <a href="?action=register">Регистрация</a></div></form>
<?php else: ?>
<form method="POST"><div class="form-group"><label>📞 Телефон</label><input type="tel" name="phone" required></div>
<div class="form-group"><label>👤 Имя</label><input type="text" name="full_name" placeholder="Иван Иванов"></div>
<div class="form-group"><label>🔒 Пароль</label><input type="password" name="password" required></div>
<div class="form-group"><label>🔒 Подтверждение</label><input type="password" name="confirm_password" required></div>
<button type="submit" name="register" class="btn">Зарегистрироваться</button><div class="link">Уже есть аккаунт? <a href="?action=login">Войти</a></div></form>
<?php endif; ?>
<hr style="margin:20px 0"><details><summary style="color:#718096;cursor:pointer;">🔐 Вход для администратора</summary>
<form method="POST" style="margin-top:15px;"><div class="form-group"><label>Логин</label><input type="text" name="admin_login_name" ></div>
<div class="form-group"><label>Пароль</label><input type="password" name="admin_password"></div>
<button type="submit" name="admin_login" class="btn">Войти как админ</button></form></details>
</div></div>
<?php else: ?>
<div class="schedule-card"><div class="schedule-header"><h1>📅 Моё расписание</h1><p>Добро пожаловать, <?php echo htmlspecialchars($_SESSION['user_name'] ?: $_SESSION['user_phone']); ?>!</p></div>
<div class="schedule-body"><div class="user-info"><span>📞 Телефон: <?php echo htmlspecialchars($userPhone); ?></span>
<div style="display:flex; gap:12px;"><?php if (isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1): ?><a href="?admin_panel=1" style="background:#e53e3e; color:white; padding:8px 20px; border-radius:40px; text-decoration:none;">🔧 Админ-панель</a><?php endif; ?>
<a href="?logout" style="background:#718096; color:white; padding:8px 20px; border-radius:40px; text-decoration:none;">Выйти</a></div></div>
<form method="GET"><div class="form-group"><label>📅 Выберите дату:</label><input type="date" name="date" value="<?php echo date('Y-m-d', strtotime($date)); ?>" required></div>
<button type="submit" name="show_schedule" class="btn">Показать расписание</button></form>
<?php if ($submitted && !$error_api && empty($schedule)): ?><div class="alert alert-info" style="margin-top:20px;">📭 Нет записей</div>
<?php elseif ($error_api): ?><div class="alert alert-error" style="margin-top:20px;">⚠️ <?php echo htmlspecialchars($error_api); ?></div>
<?php elseif (!empty($schedule)): ?>
<div style="background:#edf2f7; padding:12px; border-radius:12px; margin:20px 0;">📋 Найдено: <?php echo count($schedule); ?> на <?php echo date('d.m.Y', strtotime($date)); ?></div>
<div style="overflow-x:auto;"><table class="schedule-table"><thead><tr><th>Время</th><th>Пациент</th><th>Услуга</th><th>Диагноз</th></tr></thead>
<tbody><?php foreach ($schedule as $item): $isBreak = ($item['Услуга']??'')=='Перерыв'; $dCode = $item['Диагностика']??null; ?>
<tr class="<?php echo $isBreak?'break-row':''; ?>"><td><?php if(!empty($item['НачалоПриема'])) echo date('H:i',strtotime($item['НачалоПриема'])).'-'.date('H:i',strtotime($item['ОкончаниеПриема'])); else echo '—'; ?></td>
<td><strong><?php echo htmlspecialchars($item['Пациент']??'—'); ?></strong></td>
<td><?php echo htmlspecialchars($item['Услуга']??'—'); ?></td>
<td><?php if($dCode): ?><a href="?show_schedule=1&date=<?php echo $date; ?>&diagnostic=<?php echo $dCode; ?>" class="diagnostic-link">🧪 Диагностика: <?php echo $dCode; ?></a><?php else: echo htmlspecialchars($item['Нарушение']??'—'); endif; ?></td></tr>
<?php endforeach; ?></tbody></table></div>
<?php elseif (!$submitted): ?><div class="alert alert-info" style="margin-top:20px;">🗓️ Выберите дату</div><?php endif; ?>
</div></div>
<?php endif; ?>
<script>document.querySelectorAll('input[type="tel"]').forEach(i=>i.addEventListener('input',function(e){this.value=this.value.replace(/[^0-9]/g,'').slice(0,11);}));</script>
</div>
<br/><br/><br/><br/>
Комментарии
Пока нет комментариев. Будьте первым!