Feature: Moderation — Forum-Meldungen + POI-Korrekturen, SW by-v589

This commit is contained in:
rene 2026-05-01 19:37:46 +02:00
parent 020153484a
commit e2cd32a550
3 changed files with 127 additions and 5 deletions

View file

@ -1474,14 +1474,19 @@ window.Page_admin = (() => {
async function _loadModeration(el) {
el.innerHTML = `<div style="padding:var(--space-4);text-align:center;color:var(--c-text-muted)">Lade…</div>`;
const [zuchter, fotos] = await Promise.all([
const [zuchter, fotos, reports, poiEdits] = await Promise.all([
API.get('/wiki/zuchter/pending').catch(() => []),
API.get('/wiki/foto-submissions').catch(() => []),
API.get('/moderation/reports').catch(() => []),
API.get('/moderation/poi-edits').catch(() => []),
]);
const poiPending = poiEdits.filter(e => e.status === 'pending');
const modItems = [
{ label: 'Züchter-Einreichungen', count: zuchter.length, icon: 'certificate' },
{ label: 'Foto-Einreichungen', count: fotos.length, icon: 'image' },
{ label: 'Züchter-Einreichungen', count: zuchter.length, icon: 'certificate' },
{ label: 'Foto-Einreichungen', count: fotos.length, icon: 'image' },
{ label: 'Forum-Meldungen', count: reports.length, icon: 'warning' },
{ label: 'POI-Korrekturen', count: poiPending.length, icon: 'map-pin' },
].filter(i => i.count > 0);
let html = `
@ -1572,6 +1577,88 @@ window.Page_admin = (() => {
</div>`;
}
// --- Forum-Meldungen ---
html += `<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text-muted);text-transform:uppercase;letter-spacing:.06em;
margin:var(--space-6) 0 var(--space-3)">
Forum-Meldungen
<span style="background:${reports.length ? 'var(--c-danger)' : 'var(--c-primary)'};color:#fff;
border-radius:999px;padding:1px 8px;font-size:var(--text-xs);margin-left:6px">
${reports.length}
</span>
</h3>`;
if (!reports.length) {
html += `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-bottom:var(--space-6)">Keine offenen Meldungen.</p>`;
} else {
html += `<div style="display:flex;flex-direction:column;gap:var(--space-3);margin-bottom:var(--space-6)">
${reports.map(r => `
<div class="card" style="padding:var(--space-4);border-left:3px solid var(--c-danger)">
<div style="display:flex;align-items:flex-start;gap:var(--space-3)">
<div style="flex:1;min-width:0">
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-1)">
${_esc(r.target_type)} #${r.target_id} · Gemeldet von <strong>${_esc(r.melder_name || '?')}</strong>
</div>
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);margin-bottom:var(--space-1)">
Grund: ${_esc(r.grund)}
</div>
${r.content_preview ? `
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
padding:var(--space-2) var(--space-3);background:var(--c-surface-2);
border-radius:var(--radius-sm)">${_esc(r.content_preview)}</div>` : ''}
</div>
<button class="btn btn-sm btn-primary adm-mod-resolve" data-rid="${r.id}" title="Als erledigt markieren">
${UI.icon('check')}
</button>
</div>
</div>`).join('')}
</div>`;
}
// --- POI-Korrekturen ---
html += `<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text-muted);text-transform:uppercase;letter-spacing:.06em;
margin:var(--space-2) 0 var(--space-3)">
POI-Korrekturen
<span style="background:${poiPending.length ? 'var(--c-warning,#e65100)' : 'var(--c-primary)'};color:#fff;
border-radius:999px;padding:1px 8px;font-size:var(--text-xs);margin-left:6px">
${poiPending.length}
</span>
</h3>`;
if (!poiPending.length) {
html += `<p style="font-size:var(--text-sm);color:var(--c-text-muted)">Keine ausstehenden POI-Korrekturen.</p>`;
} else {
html += `<div class="card adm-table-card"><div class="adm-table-scroll">
<table class="adm-table">
<thead><tr style="background:var(--c-surface-2);text-align:left">
<th class="adm-th">Ort</th>
<th class="adm-th">Feld</th>
<th class="adm-th">Alt</th>
<th class="adm-th">Neu</th>
<th class="adm-th">Von</th>
<th class="adm-th"></th>
</tr></thead>
<tbody>
${poiPending.map((e, i) => `
<tr style="${i%2===1?'background:var(--c-surface-2)':''}">
<td class="adm-td" style="font-weight:var(--weight-semibold)">${_esc(e.poi_name || `OSM #${e.osm_id}`)}</td>
<td class="adm-td"><code style="font-size:var(--text-xs)">${_esc(e.field)}</code></td>
<td class="adm-td" style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(e.old_value || '—')}</td>
<td class="adm-td" style="font-size:var(--text-xs)">${_esc(e.new_value || '—')}</td>
<td class="adm-td" style="color:var(--c-text-muted)">${_esc(e.einreicher_name || '?')}</td>
<td class="adm-td" style="text-align:right;white-space:nowrap">
<button class="btn btn-sm btn-primary adm-poi-approve" data-id="${e.id}" style="margin-right:4px">
${UI.icon('check')}
</button>
<button class="btn btn-sm btn-ghost adm-poi-reject" data-id="${e.id}" style="color:var(--c-danger)">
${UI.icon('x')}
</button>
</td>
</tr>`).join('')}
</tbody>
</table>
</div></div>`;
}
el.innerHTML = html;
// Züchter freigeben
@ -1610,6 +1697,41 @@ window.Page_admin = (() => {
await _loadModeration(el);
});
});
// Forum-Meldung erledigen
el.querySelectorAll('.adm-mod-resolve').forEach(btn => {
btn.addEventListener('click', async () => {
btn.disabled = true;
try {
await API.patch(`/moderation/reports/${btn.dataset.rid}`, {});
await _loadModeration(el);
} catch (e) { UI.toast.error(e.message); btn.disabled = false; }
});
});
// POI-Korrektur freigeben
el.querySelectorAll('.adm-poi-approve').forEach(btn => {
btn.addEventListener('click', async () => {
btn.disabled = true;
try {
await API.patch(`/moderation/poi-edits/${btn.dataset.id}`, { action: 'approve' });
UI.toast.success('Korrektur übernommen.');
await _loadModeration(el);
} catch (e) { UI.toast.error(e.message); btn.disabled = false; }
});
});
// POI-Korrektur ablehnen
el.querySelectorAll('.adm-poi-reject').forEach(btn => {
btn.addEventListener('click', async () => {
btn.disabled = true;
try {
await API.patch(`/moderation/poi-edits/${btn.dataset.id}`, { action: 'reject' });
UI.toast.success('Korrektur abgelehnt.');
await _loadModeration(el);
} catch (e) { UI.toast.error(e.message); btn.disabled = false; }
});
});
}
// ------------------------------------------------------------------