Feature: Vollständige Züchter-Rolle — Antrag, Würfe, Stammbaum, Genetik
Basis-Features (Schritte 1–11): - Züchter-Antrag mit Dokument-Upload, Admin-Prüfung, E-Mail-Benachrichtigungen - Öffentliches Züchter-Profil + Karten-Marker (lila, certificate-Icon) - Wurfverwaltung: Würfe, Welpen, Gewichtsverlauf, Foto-System - Wurfbörse (öffentlich) mit Filtersuche nach Rasse/Status - Läufigkeits-Tracker: Deckdatum + Wurftermin (+63 Tage, nur für Züchter) - Interessenten-Chat: Kontakt-Button in Wurfbörse und Züchter-Profil - Sidebar-Einträge: Zuchtkartei + Wurfverwaltung für Züchter/Admin Stammbaum & Genetik (Schritte 1–8): - Zuchtkartei: Hunde-Stammdaten mit Vater/Mutter-Verknüpfung - Stammbaum-Visualisierung: 4 Generationen, horizontales CSS-Grid - Gesundheitstests (HD, ED, OCD, Augen…) mit farbigen Ergebnis-Badges - Genetische Tests (MDR1, PRA, DM…): clear/carrier/affected - Titel & Auszeichnungen (CAC, CACIB, IPO…) - Probeverpaarung: IK-Berechnung nach Wright + Ampel-Bewertung - Teilen-Link für öffentliche Hunde-Profile - Kaufvertrag: druckbares HTML-Dokument pro Welpe Technisch: 4 neue Route-Dateien, 5 neue Page-Module, 11 neue DB-Tabellen, icons shield-check + certificate + tree-structure im Sprite — SW by-v465, APP_VER 444
This commit is contained in:
parent
58cb2b4ad3
commit
91340be5a3
24 changed files with 6660 additions and 27 deletions
|
|
@ -194,6 +194,9 @@ window.Page_settings = (() => {
|
|||
padding:0 var(--space-4) var(--space-3);flex-wrap:wrap"></div>
|
||||
</div>
|
||||
|
||||
<!-- Züchter-Profil Slot -->
|
||||
<div id="breeder-card-slot"></div>
|
||||
|
||||
<div class="card" style="margin-bottom:var(--space-4)">
|
||||
<div style="padding:var(--space-3) var(--space-4);font-size:var(--text-xs);font-weight:600;
|
||||
color:var(--c-text-secondary);text-transform:uppercase;letter-spacing:0.05em;
|
||||
|
|
@ -679,6 +682,280 @@ window.Page_settings = (() => {
|
|||
});
|
||||
|
||||
_loadReferral();
|
||||
_loadBreederCard();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// ZÜCHTER-CARD — asynchron laden und in Slot rendern
|
||||
// ----------------------------------------------------------
|
||||
async function _loadBreederCard() {
|
||||
const slot = document.getElementById('breeder-card-slot');
|
||||
if (!slot) return;
|
||||
|
||||
let status = null;
|
||||
try {
|
||||
status = await API.breeder.status();
|
||||
} catch {
|
||||
// API nicht verfügbar — Card weglassen
|
||||
return;
|
||||
}
|
||||
|
||||
const { rolle, breeder_status, profile } = status;
|
||||
|
||||
const inputStyle = `width:100%;box-sizing:border-box;padding:var(--space-2) var(--space-3);
|
||||
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
||||
font-size:var(--text-sm);font-family:inherit;
|
||||
background:var(--c-surface);color:var(--c-text)`;
|
||||
|
||||
let statusBadge = '';
|
||||
let actionBlock = '';
|
||||
|
||||
if (rolle === 'breeder' || rolle === 'admin') {
|
||||
statusBadge = `<span class="badge badge-primary" style="background:var(--c-success);color:#fff">
|
||||
${UI.icon('check-circle')} ${rolle === 'admin' ? 'Admin — alle Züchter-Features verfügbar' : 'Verifizierter Züchter'}
|
||||
</span>`;
|
||||
actionBlock = `
|
||||
<div style="margin-top:var(--space-3);font-size:var(--text-sm);display:flex;flex-direction:column;gap:var(--space-1)">
|
||||
${profile?.zwingername ? `<div style="color:var(--c-text-secondary)">Zwinger: <strong>${_esc(profile.zwingername)}</strong></div>` : ''}
|
||||
${profile?.rasse_text ? `<div style="color:var(--c-text-secondary)">Rasse: <strong>${_esc(profile.rasse_text)}</strong></div>` : ''}
|
||||
</div>
|
||||
${rolle === 'breeder' && profile ? `
|
||||
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" style="margin-top:var(--space-3)">
|
||||
${UI.icon('pencil-simple')} Profil bearbeiten
|
||||
</button>` : ''}`;
|
||||
} else if (breeder_status === 'pending') {
|
||||
statusBadge = `<span class="badge" style="background:#f59e0b;color:#fff">
|
||||
${UI.icon('hourglass')} Antrag wird geprüft
|
||||
</span>`;
|
||||
} else if (breeder_status === 'rejected') {
|
||||
statusBadge = `<span class="badge" style="background:var(--c-danger);color:#fff">
|
||||
${UI.icon('x-circle')} Abgelehnt
|
||||
</span>`;
|
||||
actionBlock = `
|
||||
<div style="margin-top:var(--space-3)">
|
||||
<button class="btn btn-secondary btn-sm" id="breeder-reapply-btn">
|
||||
${UI.icon('arrow-counter-clockwise')} Neu beantragen
|
||||
</button>
|
||||
</div>`;
|
||||
} else {
|
||||
actionBlock = `
|
||||
<div style="margin-top:var(--space-3)">
|
||||
<button class="btn btn-primary btn-sm" id="breeder-apply-btn">
|
||||
${UI.icon('certificate')} Züchter werden
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
slot.innerHTML = `
|
||||
<div class="card" style="margin-bottom:var(--space-4)">
|
||||
<div style="padding:var(--space-3) var(--space-4);
|
||||
font-size:var(--text-xs);font-weight:600;
|
||||
color:var(--c-text-secondary);text-transform:uppercase;
|
||||
letter-spacing:0.05em;border-bottom:1px solid var(--c-border)">
|
||||
Züchter-Profil
|
||||
</div>
|
||||
<div style="padding:var(--space-4)">
|
||||
${statusBadge}
|
||||
${actionBlock}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Button-Handler binden
|
||||
const applyBtn = slot.querySelector('#breeder-apply-btn');
|
||||
const reapplyBtn = slot.querySelector('#breeder-reapply-btn');
|
||||
if (applyBtn || reapplyBtn) {
|
||||
(applyBtn || reapplyBtn).addEventListener('click', () => _openBreederApplyModal());
|
||||
}
|
||||
|
||||
slot.querySelector('#breeder-edit-profile-btn')?.addEventListener('click', () =>
|
||||
_openBreederEditModal(profile)
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// ZÜCHTER-PROFIL BEARBEITEN MODAL
|
||||
// ----------------------------------------------------------
|
||||
function _openBreederEditModal(profile) {
|
||||
const inputStyle = `width:100%;box-sizing:border-box;padding:var(--space-2) var(--space-3);
|
||||
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
||||
font-size:var(--text-sm);font-family:inherit;
|
||||
background:var(--c-surface);color:var(--c-text)`;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('pencil-simple')} Züchter-Profil bearbeiten`,
|
||||
body: `
|
||||
<form id="breeder-edit-form" style="display:flex;flex-direction:column;gap:var(--space-4)">
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Zwingername</label>
|
||||
<input name="zwingername" type="text" maxlength="100" style="${inputStyle}"
|
||||
value="${_esc(profile?.zwingername || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Rasse</label>
|
||||
<input name="rasse_text" type="text" maxlength="100" style="${inputStyle}"
|
||||
value="${_esc(profile?.rasse_text || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Zuchtverein</label>
|
||||
<input name="verein" type="text" maxlength="100" style="${inputStyle}"
|
||||
value="${_esc(profile?.verein || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Stadt</label>
|
||||
<input name="stadt" type="text" maxlength="80" style="${inputStyle}"
|
||||
value="${_esc(profile?.stadt || '')}">
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-3)">
|
||||
<input name="vdh_mitglied" type="checkbox" id="edit-breeder-vdh"
|
||||
style="width:18px;height:18px;cursor:pointer;flex-shrink:0"
|
||||
${profile?.vdh_mitglied ? 'checked' : ''}>
|
||||
<label for="edit-breeder-vdh" style="font-size:var(--text-sm);cursor:pointer">VDH-Mitglied</label>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Website (optional)</label>
|
||||
<input name="website" type="url" maxlength="200" style="${inputStyle}"
|
||||
value="${_esc(profile?.website || '')}" placeholder="https://mein-zwinger.de">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Beschreibung (optional)</label>
|
||||
<textarea name="beschreibung" maxlength="500" rows="3"
|
||||
style="${inputStyle};resize:vertical">${_esc(profile?.beschreibung || '')}</textarea>
|
||||
</div>
|
||||
</form>`,
|
||||
footer: `
|
||||
<div style="display:flex;gap:var(--space-2);width:100%">
|
||||
<button type="submit" form="breeder-edit-form" class="btn btn-primary flex-1" id="breeder-edit-submit">Speichern</button>
|
||||
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
|
||||
</div>`,
|
||||
});
|
||||
|
||||
document.getElementById('breeder-edit-form')?.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('breeder-edit-submit');
|
||||
await UI.asyncButton(btn, async () => {
|
||||
const form = e.target;
|
||||
const data = {
|
||||
zwingername: form.zwingername.value.trim() || undefined,
|
||||
rasse_text: form.rasse_text.value.trim() || undefined,
|
||||
verein: form.verein.value.trim() || undefined,
|
||||
stadt: form.stadt.value.trim() || undefined,
|
||||
vdh_mitglied: form.vdh_mitglied.checked ? 1 : 0,
|
||||
website: form.website.value.trim() || undefined,
|
||||
beschreibung: form.beschreibung.value.trim() || undefined,
|
||||
};
|
||||
await API.breeder.updateProfile(data);
|
||||
UI.modal.close?.();
|
||||
UI.toast.success('Profil aktualisiert.');
|
||||
_loadBreederCard();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// ZÜCHTER-ANTRAG MODAL
|
||||
// ----------------------------------------------------------
|
||||
function _openBreederApplyModal() {
|
||||
const inputStyle = `width:100%;box-sizing:border-box;padding:var(--space-2) var(--space-3);
|
||||
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
||||
font-size:var(--text-sm);font-family:inherit;
|
||||
background:var(--c-surface);color:var(--c-text)`;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('certificate')} Züchter-Antrag stellen`,
|
||||
body: `
|
||||
<form id="breeder-apply-form" style="display:flex;flex-direction:column;gap:var(--space-4)">
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
|
||||
Zwingername <span style="color:var(--c-danger)">*</span>
|
||||
</label>
|
||||
<input name="zwingername" type="text" maxlength="100" required
|
||||
placeholder="z. B. vom Sonnenfeld"
|
||||
style="${inputStyle}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
|
||||
Rasse <span style="color:var(--c-danger)">*</span>
|
||||
</label>
|
||||
<input name="rasse_text" type="text" maxlength="100" required
|
||||
placeholder="z. B. Labrador Retriever"
|
||||
style="${inputStyle}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
|
||||
Zuchtverein <span style="color:var(--c-danger)">*</span>
|
||||
</label>
|
||||
<input name="verein" type="text" maxlength="100" required
|
||||
placeholder="z. B. DLRG, VDH, BCD"
|
||||
style="${inputStyle}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
|
||||
Stadt <span style="color:var(--c-danger)">*</span>
|
||||
</label>
|
||||
<input name="stadt" type="text" maxlength="80" required
|
||||
placeholder="z. B. München"
|
||||
style="${inputStyle}">
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-3)">
|
||||
<input name="vdh_mitglied" type="checkbox" id="breeder-vdh"
|
||||
style="width:18px;height:18px;cursor:pointer;flex-shrink:0">
|
||||
<label for="breeder-vdh" style="font-size:var(--text-sm);cursor:pointer">
|
||||
VDH-Mitglied
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
|
||||
Website (optional)
|
||||
</label>
|
||||
<input name="website" type="url" maxlength="200"
|
||||
placeholder="https://mein-zwinger.de"
|
||||
style="${inputStyle}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
|
||||
Beschreibung (optional)
|
||||
</label>
|
||||
<textarea name="beschreibung" maxlength="500" rows="3"
|
||||
placeholder="Kurze Beschreibung deines Zwingers"
|
||||
style="${inputStyle};resize:vertical"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
|
||||
Dokument hochladen <span style="color:var(--c-danger)">*</span>
|
||||
</label>
|
||||
<input name="dokument" type="file" id="breeder-doc-input" required
|
||||
accept=".pdf,.jpg,.jpeg,.png,.webp"
|
||||
style="font-size:var(--text-sm);width:100%;box-sizing:border-box">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-1)">
|
||||
Zuchtbuch-Eintrag, Vereinsmitgliedschaft o.ä. (PDF, JPG, PNG, WebP)
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
footer: `
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
|
||||
<button type="submit" form="breeder-apply-form" class="btn btn-primary" id="breeder-apply-submit"
|
||||
style="width:100%">Antrag einreichen</button>
|
||||
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
document.getElementById('breeder-apply-form')?.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('breeder-apply-submit');
|
||||
await UI.asyncButton(btn, async () => {
|
||||
const form = e.target;
|
||||
const fd = new FormData(form);
|
||||
// Checkbox-Wert normalisieren
|
||||
fd.set('vdh_mitglied', form.querySelector('[name="vdh_mitglied"]').checked ? '1' : '0');
|
||||
await API.breeder.apply(fd);
|
||||
UI.modal.close?.();
|
||||
UI.toast.success('Antrag eingereicht. Du wirst benachrichtigt sobald er geprüft wurde.');
|
||||
// Card neu laden
|
||||
_loadBreederCard();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue