fixed comment count bug in moderation portal

This commit is contained in:
2026-04-28 15:50:58 +02:00
parent 5b77b0b524
commit 950ac25828
4 changed files with 226 additions and 47 deletions

View File

@@ -69,18 +69,28 @@ $stmt->execute([':mid' => $municipality['municipality_id']]);
$news_items = $stmt->fetchAll();
// Loads Comments with Contribution for Moderation
// Loads all Comments with Contribution Titles for Moderation
$stmt = $pdo->prepare("
SELECT contribution_id, title, category, description, author_name,
geom_type, status, likes_count, dislikes_count, comment_count,
photo_path, created_at, updated_at
FROM contributions
WHERE municipality_id = :mid
ORDER BY created_at DESC
SELECT cm.comment_id, cm.contribution_id, cm.author_name, cm.browser_id,
cm.content, cm.status, cm.created_at,
co.title AS contribution_title, co.category AS contribution_category
FROM comments cm
JOIN contributions co ON cm.contribution_id = co.contribution_id
WHERE co.municipality_id = :mid
ORDER BY cm.created_at DESC
");
$stmt->execute([':mid' => $municipality['municipality_id']]);
$all_comments = $stmt->fetchAll();
// Counts Comments per Status
$comment_counts = ['pending' => 0, 'approved' => 0, 'rejected' => 0];
foreach ($all_comments as $c) {
if (isset($comment_counts[$c['status']])) {
$comment_counts[$c['status']]++;
}
}
$comment_counts['total'] = count($all_comments);
// Shows Login Page if not authenticated
if ($page === 'login' || !is_admin()) {
@@ -100,7 +110,7 @@ $categories = get_categories();
// Loads all Contributions for Municipality
$stmt = $pdo->prepare("
SELECT contribution_id, title, category, description, author_name, photo_path,
geom_type, status, likes_count, dislikes_count, created_at, updated_at
geom_type, status, likes_count, dislikes_count, comment_count, created_at, updated_at
FROM contributions
WHERE municipality_id = :mid
ORDER BY created_at DESC
@@ -194,27 +204,6 @@ $counts['total'] = count($all_contributions);
<!-- ========================================================= -->
<div id="tab-contributions" class="page-tab-content">
<!-- Statistics Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number"><?= $counts['total'] ?></div>
<div class="stat-label">Alle</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $counts['pending'] ?></div>
<div class="stat-label">Ausstehend</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $counts['approved'] ?></div>
<div class="stat-label">Akzeptiert</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $counts['rejected'] ?></div>
<div class="stat-label">Abgelehnt</div>
</div>
</div>
<!-- Status Filter Tabs -->
<div class="filter-tabs">
<button class="filter-tab active" onclick="filterByStatus('all', this)">
@@ -338,7 +327,7 @@ $counts['total'] = count($all_contributions);
<?php endif; ?>
<?php if ($item['status'] !== 'pending'): ?>
<button class="btn btn-reset" onclick="changeStatus(..., 'pending')">
<button class="btn btn-reset" onclick="changeStatus(<?= $item['contribution_id'] ?>, 'pending')">
<i class="fa-solid fa-rotate-left"></i> Zurücksetzen
</button>
<?php endif; ?>
@@ -363,14 +352,33 @@ $counts['total'] = count($all_contributions);
</div>
<!-- ========================================================= -->
<!-- Comments Moderation Tab -->
<!-- ========================================================= -->
<!-- ========================================================= -->
<!-- Comments Moderation Tab -->
<!-- ========================================================= -->
<div id="tab-comments" class="page-tab-content" style="display:none;">
<!-- Status Filter Tabs for Comments -->
<div class="filter-tabs" id="comment-filter-tabs">
<button class="filter-tab active" onclick="filterCommentsByStatus('all', this)">
Alle <span class="tab-count"><?= $comment_counts['total'] ?></span>
</button>
<button class="filter-tab" onclick="filterCommentsByStatus('pending', this)">
Ausstehend <span class="tab-count"><?= $comment_counts['pending'] ?></span>
</button>
<button class="filter-tab" onclick="filterCommentsByStatus('approved', this)">
Akzeptiert <span class="tab-count"><?= $comment_counts['approved'] ?></span>
</button>
<button class="filter-tab" onclick="filterCommentsByStatus('rejected', this)">
Abgelehnt <span class="tab-count"><?= $comment_counts['rejected'] ?></span>
</button>
</div>
<!-- Sort Controls -->
<div class="sort-controls">
<span><?= count($all_comments) ?> Kommentare</span>
<span id="comment-visible-count"><?= $comment_counts['total'] ?> Kommentare</span>
<select onchange="sortCommentRows(this.value)">
<option value="date-desc">Neueste zuerst</option>
<option value="date-asc">Älteste zuerst</option>
@@ -386,28 +394,33 @@ $counts['total'] = count($all_contributions);
Noch keine Kommentare vorhanden.
</div>
<?php else: ?>
<?php foreach ($all_comments as $comment): ?>
<?php foreach ($all_comments as $comment):
$comment_cat = $categories[$comment['contribution_category'] ?? ''] ?? ['label' => 'Unbekannt', 'faIcon' => 'fa-question', 'color' => '#999'];
$comment_status_label = ['pending' => 'Ausstehend', 'approved' => 'Akzeptiert', 'rejected' => 'Abgelehnt'];
?>
<div class="contribution-row comment-mod-row"
data-status="<?= $comment['status'] ?>"
data-date="<?= $comment['created_at'] ?>"
data-contribution="<?= htmlspecialchars($comment['contribution_title']) ?>">
<!-- Collapsed: Contribution Title + Comment Status + Category -->
<div class="contribution-row-header" onclick="toggleRow(this.parentElement)">
<div class="contribution-row-summary">
<span class="title" style="flex:1;"><?= htmlspecialchars(mb_strimwidth($comment['content'], 0, 80, '...')) ?></span>
<span style="font-size:0.75rem;color:#999;">
<?= htmlspecialchars($comment['author_name']) ?>
· <?= date('d.m.Y H:i', strtotime($comment['created_at'])) ?>
<span class="title"><?= htmlspecialchars($comment['contribution_title']) ?></span>
<span class="badge badge-<?= $comment['status'] ?>"><?= $comment_status_label[$comment['status']] ?? $comment['status'] ?></span>
<span class="badge badge-category">
<i class="fa-solid <?= $comment_cat['faIcon'] ?>"></i>
<?= $comment_cat['label'] ?>
</span>
</div>
<i class="fa-solid fa-chevron-down collapse-icon"></i>
</div>
<!-- Expanded Detail -->
<div class="contribution-row-detail">
<div style="padding:12px 0;">
<!-- Reference to Contribution -->
<div style="font-size:0.8rem;color:var(--color-text-secondary);margin-bottom:8px;padding:8px 12px;background:#f8f9fa;border-radius:6px;border-left:3px solid var(--color-primary);">
<i class="fa-solid fa-reply"></i> Beitrag: <strong><?= htmlspecialchars($comment['contribution_title']) ?></strong>
</div>
<!-- Comment Content -->
<div style="font-size:0.9rem;line-height:1.6;color:var(--color-text);margin-bottom:8px;">
<div style="font-size:0.9rem;line-height:1.6;color:var(--color-text);margin-bottom:12px;">
<?= nl2br(htmlspecialchars($comment['content'])) ?>
</div>
<!-- Meta -->
@@ -416,10 +429,29 @@ $counts['total'] = count($all_contributions);
<span><i class="fa-solid fa-calendar"></i> <?= date('d.m.Y, H:i', strtotime($comment['created_at'])) ?> Uhr</span>
</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<?php if ($comment['status'] !== 'approved'): ?>
<button class="btn btn-approve" onclick="changeCommentStatus(<?= $comment['comment_id'] ?>, 'approved')">
<i class="fa-solid fa-check"></i> Akzeptieren
</button>
<?php endif; ?>
<?php if ($comment['status'] !== 'rejected'): ?>
<button class="btn btn-reject" onclick="changeCommentStatus(<?= $comment['comment_id'] ?>, 'rejected')">
<i class="fa-solid fa-xmark"></i> Ablehnen
</button>
<?php endif; ?>
<?php if ($comment['status'] !== 'pending'): ?>
<button class="btn btn-reset" onclick="changeCommentStatus(<?= $comment['comment_id'] ?>, 'pending')">
<i class="fa-solid fa-rotate-left"></i> Zurücksetzen
</button>
<?php endif; ?>
<button class="btn btn-edit" onclick="editModComment(<?= $comment['comment_id'] ?>, '<?= htmlspecialchars(addslashes($comment['content']), ENT_QUOTES) ?>')">
<i class="fa-solid fa-pen"></i> Bearbeiten
</button>
<button class="btn btn-delete" onclick="deleteModComment(<?= $comment['comment_id'] ?>)">
<i class="fa-solid fa-trash"></i> Kommentar löschen
<i class="fa-solid fa-trash"></i> Löschen
</button>
</div>
</div>
@@ -1054,6 +1086,100 @@ $counts['total'] = count($all_contributions);
});
}
// =============================================================
// Filter Comments by Status
// =============================================================
function filterCommentsByStatus(status, tabButton) {
// Updates active Filter Tab (only within Comments Tab)
document.querySelectorAll('#comment-filter-tabs .filter-tab').forEach(function (el) {
el.classList.remove('active');
});
tabButton.classList.add('active');
// Shows/Hides Comment Rows
let visibleCount = 0;
document.querySelectorAll('.comment-mod-row').forEach(function (row) {
if (status === 'all' || row.dataset.status === status) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
});
document.getElementById('comment-visible-count').textContent = visibleCount + ' Kommentare';
}
// =============================================================
// Change Comment Status (approve, reject, reset)
// =============================================================
function changeCommentStatus(commentId, newStatus) {
const labels = { approved: 'akzeptieren', rejected: 'ablehnen', pending: 'zurücksetzen' };
Swal.fire({
title: 'Kommentar ' + labels[newStatus] + '?',
showCancelButton: true,
confirmButtonText: 'Ja',
cancelButtonText: 'Abbrechen',
confirmButtonColor: PRIMARY_COLOR
}).then(function (result) {
if (!result.isConfirmed) return;
apiCall({
action: 'update_comment',
comment_id: commentId,
status: newStatus
}).then(function (response) {
if (response.error) {
Swal.fire('Fehler', response.error, 'error');
return;
}
location.reload();
});
});
}
// =============================================================
// Edit Comment Content
// =============================================================
function editModComment(commentId, currentContent) {
Swal.fire({
title: 'Kommentar bearbeiten',
html:
'<div style="text-align:left;">' +
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Inhalt</label>' +
'<textarea id="swal-comment-content" class="swal2-textarea" style="margin:0;width:100%;">' + currentContent + '</textarea>' +
'</div>',
showCancelButton: true,
confirmButtonText: 'Speichern',
cancelButtonText: 'Abbrechen',
confirmButtonColor: PRIMARY_COLOR,
preConfirm: function () {
return { content: document.getElementById('swal-comment-content').value.trim() };
}
}).then(function (result) {
if (!result.isConfirmed) return;
apiCall({
action: 'update_comment',
comment_id: commentId,
content: result.value.content
}).then(function (response) {
if (response.error) {
Swal.fire('Fehler', response.error, 'error');
return;
}
Swal.fire('Gespeichert!', 'Kommentar wurde aktualisiert.', 'success')
.then(function () { location.reload(); });
});
});
}
</script>
</body>

