View file admin/users.php

File size: 10.06Kb
<?php
/**
 * CMS: LaiCMS (v1.0 Edition 2026)
 * File: admin/users.php
 * Оптимизация: Ultra-Compact User & Economy Manager + Уведомления
 */

require_once '../system/db.php';
require_once '../system/functions.php';

if (!function_exists('isAdmin') || !isAdmin()) { die("403 Forbidden"); }

// 1. БЕЗОПАСНОЕ ОБНОВЛЕНИЕ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_user'])) {
    $target_id = (int)$_POST['user_id'];
    $new_user  = trim($_POST['username']);
    $new_role  = ($target_id === $_SESSION['user_id']) ? 'admin' : ($_POST['role'] === 'admin' ? 'admin' : 'user');
    $new_bal   = (int)$_POST['balance'];

    $stmt = $mysqli->prepare("UPDATE users SET username = ?, role = ?, balance = ? WHERE id = ?");
    $stmt->bind_param("ssii", $new_user, $new_role, $new_bal, $target_id);
    $stmt->execute();

    if (!empty($_POST['new_password'])) {
        $hash = password_hash($_POST['new_password'], PASSWORD_DEFAULT);
        $stmt_pw = $mysqli->prepare("UPDATE users SET password = ? WHERE id = ?");
        $stmt_pw->bind_param("si", $hash, $target_id);
        $stmt_pw->execute();
    }
    header("Location: users.php?success=upd"); exit;
}

// 2. БЕЗОПАСНОЕ УДАЛЕНИЕ
if (isset($_GET['delete'])) {
    $id = (int)$_GET['delete'];
    if ($id !== $_SESSION['user_id']) {
        $stmt = $mysqli->prepare("DELETE FROM users WHERE id = ?");
        $stmt->bind_param("i", $id);
        $stmt->execute();
    }
    header("Location: users.php?success=del"); exit;
}

include '../system/header.php';
$users = $mysqli->query("SELECT * FROM users ORDER BY id DESC");

// Логика отображения уведомлений
$msg = "";
if (isset($_GET['success'])) {
    if ($_GET['success'] == 'upd') $msg = "Данные пользователя успешно обновлены!";
    if ($_GET['success'] == 'del') $msg = "Пользователь удален из системы.";
}
?>

<style>
    .u-container { padding: 10px; max-width: 100%; }
    .u-card { background: var(--pico-card-background-color); border-radius: 16px; border: 1px solid var(--pico-muted-border-color); overflow: hidden; }
    
    .u-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
    .u-table td { padding: 12px 8px; border-bottom: 1px solid var(--pico-muted-border-color); vertical-align: middle; }
    
    .av-box { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-weight: bold; background: var(--pico-primary-background); color: #fff; flex-shrink: 0; }
    
    .badge { font-size: 0.6rem; font-weight: 800; padding: 2px 6px; border-radius: 6px; text-transform: uppercase; }
    .b-admin { background: #e74c3c22; color: #e74c3c; border: 1px solid #e74c3c; }
    .b-user { background: #3498db22; color: #3498db; border: 1px solid #3498db; }

    /* Красивое уведомление (Toast) */
    .toast-notify {
        position: fixed; top: 80px; right: 20px; z-index: 2000;
        background: var(--pico-primary); color: white;
        padding: 12px 24px; border-radius: 12px;
        box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        display: flex; align-items: center; gap: 10px;
        animation: slideInRight 0.5s ease, fadeOut 0.5s ease 4s forwards;
    }

    @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
    @keyframes fadeOut { to { opacity: 0; visibility: hidden; } }

    @media (max-width: 600px) {
        .u-table thead { display: none; }
        .u-table tr { display: block; padding: 10px; border-bottom: 5px solid var(--pico-muted-border-color); }
        .u-table td { display: flex; justify-content: space-between; border: none; padding: 4px 0; text-align: right; }
        .u-table td::before { content: attr(data-label); font-weight: bold; opacity: 0.5; font-size: 0.7rem; }
        .hide-mob { display: none; }
        .toast-notify { left: 20px; right: 20px; top: 20px; }
    }
</style>

<?php if ($msg): ?>
<div class="toast-notify animate__animated">
    <i class="fa-solid fa-circle-check"></i>
    <span><?= $msg ?></span>
</div>
<?php endif; ?>

<div class="u-container animate__animated animate__fadeIn">
    <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
        <h3 style="margin:0;"><i class="fa-solid fa-users-gear" style="color:var(--pico-primary)"></i> Управление</h3>
        <input type="text" id="uSearch" placeholder="Поиск пользователя..." onkeyup="uFilter()" style="margin:0; width:180px; font-size:0.8rem;">
    </div>

    <div class="u-card">
        <table class="u-table" id="uTable">
            <thead>
                <tr style="opacity:0.6; font-size:0.7rem;">
                    <th>ID</th>
                    <th>Профиль</th>
                    <th class="hide-mob">Роль</th>
                    <th>Баланс</th>
                    <th style="text-align:right;">Действия</th>
                </tr>
            </thead>
            <tbody>
                <?php while($u = $users->fetch_assoc()): ?>
                <tr class="u-item" data-search="<?= strtolower(htmlspecialchars($u['username'])) ?>">
                    <td data-label="ID"><kbd><?= $u['id'] ?></kbd></td>
                    <td data-label="Имя">
                        <div style="display:flex; align-items:center; gap:10px; justify-content: flex-end;">
                            <div style="text-align:right;">
                                <div style="font-weight:700;"><?= htmlspecialchars($u['username']) ?></div>
                                <div class="hide-mob" style="font-size:0.6rem; opacity:0.5;">Регистрация: <?= date('d.m.Y', strtotime($u['created_at'] ?? 'now')) ?></div>
                            </div>
                            <div class="av-box"><?= mb_strtoupper(mb_substr($u['username'], 0, 1)) ?></div>
                        </div>
                    </td>
                    <td data-label="Доступ" class="hide-mob">
                        <span class="badge b-<?= $u['role'] ?>"><?= $u['role'] ?></span>
                    </td>
                    <td data-label="Счет"><b style="color:var(--pico-primary);"><?= number_format($u['balance'], 0, '', ' ') ?></b> <small>LC</small></td>
                    <td data-label="Действие">
                        <div style="display:flex; gap:5px; justify-content:flex-end;">
                            <button class="outline" style="padding: 5px 10px;" onclick='openEdit(<?= json_encode($u) ?>)' title="Редактировать">
                                <i class="fa-solid fa-user-pen"></i>
                            </button>
                            <?php if($u['id'] !== $_SESSION['user_id']): ?>
                                <a href="?delete=<?= $u['id'] ?>" class="button outline secondary" style="padding: 5px 10px;" onclick="return confirm('Вы действительно хотите удалить пользователя?')" title="Удалить">
                                    <i class="fa-solid fa-user-xmark"></i>
                                </a>
                            <?php endif; ?>
                        </div>
                    </td>
                </tr>
                <?php endwhile; ?>
            </tbody>
        </table>
    </div>
</div>

<dialog id="uModal">
    <article style="padding:25px; border-radius:24px; border: 1px solid var(--pico-primary);">
        <header style="margin-bottom:20px; padding:0; background:none;">
            <a href="javascript:closeEdit()" class="close"></a>
            <h4 style="margin:0; display:flex; align-items:center; gap:10px;">
                <i class="fa-solid fa-id-card-clip"></i> Настройки аккаунта
            </h4>
        </header>
        <form method="POST">
            <input type="hidden" name="user_id" id="u_id">
            <div class="grid">
                <label>Логин пользователя
                    <input type="text" name="username" id="u_name" required>
                </label>
                <label>Приоритет (Роль)
                    <select name="role" id="u_role">
                        <option value="user">Пользователь</option>
                        <option value="admin">Администратор</option>
                    </select>
                </label>
            </div>
            <label>Баланс валюты (LC)
                <input type="number" name="balance" id="u_bal">
            </label>
            <label>Новый пароль <small>(оставьте пустым для сохранения старого)</small>
                <input type="password" name="new_password" placeholder="••••••••">
            </label>
            <footer style="margin-top:20px; padding: 0; background: none;">
                <button type="submit" name="edit_user" style="width:100%; border-radius:12px;">
                    <i class="fa-solid fa-save"></i> Сохранить изменения
                </button>
            </footer>
        </form>
    </article>
</dialog>

<script>
const uM = document.getElementById('uModal');

function openEdit(u) {
    document.getElementById('u_id').value = u.id;
    document.getElementById('u_name').value = u.username;
    document.getElementById('u_role').value = u.role;
    document.getElementById('u_bal').value = u.balance;
    uM.showModal();
}

function closeEdit() { uM.close(); }

function uFilter() {
    const q = document.getElementById('uSearch').value.toLowerCase();
    document.querySelectorAll('.u-item').forEach(i => {
        i.style.display = i.dataset.search.includes(q) ? "" : "none";
    });
}

// Закрытие при клике на фон
uM.addEventListener('click', (e) => { if(e.target === uM) closeEdit(); });

// Авто-скрытие уведомления через 4.5 сек
setTimeout(() => {
    const toast = document.querySelector('.toast-notify');
    if(toast) toast.style.display = 'none';
}, 4500);
</script>

<?php include '../system/footer.php'; ?>