View file admin/blog_manage.php

File size: 9.15Kb
<?php
/**
 * CMS: LaiCMS (v1.0 Edition 2026)
 * File: admin/blog_manage.php
 * Updated: Исправлена ошибка generate_slug, добавлен HTMX, Ultra-Compact UI.
 */

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

if (!function_exists('isAdmin') || !isAdmin()) { die("Доступ запрещен."); }

/**
 * Вспомогательная функция генерации Slug (Транслитерация)
 */
function generate_slug($text) {
    $cyr = [
        'а','б','в','г','д','е','ё','ж','з','и','й','к','л','м','н','о','п','р','с','т','у','ф','х','ц','ч','ш','щ','ъ','ы','ь','э','ю','я',
        'А','Б','В','Г','Д','Е','Ё','Ж','З','И','Й','К','Л','М','Н','О','П','Р','С','Т','У','Ф','Х','Ц','Ч','Ш','Щ','Ъ','Ы','Ь','Э','Ю','Я'
    ];
    $lat = [
        'a','b','v','g','d','e','io','zh','z','i','y','k','l','m','n','o','p','r','s','t','u','f','h','ts','ch','sh','shb','','y','','e','yu','ya',
        'a','b','v','g','d','e','io','zh','z','i','y','k','l','m','n','o','p','r','s','t','u','f','h','ts','ch','sh','shb','','y','','e','yu','ya'
    ];
    $text = str_replace($cyr, $lat, $text);
    $text = preg_replace('~[^\pL\d]+~u', '-', $text);
    $text = trim($text, '-');
    $text = strtolower($text);
    return empty($text) ? 'n-a' : $text;
}

// 1. ЛОГИКА ДОБАВЛЕНИЯ КАТЕГОРИИ
if (isset($_POST['add_category'])) {
    $cat_name = trim($_POST['cat_name']);
    if (!empty($cat_name)) {
        $stmt = $mysqli->prepare("INSERT INTO categories (name, slug) VALUES (?, ?)");
        $slug = generate_slug($cat_name);
        $stmt->bind_param("ss", $cat_name, $slug);
        $stmt->execute();
        header("Location: blog_manage.php?success=cat"); exit;
    }
}

// 2. УДАЛЕНИЕ (Поддержка HTMX и обычного GET)
if (isset($_GET['delete'])) {
    $p_id = (int)$_GET['delete'];
    $stmt = $mysqli->prepare("DELETE FROM posts WHERE id = ?");
    $stmt->bind_param("i", $p_id);
    $stmt->execute();
    
    // Если запрос от HTMX, возвращаем пустоту (строка скроется на фронте)
    if (isset($_SERVER['HTTP_HX_REQUEST'])) {
        exit(); 
    }
    header("Location: blog_manage.php?success=del"); exit;
}

// Получаем данные
$categories = $mysqli->query("SELECT * FROM categories ORDER BY name ASC")->fetch_all(MYSQLI_ASSOC);
$posts = $mysqli->query("
    SELECT p.*, u.username, u.role, c.name as cat_name
    FROM posts p 
    JOIN users u ON p.author_id = u.id 
    LEFT JOIN categories c ON p.category_id = c.id
    ORDER BY p.id DESC
");

$page_title = "Контент-центр";
include '../system/header.php';
?>

<style>
    .compact-container { padding: 10px; max-width: 100%; }
    .stat-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-bottom: 1rem; }
    .stat-mini { background: var(--pico-card-background-color); padding: 10px; border-radius: 12px; border: 1px solid var(--pico-muted-border-color); text-align: center; }
    
    .blog-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
    .blog-table tr { transition: all 0.3s ease; }
    .blog-table td { padding: 10px 5px; border-bottom: 1px solid var(--pico-muted-border-color); vertical-align: middle; }
    
    .role-badge { font-size: 0.6rem; padding: 2px 5px; border-radius: 4px; font-weight: 800; }
    .role-admin { background: #e74c3c22; color: #e74c3c; }
    .role-user { background: #3498db22; color: #3498db; }

    /* Плавное скрытие при удалении через HTMX */
    .htmx-swapping { opacity: 0; transform: translateX(20px); }

    @media screen and (max-width: 600px) {
        .blog-table thead { display: none; }
        .blog-table tr { display: block; padding: 10px; border: 1px solid var(--pico-muted-border-color); border-radius: 12px; margin-bottom: 10px; }
        .blog-table td { display: flex; justify-content: space-between; border: none; padding: 4px 0; }
        .blog-table td::before { content: attr(data-label); font-weight: bold; opacity: 0.5; font-size: 0.7rem; }
        .hide-mobile { display: none; }
    }
</style>

<div class="compact-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-folder-tree"></i> Управление</h3>
        <div style="display: flex; gap: 8px;">
            <button class="outline secondary" style="padding: 5px 15px;" onclick="document.getElementById('cat-modal').showModal()"><i class="fa-solid fa-tags"></i></button>
            <a href="/blog/add_post.php" role="button" style="padding: 5px 15px;"><i class="fa-solid fa-plus"></i></a>
        </div>
    </div>

    <div class="stat-grid">
        <div class="stat-mini"><small>Посты</small><div><strong><?= $posts->num_rows ?></strong></div></div>
        <div class="stat-mini"><small>Рубрики</small><div><strong><?= count($categories) ?></strong></div></div>
    </div>

    <div class="search-box" style="margin-bottom: 1rem;">
        <fieldset role="group">
            <input type="text" id="pSearch" placeholder="Поиск по названию..." onkeyup="fastFilter()">
            <select id="pCat" onchange="fastFilter()" style="width: auto;">
                <option value="">Все рубрики</option>
                <?php foreach($categories as $c): ?>
                    <option value="<?= htmlspecialchars($c['name']) ?>"><?= htmlspecialchars($c['name']) ?></option>
                <?php endforeach; ?>
            </select>
        </fieldset>
    </div>

    <table class="blog-table" id="blogTable">
        <thead>
            <tr style="opacity: 0.5; font-size: 0.75rem; text-transform: uppercase;">
                <th style="width: 50px;">ID</th>
                <th>Контент</th>
                <th class="hide-mobile">Рубрика</th>
                <th style="text-align: right;">Действия</th>
            </tr>
        </thead>
        <tbody>
            <?php while($p = $posts->fetch_assoc()): ?>
            <tr class="post-item" id="post-<?= $p['id'] ?>">
                <td data-label="ID">#<?= $p['id'] ?></td>
                <td data-label="Заголовок">
                    <div style="font-weight: 600;"><?= mb_strimwidth(htmlspecialchars($p['title']), 0, 50, "...") ?></div>
                    <div style="font-size: 0.75rem; opacity: 0.7;">
                        <span class="role-badge role-<?= $p['role'] ?>"><?= strtoupper($p['role']) ?></span> <?= htmlspecialchars($p['username']) ?>
                    </div>
                </td>
                <td data-label="Рубрика" class="hide-mobile">
                    <mark style="font-size: 0.7rem; padding: 2px 6px;"><?= $p['cat_name'] ?? 'Без рубрики' ?></mark>
                </td>
                <td data-label="Действия">
                    <div style="display: flex; gap: 5px; justify-content: flex-end;">
                        <a href="/blog/edit_post.php?id=<?= $p['id'] ?>" class="button outline contrast" style="padding: 4px 8px;"><i class="fa-solid fa-pen-to-square"></i></a>
                        
                        <button class="outline secondary" 
                                style="padding: 4px 8px;"
                                hx-get="?delete=<?= $p['id'] ?>" 
                                hx-target="#post-<?= $p['id'] ?>" 
                                hx-swap="outerHTML swap:0.3s"
                                hx-confirm="Удалить этот пост?">
                            <i class="fa-solid fa-trash"></i>
                        </button>
                    </div>
                </td>
            </tr>
            <?php endwhile; ?>
        </tbody>
    </table>
</div>

<dialog id="cat-modal">
    <article>
        <header>
            <a href="#close" class="close" onclick="document.getElementById('cat-modal').close()"></a>
            <strong>Управление рубриками</strong>
        </header>
        <form method="POST">
            <fieldset role="group">
                <input type="text" name="cat_name" placeholder="Название рубрики..." required>
                <button type="submit" name="add_category">Создать</button>
            </fieldset>
        </form>
        <div style="display: flex; flex-wrap: wrap; gap: 8px;">
            <?php foreach($categories as $c): ?>
                <span class="badge" style="background: var(--pico-muted-border-color); padding: 4px 10px; border-radius: 20px; font-size: 0.8rem;">
                    <?= htmlspecialchars($c['name']) ?>
                </span>
            <?php endforeach; ?>
        </div>
    </article>
</dialog>

<script>
function fastFilter() {
    const s = document.getElementById("pSearch").value.toLowerCase();
    const c = document.getElementById("pCat").value.toLowerCase();
    document.querySelectorAll(".post-item").forEach(row => {
        const text = row.innerText.toLowerCase();
        row.style.display = (text.includes(s) && text.includes(c)) ? "" : "none";
    });
}
</script>

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