Admin: Moderation-Tab für Züchter + Wiki-Fotos (SW by-v280)
This commit is contained in:
parent
180de32e57
commit
2b442ebd98
3 changed files with 145 additions and 16 deletions
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '267'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '268'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
|
||||
const App = (() => {
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ window.Page_admin = (() => {
|
|||
let _tab = 'uebersicht';
|
||||
|
||||
const TABS = [
|
||||
{ id: 'uebersicht', label: 'Übersicht', icon: 'house-line' },
|
||||
{ id: 'nutzer', label: 'Nutzer', icon: 'users' },
|
||||
{ id: 'forum', label: 'Forum & Meldungen', icon: 'chat-circle-dots' },
|
||||
{ id: 'analytics', label: 'Analytics', icon: 'target' },
|
||||
{ id: 'system', label: 'System', icon: 'gear' },
|
||||
{ id: 'jobs', label: 'Jobs', icon: 'clock' },
|
||||
{ id: 'audit', label: 'Audit-Log', icon: 'clipboard-text' },
|
||||
{ id: 'uebersicht', label: 'Übersicht', icon: 'house-line' },
|
||||
{ id: 'nutzer', label: 'Nutzer', icon: 'users' },
|
||||
{ id: 'moderation', label: 'Moderation', icon: 'shield-check' },
|
||||
{ id: 'forum', label: 'Forum & Meldungen', icon: 'chat-circle-dots' },
|
||||
{ id: 'analytics', label: 'Analytics', icon: 'target' },
|
||||
{ id: 'system', label: 'System', icon: 'gear' },
|
||||
{ id: 'jobs', label: 'Jobs', icon: 'clock' },
|
||||
{ id: 'audit', label: 'Audit-Log', icon: 'clipboard-text' },
|
||||
];
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -74,13 +75,14 @@ window.Page_admin = (() => {
|
|||
el.innerHTML = `<div style="padding:var(--space-6);text-align:center;color:var(--c-text-muted)">Lade…</div>`;
|
||||
try {
|
||||
switch (_tab) {
|
||||
case 'uebersicht': await _renderStats(el); break;
|
||||
case 'nutzer': await _renderUsers(el); break;
|
||||
case 'forum': await _renderForum(el); break;
|
||||
case 'analytics': await _renderAnalytics(el); break;
|
||||
case 'system': await _renderSystem(el); break;
|
||||
case 'jobs': await _renderJobs(el); break;
|
||||
case 'audit': await _renderAudit(el); break;
|
||||
case 'uebersicht': await _renderStats(el); break;
|
||||
case 'nutzer': await _renderUsers(el); break;
|
||||
case 'moderation': await _renderModeration(el); break;
|
||||
case 'forum': await _renderForum(el); break;
|
||||
case 'analytics': await _renderAnalytics(el); break;
|
||||
case 'system': await _renderSystem(el); break;
|
||||
case 'jobs': await _renderJobs(el); break;
|
||||
case 'audit': await _renderAudit(el); break;
|
||||
}
|
||||
} catch (e) {
|
||||
el.innerHTML = _emptyState('warning', 'Fehler', e.message || 'Unbekannter Fehler.');
|
||||
|
|
@ -737,6 +739,133 @@ window.Page_admin = (() => {
|
|||
|
||||
// ------------------------------------------------------------------
|
||||
// TAB: JOBS
|
||||
// ------------------------------------------------------------------
|
||||
// TAB: MODERATION
|
||||
// ------------------------------------------------------------------
|
||||
async function _renderModeration(el) {
|
||||
el.innerHTML = `
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:var(--space-3)">
|
||||
<button class="btn btn-ghost btn-sm" id="adm-mod-refresh">${UI.icon('arrows-clockwise')} Aktualisieren</button>
|
||||
</div>
|
||||
<div id="adm-mod-content">Lade…</div>
|
||||
`;
|
||||
el.querySelector('#adm-mod-refresh').addEventListener('click', () => _loadModeration(el.querySelector('#adm-mod-content')));
|
||||
await _loadModeration(el.querySelector('#adm-mod-content'));
|
||||
}
|
||||
|
||||
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([
|
||||
API.get('/wiki/zuchter/pending').catch(() => []),
|
||||
API.get('/wiki/foto-submissions').catch(() => []),
|
||||
]);
|
||||
|
||||
let html = '';
|
||||
|
||||
// --- Züchter-Einreichungen ---
|
||||
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-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>
|
||||
</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>`;
|
||||
} else {
|
||||
html += `<div class="card adm-table-card" style="margin-bottom:var(--space-6)"><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) => `
|
||||
<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>
|
||||
<td class="adm-td">${_esc([z.plz, z.ort, z.bundesland].filter(Boolean).join(' '))}</td>
|
||||
<td class="adm-td">${z.vdh_mitglied ? '<span style="color:var(--c-success)">✓ VDH</span>' : '—'}</td>
|
||||
<td class="adm-td">${z.website ? `<a href="${_esc(z.website)}" target="_blank" style="color:var(--c-primary);font-size:var(--text-xs)">Link</a>` : '—'}</td>
|
||||
<td class="adm-td" style="text-align:right;white-space:nowrap">
|
||||
<button class="btn btn-sm btn-primary adm-zuchter-approve" data-id="${z.id}" style="margin-right:4px">✓ Freigeben</button>
|
||||
<button class="btn btn-sm btn-ghost adm-zuchter-delete" data-id="${z.id}" style="color:var(--c-danger)">✗</button>
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody></table></div></div>`;
|
||||
}
|
||||
|
||||
// --- Wiki-Foto-Einreichungen ---
|
||||
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-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>
|
||||
</h3>`;
|
||||
|
||||
if (!fotos.length) {
|
||||
html += `<p style="font-size:var(--text-sm);color:var(--c-text-muted)">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 => `
|
||||
<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)">
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">${_esc(f.rasse_name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-3)">von ${_esc(f.user_name)}</div>
|
||||
${f.aktuell_foto ? `<img src="${_esc(f.aktuell_foto)}" alt="Aktuell"
|
||||
style="width:100%;height:80px;object-fit:cover;border-radius:var(--radius-sm);
|
||||
opacity:.5;margin-bottom:var(--space-2)">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-3)">↑ aktuelles Foto</div>` : ''}
|
||||
<div style="display:flex;gap:var(--space-2)">
|
||||
<button class="btn btn-sm btn-primary adm-foto-approve" data-id="${f.id}" style="flex:1">✓</button>
|
||||
<button class="btn btn-sm btn-ghost adm-foto-reject" data-id="${f.id}" style="color:var(--c-danger)">✗</button>
|
||||
</div>
|
||||
</div>`).join('')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
el.innerHTML = html;
|
||||
|
||||
// Züchter freigeben
|
||||
el.querySelectorAll('.adm-zuchter-approve').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
await API.patch(`/wiki/zuchter/${btn.dataset.id}/verify`, {});
|
||||
await _loadModeration(el);
|
||||
});
|
||||
});
|
||||
|
||||
// Züchter löschen
|
||||
el.querySelectorAll('.adm-zuchter-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!window.confirm('Eintrag löschen?')) return;
|
||||
btn.disabled = true;
|
||||
await API.delete(`/admin/wiki/zuchter/${btn.dataset.id}`);
|
||||
await _loadModeration(el);
|
||||
});
|
||||
});
|
||||
|
||||
// Foto freigeben
|
||||
el.querySelectorAll('.adm-foto-approve').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
await API.patch(`/wiki/foto-submissions/${btn.dataset.id}`, {action: 'approve'});
|
||||
await _loadModeration(el);
|
||||
});
|
||||
});
|
||||
|
||||
// Foto ablehnen
|
||||
el.querySelectorAll('.adm-foto-reject').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
await API.patch(`/wiki/foto-submissions/${btn.dataset.id}`, {action: 'reject', reject_reason: 'Nicht geeignet.'});
|
||||
await _loadModeration(el);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
async function _renderJobs(el) {
|
||||
el.innerHTML = `
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Offline-Cache + Push Notifications + Tile-Cache
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v279';
|
||||
const CACHE_VERSION = 'by-v280';
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue