banyaro/backend/static/js/pages/partner-dashboard.js
rene 21bcc6b962 Partner-Dashboard vereinfacht: Scans + 'unbestätigt' raus (Rene: verwirrt)
Code-Karte: nur noch Registrierungen gesamt + diesen Monat, mit Hinweis dass
alle Wege zählen (Link, eingetippter Code, QR) — erklärt warum Code-Zahl >
QR-Zahl sein kann. QR-Kontingente: 'X von Y genutzt' statt Scans/Registr./
Versuche; Einzel-Code-Liste nur noch ● genutzt (N×) / ○ frei.
Admin behält die Detail-Sicht (Scans, Versuche, Account-Liste).
2026-06-07 19:25:18 +02:00

198 lines
9.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ============================================================
BAN YARO — Partner-Dashboard
Operative Daten für Partner: Code + Einladungslink, Statistik,
QR-Kontingente mit Einzel-Code-Status, Profil-Status.
(Die öffentliche Präsenz wird in partner-profil.js gepflegt.)
============================================================ */
window.Page_partner_dashboard = (() => {
let _container = null;
let _stats = null;
let _qrBatches = [];
async function init(container) {
_container = container;
_render();
await _load();
}
function refresh() { _load(); }
function onDogChange() {}
function _render() {
_container.innerHTML = `
<div style="max-width:640px;margin:0 auto;padding:var(--space-4)">
<div style="margin-bottom:var(--space-5)">
<h1 style="font-size:var(--text-xl);font-weight:800;margin:0 0 var(--space-1)">
${UI.icon('handshake')} Partner-Bereich
</h1>
<p style="color:var(--c-text-secondary);font-size:var(--text-sm);margin:0">
Dein Code, deine Zahlen, deine QR-Kontingente.
</p>
</div>
<div id="pd-content">
<div style="text-align:center;padding:var(--space-8);color:var(--c-text-muted)">Lade…</div>
</div>
</div>
`;
}
async function _load() {
const el = _container.querySelector('#pd-content');
try {
_stats = await API.get('/partner/my-stats');
_qrBatches = (await API.get('/partner/my-qr').catch(() => [])) || [];
el.innerHTML = _renderDashboard();
_bindEvents(el);
} catch (e) {
el.innerHTML = `<p class="text-danger">${UI.escape(e.message || 'Fehler beim Laden.')}</p>`;
}
}
function _renderDashboard() {
const codes = _stats?.codes || [];
return `
${codes.length === 0 ? `
<div class="card" style="padding:var(--space-5);text-align:center;margin-bottom:var(--space-3)">
<p class="text-sm-secondary" style="margin:0">
Dir ist noch kein Partner-Code zugeordnet.<br>
Melde dich bei <a href="mailto:partner@banyaro.app" class="text-primary">partner@banyaro.app</a> — wir richten ihn ein.
</p>
</div>` : codes.map(c => _renderCodeCard(c)).join('')}
${_renderQrSection()}
${_renderProfileCard()}
`;
}
function _renderCodeCard(c) {
const link = `https://banyaro.app/?ref=${encodeURIComponent(c.code)}`;
return `
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-2)">Dein Einladungscode</div>
<div style="display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;margin-bottom:var(--space-3)">
<code style="font-size:var(--text-lg);font-weight:800;letter-spacing:.1em;color:var(--c-primary)">${UI.escape(c.code)}</code>
<button class="btn btn-sm btn-secondary pd-copy" data-link="${UI.escape(link)}">
${UI.icon('copy')} Link kopieren
</button>
</div>
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-2);text-align:center">
<div style="background:var(--c-surface-2);border-radius:var(--radius-md);padding:var(--space-3)">
<div style="font-size:var(--text-xl);font-weight:800;color:${c.registrations > 0 ? 'var(--c-success,#16a34a)' : 'var(--c-text)'}">${c.registrations}</div>
<div class="text-xs-muted">Registrierungen</div>
</div>
<div style="background:var(--c-surface-2);border-radius:var(--radius-md);padding:var(--space-3)">
<div style="font-size:var(--text-xl);font-weight:800">${c.registrations_month}</div>
<div class="text-xs-muted">diesen Monat</div>
</div>
</div>
<div class="text-xs-muted" style="margin-top:var(--space-2)">
Zählt alle Wege: geteilter Link, eingetippter Code und deine gedruckten QR-Codes.
</div>
</div>`;
}
function _renderQrSection() {
if (!_qrBatches.length) return '';
return `
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-2)">Meine QR-Codes</div>
<p class="text-xs-muted" style="margin:0 0 var(--space-3)">
Deine gedruckten QR-Codes (Sticker, Flyer) — und wie viele davon schon
neue Hundefreunde gebracht haben.
</p>
${_qrBatches.map(b => `
<div style="border-bottom:1px solid var(--c-border)">
<div style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) 0">
<div class="flex-1-min">
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(b.label)}</div>
<div class="text-xs-muted">${b.quantity} Codes · ${(b.created_at || '').slice(0, 10)}</div>
</div>
<div style="text-align:right">
<div style="font-weight:700;color:${b.codes_used > 0 ? 'var(--c-success,#16a34a)' : 'inherit'}">${b.codes_used} von ${b.quantity}</div>
<div class="text-xs-muted" title="Codes mit mindestens einer bestätigten Registrierung">genutzt</div>
</div>
<button class="btn btn-sm btn-ghost pd-qr-codes-btn" data-id="${b.id}" title="Einzel-Codes anzeigen">
${UI.icon('list')}
</button>
<a class="btn btn-sm btn-secondary" href="/api/partner/my-qr/${b.id}/pdf" download>
${UI.icon('file-pdf')} PDF
</a>
</div>
<div class="hidden" id="pd-qr-codes-${b.id}" style="padding:0 0 var(--space-3)">
<div class="text-sm-muted">Lädt…</div>
</div>
</div>`).join('')}
</div>`;
}
function _renderProfileCard() {
const p = _stats?.profile || {};
let badge;
if (p.approved === 1) badge = `<span class="badge" style="background:#dcfce7;color:#16a34a">✓ Öffentlich sichtbar</span>`;
else if (p.approved === -1) badge = `<span class="badge" style="background:#fee2e2;color:#dc2626">✗ Abgelehnt</span>`;
else if (p.submitted_at) badge = `<span class="badge" style="background:#fef9c3;color:#a16207">⏳ In Prüfung</span>`;
else if (p.exists) badge = `<span class="badge">Entwurf</span>`;
else badge = `<span class="badge">Noch nicht angelegt</span>`;
return `
<div class="card" style="padding:var(--space-4)">
<div style="display:flex;align-items:center;gap:var(--space-3)">
<div class="flex-1-min">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-1)">Öffentliches Profil</div>
${badge}
</div>
<button class="btn btn-sm btn-secondary" id="pd-edit-profile">
${UI.icon('pencil-simple')} Bearbeiten
</button>
</div>
</div>`;
}
function _bindEvents(el) {
// Einladungslink kopieren
el.querySelectorAll('.pd-copy').forEach(btn => {
btn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(btn.dataset.link);
UI.toast.success('Einladungslink kopiert.');
} catch {
UI.toast.info(btn.dataset.link);
}
});
});
// Einzel-Code-Status (lazy, .hidden via classList)
el.querySelectorAll('.pd-qr-codes-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const box = el.querySelector(`#pd-qr-codes-${btn.dataset.id}`);
if (!box) return;
box.classList.toggle('hidden');
if (box.classList.contains('hidden') || box.dataset.loaded === '1') return;
try {
const codes = await API.get(`/partner/my-qr/${btn.dataset.id}/codes`);
box.dataset.loaded = '1';
box.innerHTML = codes.map(c => {
const used = c.registrations > 0;
return `
<div style="display:flex;align-items:center;gap:var(--space-2);padding:3px 0;font-size:var(--text-xs);border-bottom:1px dashed var(--c-border)">
<span style="font-weight:700;min-width:34px">#${c.seq}</span>
<code class="flex-1-min" style="color:var(--c-text-muted)">banyaro.app/q/${UI.escape(c.token)}</code>
${used
? `<span class="badge" style="background:#dcfce7;color:#16a34a" title="Erste Registrierung am ${(c.first_registration_at || '').slice(0, 10)}">● genutzt${c.registrations > 1 ? ` (${c.registrations}×)` : ''}</span>`
: `<span class="badge" style="background:var(--c-surface-2);color:var(--c-text-muted)">○ frei</span>`}
</div>`;
}).join('');
} catch (err) { UI.toast.error(err.message); }
});
});
el.querySelector('#pd-edit-profile')?.addEventListener('click', () => App.navigate('partner-profil'));
}
return { init, refresh, onDogChange };
})();