Feature: Admin Action-Items-Karte über Tabs, SW by-v587
This commit is contained in:
parent
fb7bbe5ccc
commit
d04110c2ae
4 changed files with 98 additions and 2 deletions
|
|
@ -97,6 +97,40 @@ class ThreadAdminPatch(BaseModel):
|
||||||
is_deleted: Optional[int] = None
|
is_deleted: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# GET /api/admin/action-items
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@router.get("/action-items")
|
||||||
|
async def action_items(user=Depends(require_mod)):
|
||||||
|
with db() as conn:
|
||||||
|
jobs = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM job_applications WHERE status IN ('pending','reviewing')"
|
||||||
|
).fetchone()[0]
|
||||||
|
breeders = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM users WHERE breeder_status='pending'"
|
||||||
|
).fetchone()[0]
|
||||||
|
reports = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM forum_reports WHERE resolved=0"
|
||||||
|
).fetchone()[0]
|
||||||
|
fotos = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM wiki_foto_submissions WHERE status='pending'"
|
||||||
|
).fetchone()[0]
|
||||||
|
poi_edits = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM osm_poi_edits WHERE status='pending'"
|
||||||
|
).fetchone()[0]
|
||||||
|
users_today = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM users WHERE DATE(created_at)=DATE('now')"
|
||||||
|
).fetchone()[0]
|
||||||
|
return {
|
||||||
|
"jobs_pending": jobs,
|
||||||
|
"breeder_pending": breeders,
|
||||||
|
"reports_open": reports,
|
||||||
|
"fotos_pending": fotos,
|
||||||
|
"poi_edits_pending": poi_edits,
|
||||||
|
"users_today": users_today,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# GET /api/admin/stats
|
# GET /api/admin/stats
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
Router, State-Management, Navigation, Initialisierung.
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const APP_VER = '586'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
const APP_VER = '587'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||||
const APP_VERSION = '1.2.1'; // ← semantische Version, wird bei make release gesetzt
|
const APP_VERSION = '1.2.1'; // ← semantische Version, wird bei make release gesetzt
|
||||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ window.Page_admin = (() => {
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
function _render() {
|
function _render() {
|
||||||
_container.innerHTML = `
|
_container.innerHTML = `
|
||||||
|
<!-- Action Items -->
|
||||||
|
<div id="adm-action-items" style="padding:var(--space-3) var(--space-3) 0"></div>
|
||||||
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<div class="by-tabs adm-tabs" id="adm-tabs">
|
<div class="by-tabs adm-tabs" id="adm-tabs">
|
||||||
${TABS.map(t => `
|
${TABS.map(t => `
|
||||||
|
|
@ -73,9 +76,68 @@ window.Page_admin = (() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_renderActionItems();
|
||||||
_renderTab();
|
_renderTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _renderActionItems() {
|
||||||
|
const el = _container.querySelector('#adm-action-items');
|
||||||
|
if (!el) return;
|
||||||
|
let d;
|
||||||
|
try { d = await API.get('/admin/action-items'); } catch { return; }
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ key: 'jobs_pending', label: 'Bewerbungen', tab: 'bewerbungen', icon: 'user-plus' },
|
||||||
|
{ key: 'breeder_pending', label: 'Züchter-Anträge', tab: 'zuchter', icon: 'certificate' },
|
||||||
|
{ key: 'reports_open', label: 'Meldungen', tab: 'moderation', icon: 'warning' },
|
||||||
|
{ key: 'fotos_pending', label: 'Foto-Einreichungen',tab: 'moderation', icon: 'image' },
|
||||||
|
{ key: 'poi_edits_pending', label: 'POI-Korrekturen', tab: 'moderation', icon: 'map-pin' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const open = items.filter(i => d[i.key] > 0);
|
||||||
|
const usersToday = d.users_today || 0;
|
||||||
|
|
||||||
|
el.innerHTML = `
|
||||||
|
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center;
|
||||||
|
background:var(--c-surface);border:1px solid var(--c-border);
|
||||||
|
border-radius:var(--radius-lg);padding:var(--space-3) var(--space-4)">
|
||||||
|
<span style="font-size:var(--text-xs);font-weight:700;color:var(--c-text-muted);
|
||||||
|
text-transform:uppercase;letter-spacing:.06em;margin-right:var(--space-1)">
|
||||||
|
${UI.icon('check-square')} Zu erledigen
|
||||||
|
</span>
|
||||||
|
${open.length === 0
|
||||||
|
? `<span style="font-size:var(--text-sm);color:var(--c-success,#4caf50);font-weight:600">
|
||||||
|
${UI.icon('check-circle')} Alles erledigt
|
||||||
|
</span>`
|
||||||
|
: open.map(i => `
|
||||||
|
<button data-action-tab="${i.tab}"
|
||||||
|
style="display:inline-flex;align-items:center;gap:4px;
|
||||||
|
background:var(--c-warning-light,#fff3e0);color:var(--c-warning,#e65100);
|
||||||
|
border:1px solid var(--c-warning,#e65100);border-radius:999px;
|
||||||
|
padding:2px 10px;font-size:var(--text-xs);font-weight:700;cursor:pointer">
|
||||||
|
${UI.icon(i.icon)} ${i.label}
|
||||||
|
<span style="background:var(--c-warning,#e65100);color:#fff;
|
||||||
|
border-radius:999px;padding:0 6px;margin-left:2px">
|
||||||
|
${d[i.key]}
|
||||||
|
</span>
|
||||||
|
</button>`).join('')
|
||||||
|
}
|
||||||
|
<span style="margin-left:auto;font-size:var(--text-xs);color:var(--c-text-muted)">
|
||||||
|
${UI.icon('user-plus')} ${usersToday} neue Nutzer heute
|
||||||
|
</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
el.querySelectorAll('[data-action-tab]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
_tab = btn.dataset.actionTab;
|
||||||
|
_container.querySelectorAll('#adm-tabs .by-tab').forEach(b =>
|
||||||
|
b.classList.toggle('active', b.dataset.tab === _tab)
|
||||||
|
);
|
||||||
|
_renderTab();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function _renderTab() {
|
async function _renderTab() {
|
||||||
const el = _container.querySelector('#adm-content');
|
const el = _container.querySelector('#adm-content');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Offline-Cache + Push Notifications + Tile-Cache
|
Offline-Cache + Push Notifications + Tile-Cache
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const CACHE_VERSION = 'by-v586';
|
const CACHE_VERSION = 'by-v587';
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue