↩️ Назад

Категории

Интеграция расписания с 1С через JSON API: система авторизации и антифлуда на чистом PHP

02.06.2026 | коды из категории: 1С

про Интеграция расписания с 1С через JSON API: система авторизации и антифлуда на чистом PHP

📅 Расписание + 1С: безопасная связка на PHP

🔐 XSS-защита 🛡️ SQL-инъекции (PDO) ⏱️ Rate limiting ⚡ Интеграция с 1С 🧑‍💻 Админ-панель

Корпоративный календарь приёма пациентов — полный разбор функционала, механизмов безопасности и архитектуры.

📌 Что это за система?

Это веб-приложение для просмотра расписания на базе PHP + MySQL, которое интегрируется с внешним API (1С: Медицина / Стоматология). Пациенты (или сотрудники) авторизуются по телефону, выбирают дату и получают актуальные записи: ФИО пациента, время приёма, услугу, центр, возраст. Дополнительно система позволяет смотреть текст диагностики / заключение по специальному коду.

👥 Для кого? — Для медицинских центров, стоматологий, клиник, где расписание ведётся в 1С, а сотрудникам нужен простой и безопасный веб-доступ.
⚙️ Основные роли — Пользователь (просмотр расписания, диагностики) и администратор (управление пользователями, настройки API, лимиты).

🧩 Ключевой функционал

🔐 Регистрация и вход

  • Телефон (11 цифр) + пароль (min 4 символа)
  • Защита от ботов: математическая CAPTCHA (пример: 12 + 7 = ?)
  • Сессии PHP + хэширование паролей (password_hash)
  • Админ-логин с отдельной учётной записью

📅 Просмотр расписания

  • Выбор даты → запрос к API 1С (JSON)
  • Отображение: время приёма, пациент, услуга, центр, возраст (годы+месяцы), код диагностики
  • Фильтрация записей по центрам (Тропарёво, Ясенево, Коньково и др.)
  • Если пациенту >50 лет — возраст скрыт (GDPR‑like забота)

🔬 Диагностический блок

  • Каждая запись может содержать поле «Диагностика» (код). По клику система делает отдельный API-запрос с этим кодом.
  • Показывает полный текст заключения, дату запроса — полезно для врачей и администраторов.

🛠️ Админ-панель

  • CRUD пользователей (редактирование, сброс пароля, удаление)
  • Настройка API URL, токена, rate-limit (число запросов, интервал, блокировка)
  • Просмотр даты последнего входа, метка администратора
📡 Пример запроса к 1С API:
GET /api.php?token=***&DatRasp=20251201&telephone=79261234567 → JSON массив записей

🛡️ Защита: от XSS до ограничения частоты запросов

Приложение разработано с учётом стандартов веб-безопасности, чтобы исключить типичные уязвимости.

🧼 XSS (межсайтовый скриптинг)

Каждый вывод пользовательских данных или данных из API проходит через htmlspecialchars($string, ENT_QUOTES, 'UTF-8'). Функция h() экранирует кавычки, угловые скобки, что предотвращает внедрение JavaScript.

// принудительная фильтрация GET/POST
foreach ($_GET as $key => $value) {
    $_GET[$key] = is_string($value) ? trim($value) : $value;
}

🗄️ Защита от SQL-инъекций

Используется PDO с подготовленными запросами + ATTR_EMULATE_PREPARES = false. Ни одно значение не подставляется напрямую в SQL — только плейсхолдеры ? или именованные параметры.

$stmt = $db->prepare("SELECT ... FROM users WHERE phone = ?");
$stmt->execute([$phone]);

Таблицы создаются автоматически, но структура защищена от внедрения через разделение схемы и данных.

⏱️ Rate limiting (защита от брутфорса и DoS)

Реализовано на уровне БД: для каждого пользователя фиксируется время запросов к API 1С. Администратор может настроить:

  • Количество запросов (rate_limit_requests) — например, 5 запросов
  • Временной интервал (rate_limit_time) — например, 60 секунд
  • Задержка после превышения (cooldown) — обязательная пауза

Старые записи удаляются каждый час, таблица не разрастается.

🔐 Хэширование паролей и сессии

Пароли хранятся с password_hash() (bcrypt по умолчанию). При авторизации используется password_verify(). Администратор может сбросить пароль до значения по умолчанию, но хэш в БД всегда необратимый. Сессии защищены стандартными механизмами PHP + session_start(), logout уничтожает сессию.

🛡️ Дополнительные меры: валидация телефона (только цифры, 11 символов), чистка глобальных массивов от пробелов, проверка типа перед вставкой, ограничение размера входных данных через фильтрацию строк. Также API-токен не выводится в UI, используется из конфигурации.

⚙️ Архитектура и ключевые механики

📁 Структура конфигурации

Отдельный файл config/db_config.php содержит параметры БД, api_url, api_token, настройки rate-limit. Все поля проверяются на наличие — если чего-то нет, система выдаёт понятную ошибку.

🧮 Расчёт возраста

Функция calculateAge() работает с датами рождения из 1С, корректно обрабатывает некорректные значения ("01.01.0001") и возвращает красивый формат: «5 лет 2 месяца» или «3 дня». Логика: если возраст > 50 лет — не показывать (конфиденциальность).

🌐 CURL-запросы к 1С

Запросы выполняются через cURL с отключённой проверкой SSL для внутренних сетей, таймаут 30 секунд. Ответ очищается от BOM-символов, JSON декодируется с проверкой ошибок.

Если API возвращает не расписание, а текст запроса/диагностики — система переключается в режим отображения диагностического блока, где можно прочитать полное заключение врача.

🗂️ База данных (MySQL)

Таблицы: users, settings, rate_limits. При старте проверяется существование и создаются при необходимости. Для управления настройками используется паттерн Key-Value (settings).

📱 Пользовательский интерфейс

Адаптивный, мобильный дизайн (font Inter, grid, медиа-запросы). Основные фишки:

✔️ Фильтр по медицинским центрам (кнопки-теги) ✔️ Подсветка перерывов (оранжевая рамка) ✔️ Ссылка «Диагностика» внутри карточки ✔️ Отображение возраста с иконкой 🎂 ✔️ Интерактивная капча при регистрации

Автоматическое обновление счётчика найденных записей, динамическая фильтрация через JavaScript без перезагрузки страницы (data-атрибуты).

// Фильтр по центру: показывает только записи выбранного филиала
function filterByCenter(center) {
    cards.forEach(card => {
        if (center === 'all' || card.dataset.center === center) { ... }
    });
}

🧪 Что происходит под капотом при запросе расписания?

  1. Пользователь авторизован, выбирает дату → нажимает «Показать расписание» (GET-параметры).
  2. Срабатывает checkRateLimit(): проверяет, не превысил ли пользователь лимит запросов за период, а также не находится ли в кулдауне.
  3. Формируется URL: если есть код диагностики — используется параметр ND, иначе — телефон пользователя.
  4. cURL получает JSON → удаляется BOM → декодируется.
  5. Анализируется содержимое: если в ответе есть поля «ТекстЗапроса» или «ДатаЗапроса», то открывается режим диагностики. Иначе стандартное расписание.
  6. Для каждой записи вычисляется возраст, проверяются перерывы, формируется HTML карточка.
  7. Запись о запросе добавляется в таблицу rate_limits, устаревшие логи удаляются.
💡 Гибкость: администратор может менять параметры rate limit и API токен без изменения кода — настройки хранятся в таблице settings.

🚀 Ключевые особенности безопасности (чек-лист)

  • Защита от XSS: двойное экранирование + фильтрация входящих данных.
  • SQL injection: PDO prepare / execute, отсутствие конкатенации строк запроса.
  • Rate Limiting + cooldown: предотвращает перебор запросов и нагрузку на API 1С.
  • Пароли: только хэш в БД, никогда открытым текстом; сброс пароля через админку.
  • Контроль доступа: разделение ролей user / admin, проверка сессии перед отображением панели.
  • Капча при регистрации: динамическая сумма, хранится в скрытом поле (серверная валидация).
  • Обработка ошибок: нет показа чувствительных данных пользователю (используются общие сообщения).

Также реализована автоматическая чистка rate_limits раз в час, чтобы БД не разрасталась, и продуман механизм «администратор не может удалить сам себя».

📌 Итог: для каких задач идеально подходит?

✔️ Медицинские центры, где 1С служит системой учёта, а сотрудникам нужен быстрый доступ к расписанию на день.
✔️ Замена громоздких RDP-подключений к серверу 1С — веб-интерфейс работает с телефонов и планшетов.
✔️ Безопасная выдача диагностических заключений по коду (без прямого доступа к БД).
✔️ Администратор может управлять лимитами API и пользователями без программиста.

🔗

Готово к расширению: можно добавить двухфакторную аутентификацию, логирование действий, экспорт расписания в PDF.


✨ Архитектура демонстрирует грамотный подход: разделение логики, защита по стандартам OWASP, адаптивный интерфейс и надёжная интеграция с корпоративной экосистемой 1С.

PHP календарь

<?php
session_start();
// Защита от XSS
function h($string) {
    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}

// Защита от SQL-инъекций через глобальные массивы (доп. уровень)
foreach ($_GET as $key => $value) {
    $_GET[$key] = is_string($value) ? trim($value) : $value;
}
foreach ($_POST as $key => $value) {
    $_POST[$key] = is_string($value) ? trim($value) : $value;
}

// ========== ПОДКЛЮЧЕНИЕ КОНФИГА ==========
$configPath = __DIR__ . '/../../../papkakonfiga/db_config.php';

if (!file_exists($configPath)) {
    die("Ошибка конфигурации: файл настроек не найден по пути: " . $configPath);
}

$config = require $configPath;

// Проверяем наличие всех необходимых полей
$requiredFields = ['db_host', 'db_name', 'db_user', 'db_pass', 'api_url', 'api_token', 'rate_limit_requests', 'rate_limit_time', 'rate_limit_cooldown'];
$missingFields = [];
foreach ($requiredFields as $field) {
    if (!isset($config[$field])) {
        $missingFields[] = $field;
    }
}

if (!empty($missingFields)) {
    die("Ошибка конфигурации: отсутствуют поля: " . implode(', ', $missingFields));
}

// ========== ПОДКЛЮЧЕНИЕ К БД ==========
try {
    $db = new PDO(
        "mysql:host={$config['db_host']};dbname={$config['db_name']};charset=utf8mb4",
        $config['db_user'],
        $config['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
    )");
    
    $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)
    )");
    
    $db->exec("CREATE TABLE IF NOT EXISTS users (
        id INT AUTO_INCREMENT PRIMARY KEY,
        phone VARCHAR(20) UNIQUE NOT NULL,
        password VARCHAR(255) NOT NULL,
        full_name VARCHAR(255),
        is_admin TINYINT DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        last_login TIMESTAMP NULL
    )");
    
} catch (PDOException $e) {
    die("Ошибка базы данных: " . $e->getMessage());
}


// Отключаем эмуляцию подготовленных запросов
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

// Включаем режим ошибок (у вас уже есть)
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);


// ========== ФУНКЦИИ ==========

// Функция для расчета возраста
function calculateAge($birthDate, $currentDate = null) {
    if (empty($birthDate)) return '';
    
    // Некорректные даты
    if ($birthDate == '01.01.0001 0:00:00' || $birthDate == '0000-00-00') {
        return '';
    }
    
    try {
        $birth = new DateTime($birthDate);
        
        if ($currentDate && $currentDate != '0000-00-00') {
            $current = new DateTime($currentDate);
        } else {
            $current = new DateTime();
        }
        
        $diff = $birth->diff($current);
        
        $years = $diff->y;
        $months = $diff->m;
        $days = $diff->d;
        
        // Если больше 50 лет - не показываем
        if ($years > 50) {
            return '';
        }
        
        if ($years > 0) {
            return $years . ' ' . getYearsWord($years) . ' ' . $months . ' ' . getMonthsWord($months);
        } elseif ($months > 0) {
            return $months . ' ' . getMonthsWord($months);
        } else {
            return $days . ' ' . getDaysWord($days);
        }
    } catch (Exception $e) {
        return '';
    }
}

function getYearsWord($years) {
    if ($years % 10 == 1 && $years % 100 != 11) return 'год';
    if (in_array($years % 10, [2,3,4]) && !in_array($years % 100, [12,13,14])) return 'года';
    return 'лет';
}

function getMonthsWord($months) {
    if ($months % 10 == 1 && $months % 100 != 11) return 'месяц';
    if (in_array($months % 10, [2,3,4]) && !in_array($months % 100, [12,13,14])) return 'месяца';
    return 'месяцев';
}

function getDaysWord($days) {
    if ($days % 10 == 1 && $days % 100 != 11) return 'день';
    if (in_array($days % 10, [2,3,4]) && !in_array($days % 100, [12,13,14])) return 'дня';
    return 'дней';
}

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]);
}

function checkRateLimit($db, $userId, $config) {
    if (!$userId) return ['allowed' => true, 'message' => null];
    
    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 < $config['rate_limit_cooldown']) {
                return ['allowed' => false, 'message' => "Подождите " . ($config['rate_limit_cooldown'] - $interval) . " секунд"];
            }
        }
        
        $periodStart = new DateTime();
        $periodStart->modify('-' . $config['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'] >= $config['rate_limit_requests']) {
            return ['allowed' => false, 'message' => "Лимит: " . $config['rate_limit_requests'] . " запросов в " . $config['rate_limit_time'] . " сек"];
        }
        
        $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];
    }
}

// ========== ИНИЦИАЛИЗАЦИЯ НАСТРОЕК ==========
//if (!getSetting($db, 'api_url')) {
//    updateSetting($db, 'api_url', $config['api_url']);
//    updateSetting($db, 'api_token', $config['api_token']);
//    updateSetting($db, 'rate_limit_requests', $config['rate_limit_requests']);
//    updateSetting($db, 'rate_limit_time', $config['rate_limit_time']);
//    updateSetting($db, 'rate_limit_cooldown', $config['rate_limit_cooldown']);
//}

// Создаем администратора
$adminPasswordHash = password_hash($config['admin_default_password'] ?? '123456', 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, 'Администратор']);
}

$apiUrlBase = $config['api_url'];
$defaultToken = $config['api_token'];

// ========== ОБРАБОТКА ЗАПРОСОВ ==========
$error = '';
$success = '';
$admin_error = '';
$action = isset($_GET['action']) ? $_GET['action'] : 'login';

// РЕГИСТРАЦИЯ - ИСПРАВЛЕННАЯ ВЕРСИЯ
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['register'])) {
    $phone = preg_replace('/[^0-9]/', '', $_POST['phone']);
    $password = $_POST['password'];
    $confirm = $_POST['confirm_password'];
    $full_name = trim($_POST['full_name'] ?? '');
    
    // Получаем данные капчи
    $user_captcha = trim($_POST['captcha'] ?? '');
    $expected_captcha = trim($_POST['captcha_expected'] ?? '');
    
    // ВАЖНО: Проверяем, что капча не пустая и совпадает
    if (empty($user_captcha)) {
        $error = "Введите ответ на пример";
    } elseif (empty($expected_captcha)) {
        $error = "Ошибка验证 капчи, попробуйте снова";
    } elseif ($user_captcha != $expected_captcha) {
        $error = "Неверный ответ на пример";
    } elseif (empty($phone) || empty($password)) {
        $error = "Заполните обязательные поля";
    } elseif (strlen($phone) != 11) {
        $error = "Телефон: 11 цифр";
    } elseif (strlen($password) < 4) {
        $error = "Пароль минимум 4 символа";
    } elseif ($password != $confirm) {
        $error = "Пароли не совпадают";
    
	} else {
    $stmt = $db->prepare("SELECT id FROM users WHERE phone = ?");
    $stmt->execute([$phone]);
    if ($stmt->fetch()) {
        $error = "Пользователь уже существует";
    } else {
        $hashed = password_hash($password, PASSWORD_DEFAULT);
        // Вариант 1: указываем все 4 поля
        $stmt = $db->prepare("INSERT INTO users (phone, password, full_name, is_admin) VALUES (?, ?, ?, ?)");
        $stmt->execute([$phone, $hashed, $full_name, 0]);
        
        // ИЛИ Вариант 2: is_admin не указываем (если в БД DEFAULT 0)
        // $stmt = $db->prepare("INSERT INTO users (phone, password, full_name) VALUES (?, ?, ?)");
        // $stmt->execute([$phone, $hashed, $full_name]);
        
        if ($stmt) {
            $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'] = $user['is_admin'];
            
            $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'])) {
    $adminLogin = $_POST['admin_login_name'] ?? '';
    $adminPass = $_POST['admin_password'] ?? '';
    
    if ($adminLogin == 'admin' && password_verify($adminPass, $adminPasswordHash)) {
        $_SESSION['user_id'] = 1;
        $_SESSION['user_phone'] = 'admin';
        $_SESSION['user_name'] = 'Администратор';
        $_SESSION['is_admin'] = 1;
        header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
        exit;
    } else {
        $admin_error = "Неверный логин или пароль";
    }
}

// Выход
if (isset($_GET['logout'])) {
    session_destroy();
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

// Админ-панель
$showAdminPanel = isset($_GET['admin_panel']) && ($_SESSION['is_admin'] ?? 0) == 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);
    
    // Обработка админ-действий
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        if (isset($_POST['update_api_settings'])) {
            updateSetting($db, 'api_url', trim($_POST['api_url']));
            updateSetting($db, 'api_token', trim($_POST['api_token']));
            updateSetting($db, 'rate_limit_requests', (int)$_POST['rate_limit_requests']);
            updateSetting($db, 'rate_limit_time', (int)$_POST['rate_limit_time']);
            updateSetting($db, 'rate_limit_cooldown', (int)$_POST['rate_limit_cooldown']);
            $_SESSION['admin_message'] = "Настройки обновлены";
            header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
            exit;
        }
        
        if (isset($_POST['reset_password'])) {
            $new_hash = password_hash($config['admin_default_password'] ?? '123456', PASSWORD_DEFAULT);
            $stmt = $db->prepare("UPDATE users SET password = ? WHERE id = ?");
            $stmt->execute([$new_hash, $_POST['user_id']]);
            $_SESSION['admin_message'] = "Пароль сброшен";
            header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
            exit;
        }
        
        if (isset($_POST['delete_user']) && $_POST['user_id'] != $_SESSION['user_id']) {
            $stmt = $db->prepare("DELETE FROM users WHERE id = ?");
            $stmt->execute([$_POST['user_id']]);
            $_SESSION['admin_message'] = "Пользователь удален";
            header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
            exit;
        }
        
        if (isset($_POST['edit_user'])) {
            $phone = preg_replace('/[^0-9]/', '', $_POST['phone']);
            $stmt = $db->prepare("UPDATE users SET full_name = ?, phone = ? WHERE id = ?");
            $stmt->execute([$_POST['full_name'], $phone, $_POST['user_id']]);
            $_SESSION['admin_message'] = "Данные обновлены";
            header('Location: ' . $_SERVER['PHP_SELF'] . '?admin_panel=1');
            exit;
        }
    }
}

$isAuthenticated = isset($_SESSION['user_id']);
$userPhone = $isAuthenticated ? $_SESSION['user_phone'] : '';

// ========== ПОЛУЧЕНИЕ РАСПИСАНИЯ ==========
$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;
$showDiagnostic = false;
$diagnosticText = '';
$diagnosticDate = '';

if ($isAuthenticated && isset($_GET['show_schedule']) && !$showAdminPanel) {
    $rateLimitCheck = checkRateLimit($db, $_SESSION['user_id'], $config);
    
    if (!$rateLimitCheck['allowed']) {
        $error_api = $rateLimitCheck['message'];
    } else {
        $submitted = true;
        
        if ($diagnosticCode) {
            $url = $apiUrlBase . "?token=" . urlencode($defaultToken) . "&DatRasp=" . $date . "&ZZ=T&ND=" . urlencode($diagnosticCode);
        } else {
            $url = $apiUrlBase . "?token=" . urlencode($defaultToken) . "&DatRasp=" . $date . "&telephone=" . urlencode($userPhone);
        }
        
        $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);
        $curlError = curl_error($ch);
        curl_close($ch);
        
        if ($response === false) {
            $error_api = "Ошибка соединения: " . $curlError;
        } elseif ($httpCode != 200) {
            $error_api = "Ошибка сервера: " . $httpCode;
        } else {
            // Удаляем BOM если есть
            $bom = pack('H*','EFBBBF');
            $response = preg_replace('/^' . $bom . '/', '', $response);
            $data = json_decode($response, true);
            
            if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
                // Проверяем, есть ли поле ТекстЗапроса в любом элементе
if (isset($data[0]['ТекстЗапроса']) || isset($data[0]['ДатаЗапроса'])) {
    // Это диагностическая информация - сохраняем ВСЕ записи
    $diagnosticEntries = [];  // Массив для всех записей
    foreach ($data as $entry) {
        if (!empty($entry['ТекстЗапроса']) || !empty($entry['ДатаЗапроса'])) {
            $diagnosticEntries[] = [
                'date' => $entry['ДатаЗапроса'] ?? 'Дата не указана',
                'text' => $entry['ТекстЗапроса'] ?? ''
            ];
        }
    }

    
    if (!empty($diagnosticEntries)) {
        $diagnosticData = $diagnosticEntries;  // Сохраняем ВСЕ записи
        $showDiagnostic = true;
    }
    $schedule = []; // Очищаем расписание
} else {
                    $schedule = $data;
                }
            } else {
                $error_api = "Ошибка обработки данных: " . json_last_error_msg();
            }
        }
    }
}

$admin_message = $_SESSION['admin_message'] ?? null;
unset($_SESSION['admin_message']);
?>

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title>Моё расписание</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <style>
		.page-title {display: none !important;}
      /* Фильтр по центрам */
.filter-section {
    background: #f7fafc;
    border-radius: 16px;
    padding: 16px;
    margin-bottom: 20px;
}
.filter-title {
    font-size: 0.85rem;
    font-weight: 600;
    color: #4a5568;
    margin-bottom: 12px;
}
.filter-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.filter-btn {
    padding: 6px 14px;
    border-radius: 30px;
    font-size: 0.75rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s;
    border: 1px solid #e2e8f0;
    background: white;
    color: #4a5568;
}
.filter-btn.active {
    background: #612f8c;
    color: white;
    border-color: #612f8c;
}
.filter-btn:hover:not(.active) {
    background: #edf2f7;
}
.filter-reset {
    background: #e2e8f0;
    color: #4a5568;
}
.filter-reset.active {
    background: #718096;
    border-color: #718096;
}
.filter-count {
    font-size: 0.7rem;
    margin-left: 6px;
    opacity: 0.7;
}
.no-results {
    text-align: center;
    padding: 40px 20px;
    color: #718096;
}
		
        .card { background: white; border-radius: 24px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); overflow: hidden; margin-bottom: 20px; }
        .card-header { background: #612f8c; padding: 24px; text-align: center; color: white; }
        .card-header h1 { font-size: 1.8rem; margin-bottom: 8px; color: white}
        .card-header p { opacity: 0.9; font-size: 0.9rem; }
        .card-body { padding: 24px; background: #fff; }
        
        .admin-header { background: linear-gradient(135deg, #e53e3e 0%, #c53030 100%); }
        
        .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; transition: all 0.3s; }
        .form-group input:focus { outline: none; border-color: #612f8c; }
        
        .btnn { padding: 12px 24px; border: none; border-radius: 40px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s; text-decoration: none; display: inline-block; text-align: center; background: #612f8c; color: white; width: 100%; }
.btnn:hover { padding: 12px 24px; border: none; border-radius: 40px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s; text-decoration: none; display: inline-block; text-align: center;background: #8b4dc0; color: white; width: 100%; }		
        .btnn-secondary { border: #ccc 1px solid}
        .btnn-danger { background: #e53e3e; }
        .btnn-sm { padding: 8px 16px; font-size: 0.85rem; width: auto; }
        
        .schedule-list { display: flex; flex-direction: column; gap: 12px; margin-top: 20px; }
        .schedule-card-item { background: #f7fafc; border-radius: 16px; padding: 16px; border-left: 4px solid #612f8c; transition: all 0.2s; }
        .schedule-card-item.break { border-left-color: #ed8936; background: #fff5f0; }
        .schedule-time { font-size: 1.2rem; font-weight: 700; color: #612f8c; margin-bottom: 8px; }
        .schedule-time.break-time { color: #ed8936; }
        .schedule-patient { font-size: 1rem; font-weight: 600; color: #2d3748; margin-bottom: 4px; }
        .schedule-service { font-size: 0.85rem; color: #718096; margin-bottom: 8px; }
        .schedule-diagnostic { margin-top: 8px; }
        .diagnostic-link { display: inline-flex; align-items: center; gap: 6px; background: #e9d8fd; color: #612f8c; padding: 6px 12px; border-radius: 20px; font-size: 0.75rem; text-decoration: none; font-weight: 500; }
        .diagnostic-link:hover { background: #d6bcfa; }
        
        .diagnostic-box { background: #edf2f7; border-radius: 16px; padding: 20px; margin-top: 20px; }
.diagnostic-text {
    background: white;
    padding: 12px 16px;
    border-radius: 12px;
    border-left: 4px solid #612f8c;
    white-space: pre-wrap;
    font-family: monospace;
    font-size: 0.8rem;
    line-height: 1.4;  /* Уменьшили межстрочный интервал */
    margin-top: 0;
}
		
        .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; color: #4a5568; text-align: center; }
        
        .user-info { background: #edf2f7; padding: 12px 16px; border-radius: 12px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; }
        
        .link { text-align: center; margin-top: 20px; }
        .link a { color: #612f8c; text-decoration: none; font-weight: 600; }
        
        .users-table { width: 100%; border-collapse: collapse; }
        .users-table th, .users-table td { padding: 12px; text-align: left; border-bottom: 1px solid #e2e8f0; }
        .users-table th { background: #f7fafc; font-weight: 600; }
        .badge-admin { background: #e53e3e; color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.7rem; }
        .badge-user { background: #48bb78; color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.7rem; }
        .action-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
.schedule-center {
    margin-bottom: 8px;
}
.schedule-center span {
    display: inline-block;
    padding: 4px 10px;
    border-radius: 20px;
    font-size: 0.9rem;
    font-weight: 600;
}		
        
        hr { margin: 20px 0; border-color: #e2e8f0; }
        
        @media (max-width: 480px) {
           
            .card-header { padding: 20px; }
            .card-body { padding: 20px; }
            .schedule-card-item { padding: 12px; }
        }
    </style>
</head>
<body>
<div class="container">

<?php if ($showAdminPanel): ?>
    <!-- АДМИН-ПАНЕЛЬ -->
    <div class="card">
        <div class="card-header admin-header">
            <h1>🔧 Админ-панель</h1>
            <p>Управление пользователями и настройками</p>
        </div>
        <div class="card-body">
            <?php if ($admin_message): ?>
                <div class="alert alert-success">✅ <?php echo htmlspecialchars($admin_message); ?></div>
            <?php endif; ?>
            
            <div style="display: flex; gap: 12px; margin-bottom: 24px;">
                <a href="<?php echo $_SERVER['PHP_SELF']; ?>" class="btnn btn-sm" style="background:#edf2f7; color:#4a5568;">📅 Основная</a>
                <a href="?logout" class="btnn btn-sm btn-danger">🚪 Выйти</a>
            </div>
           
            
            <h3>👥 Пользователи</h3>
            <div style="overflow-x: auto; margin-top: 16px;">
                <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'] ? '<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="btnn btn-sm" style="background:#4299e1;">✏️</button>
                                <button onclick='resetPassword(<?php echo $user['id']; ?>)' class="btnn btn-sm" style="background:#ed8936;">🔑</button>
                                <?php if ($user['id'] != $_SESSION['user_id']): ?>
                                    <button onclick='deleteUser(<?php echo $user['id']; ?>)' class="btnn btn-sm btn-danger">🗑️</button>
                                <?php endif; ?>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        </div>
    </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; z-index:1000;">
        <div style="background:white; padding:30px; border-radius:24px; max-width:400px; width:90%;">
            <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="btnn">Сохранить</button>
                    <button type="button" onclick="closeModal()" class="btnn btn-secondary">Отмена</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>

<?php elseif (!$isAuthenticated): ?>
    <!-- АВТОРИЗАЦИЯ -->
    <div class="card">
        <div class="card-header">
            <h1>📅 Расписание</h1>
            <p>Вход в систему</p>
        </div>
        <div class="card-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" placeholder="79260000000" required></div>
        <div class="form-group"><label>🔒 Пароль</label><input type="password" name="password" required></div>
        <button type="submit" name="login" class="btnn">Войти</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" placeholder="79260000000" 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>
        <div class="form-group">
            <?php 
            $num1 = rand(1, 20);
            $num2 = rand(1, 20);
            $captcha_result = $num1 + $num2;
            ?>
            <label>🧩 Решите пример: <?php echo $num1; ?> + <?php echo $num2; ?> = ?</label>
            <input type="text" name="captcha" placeholder="Введите число" required autocomplete="off">
            <input type="hidden" name="captcha_expected" value="<?php echo $captcha_result; ?>">
        </div>				
        <button type="submit" name="register" class="btnn">Зарегистрироваться</button>
        <div class="link">Уже есть аккаунт? <a href="?action=login">Войти</a></div>
    </form>
<?php endif; ?>
            
            <hr>
 
        </div>
    </div>

<?php else: ?>
    <!-- ОСНОВНАЯ СТРАНИЦА -->
    <div class="card">
        <div class="card-header">
            <h1>📅 Моё расписание</h1>
            <p>Добро пожаловать, <?php echo htmlspecialchars($_SESSION['user_name'] ?: substr($_SESSION['user_phone'], -4)); ?>!</p>
        </div>
        <div class="card-body">
            <div class="user-info">
                <span>📞 Телефон: <?php echo substr(htmlspecialchars($userPhone), 0, 4) . '***' . substr(htmlspecialchars($userPhone), -4); ?></span>
                <div style="display:flex; gap:8px;">
                    <?php if ($_SESSION['is_admin'] ?? 0): ?>
                        <a href="?admin_panel=1" class="btnn btn-sm" style="background:#e53e3e;">🔧 Админка</a>
                    <?php endif; ?>
                    <a href="?logout" class="btnn btn-sm btn-secondary">🚪 Выйти</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="btnn">Показать расписание</button>
            </form>
            
            <?php if ($submitted && !$error_api && empty($schedule) && !$showDiagnostic): ?>
                <div class="alert alert-info">📭 Нет записей на выбранную дату</div>
            <?php elseif ($error_api): ?>
                <div class="alert alert-error">⚠️ <?php echo htmlspecialchars($error_api); ?></div>
<?php elseif ($showDiagnostic && !empty($diagnosticData)): ?>
    <div class="diagnostic-box">
        <h3 style="margin-bottom: 16px;">📋 История по диагностике</h3>
        <div style="display: flex; flex-direction: column; gap: 20px;">
            <?php foreach ($diagnosticData as $entry): ?>
                <div style="border-left: 3px solid #612f8c; padding-left: 16px;">
                    <div style="font-weight: 600; color: #612f8c;">
                        📅 <?php echo htmlspecialchars($entry['date']); ?>
                    </div>
<div class="diagnostic-text" style="margin-top: 0; white-space: pre-wrap; line-height: 1.3;">
    <?php 
    // Чистим текст от лишних переносов
    $cleanText = preg_replace("/\n{3,}/", "\n\n", $entry['text']);  // 3 и более переносов -> 2
    $cleanText = preg_replace("/<br\s*\/?>\s*<br\s*\/?>/i", "<br>", nl2br(htmlspecialchars($cleanText))); // убираем двойные <br>
    echo $cleanText;
    ?>
</div>
                </div>
                <?php if (next($diagnosticData)): ?>
                    <hr style="margin: 5px 0;">
                <?php endif; ?>
            <?php endforeach; ?>
        </div>
        <div style="margin-top: 20px;">
            <a href="?show_schedule=1&date=<?php echo $date; ?>" class="btnn btn-sm" style="width:auto;">← Вернуться к расписанию</a>
        </div>
    </div>       
		<?php elseif (!empty($schedule)): ?>
    <?php 
    // Собираем уникальные центры для фильтра
    $centers = [];
    foreach ($schedule as $item) {
        $center = trim($item['Центр'] ?? '');
        if (!empty($center) && !in_array($center, $centers)) {
            $centers[] = $center;
        }
    }
    sort($centers); // Сортируем по алфавиту
    ?>
    
    <div class="alert alert-info" style="margin-top:20px;">
        📋 Найдено: <span id="totalCount"><?php echo count($schedule); ?></span> записей на <?php echo date('d.m.Y', strtotime($date)); ?>
    </div>
    
    <!-- Фильтр по центрам -->
    <?php if (!empty($centers)): ?>
    <div class="filter-section">
        <div class="filter-title">📍 Фильтр по центру:</div>
        <div class="filter-buttons">
            <button class="filter-btn active" data-center="all" onclick="filterByCenter('all')">
                📌 Все <span class="filter-count">(<?php echo count($schedule); ?>)</span>
            </button>
            <?php foreach ($centers as $center): 
                // Считаем количество записей в этом центре
                $centerCount = 0;
                foreach ($schedule as $item) {
                    if (trim($item['Центр'] ?? '') === $center) $centerCount++;
                }
            ?>
                <button class="filter-btn" data-center="<?php echo htmlspecialchars($center); ?>" onclick="filterByCenter('<?php echo htmlspecialchars($center); ?>')">
                    📍 <?php echo htmlspecialchars($center); ?> <span class="filter-count">(<?php echo $centerCount; ?>)</span>
                </button>
            <?php endforeach; ?>
        </div>
    </div>
    <?php endif; ?>
    
    <div class="schedule-list">
        <?php foreach ($schedule as $item): 
$birthDate = $item['ДатаРождения'] ?? '';
$age = ($item['Услуга'] == 'Перерыв') ? '' : calculateAge($birthDate, $date);
            $isBreak = ($item['Услуга'] ?? '') == 'Перерыв';
            $dCode = $item['Диагностика'] ?? null;
            $startTime = !empty($item['НачалоПриема']) ? date('H:i', strtotime($item['НачалоПриема'])) : '';
            $endTime = !empty($item['ОкончаниеПриема']) ? date('H:i', strtotime($item['ОкончаниеПриема'])) : '';
            $center = trim($item['Центр'] ?? '');
            
            // Определяем цвет для центра
            $centerColor = '';
            if (stripos($center, 'тропарево') !== false) {
                $centerColor = '#9b59b6';
            } elseif (stripos($center, 'ясенево') !== false) {
                $centerColor = '#2ecc71';
            } elseif (stripos($center, 'коньково') !== false) {
                $centerColor = '#e74c3c';
            } else {
                $centerColor = '#3498db';
            }
        ?>
      <div class="schedule-card-item <?php echo $isBreak ? 'break' : ''; ?>" data-center="<?php echo htmlspecialchars($center); ?>">
    <div class="schedule-time <?php echo $isBreak ? 'break-time' : ''; ?>">
        🕐 <?php echo $startTime; ?> - <?php echo $endTime; ?>
    </div>
    
    <?php if ($center): ?>
        <div class="schedule-center" style="margin-bottom: 8px;">
            <span style="display: inline-block; background: <?php echo $centerColor; ?>20; color: <?php echo $centerColor; ?>; padding: 4px 10px; border-radius: 20px; font-size: 0.7rem; font-weight: 600;">
                📍 <?php echo htmlspecialchars($center); ?>
            </span>
        </div>
    <?php endif; ?>
    
    <div class="schedule-patient">
        👤 <?php echo htmlspecialchars($item['Пациент'] ?? '—'); ?>
        <?php if ($age): ?>
            <span style="font-size: 0.65rem; background: #e74c3c20; color: #e74c3c; padding: 2px 8px; border-radius: 20px; margin-left: 8px;">
                🎂 <?php echo $age; ?>
            </span>
        <?php endif; ?>
    </div>
    
    <div class="schedule-service">
        📋 <?php echo htmlspecialchars($item['Услуга'] ?? '—'); ?>
    </div>
    
  <?php if ($birthDate && !$age && !$isBreak && $birthDate != '0000-00-00' && $birthDate != '01.01.0001 0:00:00' && strpos($birthDate, '0001') === false): ?>
    <div class="schedule-birth" style="font-size: 0.65rem; color: #718096;">
        🎂 ДР: <?php echo htmlspecialchars($birthDate); ?>
    </div>
<?php endif; ?>
    
    <div class="schedule-diagnostic">
        <?php if ($dCode): ?>
            <a href="?show_schedule=1&date=<?php echo $date; ?>&diagnostic=<?php echo $dCode; ?>" class="diagnostic-link">
                🔬 Диагностика
            </a>
        <?php else: ?>
            <span style="font-size:0.75rem; color:#718096;">
                📝 <?php echo htmlspecialchars($item['Нарушение'] ?? '—'); ?>
            </span>
        <?php endif; ?>
    </div>
</div>     
		
        <?php endforeach; ?>
    </div>
<?php elseif (!$submitted): ?>
                <div class="alert alert-info">🗓️ Выберите дату и нажмите "Показать расписание"</div>
            <?php endif; ?>
        </div>
    </div>
<?php endif; ?>

</div>

<script>
document.querySelectorAll('input[type="tel"]').forEach(input => {
    input.addEventListener('input', function(e) {
        this.value = this.value.replace(/[^0-9]/g, '').slice(0, 11);
    });
});

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('Сбросить пароль на 123456?')) {
        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>

	
<script>
// Фильтр по центрам
let currentCenterFilter = 'all';

function filterByCenter(center) {
    currentCenterFilter = center;
    
    // Обновляем активные кнопки
    document.querySelectorAll('.filter-btn').forEach(btn => {
        btn.classList.remove('active');
        if (btn.dataset.center === center) {
            btn.classList.add('active');
        }
    });
    
    // Фильтруем карточки
    const cards = document.querySelectorAll('.schedule-card-item');
    let visibleCount = 0;
    
    cards.forEach(card => {
        const cardCenter = card.dataset.center || '';
        if (center === 'all' || cardCenter === center) {
            card.style.display = 'block';
            visibleCount++;
        } else {
            card.style.display = 'none';
        }
    });
    
    // Показываем сообщение если ничего не найдено
    let noResultsMsg = document.querySelector('.no-results-message');
    if (!noResultsMsg && visibleCount === 0) {
        const scheduleList = document.querySelector('.schedule-list');
        const msg = document.createElement('div');
        msg.className = 'no-results no-results-message';
        msg.innerHTML = '😕 Нет записей в выбранном центре';
        scheduleList.appendChild(msg);
    } else if (noResultsMsg && visibleCount > 0) {
        noResultsMsg.remove();
    } else if (noResultsMsg && visibleCount === 0) {
        // уже есть
    }
}
</script>	
	
</body>
</html>

админка

<?php
session_start();

// Подавляем вывод ошибок в буфер
ob_start();

// Подключаем конфиг
$configPath = __DIR__ . '/../../../papkakonfiga/db_config.php';
if (!file_exists($configPath)) {
    die("Ошибка конфигурации");
}
$config = require $configPath;

// Проверяем пароль
$error = '';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $login = $_POST['login'] ?? '';
    $password = $_POST['password'] ?? '';
    
    // Хеш админ-пароля из конфига
    $adminPasswordHash = password_hash($config['admin_default_password'] ?? '123456', PASSWORD_DEFAULT);
    
    if ($login == 'admin' && password_verify($password, $adminPasswordHash)) {
        $_SESSION['user_id'] = 1;
        $_SESSION['user_phone'] = 'admin';
        $_SESSION['user_name'] = 'Администратор';
        $_SESSION['is_admin'] = 1;
        
        // Очищаем буфер и делаем редирект
        ob_end_clean();
        header('Location: /raspisaniee/?admin_panel=1');
        exit;
    } else {
        $error = "Неверные данные";
    }
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Админ-вход</title>
    <style>
        body {
            font-family: 'Inter', sans-serif;
            background: #f0f2f5;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            padding: 20px;
        }
        .admin-login-card {
            background: white;
            border-radius: 24px;
            padding: 32px;
            max-width: 400px;
            width: 100%;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        }
        .admin-login-card h1 {
            color: #e53e3e;
            margin-bottom: 24px;
            text-align: center;
            font-size: 1.5rem;
        }
        .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 {
            width: 90%;
            padding: 12px 16px;
            border: 2px solid #e2e8f0;
            border-radius: 12px;
            font-size: 1rem;
        }
        .btn {
            width: 100%;
            padding: 12px;
            background: #e53e3e;
            color: white;
            border: none;
            border-radius: 40px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
        }
        .error {
            background: #fed7d7;
            color: #c53030;
            padding: 12px;
            border-radius: 12px;
            margin-bottom: 20px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="admin-login-card">
        <h1>🔐 Админ-панель</h1>
        <?php if ($error): ?>
            <div class="error"><?php echo htmlspecialchars($error); ?></div>
        <?php endif; ?>
        <form method="POST">
            <div class="form-group">
                <label>Логин</label>
                <input type="text" name="login" required autocomplete="off">
            </div>
            <div class="form-group">
                <label>Пароль</label>
                <input type="password" name="password" required>
            </div>
            <button type="submit" class="btn">Войти</button>
        </form>
    </div>
</body>
</html>
Оцените статью
Всего: 0



Категории:

Категории

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку

Посетителей сегодня: 0
о блоге | карта блога | 📡 Подписаться на RSS

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA