Feature: Moderation History — Log für alle 4 Bereiche, resolved_by/at Migration, SW by-v590

This commit is contained in:
rene 2026-05-01 19:44:59 +02:00
parent e2cd32a550
commit 87039994ce
6 changed files with 125 additions and 43 deletions

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '589'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '590'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.2.1'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';

View file

@ -1460,6 +1460,30 @@ window.Page_admin = (() => {
// ------------------------------------------------------------------
// TAB: MODERATION
// ------------------------------------------------------------------
function _historySection(label, items, renderItem) {
const id = `hist-${label.replace(/\W/g,'').toLowerCase()}`;
return `
<details style="margin-bottom:var(--space-4)">
<summary style="cursor:pointer;list-style:none;display:flex;align-items:center;gap:var(--space-2);
font-size:var(--text-xs);font-weight:700;color:var(--c-text-muted);
text-transform:uppercase;letter-spacing:.06em;padding:var(--space-2) 0;
border-top:1px solid var(--c-border)">
${UI.icon('clock-countdown')} ${items.length} erledigte ${label}
<svg class="ph-icon" style="margin-left:auto;transition:transform .2s" aria-hidden="true">
<use href="/icons/phosphor.svg#caret-down"></use>
</svg>
</summary>
<div style="margin-top:var(--space-2);display:flex;flex-direction:column;gap:var(--space-1)">
${items.map(item => `
<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);display:flex;align-items:center;flex-wrap:wrap;gap:4px">
${renderItem(item)}
</div>`).join('')}
</div>
</details>`;
}
async function _renderModeration(el) {
el.innerHTML = `
<div style="display:flex;justify-content:flex-end;margin-bottom:var(--space-3)">
@ -1480,13 +1504,20 @@ window.Page_admin = (() => {
API.get('/moderation/reports').catch(() => []),
API.get('/moderation/poi-edits').catch(() => []),
]);
const poiPending = poiEdits.filter(e => e.status === 'pending');
const zuchterPending = zuchter.filter(z => !z.verified);
const zuchterDone = zuchter.filter(z => z.verified);
const fotosPending = fotos.filter(f => f.status === 'pending');
const fotosDone = fotos.filter(f => f.status !== 'pending');
const reportsPending = reports.filter(r => !r.resolved);
const reportsDone = reports.filter(r => r.resolved);
const poiPending = poiEdits.filter(e => e.status === 'pending');
const poiDone = 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: 'Forum-Meldungen', count: reports.length, icon: 'warning' },
{ label: 'POI-Korrekturen', count: poiPending.length, icon: 'map-pin' },
{ label: 'Züchter-Einreichungen', count: zuchterPending.length, icon: 'certificate' },
{ label: 'Foto-Einreichungen', count: fotosPending.length, icon: 'image' },
{ label: 'Forum-Meldungen', count: reportsPending.length, icon: 'warning' },
{ label: 'POI-Korrekturen', count: poiPending.length, icon: 'map-pin' },
].filter(i => i.count > 0);
let html = `
@ -1520,18 +1551,18 @@ window.Page_admin = (() => {
margin-bottom:var(--space-3)">
Züchter-Einreichungen
<span style="background:var(--c-primary);color:#fff;border-radius:999px;
padding:1px 8px;font-size:var(--text-xs);margin-left:6px">${zuchter.length}</span>
padding:1px 8px;font-size:var(--text-xs);margin-left:6px">${zuchterPending.length}</span>
</h3>`;
if (!zuchter.length) {
html += `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-bottom:var(--space-6)">Keine ausstehenden Einreichungen.</p>`;
if (!zuchterPending.length) {
html += `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-bottom:var(--space-3)">Keine ausstehenden Einreichungen.</p>`;
} else {
html += `<div class="card adm-table-card" style="margin-bottom:var(--space-6)"><div class="adm-table-scroll"><table class="adm-table">
html += `<div class="card adm-table-card" style="margin-bottom:var(--space-3)"><div class="adm-table-scroll"><table class="adm-table">
<thead><tr style="background:var(--c-surface-2);text-align:left">
<th class="adm-th">Rasse</th><th class="adm-th">Name / Zwingername</th>
<th class="adm-th">Ort</th><th class="adm-th">VDH</th><th class="adm-th">Website</th><th class="adm-th"></th>
</tr></thead><tbody>
${zuchter.map((z, i) => `
${zuchterPending.map((z, i) => `
<tr style="${i%2===1?'background:var(--c-surface-2)':''}">
<td class="adm-td" style="font-weight:var(--weight-semibold)">${_esc(z.rasse_slug)}</td>
<td class="adm-td">${_esc(z.name)}${z.zwingername ? `<br><span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(z.zwingername)}</span>` : ''}</td>
@ -1545,6 +1576,10 @@ window.Page_admin = (() => {
</tr>`).join('')}
</tbody></table></div></div>`;
}
// Züchter-History
if (zuchterDone.length) html += _historySection('Züchter-Einreichungen', zuchterDone,
z => `<span style="font-weight:600">${_esc(z.name)}</span> · ${_esc(z.rasse_slug)} ·
${UI.icon('check-circle')} ${_esc(z.verified_by_name||'?')} · ${(z.verified_at||'').slice(0,10)}`);
// --- Wiki-Foto-Einreichungen ---
html += `<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
@ -1552,14 +1587,14 @@ window.Page_admin = (() => {
margin-bottom:var(--space-3)">
Wiki-Foto-Einreichungen
<span style="background:var(--c-primary);color:#fff;border-radius:999px;
padding:1px 8px;font-size:var(--text-xs);margin-left:6px">${fotos.length}</span>
padding:1px 8px;font-size:var(--text-xs);margin-left:6px">${fotosPending.length}</span>
</h3>`;
if (!fotos.length) {
html += `<p style="font-size:var(--text-sm);color:var(--c-text-muted)">Keine ausstehenden Foto-Einreichungen.</p>`;
if (!fotosPending.length) {
html += `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-bottom:var(--space-3)">Keine ausstehenden Foto-Einreichungen.</p>`;
} else {
html += `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:var(--space-4)">
${fotos.map(f => `
html += `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:var(--space-4);margin-bottom:var(--space-3)">
${fotosPending.map(f => `
<div class="card" style="padding:var(--space-4)">
<img src="${_esc(f.foto_url)}" alt=""
style="width:100%;height:140px;object-fit:cover;border-radius:var(--radius-md);margin-bottom:var(--space-3)">
@ -1577,21 +1612,28 @@ window.Page_admin = (() => {
</div>`;
}
// Fotos-History
if (fotosDone.length) html += _historySection('Foto-Einreichungen', fotosDone,
f => `<img src="${_esc(f.foto_url)}" style="width:32px;height:32px;object-fit:cover;border-radius:4px;vertical-align:middle;margin-right:6px">
<span style="font-weight:600">${_esc(f.rasse_name||'?')}</span> · von ${_esc(f.user_name||'?')} ·
${f.status==='approved' ? `${UI.icon('check-circle')} genehmigt` : `${UI.icon('x-circle')} abgelehnt`}
${f.reviewed_by_name ? ` von ${_esc(f.reviewed_by_name)}` : ''} · ${(f.reviewed_at||'').slice(0,10)}`);
// --- 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)">
margin:var(--space-4) 0 var(--space-3)">
Forum-Meldungen
<span style="background:${reports.length ? 'var(--c-danger)' : 'var(--c-primary)'};color:#fff;
<span style="background:${reportsPending.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}
${reportsPending.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>`;
if (!reportsPending.length) {
html += `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-bottom:var(--space-3)">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 => `
html += `<div style="display:flex;flex-direction:column;gap:var(--space-3);margin-bottom:var(--space-3)">
${reportsPending.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">
@ -1614,6 +1656,11 @@ window.Page_admin = (() => {
</div>`;
}
// Meldungen-History
if (reportsDone.length) html += _historySection('Forum-Meldungen', reportsDone,
r => `${_esc(r.target_type)} #${r.target_id} · ${_esc(r.grund)} · Gemeldet von ${_esc(r.melder_name||'?')} ·
${UI.icon('check-circle')} ${_esc(r.resolved_by_name||'?')} · ${(r.resolved_at||'').slice(0,10)}`);
// --- 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;
@ -1658,6 +1705,14 @@ window.Page_admin = (() => {
</table>
</div></div>`;
}
// POI-History
if (poiDone.length) html += _historySection('POI-Korrekturen', poiDone,
e => `<span style="font-weight:600">${_esc(e.poi_name||`OSM #${e.osm_id}`)}</span> ·
<code style="font-size:var(--text-xs)">${_esc(e.field)}</code>:
<span style="text-decoration:line-through;color:var(--c-text-muted)">${_esc(e.old_value||'—')}</span>
${_esc(e.new_value||'—')} ·
${e.status==='approved' ? `${UI.icon('check-circle')} freigegeben` : `${UI.icon('x-circle')} abgelehnt`}
${e.mod_name ? ` von ${_esc(e.mod_name)}` : ''} · ${(e.resolved_at||'').slice(0,10)}`);
el.innerHTML = html;