nordabiz/templates/admin/forum_reports.html
Maciej Pienczyn f22342ea37 feat: Add forum modernization with reactions, subscriptions, and moderation
- Add edit tracking (24h limit), soft delete, and JSONB reactions to ForumTopic/ForumReply
- Create ForumTopicSubscription, ForumReport, ForumEditHistory models
- Add 15 new API endpoints for user actions and admin moderation
- Implement reactions (👍❤️🎉), topic subscriptions, content reporting
- Add solution marking, restore deleted content, edit history for admins
- Create forum_reports.html and forum_deleted.html admin templates
- Integrate notifications for replies, reactions, solutions, and reports
- Add SQL migration 024_forum_modernization.sql

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 18:55:40 +01:00

290 lines
8.2 KiB
HTML

{% extends "base.html" %}
{% block title %}Zgłoszenia Forum - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.admin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xl);
}
.admin-header h1 {
font-size: var(--font-size-3xl);
color: var(--text-primary);
}
.stats-bar {
display: flex;
gap: var(--spacing-lg);
margin-bottom: var(--spacing-xl);
}
.stat-pill {
background: var(--surface);
padding: var(--spacing-sm) var(--spacing-lg);
border-radius: 20px;
box-shadow: var(--shadow);
font-size: var(--font-size-sm);
}
.stat-pill.active {
background: var(--primary);
color: white;
}
.stat-pill .count {
font-weight: 700;
margin-left: var(--spacing-xs);
}
.reports-list {
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
overflow: hidden;
}
.report-card {
padding: var(--spacing-lg);
border-bottom: 1px solid var(--border);
}
.report-card:last-child {
border-bottom: none;
}
.report-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--spacing-md);
}
.report-meta {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.report-reason {
display: inline-block;
padding: 4px 10px;
border-radius: var(--radius);
font-size: var(--font-size-sm);
font-weight: 500;
}
.reason-spam { background: #fef2f2; color: #991b1b; }
.reason-offensive { background: #fef3c7; color: #92400e; }
.reason-off-topic { background: #eff6ff; color: #1e40af; }
.reason-other { background: #f5f5f5; color: #525252; }
.report-content {
background: var(--background);
padding: var(--spacing-md);
border-radius: var(--radius);
margin-bottom: var(--spacing-md);
font-size: var(--font-size-sm);
}
.report-content .label {
font-weight: 600;
color: var(--text-secondary);
margin-bottom: var(--spacing-xs);
}
.report-actions {
display: flex;
gap: var(--spacing-sm);
}
.btn-review {
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius);
font-size: var(--font-size-sm);
cursor: pointer;
border: 1px solid;
transition: all 0.2s;
}
.btn-review.accept {
background: #dcfce7;
color: #166534;
border-color: #86efac;
}
.btn-review.accept:hover {
background: #bbf7d0;
}
.btn-review.dismiss {
background: #fee2e2;
color: #991b1b;
border-color: #fecaca;
}
.btn-review.dismiss:hover {
background: #fecaca;
}
.empty-state {
text-align: center;
padding: var(--spacing-2xl);
color: var(--text-secondary);
}
.nav-tabs {
display: flex;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-lg);
}
.nav-tabs a {
padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius);
text-decoration: none;
color: var(--text-secondary);
border: 1px solid var(--border);
}
.nav-tabs a.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.back-link {
margin-bottom: var(--spacing-lg);
}
.back-link a {
color: var(--text-secondary);
text-decoration: none;
}
.back-link a:hover {
color: var(--primary);
}
</style>
{% endblock %}
{% block content %}
<div class="back-link">
<a href="{{ url_for('admin_forum') }}">&larr; Powrót do moderacji forum</a>
</div>
<div class="admin-header">
<h1>Zgłoszenia Forum</h1>
</div>
<div class="nav-tabs">
<a href="{{ url_for('admin_forum_reports', status='pending') }}" class="{% if status_filter == 'pending' %}active{% endif %}">
Oczekujące ({{ pending_count }})
</a>
<a href="{{ url_for('admin_forum_reports', status='reviewed') }}" class="{% if status_filter == 'reviewed' %}active{% endif %}">
Rozpatrzone ({{ reviewed_count }})
</a>
<a href="{{ url_for('admin_forum_reports', status='dismissed') }}" class="{% if status_filter == 'dismissed' %}active{% endif %}">
Odrzucone ({{ dismissed_count }})
</a>
</div>
{% if reports %}
<div class="reports-list">
{% for report in reports %}
<div class="report-card" id="report-{{ report.id }}">
<div class="report-header">
<div>
<span class="report-reason reason-{{ report.reason }}">{{ reason_labels.get(report.reason, report.reason) }}</span>
<span class="report-meta">
Zgłoszone przez {{ report.reporter.name or report.reporter.email.split('@')[0] }}
&bull; {{ report.created_at.strftime('%d.%m.%Y %H:%M') }}
</span>
</div>
<span class="report-meta">
{{ report.content_type|capitalize }} #{{ report.topic_id or report.reply_id }}
</span>
</div>
{% if report.description %}
<div class="report-content">
<div class="label">Opis zgłoszenia:</div>
{{ report.description }}
</div>
{% endif %}
<div class="report-content">
<div class="label">Zgłoszona treść:</div>
{% if report.content_type == 'topic' and report.topic %}
<strong>{{ report.topic.title }}</strong><br>
{{ report.topic.content[:300] }}{% if report.topic.content|length > 300 %}...{% endif %}
<br><a href="{{ url_for('forum_topic', topic_id=report.topic.id) }}" target="_blank">Zobacz temat &rarr;</a>
{% elif report.content_type == 'reply' and report.reply %}
{{ report.reply.content[:300] }}{% if report.reply.content|length > 300 %}...{% endif %}
<br><a href="{{ url_for('forum_topic', topic_id=report.reply.topic_id) }}#reply-{{ report.reply.id }}" target="_blank">Zobacz odpowiedź &rarr;</a>
{% else %}
<em>Treść niedostępna</em>
{% endif %}
</div>
{% if report.status == 'pending' %}
<div class="report-actions">
<button class="btn-review accept" onclick="reviewReport({{ report.id }}, 'reviewed')">
✓ Rozpatrzone
</button>
<button class="btn-review dismiss" onclick="reviewReport({{ report.id }}, 'dismissed')">
✕ Odrzuć
</button>
</div>
{% else %}
<div class="report-meta">
{% if report.reviewed_by %}
Rozpatrzone przez {{ report.reviewer.name or report.reviewer.email.split('@')[0] }}
&bull; {{ report.reviewed_at.strftime('%d.%m.%Y %H:%M') }}
{% endif %}
{% if report.review_note %}
<br>Notatka: {{ report.review_note }}
{% endif %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<div class="reports-list">
<div class="empty-state">
Brak zgłoszeń w tej kategorii.
</div>
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
function reviewReport(reportId, status) {
const note = status === 'dismissed' ? prompt('Opcjonalna notatka (powód odrzucenia):') : '';
fetch(`/admin/forum/report/${reportId}/review`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
},
body: JSON.stringify({ status: status, note: note || '' })
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('report-' + reportId).remove();
location.reload();
} else {
alert(data.error || 'Błąd');
}
})
.catch(err => {
alert('Błąd połączenia');
});
}
{% endblock %}