refactor: update card grid filtering and pagination logic to support category/tag filters and display empty states
This commit is contained in:
@@ -188,13 +188,22 @@
|
||||
<button type="button" class="tab-btn active" data-filter="ALL">Tất cả</button>
|
||||
</div>
|
||||
|
||||
<!-- 2. CARD GRID -->
|
||||
<div class="news-grid" th:data-items-per-page="${block.data['itemsPerPage'] != null ? block.data['itemsPerPage'] : (block.data['limit'] != null ? block.data['limit'] : 9)}">
|
||||
<th:block th:each="p : ${posts}" th:if="${(block.data['layoutFilter'] == null or block.data['layoutFilter'] == 'ALL' or (p.layout != null and #strings.equals(p.layout.name(), block.data['layoutFilter'])))}">
|
||||
<!-- 2. CARD GRID (Strictly capped at 12 items per page max) -->
|
||||
<div class="news-grid" th:data-items-per-page="${block.data['limit'] != null and block.data['limit'] > 0 ? block.data['limit'] : 12}">
|
||||
<th:block th:each="p : ${posts}"
|
||||
th:if="${(
|
||||
(block.data['categoryFilter'] == null or #strings.isEmpty(block.data['categoryFilter']) or #strings.contains(block.data['categoryFilter'], 'ALL'))
|
||||
and
|
||||
(block.data['tagFilter'] == null or #strings.isEmpty(block.data['tagFilter']) or #strings.contains(block.data['tagFilter'], 'ALL'))
|
||||
) or (
|
||||
(p.category != null and block.data['categoryFilter'] != null and !#strings.isEmpty(block.data['categoryFilter']) and #strings.containsIgnoreCase(block.data['categoryFilter'], p.category.name))
|
||||
or
|
||||
(p.tags != null and block.data['tagFilter'] != null and !#strings.isEmpty(block.data['tagFilter']) and #strings.containsIgnoreCase(block.data['tagFilter'], p.tagNamesString))
|
||||
)}">
|
||||
<a th:href="@{'/posts/' + ${p.slug}}"
|
||||
class="news-card"
|
||||
th:data-category="${p.category != null ? p.category.name : ''}"
|
||||
th:data-tags="${p.tags != null ? #strings.arrayJoin(p.tags.![name], ',') : ''}">
|
||||
th:data-tags="${p.tags != null and !#sets.isEmpty(p.tags) ? #strings.listJoin(p.tags.![name], ',') : ''}">
|
||||
<div class="card-thumb">
|
||||
<img th:src="${p.featuredImage}" th:alt="${p.title}" />
|
||||
<span class="year-badge" th:if="${p.createdDate != null}" th:text="${#temporals.format(p.createdDate, 'yyyy')}">2026</span>
|
||||
@@ -224,28 +233,25 @@
|
||||
if (!grid) return;
|
||||
|
||||
const cards = Array.from(grid.querySelectorAll('.news-card'));
|
||||
const itemsPerPage = parseInt(grid.getAttribute('data-items-per-page')) || 9;
|
||||
const itemsPerPage = Math.min(parseInt(grid.getAttribute('data-items-per-page')) || 12, 12);
|
||||
let currentPage = 1;
|
||||
let activeFilter = 'ALL';
|
||||
|
||||
// Render Tabs from posts.js multi-choice selection or extracted cards
|
||||
if (tabsContainer) {
|
||||
const rawConfigured = tabsContainer.getAttribute('data-configured-tabs') || '';
|
||||
let tabNames = rawConfigured.split(',').map(function (s) { return s.trim(); }).filter(Boolean);
|
||||
|
||||
// Fallback to extracting from cards if no specific multi-choice selection was made
|
||||
if (tabNames.length === 0) {
|
||||
const foundSet = new Set();
|
||||
cards.forEach(function (card) {
|
||||
const cat = card.getAttribute('data-category');
|
||||
if (cat) foundSet.add(cat.trim());
|
||||
if (cat && cat.trim()) foundSet.add(cat.trim());
|
||||
const tags = (card.getAttribute('data-tags') || '').split(',');
|
||||
tags.forEach(function (t) { if (t.trim()) foundSet.add(t.trim()); });
|
||||
});
|
||||
tabNames = Array.from(foundSet);
|
||||
}
|
||||
|
||||
// Build tab button elements
|
||||
tabNames.forEach(function (name) {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
@@ -255,7 +261,6 @@
|
||||
tabsContainer.appendChild(btn);
|
||||
});
|
||||
|
||||
// Single active tab switching
|
||||
tabsContainer.addEventListener('click', function (e) {
|
||||
const btn = e.target.closest('.tab-btn');
|
||||
if (!btn) return;
|
||||
@@ -274,29 +279,40 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Render card visibility based on active tab & calculate pagination
|
||||
function render() {
|
||||
let visibleCards = cards.filter(function (card) {
|
||||
if (activeFilter === 'ALL') return true;
|
||||
const cardCat = (card.getAttribute('data-category') || '').trim();
|
||||
const cardTags = (card.getAttribute('data-tags') || '').split(',').map(function (s) { return s.trim(); });
|
||||
return cardCat === activeFilter || cardTags.includes(activeFilter);
|
||||
const targetFilter = activeFilter.trim().toLowerCase();
|
||||
const cardCat = (card.getAttribute('data-category') || '').trim().toLowerCase();
|
||||
const cardTags = (card.getAttribute('data-tags') || '').split(',').map(function (s) { return s.trim().toLowerCase(); });
|
||||
return cardCat === targetFilter || cardTags.includes(targetFilter);
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(visibleCards.length / itemsPerPage) || 1;
|
||||
if (currentPage > totalPages) currentPage = totalPages;
|
||||
|
||||
// Hide all
|
||||
cards.forEach(function (c) { c.style.display = 'none'; });
|
||||
|
||||
// Show current page slice
|
||||
let noPostsMsg = grid.querySelector('.no-posts-found');
|
||||
if (visibleCards.length === 0) {
|
||||
if (!noPostsMsg) {
|
||||
noPostsMsg = document.createElement('div');
|
||||
noPostsMsg.className = 'no-posts-found';
|
||||
noPostsMsg.style.cssText = 'grid-column: 1 / -1; text-align: center; color: #64748b; padding: 40px 20px; font-size: 15px; background: #f8fafc; border-radius: 8px; border: 1px dashed #cbd5e1; margin: 20px 0;';
|
||||
noPostsMsg.textContent = 'Chưa có bài viết nào thuộc danh mục/thẻ này.';
|
||||
grid.appendChild(noPostsMsg);
|
||||
}
|
||||
noPostsMsg.style.display = 'block';
|
||||
} else {
|
||||
if (noPostsMsg) noPostsMsg.style.display = 'none';
|
||||
}
|
||||
|
||||
const startIdx = (currentPage - 1) * itemsPerPage;
|
||||
const endIdx = startIdx + itemsPerPage;
|
||||
visibleCards.slice(startIdx, endIdx).forEach(function (c) {
|
||||
c.style.display = 'flex';
|
||||
});
|
||||
|
||||
// Render Pagination
|
||||
if (pagination) {
|
||||
pagination.innerHTML = '';
|
||||
if (totalPages <= 1) return;
|
||||
|
||||
Reference in New Issue
Block a user