banyaro/backend/static/js/pages/wurfboerse.js
rene c517c9281d Refactor: 1167 _esc() → UI.escape() in 36 Dateien, SW by-v1113
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.
2026-05-27 10:15:33 +02:00

276 lines
9.6 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 — 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 };
})();