feat: Dodano moderację tablicy B2B dla administratora
- Przycisk usuwania ogłoszenia z potwierdzeniem - Przycisk aktywacji/dezaktywacji ogłoszenia - Endpointy: /delete, /toggle-active - Badge "Nieaktywne" dla dezaktywowanych ogłoszeń Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8bcb339bff
commit
e6acc2ec6f
@ -148,3 +148,52 @@ def close(classified_id):
|
||||
return jsonify({'success': True, 'message': 'Ogłoszenie zamknięte'})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/<int:classified_id>/delete', methods=['POST'], endpoint='classifieds_delete')
|
||||
@login_required
|
||||
def delete(classified_id):
|
||||
"""Usuń ogłoszenie (admin only)"""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
classified = db.query(Classified).filter(
|
||||
Classified.id == classified_id
|
||||
).first()
|
||||
|
||||
if not classified:
|
||||
return jsonify({'success': False, 'error': 'Ogłoszenie nie istnieje'}), 404
|
||||
|
||||
db.delete(classified)
|
||||
db.commit()
|
||||
|
||||
return jsonify({'success': True, 'message': 'Ogłoszenie usunięte'})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/<int:classified_id>/toggle-active', methods=['POST'], endpoint='classifieds_toggle_active')
|
||||
@login_required
|
||||
def toggle_active(classified_id):
|
||||
"""Aktywuj/dezaktywuj ogłoszenie (admin only)"""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
classified = db.query(Classified).filter(
|
||||
Classified.id == classified_id
|
||||
).first()
|
||||
|
||||
if not classified:
|
||||
return jsonify({'success': False, 'error': 'Ogłoszenie nie istnieje'}), 404
|
||||
|
||||
classified.is_active = not classified.is_active
|
||||
db.commit()
|
||||
|
||||
status = 'aktywowane' if classified.is_active else 'dezaktywowane'
|
||||
return jsonify({'success': True, 'message': f'Ogłoszenie {status}', 'is_active': classified.is_active})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@ -202,6 +202,69 @@
|
||||
.close-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Admin actions */
|
||||
.admin-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.admin-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border: 1px solid;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.admin-btn svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.admin-btn-delete {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
|
||||
.admin-btn-delete:hover {
|
||||
background: #fee2e2;
|
||||
border-color: #f87171;
|
||||
}
|
||||
|
||||
.admin-btn-toggle {
|
||||
background: #f5f5f5;
|
||||
color: #525252;
|
||||
border-color: #d4d4d4;
|
||||
}
|
||||
|
||||
.admin-btn-toggle:hover {
|
||||
background: #e5e5e5;
|
||||
border-color: #a3a3a3;
|
||||
}
|
||||
|
||||
.admin-btn-toggle.inactive {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
border-color: #fcd34d;
|
||||
}
|
||||
|
||||
.inactive-badge {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
margin-left: var(--spacing-sm);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@ -219,10 +282,33 @@
|
||||
<div>
|
||||
<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 not classified.is_active %}
|
||||
<span class="inactive-badge">Nieaktywne</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if classified.author_id == current_user.id %}
|
||||
<button class="btn btn-secondary btn-sm close-btn" onclick="closeClassified()">Zamknij ogloszenie</button>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
<div class="admin-actions">
|
||||
<button type="button" class="admin-btn admin-btn-toggle {% if not classified.is_active %}inactive{% endif %}" onclick="toggleActive()" title="{% if classified.is_active %}Dezaktywuj{% else %}Aktywuj{% endif %}">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
{% if classified.is_active %}
|
||||
<path d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
|
||||
{% else %}
|
||||
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
{% endif %}
|
||||
</svg>
|
||||
{% if classified.is_active %}Dezaktywuj{% else %}Aktywuj{% endif %}
|
||||
</button>
|
||||
<button type="button" class="admin-btn admin-btn-delete" onclick="deleteClassified()" title="Usuń ogłoszenie">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
Usuń
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h1 class="classified-title">{{ classified.title }}</h1>
|
||||
@ -308,6 +394,12 @@
|
||||
.toast.error { border-left-color: var(--error); }
|
||||
@keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
||||
@keyframes toastOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
.btn-danger { background: #dc2626; color: white; border: none; }
|
||||
.btn-danger:hover { background: #b91c1c; }
|
||||
.btn-warning { background: #f59e0b; color: white; border: none; }
|
||||
.btn-warning:hover { background: #d97706; }
|
||||
.btn-success { background: #10b981; color: white; border: none; }
|
||||
.btn-success:hover { background: #059669; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@ -375,4 +467,68 @@ async function closeClassified() {
|
||||
showToast('Błąd połączenia', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Admin functions
|
||||
async function deleteClassified() {
|
||||
const confirmed = await showConfirm('Czy na pewno chcesz usunąć to ogłoszenie?<br><br><strong>Ta operacja jest nieodwracalna.</strong>', {
|
||||
icon: '🗑️',
|
||||
title: 'Usuń ogłoszenie',
|
||||
okText: 'Usuń',
|
||||
okClass: 'btn-danger'
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('{{ url_for("classifieds.classifieds_delete", classified_id=classified.id) }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showToast('Ogłoszenie usunięte', 'success');
|
||||
setTimeout(() => window.location.href = '{{ url_for("classifieds.classifieds_index") }}', 1500);
|
||||
} else {
|
||||
showToast(data.error || 'Wystąpił błąd', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Błąd połączenia', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleActive() {
|
||||
const isActive = {{ 'true' if classified.is_active else 'false' }};
|
||||
const action = isActive ? 'dezaktywować' : 'aktywować';
|
||||
|
||||
const confirmed = await showConfirm(`Czy na pewno chcesz ${action} to ogłoszenie?`, {
|
||||
icon: isActive ? '🚫' : '✅',
|
||||
title: isActive ? 'Dezaktywuj ogłoszenie' : 'Aktywuj ogłoszenie',
|
||||
okText: isActive ? 'Dezaktywuj' : 'Aktywuj',
|
||||
okClass: isActive ? 'btn-warning' : 'btn-success'
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('{{ url_for("classifieds.classifieds_toggle_active", classified_id=classified.id) }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showToast(data.message, 'success');
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} else {
|
||||
showToast(data.error || 'Wystąpił błąd', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Błąd połączenia', 'error');
|
||||
}
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user