replaced inline javascript in admin.php with admin.js
This commit is contained in:
660
public/admin.php
660
public/admin.php
@@ -522,657 +522,27 @@ $counts['total'] = count($all_contributions);
|
|||||||
|
|
||||||
|
|
||||||
<!-- ============================================================= -->
|
<!-- ============================================================= -->
|
||||||
<!-- JavaScript: Leaflet, Interactions, API Calls -->
|
<!-- Loads JavaScript Dependencies -->
|
||||||
<!-- ============================================================= -->
|
<!-- ============================================================= -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||||
|
|
||||||
|
<!-- ============================================================= -->
|
||||||
|
<!-- Admin Configuration passed to JavaScript -->
|
||||||
|
<!-- ============================================================= -->
|
||||||
<script>
|
<script>
|
||||||
// Municipality Configuration for Map Previews
|
const ADMIN_CONFIG = {
|
||||||
const MUNICIPALITY_CENTER = [<?= $municipality['center_lat'] ?>, <?= $municipality['center_lng'] ?>];
|
id: <?= $municipality['municipality_id'] ?>,
|
||||||
const MUNICIPALITY_ID = <?= $municipality['municipality_id'] ?>;
|
name: "<?= htmlspecialchars($municipality['name'], ENT_QUOTES) ?>",
|
||||||
const API_URL = 'api/contributions.php';
|
slug: "<?= htmlspecialchars($municipality['slug'], ENT_QUOTES) ?>",
|
||||||
const PRIMARY_COLOR = '<?= htmlspecialchars($municipality['primary_color']) ?>';
|
center: [<?= $municipality['center_lat'] ?>, <?= $municipality['center_lng'] ?>],
|
||||||
|
zoom: <?= $municipality['default_zoom'] ?>,
|
||||||
// Current Status Filter
|
primaryColor: "<?= htmlspecialchars($municipality['primary_color'], ENT_QUOTES) ?>"
|
||||||
let currentFilter = 'all';
|
};
|
||||||
|
|
||||||
// Restores active Tab after Page Reload
|
|
||||||
const savedTab = sessionStorage.getItem('admin_active_tab');
|
|
||||||
if (savedTab) {
|
|
||||||
// Delays to ensure DOM is ready
|
|
||||||
setTimeout(function () {
|
|
||||||
const tabBtn = document.querySelector('.page-tab[onclick*="' + savedTab + '"]');
|
|
||||||
if (tabBtn) tabBtn.click();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Page Tab Navigation
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function showPageTab(tabName) {
|
|
||||||
// Saves active Tab for Persistence after Reload
|
|
||||||
sessionStorage.setItem('admin_active_tab', tabName);
|
|
||||||
|
|
||||||
document.querySelectorAll('.page-tab-content').forEach(function (el) {
|
|
||||||
el.style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Deactivates all Tab Buttons
|
|
||||||
document.querySelectorAll('.page-tab').forEach(function (el) {
|
|
||||||
el.classList.remove('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Shows selected Tab and activates Button
|
|
||||||
document.getElementById('tab-' + tabName).style.display = 'block';
|
|
||||||
event.currentTarget.classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Collapsible Rows
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function toggleRow(row) {
|
|
||||||
const wasOpen = row.classList.contains('open');
|
|
||||||
|
|
||||||
// Closes all open Rows
|
|
||||||
document.querySelectorAll('.contribution-row.open').forEach(function (el) {
|
|
||||||
el.classList.remove('open');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggles clicked Row
|
|
||||||
if (!wasOpen) {
|
|
||||||
row.classList.add('open');
|
|
||||||
|
|
||||||
// Loads Map Preview if not already loaded
|
|
||||||
const mapDiv = row.querySelector('.detail-map');
|
|
||||||
if (mapDiv && !mapDiv.dataset.loaded) {
|
|
||||||
loadMapPreview(mapDiv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Detail Slider for Maps and Photos
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function slideDetail(contributionId, direction) {
|
|
||||||
const slider = document.getElementById('slider-' + contributionId);
|
|
||||||
if (!slider) return;
|
|
||||||
|
|
||||||
const slides = slider.querySelectorAll('.detail-slide');
|
|
||||||
let activeIndex = -1;
|
|
||||||
|
|
||||||
// Finds currently active Slide
|
|
||||||
slides.forEach(function (slide, i) {
|
|
||||||
if (slide.style.display !== 'none') activeIndex = i;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Calculates next Slide Index (wraps around)
|
|
||||||
const nextIndex = (activeIndex + direction + slides.length) % slides.length;
|
|
||||||
|
|
||||||
// Switches Slides
|
|
||||||
slides.forEach(function (slide) { slide.style.display = 'none'; });
|
|
||||||
slides[nextIndex].style.display = 'block';
|
|
||||||
|
|
||||||
// Loads Map if switching to Map Slide and not yet loaded
|
|
||||||
if (slides[nextIndex].dataset.slide === 'map') {
|
|
||||||
const mapDiv = slides[nextIndex].querySelector('.detail-map');
|
|
||||||
if (mapDiv && !mapDiv.dataset.loaded) {
|
|
||||||
loadMapPreview(mapDiv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Map Preview (Leaflet Mini Map per Contribution)
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function loadMapPreview(mapDiv) {
|
|
||||||
const contributionId = mapDiv.dataset.contributionId;
|
|
||||||
|
|
||||||
// Fetches all Contributions to find the Geometry
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('action', 'read');
|
|
||||||
formData.append('municipality_id', MUNICIPALITY_ID);
|
|
||||||
formData.append('status', 'all');
|
|
||||||
|
|
||||||
fetch(API_URL, { method: 'POST', body: formData })
|
|
||||||
.then(function (r) { return r.json(); })
|
|
||||||
.then(function (data) {
|
|
||||||
if (!data.features) return;
|
|
||||||
|
|
||||||
// Finds specific Contribution
|
|
||||||
const feature = data.features.find(function (f) {
|
|
||||||
return f.properties.contribution_id == contributionId;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!feature) {
|
|
||||||
mapDiv.innerHTML = '<div style="padding:20px;color:#999;text-align:center;font-size:0.8rem;">Geometrie nicht gefunden.</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates Leaflet Mini Map
|
|
||||||
const miniMap = L.map(mapDiv, {
|
|
||||||
zoomControl: false,
|
|
||||||
attributionControl: false,
|
|
||||||
dragging: true,
|
|
||||||
scrollWheelZoom: false
|
|
||||||
});
|
|
||||||
|
|
||||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
|
||||||
maxZoom: 20
|
|
||||||
}).addTo(miniMap);
|
|
||||||
|
|
||||||
// Adds Geometry to Mini Map
|
|
||||||
const geojsonLayer = L.geoJSON(feature, {
|
|
||||||
style: { color: PRIMARY_COLOR, weight: 3, fillOpacity: 0.2 },
|
|
||||||
pointToLayer: function (f, latlng) {
|
|
||||||
return L.circleMarker(latlng, {
|
|
||||||
radius: 8, color: '#ffffff', weight: 2,
|
|
||||||
fillColor: PRIMARY_COLOR, fillOpacity: 0.9
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).addTo(miniMap);
|
|
||||||
|
|
||||||
// Fits Map to Geometry Bounds
|
|
||||||
const bounds = geojsonLayer.getBounds();
|
|
||||||
if (bounds.isValid()) {
|
|
||||||
miniMap.fitBounds(bounds, { padding: [25, 25], maxZoom: 17 });
|
|
||||||
} else {
|
|
||||||
miniMap.setView(MUNICIPALITY_CENTER, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
mapDiv.dataset.loaded = 'true';
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
mapDiv.innerHTML = '<div style="padding:20px;color:#999;text-align:center;font-size:0.8rem;">Karte nicht verfügbar.</div>';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Status Filter
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function filterByStatus(status, tabButton) {
|
|
||||||
currentFilter = status;
|
|
||||||
|
|
||||||
// Updates active Tab
|
|
||||||
document.querySelectorAll('.filter-tab').forEach(function (el) {
|
|
||||||
el.classList.remove('active');
|
|
||||||
});
|
|
||||||
tabButton.classList.add('active');
|
|
||||||
|
|
||||||
// Shows/Hides Contribution Rows
|
|
||||||
let visibleCount = 0;
|
|
||||||
document.querySelectorAll('.contribution-row').forEach(function (row) {
|
|
||||||
if (status === 'all' || row.dataset.status === status) {
|
|
||||||
row.style.display = '';
|
|
||||||
visibleCount++;
|
|
||||||
} else {
|
|
||||||
row.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Updates Count Display
|
|
||||||
document.getElementById('visible-count').textContent = visibleCount + ' Beiträge';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Sort Contributions
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function sortContributions(sortBy) {
|
|
||||||
const container = document.getElementById('contributions-container');
|
|
||||||
const rows = Array.from(container.querySelectorAll('.contribution-row'));
|
|
||||||
|
|
||||||
rows.sort(function (a, b) {
|
|
||||||
if (sortBy === 'date-desc') {
|
|
||||||
return new Date(b.dataset.date) - new Date(a.dataset.date);
|
|
||||||
} else if (sortBy === 'date-asc') {
|
|
||||||
return new Date(a.dataset.date) - new Date(b.dataset.date);
|
|
||||||
} else if (sortBy === 'category') {
|
|
||||||
return a.dataset.category.localeCompare(b.dataset.category);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reappends sorted Rows
|
|
||||||
rows.forEach(function (row) {
|
|
||||||
container.appendChild(row);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// API Helper
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function apiCall(data) {
|
|
||||||
const formData = new FormData();
|
|
||||||
for (const key in data) {
|
|
||||||
formData.append(key, data[key]);
|
|
||||||
}
|
|
||||||
return fetch(API_URL, { method: 'POST', body: formData })
|
|
||||||
.then(function (r) { return r.json(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Change Contribution Status
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function changeStatus(contributionId, newStatus) {
|
|
||||||
const labels = { approved: 'freigeben', rejected: 'ablehnen', pending: 'zurücksetzen' };
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Beitrag ' + labels[newStatus] + '?',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: 'Ja',
|
|
||||||
cancelButtonText: 'Abbrechen',
|
|
||||||
confirmButtonColor: PRIMARY_COLOR
|
|
||||||
}).then(function (result) {
|
|
||||||
if (!result.isConfirmed) return;
|
|
||||||
|
|
||||||
apiCall({
|
|
||||||
action: 'update',
|
|
||||||
contribution_id: contributionId,
|
|
||||||
status: newStatus
|
|
||||||
}).then(function (response) {
|
|
||||||
if (response.error) {
|
|
||||||
Swal.fire('Fehler', response.error, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Reloads Page to reflect Changes
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Edit Contribution
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function editContribution(contributionId, currentTitle, currentDescription) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Beitrag 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-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;">Beschreibung</label>' +
|
|
||||||
'<textarea id="swal-description" class="swal2-textarea" style="margin:0;width:100%;">' + currentDescription + '</textarea>' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: 'Speichern',
|
|
||||||
cancelButtonText: 'Abbrechen',
|
|
||||||
confirmButtonColor: PRIMARY_COLOR,
|
|
||||||
preConfirm: function () {
|
|
||||||
return {
|
|
||||||
title: document.getElementById('swal-title').value.trim(),
|
|
||||||
description: document.getElementById('swal-description').value.trim()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}).then(function (result) {
|
|
||||||
if (!result.isConfirmed) return;
|
|
||||||
|
|
||||||
apiCall({
|
|
||||||
action: 'update',
|
|
||||||
contribution_id: contributionId,
|
|
||||||
title: result.value.title,
|
|
||||||
description: result.value.description
|
|
||||||
}).then(function (response) {
|
|
||||||
if (response.error) {
|
|
||||||
Swal.fire('Fehler', response.error, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Swal.fire('Gespeichert!', 'Beitrag wurde aktualisiert.', 'success')
|
|
||||||
.then(function () { location.reload(); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Delete Contribution
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function deleteContribution(contributionId) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Beitrag löschen?',
|
|
||||||
text: 'Diese Aktion kann nicht rückgängig gemacht werden.',
|
|
||||||
icon: 'warning',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: 'Beitrag löschen',
|
|
||||||
cancelButtonText: 'Abbrechen',
|
|
||||||
confirmButtonColor: '#c62828'
|
|
||||||
}).then(function (result) {
|
|
||||||
if (!result.isConfirmed) return;
|
|
||||||
|
|
||||||
apiCall({
|
|
||||||
action: 'delete',
|
|
||||||
contribution_id: contributionId
|
|
||||||
}).then(function (response) {
|
|
||||||
if (response.error) {
|
|
||||||
Swal.fire('Fehler', response.error, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Swal.fire('Gelöscht!', 'Beitrag wurde gelöscht.', 'success')
|
|
||||||
.then(function () { location.reload(); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Create News Article
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function createNews() {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Neuigkeit hinzufügen',
|
|
||||||
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 Neuigkeit">' +
|
|
||||||
'</div>' +
|
|
||||||
'<div style="margin-bottom:12px;">' +
|
|
||||||
'<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="Neuigkeit verfassen..."></textarea>' +
|
|
||||||
'</div>' +
|
|
||||||
'<div>' +
|
|
||||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Autor</label>' +
|
|
||||||
'<input id="swal-news-author" class="swal2-input" style="margin:0;width:100%;" value="Stadtverwaltung">' +
|
|
||||||
'</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();
|
|
||||||
const author = document.getElementById('swal-news-author').value.trim() || 'Stadtverwaltung';
|
|
||||||
if (!title || !content) {
|
|
||||||
Swal.showValidationMessage('Titel und Inhalt sind Pflichtfelder.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return { title: title, content: content, author_name: author };
|
|
||||||
}
|
|
||||||
}).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);
|
|
||||||
formData.append('author_name', result.value.author_name);
|
|
||||||
|
|
||||||
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!', 'Neuigkeit wurde veröffentlicht.', 'success')
|
|
||||||
.then(function () { location.reload(); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Edit News Article
|
|
||||||
// =============================================================
|
|
||||||
function editNews(newsId, currentTitle, currentContent, currentAuthor) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Neuigkeit 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 style="margin-bottom:12px;">' +
|
|
||||||
'<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>' +
|
|
||||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Autor</label>' +
|
|
||||||
'<input id="swal-news-author" class="swal2-input" style="margin:0;width:100%;" value="' + currentAuthor + '">' +
|
|
||||||
'</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(),
|
|
||||||
author_name: document.getElementById('swal-news-author').value.trim() || 'Stadtverwaltung'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}).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);
|
|
||||||
formData.append('author_name', result.value.author_name);
|
|
||||||
|
|
||||||
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!', 'Neuigkeit wurde aktualisiert.', 'success')
|
|
||||||
.then(function () { location.reload(); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Create News Article
|
|
||||||
// =============================================================
|
|
||||||
function deleteNews(newsId) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Neuigkeit 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!', 'Neuigkeit wurde gelöscht.', 'success')
|
|
||||||
.then(function () { location.reload(); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Sort Comments
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function sortCommentRows(sortBy) {
|
|
||||||
const container = document.getElementById('comments-mod-container');
|
|
||||||
const rows = Array.from(container.querySelectorAll('.comment-mod-row'));
|
|
||||||
|
|
||||||
rows.sort(function (a, b) {
|
|
||||||
if (sortBy === 'date-desc') {
|
|
||||||
return new Date(b.dataset.date) - new Date(a.dataset.date);
|
|
||||||
} else if (sortBy === 'date-asc') {
|
|
||||||
return new Date(a.dataset.date) - new Date(b.dataset.date);
|
|
||||||
} else if (sortBy === 'contribution') {
|
|
||||||
return a.dataset.contribution.localeCompare(b.dataset.contribution);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
rows.forEach(function (row) { container.appendChild(row); });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// Delete Comments
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
function deleteModComment(commentId) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Kommentar 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;
|
|
||||||
|
|
||||||
apiCall({
|
|
||||||
action: 'delete_comment',
|
|
||||||
comment_id: commentId
|
|
||||||
}).then(function (response) {
|
|
||||||
if (response.error) {
|
|
||||||
Swal.fire('Fehler', response.error, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Swal.fire('Gelöscht!', 'Kommentar wurde entfernt.', 'success')
|
|
||||||
.then(function () { location.reload(); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// 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>
|
</script>
|
||||||
|
|
||||||
|
<!-- Application Logic -->
|
||||||
|
<script src="js/admin.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ $news_items = $stmt->fetchAll();
|
|||||||
|
|
||||||
|
|
||||||
<!-- ============================================================= -->
|
<!-- ============================================================= -->
|
||||||
<!-- Loads JavaScript Dependencies -->
|
<!-- Loads JavaScript Dependencies -->
|
||||||
<!-- ============================================================= -->
|
<!-- ============================================================= -->
|
||||||
|
|
||||||
<!-- Leaflet 1.9.4 -->
|
<!-- Leaflet 1.9.4 -->
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
// Depends on: ADMIN_CONFIG Object set in Moderation Page
|
// Depends on: ADMIN_CONFIG Object set in Moderation Page
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// Block 0: Configuration and Application State
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
// API Endpoint as relative Path
|
||||||
|
const API_URL = 'api/contributions.php';
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Block 1: Page Tab Navigation
|
// Block 1: Page Tab Navigation
|
||||||
@@ -112,10 +118,10 @@ function loadMapPreview(mapDiv) {
|
|||||||
// Fetches all Contributions to find the Geometry
|
// Fetches all Contributions to find the Geometry
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('action', 'read');
|
formData.append('action', 'read');
|
||||||
formData.append('municipality_id', ADMIN_CONFIG.municipalityId);
|
formData.append('municipality_id', ADMIN_CONFIG.id);
|
||||||
formData.append('status', 'all');
|
formData.append('status', 'all');
|
||||||
|
|
||||||
fetch(ADMIN_CONFIG.apiUrl, { method: 'POST', body: formData })
|
fetch(API_URL, { method: 'POST', body: formData })
|
||||||
.then(function (r) { return r.json(); })
|
.then(function (r) { return r.json(); })
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
if (!data.features) return;
|
if (!data.features) return;
|
||||||
@@ -165,7 +171,7 @@ function loadMapPreview(mapDiv) {
|
|||||||
if (bounds.isValid()) {
|
if (bounds.isValid()) {
|
||||||
miniMap.fitBounds(bounds, { padding: [25, 25], maxZoom: 17 });
|
miniMap.fitBounds(bounds, { padding: [25, 25], maxZoom: 17 });
|
||||||
} else {
|
} else {
|
||||||
miniMap.setView(ADMIN_CONFIG.municipalityCenter, 15);
|
miniMap.setView(ADMIN_CONFIG.center, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapDiv.dataset.loaded = 'true';
|
mapDiv.dataset.loaded = 'true';
|
||||||
@@ -280,7 +286,7 @@ function apiCall(data) {
|
|||||||
formData.append(key, data[key]);
|
formData.append(key, data[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(ADMIN_CONFIG.apiUrl, { method: 'POST', body: formData })
|
return fetch(API_URL, { method: 'POST', body: formData })
|
||||||
.then(function (r) { return r.json(); });
|
.then(function (r) { return r.json(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,7 +542,7 @@ function createNews() {
|
|||||||
|
|
||||||
apiCall({
|
apiCall({
|
||||||
action: 'create_news',
|
action: 'create_news',
|
||||||
municipality_id: ADMIN_CONFIG.municipalityId,
|
municipality_id: ADMIN_CONFIG.id,
|
||||||
title: result.value.title,
|
title: result.value.title,
|
||||||
content: result.value.content,
|
content: result.value.content,
|
||||||
author_name: result.value.author_name
|
author_name: result.value.author_name
|
||||||
|
|||||||
Reference in New Issue
Block a user