feat(categories): Hierarchiczny filtr kategorii w UI
- Główne kategorie jako wyróżnione przyciski - Podkategorie z mniejszym fontem - filterCategoryGroup() - filtruje po grupie (główna + podkategorie) - Nowe style: category-main, category-sub, category-group - Zachowano kompatybilność wsteczną z płaską strukturą Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4b38f8953c
commit
c09a622463
8
app.py
8
app.py
@ -985,6 +985,13 @@ def index():
|
||||
try:
|
||||
from datetime import date
|
||||
companies = db.query(Company).filter_by(status='active').order_by(Company.name).all()
|
||||
|
||||
# Get hierarchical categories (main categories with subcategories)
|
||||
main_categories = db.query(Category).filter(
|
||||
Category.parent_id.is_(None)
|
||||
).order_by(Category.display_order, Category.name).all()
|
||||
|
||||
# All categories for backwards compatibility
|
||||
categories = db.query(Category).order_by(Category.sort_order).all()
|
||||
|
||||
total_companies = len(companies)
|
||||
@ -1007,6 +1014,7 @@ def index():
|
||||
'index.html',
|
||||
companies=companies,
|
||||
categories=categories,
|
||||
main_categories=main_categories,
|
||||
total_companies=total_companies,
|
||||
total_categories=total_categories,
|
||||
next_event=next_event,
|
||||
|
||||
@ -583,6 +583,35 @@
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
/* Category hierarchy */
|
||||
.category-group {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.category-badge.category-main {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark, #1d4ed8) 100%);
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.category-badge.category-main:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.category-badge.category-sub {
|
||||
font-size: var(--font-size-xs);
|
||||
padding: 4px var(--spacing-sm);
|
||||
background-color: var(--bg-secondary);
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
.category-badge.category-sub:hover,
|
||||
.category-badge.category-sub.active {
|
||||
background-color: var(--primary-light, #60a5fa);
|
||||
border-color: var(--primary-light, #60a5fa);
|
||||
}
|
||||
|
||||
/* Company Grid */
|
||||
.companies-grid {
|
||||
display: grid;
|
||||
@ -832,7 +861,37 @@
|
||||
</div>
|
||||
|
||||
<!-- Category Filter -->
|
||||
{% if categories %}
|
||||
{% if main_categories %}
|
||||
<div class="category-filter">
|
||||
<button class="category-badge active" onclick="filterCategory('all')">
|
||||
Wszystkie ({{ total_companies }})
|
||||
</button>
|
||||
{% for main_cat in main_categories %}
|
||||
{% set main_count = companies|selectattr('category_id', 'equalto', main_cat.id)|list|length %}
|
||||
{% set sub_count = 0 %}
|
||||
{% for sub in main_cat.subcategories %}
|
||||
{% set sub_count = sub_count + companies|selectattr('category_id', 'equalto', sub.id)|list|length %}
|
||||
{% endfor %}
|
||||
{% set total_count = main_count + sub_count %}
|
||||
{% if total_count > 0 %}
|
||||
<span class="category-group">
|
||||
<button class="category-badge category-main" onclick="filterCategoryGroup('{{ main_cat.slug }}')">
|
||||
{{ main_cat.name }} ({{ total_count }})
|
||||
</button>
|
||||
{% for sub in main_cat.subcategories %}
|
||||
{% set count = companies|selectattr('category_id', 'equalto', sub.id)|list|length %}
|
||||
{% if count > 0 %}
|
||||
<button class="category-badge category-sub" onclick="filterCategory('{{ sub.slug }}')" data-parent="{{ main_cat.slug }}">
|
||||
{{ sub.name }} ({{ count }})
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif categories %}
|
||||
<!-- Fallback dla starej struktury -->
|
||||
<div class="category-filter">
|
||||
<button class="category-badge active" onclick="filterCategory('all')">
|
||||
Wszystkie ({{ total_companies }})
|
||||
@ -972,7 +1031,7 @@
|
||||
// Update active badge
|
||||
badges.forEach(badge => {
|
||||
badge.classList.remove('active');
|
||||
if (badge.textContent.toLowerCase().includes(slug) ||
|
||||
if ((badge.onclick && badge.onclick.toString().includes("'" + slug + "'")) ||
|
||||
(slug === 'all' && badge.textContent.includes('Wszystkie'))) {
|
||||
badge.classList.add('active');
|
||||
}
|
||||
@ -989,6 +1048,38 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by main category group (includes all subcategories)
|
||||
function filterCategoryGroup(mainSlug) {
|
||||
const cards = document.querySelectorAll('.company-card');
|
||||
const badges = document.querySelectorAll('.category-badge');
|
||||
|
||||
// Get all subcategory slugs for this main category
|
||||
const subBadges = document.querySelectorAll('.category-badge.category-sub[data-parent="' + mainSlug + '"]');
|
||||
const validSlugs = [mainSlug];
|
||||
subBadges.forEach(badge => {
|
||||
const onclick = badge.getAttribute('onclick');
|
||||
if (onclick) {
|
||||
const match = onclick.match(/filterCategory\('([^']+)'\)/);
|
||||
if (match) validSlugs.push(match[1]);
|
||||
}
|
||||
});
|
||||
|
||||
// Update active badge
|
||||
badges.forEach(badge => {
|
||||
badge.classList.remove('active');
|
||||
const onclick = badge.getAttribute('onclick');
|
||||
if (onclick && onclick.includes("filterCategoryGroup('" + mainSlug + "')")) {
|
||||
badge.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Filter cards
|
||||
cards.forEach(card => {
|
||||
const cardCategory = card.getAttribute('data-category');
|
||||
card.style.display = validSlugs.includes(cardCategory) ? 'flex' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth scroll to companies on search
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('q')) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user