feat: classified expiry handling - badges, extend button, homepage filter
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

- Expired classifieds show 'Wygasło' badge on list and detail view
- Closed classifieds show 'Zamknięte' badge on list
- Author can extend by 30 days with one click
- Homepage 'Nowe na portalu' excludes expired classifieds
- List shows all classifieds, active first

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-04-10 07:49:38 +02:00
parent 9027e4fafc
commit ca5e7fd0a8
4 changed files with 76 additions and 6 deletions

View File

@ -35,9 +35,8 @@ def index():
db = SessionLocal()
try:
query = db.query(Classified).filter(
Classified.is_active == True
)
now = datetime.now()
query = db.query(Classified)
# Filtry
if listing_type:
@ -45,8 +44,15 @@ def index():
if category:
query = query.filter(Classified.category == category)
# Sortowanie - najnowsze pierwsze
query = query.order_by(Classified.created_at.desc())
# Sortowanie: aktywne i niewygasłe na górze, potem reszta
from sqlalchemy import case
query = query.order_by(
case(
(Classified.is_active == True, 0),
else_=1
),
Classified.created_at.desc()
)
total = query.count()
classifieds = query.limit(per_page).offset((page - 1) * per_page).all()
@ -310,6 +316,39 @@ def edit(classified_id):
db.close()
@bp.route('/<int:classified_id>/przedluz', methods=['POST'], endpoint='classifieds_extend')
@login_required
@member_required
def extend(classified_id):
"""Przedłuż ogłoszenie o 30 dni"""
db = SessionLocal()
try:
classified = db.query(Classified).filter(
Classified.id == classified_id,
Classified.author_id == current_user.id
).first()
if not classified:
return jsonify({'success': False, 'error': 'Ogłoszenie nie istnieje lub brak uprawnień'}), 404
# Przedłuż od teraz lub od daty wygaśnięcia (jeśli jeszcze aktywne)
base_date = datetime.now()
if classified.expires_at and classified.expires_at > base_date:
base_date = classified.expires_at
classified.expires_at = base_date + timedelta(days=30)
classified.is_active = True
classified.updated_at = datetime.now()
db.commit()
return jsonify({
'success': True,
'message': 'Ogłoszenie przedłużone o 30 dni',
'new_expires': classified.expires_at.strftime('%d.%m.%Y')
})
finally:
db.close()
@bp.route('/<int:classified_id>/zakoncz', methods=['POST'], endpoint='classifieds_close')
@login_required
@member_required

View File

@ -218,9 +218,11 @@ def index():
latest_classifieds = []
try:
from database import Classified
from datetime import datetime as dt
latest_classifieds = db.query(Classified).filter(
Classified.is_active == True,
Classified.is_test == False
Classified.is_test == False,
(Classified.expires_at == None) | (Classified.expires_at > dt.now())
).order_by(Classified.created_at.desc()).limit(2).all()
except Exception:
pass

View File

@ -293,6 +293,11 @@
</div>
<div class="classified-title">
<a href="{{ url_for('classifieds.classifieds_view', classified_id=classified.id) }}">{{ classified.title }}</a>
{% if classified.is_expired %}
<span style="display:inline-block; background:#fef2f2; color:#dc2626; font-size:11px; font-weight:600; padding:2px 8px; border-radius:4px; vertical-align:middle; margin-left:6px;">Wygasło</span>
{% elif not classified.is_active %}
<span style="display:inline-block; background:#f3f4f6; color:#6b7280; font-size:11px; font-weight:600; padding:2px 8px; border-radius:4px; vertical-align:middle; margin-left:6px;">Zamknięte</span>
{% endif %}
</div>
<div class="classified-description">
{{ classified.description[:200] }}{% if classified.description|length > 200 %}...{% endif %}

View File

@ -684,8 +684,14 @@
<span class="inactive-badge">Nieaktywne</span>
{% endif %}
</div>
{% if classified.is_expired %}
<span style="display:inline-block; background:#fef2f2; color:#dc2626; font-size:var(--font-size-sm); font-weight:600; padding:4px 12px; border-radius:var(--radius); border:1px solid #fecaca;">Wygasło</span>
{% endif %}
{% if classified.author_id == current_user.id %}
<a href="{{ url_for('classifieds.classifieds_edit', classified_id=classified.id) }}" class="btn btn-primary btn-sm">Edytuj</a>
{% if classified.is_expired or not classified.is_active %}
<button class="btn btn-primary btn-sm" onclick="extendClassified()" style="background:#10b981;border-color:#10b981;">Przedłuż o 30 dni</button>
{% endif %}
<button class="btn btn-secondary btn-sm close-btn" onclick="closeClassified()">Zamknij ogloszenie</button>
{% endif %}
{% if current_user.is_authenticated and current_user.can_access_admin_panel() %}
@ -1231,6 +1237,24 @@ async function toggleQuestionVisibility(questionId) {
}
}
async function extendClassified() {
try {
const resp = await fetch('{{ url_for("classifieds.classifieds_extend", classified_id=classified.id) }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }
});
const data = await resp.json();
if (data.success) {
showToast('Ogłoszenie przedłużone do ' + data.new_expires, 'success');
setTimeout(() => location.reload(), 1000);
} else {
showToast(data.error || 'Błąd', 'error');
}
} catch (e) {
showToast('Błąd połączenia', 'error');
}
}
function openLightbox(src) {
document.getElementById('lightboxImage').src = src;
document.getElementById('lightbox').classList.add('active');