Как реализовать рейтинговую таблицу с фильтром по сезонам?

Отличный вопрос! Реализация рейтинговой таблицы с фильтрацией по сезонам в WordPress — комплексная задача, которая затрагивает несколько ключевых аспектов разработки. Я подробно разберу несколько подходов — от самого простого до профессионального.

Анализ задачи

Перед началом реализации определим основные компоненты системы:

  1. Хранение данных - где и как хранить рейтинги
  2. Админ-интерфейс - ввод и управление данными
  3. Фронтенд-отображение - таблица на сайте
  4. Фильтрация - механизм выбора сезонов
  5. Сортировка - ordering по рейтингу

Способ 1: Использование плагинов (Для начинающих)

Рекомендуемые плагины:

TablePress + Дополнительные поля

  • TablePress для создания таблиц
  • Custom Post Type для сезонов
  • Интеграция через шорткоды

SportsPress (для спортивных рейтингов)

  • Специализированный плагин для спортивных данных
  • Встроенная поддержка сезонов, лиг, турниров
  • Готовые виджеты и шорткоды

Способ 2: Кастомная разработка (Рекомендуемый подход)

Шаг 1: Создание структуры данных

Создаем кастомный тип записи для рейтингов:

// functions.php
function create_rating_post_type() {
    register_post_type('rating', [
        'labels' => [
            'name' => 'Рейтинги',
            'singular_name' => 'Рейтинг'
        ],
        'public' => true,
        'has_archive' => true,
        'supports' => ['title', 'custom-fields'],
        'show_in_rest' => true
    ]);
    
    // Таксономия для сезонов
    register_taxonomy('season', ['rating'], [
        'labels' => [
            'name' => 'Сезоны',
            'singular_name' => 'Сезон'
        ],
        'hierarchical' => true,
        'show_in_rest' => true
    ]);
}
add_action('init', 'create_rating_post_type');

Шаг 2: Мета-поля для рейтингов

// Добавляем мета-поля
function add_rating_meta_boxes() {
    add_meta_box(
        'rating_details',
        'Детали рейтинга',
        'render_rating_meta_box',
        'rating',
        'normal',
        'high'
    );
}

function render_rating_meta_box($post) {
    wp_nonce_field('save_rating_meta', 'rating_meta_nonce');
    
    $rating = get_post_meta($post->ID, '_rating_value', true);
    $player_name = get_post_meta($post->ID, '_player_name', true);
    $team = get_post_meta($post->ID, '_team', true);
    $games_played = get_post_meta($post->ID, '_games_played', true);
    
    echo '
    <p>
        <label>Имя игрока:</label>
        <input type="text" name="player_name" value="'.esc_attr($player_name).'" style="width:100%;">
    </p>
    <p>
        <label>Команда:</label>
        <input type="text" name="team" value="'.esc_attr($team).'" style="width:100%;">
    </p>
    <p>
        <label>Рейтинг:</label>
        <input type="number" name="rating_value" value="'.esc_attr($rating).'" step="0.1" min="0" max="10">
    </p>
    <p>
        <label>Сыграно игр:</label>
        <input type="number" name="games_played" value="'.esc_attr($games_played).'">
    </p>';
}

function save_rating_meta($post_id) {
    if (!isset($_POST['rating_meta_nonce']) || 
        !wp_verify_nonce($_POST['rating_meta_nonce'], 'save_rating_meta')) {
        return;
    }
    
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    
    $fields = ['player_name', 'team', 'rating_value', 'games_played'];
    foreach ($fields as $field) {
        if (isset($_POST[$field])) {
            update_post_meta($post_id, '_'.$field, sanitize_text_field($_POST[$field]));
        }
    }
}
add_action('add_meta_boxes', 'add_rating_meta_boxes');
add_action('save_post', 'save_rating_meta');

Шаг 3: Шорткод для отображения таблицы

function rating_table_shortcode($atts) {
    $atts = shortcode_atts([
        'season' => '',
        'limit' => 50
    ], $atts);
    
    ob_start();
    
    // Получаем сезоны для фильтра
    $seasons = get_terms([
        'taxonomy' => 'season',
        'hide_empty' => false
    ]);
    
    // Форма фильтра
    echo '<div class="rating-filter">';
    echo '<select id="season-filter" onchange="filterRatingTable()">';
    echo '<option value="">Все сезоны</option>';
    foreach ($seasons as $season) {
        $selected = $season->slug == $atts['season'] ? 'selected' : '';
        echo '<option value="'.$season->slug.'" '.$selected.'>'.$season->name.'</option>';
    }
    echo '</select>';
    echo '</div>';
    
    // Аргументы для WP_Query
    $args = [
        'post_type' => 'rating',
        'posts_per_page' => $atts['limit'],
        'meta_key' => '_rating_value',
        'orderby' => 'meta_value_num',
        'order' => 'DESC'
    ];
    
    if (!empty($atts['season'])) {
        $args['tax_query'] = [
            [
                'taxonomy' => 'season',
                'field' => 'slug',
                'terms' => $atts['season']
            ]
        ];
    }
    
    $ratings_query = new WP_Query($args);
    
    if ($ratings_query->have_posts()) {
        echo '<table class="rating-table">';
        echo '<thead>
                <tr>
                    <th>Место</th>
                    <th>Игрок</th>
                    <th>Команда</th>
                    <th>Рейтинг</th>
                    <th>Игры</th>
                    <th>Сезон</th>
                </tr>
              </thead>
              <tbody>';
        
        $position = 1;
        while ($ratings_query->have_posts()) {
            $ratings_query->the_post();
            $post_id = get_the_ID();
            
            $player_name = get_post_meta($post_id, '_player_name', true);
            $team = get_post_meta($post_id, '_team', true);
            $rating = get_post_meta($post_id, '_rating_value', true);
            $games = get_post_meta($post_id, '_games_played', true);
            $season_terms = get_the_terms($post_id, 'season');
            $season_name = $season_terms ? $season_terms[0]->name : '—';
            
            echo '<tr>
                    <td>'.$position.'</td>
                    <td>'.esc_html($player_name).'</td>
                    <td>'.esc_html($team).'</td>
                    <td>'.number_format($rating, 1).'</td>
                    <td>'.$games.'</td>
                    <td>'.$season_name.'</td>
                  </tr>';
            $position++;
        }
        
        echo '</tbody></table>';
        wp_reset_postdata();
    } else {
        echo '<p>Рейтинги не найдены.</p>';
    }
    
    return ob_get_clean();
}
add_shortcode('rating_table', 'rating_table_shortcode');

Шаг 4: JavaScript для AJAX-фильтрации

// rating-script.js
function filterRatingTable() {
    const seasonSlug = document.getElementById('season-filter').value;
    const tableContainer = document.querySelector('.rating-table-container');
    
    // Показываем индикатор загрузки
    tableContainer.innerHTML = '<div class="loading">Загрузка...</div>';
    
    // AJAX запрос
    fetch('/wp-admin/admin-ajax.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: 'action=filter_ratings&season=' + seasonSlug
    })
    .then(response => response.text())
    .then(data => {
        tableContainer.innerHTML = data;
    })
    .catch(error => {
        console.error('Error:', error);
        tableContainer.innerHTML = '<p>Ошибка загрузки данных</p>';
    });
}

// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', function() {
    // Добавляем обработчик для селекта
    const seasonFilter = document.getElementById('season-filter');
    if (seasonFilter) {
        seasonFilter.addEventListener('change', filterRatingTable);
    }
});

Шаг 5: AJAX обработчик

// AJAX обработка фильтрации
function ajax_filter_ratings() {
    $season = sanitize_text_field($_POST['season']);
    
    $args = [
        'post_type' => 'rating',
        'posts_per_page' => 50,
        'meta_key' => '_rating_value',
        'orderby' => 'meta_value_num',
        'order' => 'DESC'
    ];
    
    if (!empty($season)) {
        $args['tax_query'] = [
            [
                'taxonomy' => 'season',
                'field' => 'slug',
                'terms' => $season
            ]
        ];
    }
    
    $ratings_query = new WP_Query($args);
    
    ob_start();
    
    if ($ratings_query->have_posts()) {
        echo '<table class="rating-table">';
        echo '<thead><tr><th>Место</th><th>Игрок</th><th>Команда</th><th>Рейтинг</th><th>Игры</th><th>Сезон</th></tr></thead><tbody>';
        
        $position = 1;
        while ($ratings_query->have_posts()) {
            $ratings_query->the_post();
            $post_id = get_the_ID();
            
            $player_name = get_post_meta($post_id, '_player_name', true);
            $team = get_post_meta($post_id, '_team', true);
            $rating = get_post_meta($post_id, '_rating_value', true);
            $games = get_post_meta($post_id, '_games_played', true);
            $season_terms = get_the_terms($post_id, 'season');
            $season_name = $season_terms ? $season_terms[0]->name : '—';
            
            echo '<tr>
                    <td>'.$position.'</td>
                    <td>'.esc_html($player_name).'</td>
                    <td>'.esc_html($team).'</td>
                    <td>'.number_format($rating, 1).'</td>
                    <td>'.$games.'</td>
                    <td>'.$season_name.'</td>
                  </tr>';
            $position++;
        }
        
        echo '</tbody></table>';
        wp_reset_postdata();
    } else {
        echo '<p>Рейтинги не найдены для выбранного сезона.</p>';
    }
    
    echo ob_get_clean();
    wp_die();
}
add_action('wp_ajax_filter_ratings', 'ajax_filter_ratings');
add_action('wp_ajax_nopriv_filter_ratings', 'ajax_filter_ratings');

Шаг 6: Стилизация

/* rating-styles.css */
.rating-filter {
    margin-bottom: 20px;
}

#season-filter {
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
}

.rating-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 20px;
}

.rating-table th,
.rating-table td {
    padding: 12px;
    text-align: left;
    border-bottom: 1px solid #eee;
}

.rating-table th {
    background-color: #f8f9fa;
    font-weight: 600;
}

.rating-table tr:hover {
    background-color: #f8f9fa;
}

.loading {
    text-align: center;
    padding: 40px;
    font-size: 16px;
    color: #666;
}

Шаг 7: Подключение скриптов и стилей

function enqueue_rating_assets() {
    wp_enqueue_script(
        'rating-script',
        get_template_directory_uri() . '/js/rating-script.js',
        [],
        '1.0',
        true
    );
    
    wp_enqueue_style(
        'rating-styles',
        get_template_directory_uri() . '/css/rating-styles.css'
    );
    
    // Локализация для AJAX
    wp_localize_script('rating-script', 'rating_ajax', [
        'ajax_url' => admin_url('admin-ajax.php')
    ]);
}
add_action('wp_enqueue_scripts', 'enqueue_rating_assets');

Способ 3: Использование Advanced Custom Fields (ACF)

Если вы используете ACF, процесс упрощается:

Настройка полей ACF:

  • Группа полей: "Рейтинг"
  • Поля:
  • player_name (Text)
  • team (Text)
  • rating_value (Number)
  • games_played (Number)
  • Таксономия: season (создается автоматически)

Код для шорткода с ACF:

function acf_rating_table_shortcode($atts) {
    $atts = shortcode_atts(['season' => ''], $atts);
    
    ob_start();
    
    // Фильтр сезонов
    $seasons = get_terms(['taxonomy' => 'season', 'hide_empty' => false]);
    
    echo '<div class="rating-filter">';
    echo '<select id="season-filter">';
    echo '<option value="">Все сезоны</option>';
    foreach ($seasons as $season) {
        $selected = $season->slug == $atts['season'] ? 'selected' : '';
        echo '<option value="'.$season->slug.'" '.$selected.'>'.$season->name.'</option>';
    }
    echo '</select></div>';
    
    // Таблица
    $args = [
        'post_type' => 'rating',
        'posts_per_page' => -1,
        'meta_key' => 'rating_value',
        'orderby' => 'meta_value_num',
        'order' => 'DESC'
    ];
    
    if (!empty($atts['season'])) {
        $args['tax_query'] = [[
            'taxonomy' => 'season',
            'field' => 'slug',
            'terms' => $atts['season']
        ]];
    }
    
    $query = new WP_Query($args);
    
    if ($query->have_posts()) {
        echo '<div class="rating-table-container"><table class="rating-table"><thead><tr>
                <th>Место</th><th>Игрок</th><th>Команда</th><th>Рейтинг</th><th>Игры</th>
              </tr></thead><tbody>';
        
        $position = 1;
        while ($query->have_posts()) {
            $query->the_post();
            echo '<tr>
                    <td>'.$position.'</td>
                    <td>'.get_field('player_name').'</td>
                    <td>'.get_field('team').'</td>
                    <td>'.get_field('rating_value').'</td>
                    <td>'.get_field('games_played').'</td>
                  </tr>';
            $position++;
        }
        echo '</tbody></table></div>';
        wp_reset_postdata();
    }
    
    return ob_get_clean();
}
add_shortcode('acf_rating_table', 'acf_rating_table_shortcode');

Способ 4: Использование кастомной таблицы в БД

Для высоконагруженных проектов рекомендуется использовать кастомную таблицу:

// Создание таблицы
function create_ratings_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'ratings';
    
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        player_name varchar(100) NOT NULL,
        team varchar(100) NOT NULL,
        rating_value decimal(3,1) NOT NULL,
        games_played int NOT NULL,
        season_slug varchar(50) NOT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}
register_activation_hook(__FILE__, 'create_ratings_table');

Рекомендации по использованию

  1. Для простых проектов - используйте Способ 1 с плагинами
  2. Для типичных сайтов - Способ 2 или 3 с ACF
  3. Для высоконагруженных систем - Способ 4 с кастомной таблицей

Дополнительные улучшения

  • Пагинация для больших