View File

@@ -56,6 +56,9 @@ switch ($action) {
case 'delete_comment':
handle_delete_comment($input);
break;
case 'update_comment':
handle_update_comment($input);
break;
default:
error_response('Unknown Action. Supported Actions are read, create, update, delete, vote.');
}
@@ -576,9 +579,9 @@ function handle_read_comments($input) {
try {
$stmt = $pdo->prepare("
SELECT comment_id, contribution_id, author_name, browser_id, content, created_at
SELECT comment_id, contribution_id, author_name, browser_id, content, status, created_at
FROM comments
WHERE contribution_id = :cid
WHERE contribution_id = :cid AND status = 'approved'
ORDER BY created_at ASC
");
$stmt->execute([':cid' => $input['contribution_id']]);
@@ -661,4 +664,49 @@ function handle_delete_comment($input) {
} catch (PDOException $e) {
error_response('Database Error: ' . $e->getMessage(), 500);
}
}
// ---------------------------------------------------------------------
// UPDATE COMMENT: Changes Comment Status or Content
// Required: comment_id
// Optional: status, content
// ---------------------------------------------------------------------
function handle_update_comment($input) {
$pdo = get_db();
$missing = validate_required($input, ['comment_id']);
if (!empty($missing)) {
error_response('Missing Fields: ' . implode(', ', $missing));
}
$set = [];
$params = [':id' => $input['comment_id']];
// Updates Status if provided
if (isset($input['status']) && $input['status'] !== '') {
$valid = ['pending', 'approved', 'rejected'];
if (!in_array($input['status'], $valid)) {
error_response('Invalid Status.');
}
$set[] = "status = :status";
$params[':status'] = $input['status'];
}
// Updates Content if provided
if (isset($input['content']) && $input['content'] !== '') {
$set[] = "content = :content";
$params[':content'] = $input['content'];
}
if (empty($set)) {
error_response('No Fields to update.');
}
try {
$stmt = $pdo->prepare("UPDATE comments SET " . implode(', ', $set) . " WHERE comment_id = :id");
$stmt->execute($params);
json_response(['message' => 'Comment updated successfully.']);
} catch (PDOException $e) {
error_response('Database Error: ' . $e->getMessage(), 500);
}
}

View File

@@ -1131,9 +1131,14 @@ function submitComment(contributionId) {
Swal.fire('Fehler', response.error, 'error');
return;
}
// Clears Input and reloads Comments
if (input) input.value = '';
loadComments(contributionId);
Swal.fire({
title: 'Eingereicht!',
text: 'Ihr Kommentar wurde erfolgreich eingereicht und wird nach Prüfung durch das Moderationsteam veröffentlicht.',
icon: 'success',
timer: 3000,
showConfirmButton: true
});
});
}