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:
popovskikandClaude Opus 4.8 committed 2026-07-28 11:04:56 +02:00
1 parent 13e4c04c3e
commit dc1653ab57
5 files changed
+411 -22

No files matched your search

+182 -1
View File
@@ -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();