Fix: Admin Züchter-Tab — Alle Züchter Liste + Antraege-Section (SW by-v921)
- GET /api/admin/breeders: neuer Endpunkt listet alle aktiven Züchter mit Zwingername, Rasse, Stadt, Würfe/Zuchthunde-Zähler, subscription_tier - _renderZuechter: zwei Sektionen parallel geladen - "Offene Anträge" (wie vorher, aber mit Section-Header auch wenn leer) - "Alle Züchter": Tabelle analog Nutzer-Tab mit Abo-Button → _changeTier - api.js: API.breeder.allList() hinzugefügt - SW by-v921, APP_VER 921
This commit is contained in:
parent
f6b37717b4
commit
52160e4dc0
6 changed files with 113 additions and 10 deletions
|
|
@ -691,6 +691,7 @@ const API = (() => {
|
|||
updateProfile(data) { return put('/breeder/profile', data); },
|
||||
adminCreateProfile() { return post('/admin/breeder/create-profile', {}); },
|
||||
pendingList() { return get('/admin/breeders/pending'); },
|
||||
allList() { return get('/admin/breeders'); },
|
||||
documents(userId) { return get(`/admin/breeder/${userId}/documents`); },
|
||||
documentUrl(userId, docId) { return `/api/admin/breeder/${userId}/document/${docId}`; },
|
||||
approve(userId) { return post(`/admin/breeder/${userId}/approve`, {}); },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '920'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '921'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt
|
||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||
// Cache-Bust-Parameter nach Update-Reload sofort entfernen
|
||||
|
|
|
|||
|
|
@ -1893,12 +1893,17 @@ window.Page_admin = (() => {
|
|||
${UI.icon('arrows-clockwise')} Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
<div id="adm-zuchter-list">Lade…</div>
|
||||
<div id="adm-zuchter-antraege">Lade…</div>
|
||||
<div id="adm-zuchter-liste" style="margin-top:var(--space-4)">Lade…</div>
|
||||
`;
|
||||
el.querySelector('#adm-zuchter-refresh').addEventListener('click', () =>
|
||||
_loadZuechterAntraege(el.querySelector('#adm-zuchter-list'))
|
||||
);
|
||||
await _loadZuechterAntraege(el.querySelector('#adm-zuchter-list'));
|
||||
el.querySelector('#adm-zuchter-refresh').addEventListener('click', () => {
|
||||
_loadZuechterAntraege(el.querySelector('#adm-zuchter-antraege'));
|
||||
_loadZuechterListe(el.querySelector('#adm-zuchter-liste'));
|
||||
});
|
||||
await Promise.all([
|
||||
_loadZuechterAntraege(el.querySelector('#adm-zuchter-antraege')),
|
||||
_loadZuechterListe(el.querySelector('#adm-zuchter-liste')),
|
||||
]);
|
||||
}
|
||||
|
||||
async function _loadZuechterAntraege(el) {
|
||||
|
|
@ -1912,12 +1917,20 @@ window.Page_admin = (() => {
|
|||
}
|
||||
|
||||
if (!antraege.length) {
|
||||
el.innerHTML = _emptyState('certificate', 'Keine offenen Anträge', 'Aktuell liegen keine Züchter-Anträge zur Prüfung vor.');
|
||||
el.innerHTML = `<div class="card" style="padding:var(--space-4)">
|
||||
<div class="by-card-section-header">Offene Anträge</div>
|
||||
<div style="padding:var(--space-3) var(--space-4);font-size:var(--text-sm);color:var(--c-text-muted)">
|
||||
${UI.icon('check-circle')} Keine offenen Anträge
|
||||
</div>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
el.innerHTML = `
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
|
||||
<div class="card" style="margin-bottom:0">
|
||||
<div class="by-card-section-header">Offene Anträge (${antraege.length})</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-3);margin-top:var(--space-3)">
|
||||
${antraege.map(a => `
|
||||
<div class="card" style="padding:var(--space-4)">
|
||||
<div style="display:flex;align-items:flex-start;gap:var(--space-3);flex-wrap:wrap">
|
||||
|
|
@ -2072,6 +2085,74 @@ window.Page_admin = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
async function _loadZuechterListe(el) {
|
||||
el.innerHTML = `<div style="padding:var(--space-4);text-align:center;color:var(--c-text-muted)">Lade…</div>`;
|
||||
let breeders;
|
||||
try {
|
||||
breeders = await API.breeder.allList();
|
||||
} catch (e) {
|
||||
el.innerHTML = _emptyState('warning', 'Fehler', e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const tierBadge = t => {
|
||||
if (t === 'breeder') return `<span style="display:inline-block;padding:1px 7px;border-radius:999px;font-size:10px;font-weight:700;background:#C4843A;color:#fff">Züchter-Abo</span>`;
|
||||
if (t === 'breeder_test') return `<span style="display:inline-block;padding:1px 7px;border-radius:999px;font-size:10px;font-weight:700;background:#aaa;color:#fff">Test</span>`;
|
||||
return `<span style="display:inline-block;padding:1px 7px;border-radius:999px;font-size:10px;font-weight:700;background:#eee;color:#666">Standard</span>`;
|
||||
};
|
||||
|
||||
const rows = breeders.map(b => `
|
||||
<tr>
|
||||
<td style="padding:var(--space-2) var(--space-3)">
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(b.name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${_esc(b.email)}</div>
|
||||
</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-sm)">${_esc(b.zwingername || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(b.rasse_text || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(b.stadt || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);text-align:center;font-size:var(--text-xs)">
|
||||
${b.wuerfe_count || 0} Würfe<br>
|
||||
<span style="color:var(--c-text-muted)">${b.zuchthunde_count || 0} Zuchthunde</span>
|
||||
</td>
|
||||
<td style="padding:var(--space-2) var(--space-3)">${tierBadge(b.subscription_tier)}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-xs);color:var(--c-text-muted)">
|
||||
${b.verified_at ? new Date(b.verified_at).toLocaleDateString('de-DE') : '—'}
|
||||
</td>
|
||||
<td style="padding:var(--space-2) var(--space-3)">
|
||||
<button class="btn btn-sm btn-ghost adm-breeder-tier-btn"
|
||||
data-uid="${b.id}" data-name="${_esc(b.name)}" data-tier="${_esc(b.subscription_tier || 'standard')}"
|
||||
style="font-size:var(--text-xs)">
|
||||
Abo
|
||||
</button>
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="card adm-table-card">
|
||||
<div class="by-card-section-header">Alle Züchter (${breeders.length})</div>
|
||||
<div class="adm-table-scroll">
|
||||
<table class="adm-table" style="width:100%;border-collapse:collapse">
|
||||
<thead><tr>
|
||||
${['Nutzer','Zwingername','Rasse','Stadt','Aktivität','Abo','Seit',''].map(h =>
|
||||
`<th style="padding:var(--space-2) var(--space-3);text-align:left;
|
||||
font-size:var(--text-xs);color:var(--c-text-muted);font-weight:600;
|
||||
border-bottom:1px solid var(--c-border);white-space:nowrap">${h}</th>`
|
||||
).join('')}
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
${rows || `<tr><td colspan="8" style="padding:var(--space-4);text-align:center;color:var(--c-text-muted)">Noch keine Züchter</td></tr>`}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
el.querySelectorAll('.adm-breeder-tier-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () =>
|
||||
_changeTier(btn.dataset.uid, btn.dataset.name, btn.dataset.tier)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
async function _renderJobs(el) {
|
||||
el.innerHTML = `
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Offline-Cache + Push Notifications + Tile-Cache
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v920';
|
||||
const CACHE_VERSION = 'by-v921';
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue