Bündel 1 aus dem Duplikat-Audit: existierende zentrale Helper nutzen
statt lokale Duplikate.
Pure Migration ohne neuen Code:
- 1167 _esc()-Aufrufe in 36 Page-Modulen migriert auf UI.escape()
- 24 lokale _esc/_escape-Definitionen entfernt
- lost.js hatte _escape() (Variante) — 17 Aufrufe ebenfalls migriert
- jobs.js + breeder.js: tote Alias-Wrapper entfernt
UI.escape() existierte schon — wurde nur überall lokal nochmal
implementiert. Funktional identisch (gleiche 4-replace-chain für
& < > ").
Tests 19/19 grün. Frontend-LOC um ~120 Zeilen reduziert.
Hinweis: _emptyState (7 Stellen) und _icon (8 Stellen) wurden NICHT
migriert — sie haben abweichende Signaturen von UI.emptyState({...})
bzw. UI.icon(name). Eigener Sprint nötig.
276 lines
9.6 KiB
JavaScript
276 lines
9.6 KiB
JavaScript
/* ============================================================
|
||
BAN YARO — Wurfbörse
|
||
Öffentliche Wurfankündigungen aller Züchter
|
||
============================================================ */
|
||
|
||
window.Page_wurfboerse = (() => {
|
||
|
||
let _container = null;
|
||
let _appState = null;
|
||
let _data = [];
|
||
|
||
// ----------------------------------------------------------
|
||
// Hilfsfunktionen
|
||
// ----------------------------------------------------------
|
||
|
||
function _fmtDate(iso) {
|
||
if (!iso) return '—';
|
||
const d = new Date(iso + 'T12:00:00');
|
||
return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||
}
|
||
|
||
function _statusBadge(status) {
|
||
const map = {
|
||
geplant: { label: 'Geplant', cls: 'wb-badge--geplant' },
|
||
geboren: { label: 'Geboren', cls: 'wb-badge--geboren' },
|
||
verfuegbar: { label: 'Verfügbar', cls: 'wb-badge--verfuegbar' },
|
||
abgeschlossen: { label: 'Abgeschlossen', cls: 'wb-badge--abgeschlossen' },
|
||
};
|
||
const s = map[status] || { label: status, cls: 'wb-badge--geplant' };
|
||
return `<span class="wb-badge ${s.cls}">${s.label}</span>`;
|
||
}
|
||
|
||
function _truncate(text, max) {
|
||
if (!text) return '';
|
||
return text.length > max ? text.slice(0, max).trimEnd() + '…' : text;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// INIT
|
||
// ----------------------------------------------------------
|
||
async function init(container, appState) {
|
||
_container = container;
|
||
_appState = appState;
|
||
_render();
|
||
await _loadData();
|
||
}
|
||
|
||
function refresh() { _loadData(); }
|
||
function onDogChange() {}
|
||
|
||
// ----------------------------------------------------------
|
||
// Grundstruktur rendern
|
||
// ----------------------------------------------------------
|
||
function _render() {
|
||
_container.innerHTML = `
|
||
<div class="wb-layout">
|
||
|
||
<!-- Filter-Leiste -->
|
||
<div class="wb-filter-bar">
|
||
<div class="wb-filter-fields">
|
||
<input
|
||
class="form-control wb-filter-rasse"
|
||
id="wb-filter-rasse"
|
||
type="text"
|
||
placeholder="Rasse suchen…"
|
||
autocomplete="off"
|
||
>
|
||
<select class="form-control wb-filter-status" id="wb-filter-status">
|
||
<option value="">Alle Status</option>
|
||
<option value="geplant">Geplant</option>
|
||
<option value="verfuegbar">Verfügbar</option>
|
||
<option value="geboren">Geboren</option>
|
||
</select>
|
||
</div>
|
||
<button class="btn btn-primary wb-filter-btn" id="wb-search-btn">
|
||
${UI.icon('magnifying-glass')} Suchen
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Ergebnisliste -->
|
||
<div id="wb-list">
|
||
<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt…</p>
|
||
</div>
|
||
|
||
</div>
|
||
`;
|
||
|
||
// Suchen-Button
|
||
document.getElementById('wb-search-btn').addEventListener('click', () => _loadData());
|
||
|
||
// Enter im Rasse-Feld
|
||
document.getElementById('wb-filter-rasse').addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') _loadData();
|
||
});
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Daten laden
|
||
// ----------------------------------------------------------
|
||
async function _loadData() {
|
||
const rasseEl = document.getElementById('wb-filter-rasse');
|
||
const statusEl = document.getElementById('wb-filter-status');
|
||
|
||
const params = {};
|
||
if (rasseEl?.value.trim()) params.rasse = rasseEl.value.trim();
|
||
if (statusEl?.value) params.status = statusEl.value;
|
||
|
||
try {
|
||
_data = await API.litters.public(params);
|
||
_renderList();
|
||
} catch (err) {
|
||
UI.toast.error(err.message || 'Fehler beim Laden der Wurfbörse.');
|
||
_renderEmpty('Fehler beim Laden', 'Bitte später erneut versuchen.');
|
||
}
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Liste rendern
|
||
// ----------------------------------------------------------
|
||
function _renderList() {
|
||
const el = document.getElementById('wb-list');
|
||
if (!el) return;
|
||
|
||
if (!_data.length) {
|
||
_renderEmpty('Keine Würfe gefunden', 'Für die gewählten Filter gibt es aktuell keine Wurfankündigungen.');
|
||
return;
|
||
}
|
||
|
||
el.innerHTML = `<div class="wb-cards">${_data.map(b => _cardHTML(b)).join('')}</div>`;
|
||
|
||
el.querySelectorAll('.wb-profile-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const zwingername = btn.dataset.zwingername;
|
||
App.navigate('breeder', true, { zwingername });
|
||
});
|
||
});
|
||
|
||
el.querySelectorAll('.wb-chat-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const breederId = parseInt(btn.dataset.breederUserId, 10);
|
||
_contactBreeder(breederId);
|
||
});
|
||
});
|
||
|
||
el.querySelectorAll('.wb-login-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => App.navigate('settings'));
|
||
});
|
||
}
|
||
|
||
function _renderEmpty(title, text) {
|
||
const el = document.getElementById('wb-list');
|
||
if (!el) return;
|
||
el.innerHTML = `
|
||
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
|
||
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('dog')}</div>
|
||
<h3 style="margin:0 0 var(--space-2)">${UI.escape(title)}</h3>
|
||
<p style="color:var(--c-text-secondary);margin:0">${UI.escape(text)}</p>
|
||
</div>`;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Card HTML
|
||
// ----------------------------------------------------------
|
||
function _cardHTML(b) {
|
||
// Züchter-Kopfzeile
|
||
const zuechterName = b.zuechter_name || b.zwingername || '—';
|
||
const zwingername = b.zwingername ? ` (${UI.escape(b.zwingername)})` : '';
|
||
const stadtLine = b.stadt ? ` · ${UI.escape(b.stadt)}` : '';
|
||
|
||
// Elterntiere
|
||
const elternParts = [];
|
||
if (b.vater_name) elternParts.push(UI.escape(b.vater_name));
|
||
if (b.mutter_name) elternParts.push(UI.escape(b.mutter_name));
|
||
const elternLine = elternParts.length === 2
|
||
? `<div class="wb-card-eltern">${UI.icon('gender-male')} ${elternParts[0]} × ${UI.icon('gender-female')} ${elternParts[1]}</div>`
|
||
: elternParts.length === 1
|
||
? `<div class="wb-card-eltern">${elternParts[0]}</div>`
|
||
: '';
|
||
|
||
// Datum
|
||
let datumLine = '';
|
||
if (b.geburt_datum) {
|
||
datumLine = `<div class="wb-card-datum">${UI.icon('calendar-dots')} Geboren: ${_fmtDate(b.geburt_datum)}</div>`;
|
||
} else if (b.erwartetes_datum) {
|
||
datumLine = `<div class="wb-card-datum">${UI.icon('calendar-dots')} Erwartet: ${_fmtDate(b.erwartetes_datum)}</div>`;
|
||
}
|
||
|
||
// Welpen-Verfügbarkeit
|
||
let welpenLine = '';
|
||
if (b.welpen_gesamt != null || b.welpen_verfuegbar != null) {
|
||
const gesamt = b.welpen_gesamt != null ? b.welpen_gesamt : '?';
|
||
const verfuegb = b.welpen_verfuegbar != null ? b.welpen_verfuegbar : '?';
|
||
welpenLine = `<div class="wb-card-welpen">${UI.icon('paw-print')} Welpen verfügbar: ${UI.escape(String(verfuegb))} von ${UI.escape(String(gesamt))}</div>`;
|
||
}
|
||
|
||
// Preis
|
||
const preisLine = b.preis_spanne
|
||
? `<div class="wb-card-preis">${UI.icon('currency-eur')} Preis: ${UI.escape(b.preis_spanne)} €</div>`
|
||
: '';
|
||
|
||
// Gesundheitstests
|
||
const gesundheitLine = b.gesundheitstests
|
||
? `<div class="wb-card-gesundheit">${UI.icon('heart')} ${UI.escape(b.gesundheitstests)}</div>`
|
||
: '';
|
||
|
||
// Beschreibung (max. 150 Zeichen)
|
||
const beschreibungLine = b.beschreibung
|
||
? `<div class="wb-card-beschreibung">${UI.escape(_truncate(b.beschreibung, 150))}</div>`
|
||
: '';
|
||
|
||
return `
|
||
<div class="wb-card">
|
||
<div class="wb-card-header">
|
||
<div class="wb-card-zuechter">
|
||
${UI.escape(zuechterName)}${zwingername}${stadtLine}
|
||
</div>
|
||
${_statusBadge(b.status)}
|
||
</div>
|
||
|
||
${b.rasse_text ? `<div class="wb-card-rasse">${UI.icon('dog')} ${UI.escape(b.rasse_text)}</div>` : ''}
|
||
|
||
<div class="wb-card-details">
|
||
${elternLine}
|
||
${datumLine}
|
||
${welpenLine}
|
||
${preisLine}
|
||
${gesundheitLine}
|
||
${beschreibungLine}
|
||
</div>
|
||
|
||
<div class="wb-card-footer">
|
||
<button
|
||
class="btn btn-secondary btn-sm wb-profile-btn"
|
||
data-zwingername="${UI.escape(b.zwingername || '')}"
|
||
>
|
||
${UI.icon('user')} Profil ansehen
|
||
</button>
|
||
${(() => {
|
||
const isLoggedIn = !!_appState?.user;
|
||
const isOwnProfile = _appState?.user?.id === b.breeder_user_id;
|
||
if (isOwnProfile) return '';
|
||
if (isLoggedIn) {
|
||
return `<button
|
||
class="btn btn-primary btn-sm wb-chat-btn"
|
||
data-breeder-user-id="${b.breeder_user_id || ''}"
|
||
>
|
||
${UI.icon('chat-circle')} Nachricht senden
|
||
</button>`;
|
||
}
|
||
return `<button class="btn btn-primary btn-sm wb-login-btn">
|
||
${UI.icon('sign-in')} Anmelden um zu schreiben
|
||
</button>`;
|
||
})()}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Züchter per Chat kontaktieren
|
||
// ----------------------------------------------------------
|
||
async function _contactBreeder(breederId) {
|
||
if (!_appState?.user) {
|
||
App.navigate('settings');
|
||
return;
|
||
}
|
||
try {
|
||
const conv = await API.chat.start(breederId);
|
||
App.navigate('chat');
|
||
} catch (e) {
|
||
UI.toast.error(e.message || 'Chat konnte nicht geöffnet werden.');
|
||
}
|
||
}
|
||
|
||
return { init, refresh, onDogChange };
|
||
|
||
})();
|