added news CRUD functionality in moderation portal
This commit is contained in:
197
public/admin.php
197
public/admin.php
@@ -57,6 +57,16 @@ $stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug");
|
||||
$stmt->execute([':slug' => getenv('MUNICIPALITY_SLUG')]);
|
||||
$municipality = $stmt->fetch();
|
||||
|
||||
// Loads News for Moderation
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT news_id, title, content, author_name, published_at, created_at
|
||||
FROM news
|
||||
WHERE municipality_id = :mid
|
||||
ORDER BY published_at DESC
|
||||
");
|
||||
$stmt->execute([':mid' => $municipality['municipality_id']]);
|
||||
$news_items = $stmt->fetchAll();
|
||||
|
||||
// Shows Login Page if not authenticated
|
||||
if ($page === 'login' || !is_admin()) {
|
||||
show_login_page($municipality, $login_error ?? null);
|
||||
@@ -312,15 +322,56 @@ $counts['total'] = count($all_contributions);
|
||||
|
||||
|
||||
<!-- ========================================================= -->
|
||||
<!-- Placeholder Tabs for future Features -->
|
||||
<!-- News Article Tab -->
|
||||
<!-- ========================================================= -->
|
||||
<div id="tab-news" class="page-tab-content" style="display:none;">
|
||||
<div class="placeholder-content">
|
||||
<i class="fa-solid fa-newspaper"></i>
|
||||
<p>Neuigkeiten verwalten - geplant in zukünftiger Version.</p>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;">
|
||||
<h2 style="margin:0;border:none;padding:0;"><i class="fa-solid fa-newspaper"></i> Neuigkeiten</h2>
|
||||
<button class="btn btn-approve" onclick="createNews()">
|
||||
<i class="fa-solid fa-plus"></i> Nachricht hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if (empty($news_items)): ?>
|
||||
<div class="empty-state">
|
||||
<i class="fa-solid fa-newspaper" style="font-size:2rem;margin-bottom:8px;display:block;"></i>
|
||||
Noch keine Neuigkeiten veröffentlicht.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($news_items as $news): ?>
|
||||
<div class="contribution-row" data-id="<?= $news['news_id'] ?>">
|
||||
<div class="contribution-row-header" onclick="toggleRow(this.parentElement)">
|
||||
<div class="contribution-row-summary">
|
||||
<span class="title"><?= htmlspecialchars($news['title']) ?></span>
|
||||
<span style="font-size:0.8rem;color:#999;">
|
||||
<?= date('d.m.Y', strtotime($news['published_at'])) ?>
|
||||
· <?= htmlspecialchars($news['author_name']) ?>
|
||||
</span>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-down collapse-icon"></i>
|
||||
</div>
|
||||
<div class="contribution-row-detail">
|
||||
<div style="padding:12px 0;font-size:0.9rem;line-height:1.6;color:#5a5a7a;">
|
||||
<?= nl2br(htmlspecialchars($news['content'])) ?>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-edit" onclick="editNews(<?= $news['news_id'] ?>, '<?= htmlspecialchars(addslashes($news['title']), ENT_QUOTES) ?>', '<?= htmlspecialchars(addslashes($news['content']), ENT_QUOTES) ?>')">
|
||||
<i class="fa-solid fa-pen"></i> Bearbeiten
|
||||
</button>
|
||||
<button class="btn btn-delete" onclick="deleteNews(<?= $news['news_id'] ?>)">
|
||||
<i class="fa-solid fa-trash"></i> Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ========================================================= -->
|
||||
<!-- Placeholder Tabs for future Features -->
|
||||
<!-- ========================================================= -->
|
||||
<div id="tab-stats" class="page-tab-content" style="display:none;">
|
||||
<div class="placeholder-content">
|
||||
<i class="fa-solid fa-chart-bar"></i>
|
||||
@@ -645,6 +696,144 @@ $counts['total'] = count($all_contributions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =============================================================
|
||||
// Create News Article
|
||||
// =============================================================
|
||||
|
||||
function createNews() {
|
||||
Swal.fire({
|
||||
title: 'Neue Nachricht',
|
||||
html:
|
||||
'<div style="text-align:left;">' +
|
||||
'<div style="margin-bottom:12px;">' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Titel</label>' +
|
||||
'<input id="swal-news-title" class="swal2-input" style="margin:0;width:100%;" placeholder="Titel der Nachricht">' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Inhalt</label>' +
|
||||
'<textarea id="swal-news-content" class="swal2-textarea" style="margin:0;width:100%;" placeholder="Nachricht verfassen..."></textarea>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Veröffentlichen',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: PRIMARY_COLOR,
|
||||
preConfirm: function () {
|
||||
const title = document.getElementById('swal-news-title').value.trim();
|
||||
const content = document.getElementById('swal-news-content').value.trim();
|
||||
if (!title || !content) {
|
||||
Swal.showValidationMessage('Titel und Inhalt sind Pflichtfelder.');
|
||||
return false;
|
||||
}
|
||||
return { title: title, content: content };
|
||||
}
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'create_news');
|
||||
formData.append('municipality_id', MUNICIPALITY_ID);
|
||||
formData.append('title', result.value.title);
|
||||
formData.append('content', result.value.content);
|
||||
|
||||
fetch(API_URL, { method: 'POST', body: formData })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Veröffentlicht!', 'Nachricht wurde erstellt.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =============================================================
|
||||
// Edit News Article
|
||||
// =============================================================
|
||||
function editNews(newsId, currentTitle, currentContent) {
|
||||
Swal.fire({
|
||||
title: 'Nachricht bearbeiten',
|
||||
html:
|
||||
'<div style="text-align:left;">' +
|
||||
'<div style="margin-bottom:12px;">' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Titel</label>' +
|
||||
'<input id="swal-news-title" class="swal2-input" style="margin:0;width:100%;" value="' + currentTitle + '">' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Inhalt</label>' +
|
||||
'<textarea id="swal-news-content" class="swal2-textarea" style="margin:0;width:100%;">' + currentContent + '</textarea>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Speichern',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: PRIMARY_COLOR,
|
||||
preConfirm: function () {
|
||||
return {
|
||||
title: document.getElementById('swal-news-title').value.trim(),
|
||||
content: document.getElementById('swal-news-content').value.trim()
|
||||
};
|
||||
}
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'update_news');
|
||||
formData.append('news_id', newsId);
|
||||
formData.append('title', result.value.title);
|
||||
formData.append('content', result.value.content);
|
||||
|
||||
fetch(API_URL, { method: 'POST', body: formData })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Gespeichert!', 'Nachricht wurde aktualisiert.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =============================================================
|
||||
// Create News Article
|
||||
// =============================================================
|
||||
function deleteNews(newsId) {
|
||||
Swal.fire({
|
||||
title: 'Nachricht löschen?',
|
||||
text: 'Diese Aktion kann nicht rückgängig gemacht werden.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Löschen',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: '#c62828'
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'delete_news');
|
||||
formData.append('news_id', newsId);
|
||||
|
||||
fetch(API_URL, { method: 'POST', body: formData })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Gelöscht!', 'Nachricht wurde entfernt.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -38,6 +38,15 @@ switch ($action) {
|
||||
case 'vote':
|
||||
handle_vote($input);
|
||||
break;
|
||||
case 'create_news':
|
||||
handle_create_news($input);
|
||||
break;
|
||||
case 'update_news':
|
||||
handle_update_news($input);
|
||||
break;
|
||||
case 'delete_news':
|
||||
handle_delete_news($input);
|
||||
break;
|
||||
default:
|
||||
error_response('Unknown Action. Supported Actions are read, create, update, delete, vote.');
|
||||
}
|
||||
@@ -358,3 +367,86 @@ function handle_vote($input) {
|
||||
error_response('Database Error: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// CREATE NEWS: Inserts new News Entry
|
||||
// Required: municipality_id, title, content
|
||||
// ---------------------------------------------------------------------
|
||||
function handle_create_news($input) {
|
||||
$pdo = get_db();
|
||||
$missing = validate_required($input, ['municipality_id', 'title', 'content']);
|
||||
if (!empty($missing)) {
|
||||
error_response('Missing Fields: ' . implode(', ', $missing));
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO news (municipality_id, title, content)
|
||||
VALUES (:mid, :title, :content)
|
||||
");
|
||||
$stmt->execute([
|
||||
':mid' => $input['municipality_id'],
|
||||
':title' => $input['title'],
|
||||
':content' => $input['content']
|
||||
]);
|
||||
json_response(['message' => 'News created successfully.', 'news_id' => (int) $pdo->lastInsertId()], 201);
|
||||
} catch (PDOException $e) {
|
||||
error_response('Database Error: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// UPDATE NEWS: Updates existing News Entry
|
||||
// Required: news_id
|
||||
// Optional: title, content
|
||||
// ---------------------------------------------------------------------
|
||||
function handle_update_news($input) {
|
||||
$pdo = get_db();
|
||||
$missing = validate_required($input, ['news_id']);
|
||||
if (!empty($missing)) {
|
||||
error_response('Missing Fields: ' . implode(', ', $missing));
|
||||
}
|
||||
|
||||
$set = [];
|
||||
$params = [':id' => $input['news_id']];
|
||||
|
||||
foreach (['title', 'content'] as $field) {
|
||||
if (isset($input[$field]) && $input[$field] !== '') {
|
||||
$set[] = "$field = :$field";
|
||||
$params[":$field"] = $input[$field];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($set)) {
|
||||
error_response('No Fields to update.');
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE news SET " . implode(', ', $set) . " WHERE news_id = :id");
|
||||
$stmt->execute($params);
|
||||
json_response(['message' => 'News updated successfully.']);
|
||||
} catch (PDOException $e) {
|
||||
error_response('Database Error: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// DELETE NEWS: Deletes existing News Entry
|
||||
// Required: news_id
|
||||
// ---------------------------------------------------------------------
|
||||
function handle_delete_news($input) {
|
||||
$pdo = get_db();
|
||||
$missing = validate_required($input, ['news_id']);
|
||||
if (!empty($missing)) {
|
||||
error_response('Missing Fields: ' . implode(', ', $missing));
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM news WHERE news_id = :id");
|
||||
$stmt->execute([':id' => $input['news_id']]);
|
||||
json_response(['message' => 'News deleted successfully.']);
|
||||
} catch (PDOException $e) {
|
||||
error_response('Database Error: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user