Add category filter and functional pagination to projects listing
Add a Proxify-style category filter and real client-side pagination to the Events & Projects listing across all three languages (EN/SI/MK). - Filter: a pill nav (All / Events / Student Life / Projects / Webinars) plus a contextual sub-chip row for Events (Sports / Cultural / Entertainment / Educational). Buttons carry stable data-filter / data-subfilter keys and cards carry data-category / data-subcategory, so a single language-agnostic handler drives all three locales. - Pagination: replaces the hardcoded dead 1..10 links with real client-side paging (9 per page). Shows a single page today and grows automatically as content is added, with windowed page numbers, enabled/disabled Prev/Next, and smooth scroll-to-top. Integrates with the filter (resets to page 1, repaginates, hides when no matches). - Show a localized empty-state message when a filter has no results. - Nudge the filter block up and add space below it for even spacing. - Drop the duplicate News pill (News has its own top-nav section) and retag the placeholder card from announcements to projects so it stays reachable. Verified with jsdom across EN/SI/MK: filtering, sub-filtering, empty state, single/multi-page navigation, and filter/pagination interaction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
13e4c04c3e
commit
dc1653ab57
5 files changed
+411
-22
No files matched your search
@@ -467,7 +467,188 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
/**
|
||||
* ===================================================================
|
||||
* 6. ZAGON FUNKCIJ PO NALOŽITVI STRANI
|
||||
* 6. FILTER KATEGORIJ ZA SEZNAM PROJEKTOV / OBJAV
|
||||
* ===================================================================
|
||||
*
|
||||
* Deluje na katerikoli strani s seznamom (projekti, novice, ...).
|
||||
* Popolnoma jezikovno neodvisen: gumbi nosijo stabilni ključ
|
||||
* (data-filter / data-subfilter), kartice pa data-category /
|
||||
* data-subcategory. Oznake so lahko v katerem koli jeziku.
|
||||
*/
|
||||
try {
|
||||
const grid = document.querySelector('.posts-grid');
|
||||
|
||||
if (grid) {
|
||||
const filterSection = document.querySelector('.filter-section');
|
||||
const pagination = document.querySelector('.pagination');
|
||||
const cards = Array.from(grid.querySelectorAll('.post-card'));
|
||||
const emptyMsg = document.querySelector('.filter-empty');
|
||||
|
||||
// Koliko kartic na stran. Ko bo objav več kot toliko, se
|
||||
// paginacija samodejno vklopi; do takrat prikaže eno stran.
|
||||
const PAGE_SIZE = 9;
|
||||
|
||||
let activeCat = 'all';
|
||||
let activeSub = 'all';
|
||||
let currentPage = 1;
|
||||
let matching = cards.slice();
|
||||
|
||||
// --- Paginacija ---
|
||||
const pageNumbers = pagination ? pagination.querySelector('.pagination-numbers') : null;
|
||||
const arrows = pagination ? pagination.querySelectorAll('.pagination-arrow') : [];
|
||||
const prevArrow = arrows.length ? arrows[0] : null;
|
||||
const nextArrow = arrows.length > 1 ? arrows[arrows.length - 1] : null;
|
||||
|
||||
const totalPages = () => Math.max(1, Math.ceil(matching.length / PAGE_SIZE));
|
||||
|
||||
const renderPageNumbers = (total) => {
|
||||
if (!pageNumbers) return;
|
||||
pageNumbers.innerHTML = '';
|
||||
const addNum = (n) => {
|
||||
const a = document.createElement('a');
|
||||
a.href = '#';
|
||||
a.className = 'page-number' + (n === currentPage ? ' active' : '');
|
||||
a.textContent = n;
|
||||
a.addEventListener('click', (e) => { e.preventDefault(); goToPage(n); });
|
||||
pageNumbers.appendChild(a);
|
||||
};
|
||||
const addDots = () => {
|
||||
const s = document.createElement('span');
|
||||
s.className = 'page-dots';
|
||||
s.textContent = '...';
|
||||
pageNumbers.appendChild(s);
|
||||
};
|
||||
// Do 7 strani pokažemo vse; sicer okno okoli trenutne strani.
|
||||
const seq = [];
|
||||
if (total <= 7) {
|
||||
for (let i = 1; i <= total; i++) seq.push(i);
|
||||
} else {
|
||||
seq.push(1);
|
||||
const start = Math.max(2, currentPage - 1);
|
||||
const end = Math.min(total - 1, currentPage + 1);
|
||||
if (start > 2) seq.push('...');
|
||||
for (let i = start; i <= end; i++) seq.push(i);
|
||||
if (end < total - 1) seq.push('...');
|
||||
seq.push(total);
|
||||
}
|
||||
seq.forEach(p => (p === '...' ? addDots() : addNum(p)));
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const total = totalPages();
|
||||
if (currentPage > total) currentPage = total;
|
||||
if (currentPage < 1) currentPage = 1;
|
||||
|
||||
cards.forEach(c => { c.hidden = true; });
|
||||
const start = (currentPage - 1) * PAGE_SIZE;
|
||||
matching.slice(start, start + PAGE_SIZE).forEach(c => { c.hidden = false; });
|
||||
|
||||
if (emptyMsg) emptyMsg.hidden = matching.length !== 0;
|
||||
|
||||
if (pagination) {
|
||||
// Skrij paginacijo, ko ni zadetkov.
|
||||
pagination.hidden = matching.length === 0;
|
||||
renderPageNumbers(total);
|
||||
if (prevArrow) prevArrow.classList.toggle('disabled', currentPage <= 1);
|
||||
if (nextArrow) nextArrow.classList.toggle('disabled', currentPage >= total);
|
||||
}
|
||||
};
|
||||
|
||||
function goToPage(n) {
|
||||
const total = totalPages();
|
||||
currentPage = Math.min(Math.max(1, n), total);
|
||||
render();
|
||||
// Po menjavi strani se pomaknemo na vrh seznama.
|
||||
const anchor = filterSection || grid;
|
||||
const y = anchor.getBoundingClientRect().top + window.scrollY - 100;
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
if (prevArrow) {
|
||||
prevArrow.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (currentPage > 1) goToPage(currentPage - 1);
|
||||
});
|
||||
}
|
||||
if (nextArrow) {
|
||||
nextArrow.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (currentPage < totalPages()) goToPage(currentPage + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Filter (če je na strani) ---
|
||||
const computeMatching = () => {
|
||||
matching = cards.filter(card => {
|
||||
const cat = (card.getAttribute('data-category') || '').trim();
|
||||
const sub = (card.getAttribute('data-subcategory') || '').trim();
|
||||
let show = activeCat === 'all' || cat === activeCat;
|
||||
if (show && activeCat !== 'all' && activeSub !== 'all') {
|
||||
show = sub === activeSub;
|
||||
}
|
||||
return show;
|
||||
});
|
||||
};
|
||||
|
||||
if (filterSection) {
|
||||
const pills = Array.from(filterSection.querySelectorAll('.filter-pill'));
|
||||
const subnavs = Array.from(filterSection.querySelectorAll('.filter-subnav'));
|
||||
|
||||
const showSubnavFor = (cat) => {
|
||||
subnavs.forEach(sn => {
|
||||
const match = sn.getAttribute('data-parent') === cat;
|
||||
sn.hidden = !match;
|
||||
if (match) {
|
||||
sn.querySelectorAll('.filter-chip').forEach(chip =>
|
||||
chip.classList.toggle('active',
|
||||
(chip.getAttribute('data-subfilter') || 'all') === 'all'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
pills.forEach(pill => {
|
||||
pill.addEventListener('click', () => {
|
||||
activeCat = pill.getAttribute('data-filter') || 'all';
|
||||
activeSub = 'all';
|
||||
currentPage = 1;
|
||||
pills.forEach(p => {
|
||||
const on = p === pill;
|
||||
p.classList.toggle('active', on);
|
||||
p.setAttribute('aria-pressed', on ? 'true' : 'false');
|
||||
});
|
||||
showSubnavFor(activeCat);
|
||||
computeMatching();
|
||||
render();
|
||||
});
|
||||
});
|
||||
|
||||
subnavs.forEach(sn => {
|
||||
sn.querySelectorAll('.filter-chip').forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
activeSub = chip.getAttribute('data-subfilter') || 'all';
|
||||
currentPage = 1;
|
||||
sn.querySelectorAll('.filter-chip').forEach(c =>
|
||||
c.classList.toggle('active', c === chip));
|
||||
computeMatching();
|
||||
render();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Začetno stanje: "Vse" aktivno, podfiltri skriti.
|
||||
showSubnavFor(activeCat);
|
||||
}
|
||||
|
||||
computeMatching();
|
||||
render();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in Project Filter/Pagination setup:', error);
|
||||
}
|
||||
|
||||
/**
|
||||
* ===================================================================
|
||||
* 7. ZAGON FUNKCIJ PO NALOŽITVI STRANI
|
||||
* ===================================================================
|
||||
*/
|
||||
highlightActiveLink();
|
||||
|
||||
Reference in new issue
Block a user