feat: Add toggle button to hide/show test items on B2B board

- Add is_test field to Classified model
- Add test-item styling (opacity + gray border + badge)
- Add yellow toggle button with localStorage persistence
- Add script to mark existing classifieds as test
This commit is contained in:
Maciej Pienczyn 2026-01-13 13:08:11 +01:00
parent e7d32b6b06
commit ffc6d8219f
3 changed files with 128 additions and 1 deletions

View File

@ -1139,6 +1139,7 @@ class Classified(Base):
# Status
is_active = Column(Boolean, default=True)
is_ai_generated = Column(Boolean, default=False)
is_test = Column(Boolean, default=False) # Oznaczenie dla testowych ogłoszeń
expires_at = Column(DateTime) # Auto-wygaśnięcie po 30 dniach
views_count = Column(Integer, default=0)

View File

@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Oznacz wszystkie obecne ogłoszenia B2B jako testowe."""
import os
import sys
from dotenv import load_dotenv
# Load .env first
load_dotenv()
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import text
from database import SessionLocal, Classified
def main():
db = SessionLocal()
# First, ensure the is_test column exists
try:
db.execute(text("ALTER TABLE classifieds ADD COLUMN IF NOT EXISTS is_test BOOLEAN DEFAULT FALSE"))
db.commit()
print("Kolumna is_test dodana (lub już istnieje)")
except Exception as e:
print(f"Uwaga przy dodawaniu kolumny: {e}")
db.rollback()
# Get all classifieds
classifieds = db.query(Classified).all()
if not classifieds:
print("Brak ogłoszeń w bazie danych.")
db.close()
return
print(f"Znaleziono {len(classifieds)} ogłoszeń do oznaczenia jako testowe...")
print()
for c in classifieds:
c.is_test = True
print(f" ID {c.id}: {c.title[:50]}... ({c.listing_type})")
db.commit()
db.close()
print()
print(f"Oznaczono {len(classifieds)} ogłoszeń jako testowe")
print("Gotowe!")
if __name__ == "__main__":
main()

View File

@ -140,6 +140,39 @@
color: #3730a3;
}
/* Test item styling */
.classified-card.test-item {
opacity: 0.6;
border-right: 4px solid #9ca3af;
}
.test-badge {
display: inline-block;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: 500;
background: #f3f4f6;
color: #6b7280;
margin-left: var(--spacing-xs);
}
.toggle-test-btn {
background: #fef3c7;
border-color: #fcd34d;
color: #92400e;
}
.toggle-test-btn:hover {
background: #fde68a;
}
.toggle-test-btn.active {
background: var(--primary);
border-color: var(--primary);
color: white;
}
.classified-title {
font-size: var(--font-size-lg);
font-weight: 600;
@ -240,15 +273,23 @@
<a href="{{ url_for('classifieds_index', type=listing_type, category=cat_value) }}" class="filter-btn {% if category_filter == cat_value %}active{% endif %}">{{ cat_label }}</a>
{% endfor %}
</div>
<div class="filter-group" style="margin-left: auto;">
<button type="button" id="toggleTestBtn" class="filter-btn toggle-test-btn" onclick="toggleTestItems()">
<span class="hide-label">🙈 Ukryj testowe</span>
<span class="show-label" style="display:none;">👁 Pokaż testowe</span>
</button>
</div>
</div>
<div class="classifieds-grid">
{% if classifieds %}
{% for classified in classifieds %}
<div class="classified-card {{ classified.listing_type }}">
<div class="classified-card {{ classified.listing_type }} {% if classified.is_test %}test-item{% endif %}">
<div class="classified-header">
<span class="classified-type {{ classified.listing_type }}">{{ 'Szukam' if classified.listing_type == 'szukam' else 'Oferuje' }}</span>
<span class="classified-category category-{{ classified.category }}">{{ classified.category|replace('uslugi', 'Usługi')|replace('produkty', 'Produkty')|replace('wspolpraca', 'Współpraca')|replace('praca', 'Praca')|replace('inne', 'Inne')|replace('nieruchomosci', 'Nieruchomości') }}</span>
{% if classified.is_test %}<span class="test-badge">Testowe</span>{% endif %}
</div>
<div class="classified-title">
<a href="{{ url_for('classifieds_view', classified_id=classified.id) }}">{{ classified.title }}</a>
@ -287,3 +328,37 @@
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
function setTestItemsVisibility(hidden) {
const btn = document.getElementById('toggleTestBtn');
const hideLabel = btn.querySelector('.hide-label');
const showLabel = btn.querySelector('.show-label');
const testItems = document.querySelectorAll('.test-item');
if (hidden) {
testItems.forEach(t => t.style.display = 'none');
hideLabel.style.display = 'none';
showLabel.style.display = '';
btn.classList.add('active');
} else {
testItems.forEach(t => t.style.display = '');
hideLabel.style.display = '';
showLabel.style.display = 'none';
btn.classList.remove('active');
}
localStorage.setItem('hideTestClassifieds', hidden ? 'true' : 'false');
}
function toggleTestItems() {
const isCurrentlyHidden = localStorage.getItem('hideTestClassifieds') === 'true';
setTestItemsVisibility(!isCurrentlyHidden);
}
// Apply saved preference on page load
(function() {
if (localStorage.getItem('hideTestClassifieds') === 'true') {
setTestItemsVisibility(true);
}
})();
{% endblock %}