/* ============================================================ BAN YARO — Adoption (Tierheim-Hunde in der Nähe) Seiten-Modul: Hunde aus deutschen Tierheimen finden. ============================================================ */ window.Page_adoption = (() => { // ---------------------------------------------------------- // MODUL-STATE // ---------------------------------------------------------- let _container = null; let _appState = null; let _lat = null; let _lon = null; let _radius = 50; let _rasseFilter = ''; let _activeTab = 'hunde'; let _data = null; // { animals, shelters, has_petfinder } let _loading = false; let _communityData = null; // [] listings from /adoption/community let _myListings = null; // [] eigene Inserate // ---------------------------------------------------------- // INIT // ---------------------------------------------------------- async function init(container, appState) { _container = container; _appState = appState; _render(); // Standort automatisch versuchen _tryAutoLocate(); } // ---------------------------------------------------------- // REFRESH // ---------------------------------------------------------- async function refresh() { if (_lat && _lon) { await _loadData(); } } // ---------------------------------------------------------- // RENDER — Grundstruktur // ---------------------------------------------------------- function _render() { _container.innerHTML = `
${UI.skeleton(4)}
`; // Events _container.querySelector('#adp-radius') ?.addEventListener('change', e => { _radius = parseInt(e.target.value); if (_lat && _lon) _loadData(); }); _container.querySelector('#adp-rasse') ?.addEventListener('input', e => { _rasseFilter = e.target.value.trim().toLowerCase(); _renderContent(); }); _container.querySelector('#adp-btn-locate') ?.addEventListener('click', _locateUser); _container.querySelector('#adp-btn-geocode') ?.addEventListener('click', _geocodePLZ); _container.querySelector('#adp-plz') ?.addEventListener('keydown', e => { if (e.key === 'Enter') _geocodePLZ(); }); _container.querySelectorAll('.adp-tab').forEach(btn => { btn.addEventListener('click', () => { _activeTab = btn.dataset.tab; _container.querySelectorAll('.adp-tab').forEach(b => { const isActive = b.dataset.tab === _activeTab; b.style.color = isActive ? 'var(--c-primary)' : 'var(--c-text-secondary)'; b.style.fontWeight = isActive ? '600' : 'normal'; b.style.borderBottom = isActive ? '2px solid var(--c-primary)' : '2px solid transparent'; }); _renderContent(); }); }); } // ---------------------------------------------------------- // STANDORT AUTOMATISCH ERMITTELN // ---------------------------------------------------------- async function _tryAutoLocate() { try { const pos = await API.getLocation({ timeout: 6000, maximumAge: 300_000 }); _lat = pos.lat; _lon = pos.lon; await _loadData(); } catch { // Standort nicht verfügbar → PLZ-Eingabe zeigen document.getElementById('adp-plz-row')?.style.setProperty('display', 'flex', 'important'); document.getElementById('adp-plz-row').style.display = 'flex'; _showNoLocation(); } } async function _locateUser() { const btn = _container.querySelector('#adp-btn-locate'); if (btn) btn.disabled = true; try { const pos = await API.getLocation({ timeout: 10000 }); _lat = pos.lat; _lon = pos.lon; document.getElementById('adp-plz-row').style.display = 'none'; await _loadData(); } catch { UI.toast.error('Standort konnte nicht ermittelt werden. Bitte PLZ eingeben.'); document.getElementById('adp-plz-row').style.display = 'flex'; } finally { if (btn) btn.disabled = false; } } async function _geocodePLZ() { const plz = (_container.querySelector('#adp-plz')?.value || '').trim(); if (!plz) return; const btn = _container.querySelector('#adp-btn-geocode'); if (btn) btn.disabled = true; try { const geo = await API.get(`/adoption/geocode?plz=${encodeURIComponent(plz)}`); if (geo.lat && geo.lon) { _lat = geo.lat; _lon = geo.lon; await _loadData(); } else { UI.toast.error(`PLZ "${plz}" nicht gefunden.`); } } catch { UI.toast.error('Geocoding fehlgeschlagen. Bitte erneut versuchen.'); } finally { if (btn) btn.disabled = false; } } // ---------------------------------------------------------- // DATEN LADEN // ---------------------------------------------------------- async function _loadData() { if (_loading || !_lat || !_lon) return; _loading = true; const content = _container.querySelector('#adp-content'); if (content) content.innerHTML = UI.skeleton(4); try { _data = await API.get(`/adoption/nearby?lat=${_lat}&lon=${_lon}&radius=${_radius}`); _renderContent(); } catch { if (content) content.innerHTML = UI.emptyState({ icon: 'warning', title: 'Daten konnten nicht geladen werden', text: 'Bitte versuche es erneut.', }); } finally { _loading = false; } } async function _loadCommunity() { const content = _container.querySelector('#adp-content'); if (content) content.innerHTML = UI.skeleton(4); try { const url = _lat && _lon ? `/adoption/community?lat=${_lat}&lon=${_lon}` : '/adoption/community'; _communityData = await API.get(url); if (_appState?.user) { try { _myListings = await API.get('/adoption/community/my'); } catch { _myListings = []; } } _renderCommunity(content); } catch { if (content) content.innerHTML = UI.emptyState({ icon: 'warning', title: 'Weitervermittlungs-Inserate konnten nicht geladen werden', text: 'Bitte versuche es erneut.', }); } } // ---------------------------------------------------------- // INHALT RENDERN (je nach Tab) // ---------------------------------------------------------- function _renderContent() { const content = _container.querySelector('#adp-content'); if (!content) return; if (_activeTab === 'community') { _loadCommunity(); return; } if (!_data) { _showNoLocation(); return; } if (_activeTab === 'hunde') _renderHunde(content); else _renderTierheime(content); } function _showNoLocation() { const content = _container.querySelector('#adp-content'); if (!content) return; content.innerHTML = `
🐾

Finde Hunde in deiner Nähe

Erlaube den Zugriff auf deinen Standort oder gib eine PLZ ein, um Tierheim-Hunde in deiner Umgebung zu finden.

${UI.icon('arrow-square-out')} Alle Hunde auf Tierheimhelden.de
`; } // ------------------------------------------------------------------ // TAB: HUNDE // ------------------------------------------------------------------ function _renderHunde(content) { let animals = (_data?.animals || []); // Rasse-Filter if (_rasseFilter) { animals = animals.filter(a => (a.rasse || '').toLowerCase().includes(_rasseFilter) || (a.name || '').toLowerCase().includes(_rasseFilter) ); } const hasPetFinder = _data?.has_petfinder; const infoText = hasPetFinder ? `${animals.length} Hunde im Umkreis von ${_radius} km (via PetFinder)` : ''; if (!animals.length) { content.innerHTML = `

${_rasseFilter ? `Keine Hunde gefunden für "${_esc(_rasseFilter)}"` : `Keine Hunde im Umkreis von ${_radius} km gefunden.`}

${UI.icon('arrow-square-out')} Hunde auf Tierheimhelden.de suchen ${UI.icon('magnifying-glass')} Tierheimsuche auf tierschutz.com

Tipp: Schau auch im Tab „Tierheime" nach lokalen Tierheimen direkt.

`; return; } content.innerHTML = ` ${infoText ? `

${infoText}

` : ''}
${animals.map(a => _animalCard(a)).join('')}

Mehr Hunde finden:

${UI.icon('arrow-square-out')} Tierheimhelden.de — alle Hunde
`; // Klick-Events content.querySelectorAll('[data-adp-url]').forEach(card => { card.addEventListener('click', () => { window.open(card.dataset.adpUrl, '_blank', 'noopener,noreferrer'); }); }); } function _animalCard(a) { const foto = a.foto_url ? `${_esc(a.name)}` : '
🐶
'; const distTxt = a.distanz_km != null ? `${a.distanz_km} km` : ''; const alterTxt = a.alter_jahre != null ? `${_formatAlter(a.alter_jahre)}` : ''; const rasseTxt = a.rasse || ''; const tierheim = a.tierheim || ''; return `
${foto}
${_esc(a.name)}
${rasseTxt ? `
${_esc(rasseTxt)}
` : ''}
${alterTxt ? ` ${_esc(alterTxt)} ` : ''} ${a.geschlecht ? ` ${a.geschlecht === 'männlich' ? '♂' : '♀'} ` : ''} ${distTxt ? ` ${_esc(distTxt)} ` : ''}
${tierheim ? `
${UI.icon('house-line')} ${_esc(tierheim)}
` : ''}
`; } // ------------------------------------------------------------------ // TAB: TIERHEIME // ------------------------------------------------------------------ function _renderTierheime(content) { const shelters = _data?.shelters || []; if (!shelters.length) { content.innerHTML = `

Keine Tierheime im Umkreis von ${_radius} km gefunden.

${UI.icon('arrow-square-out')} Tierheimhelden.de
`; return; } content.innerHTML = `

${shelters.length} Tierheim${shelters.length !== 1 ? 'e' : ''} im Umkreis von ${_radius} km

${shelters.map(s => _shelterRow(s)).join('')}

Noch mehr Tierheime:

${UI.icon('arrow-square-out')} Tierheimhelden.de ${UI.icon('magnifying-glass')} tierschutz.com
`; } function _shelterRow(s) { return `
🏠
${_esc(s.name)}
${_esc(s.plz)} ${_esc(s.stadt)}
${s.distanz_km} km Hunde ansehen ${UI.icon('arrow-right')}
`; } // ------------------------------------------------------------------ // TAB: WEITERVERMITTLUNG (Community) // ------------------------------------------------------------------ function _renderCommunity(content) { if (!content) return; const listings = _communityData || []; const isLoggedIn = !!_appState?.user; const fabHtml = isLoggedIn ? ` ` : ''; if (!listings.length) { content.innerHTML = `
🐾

Noch keine Hunde zur Weitervermittlung

Hier können Halter Hunde privat zur Weitervermittlung anbieten — zum Beispiel bei Umzug, Krankheit oder Allergie.

${isLoggedIn ? ` ` : `

Bitte anmelden, um ein Inserat zu erstellen.

`}
${fabHtml} `; content.querySelector('#adp-empty-create')?.addEventListener('click', _openCreateModal); content.querySelector('#adp-fab-create')?.addEventListener('click', _openCreateModal); return; } // Eigene Inserate trennen const myIds = new Set((_myListings || []).map(l => l.id)); content.innerHTML = `

${listings.length} Inserat${listings.length !== 1 ? 'e' : ''} zur Weitervermittlung

${listings.map(l => _communityCard(l)).join('')}
${isLoggedIn && _myListings && _myListings.length ? `

Meine Inserate

${_myListings.map(l => _myListingRow(l)).join('')}
` : ''} ${fabHtml} `; // Interest-Button Events content.querySelectorAll('[data-adp-interest]').forEach(btn => { btn.addEventListener('click', () => { const id = btn.dataset.adpInterest; const interested = btn.dataset.adpInterested === 'true'; _handleInterest(id, interested, btn); }); }); // FAB content.querySelector('#adp-fab-create')?.addEventListener('click', _openCreateModal); // Meine Inserate: Status-Dropdown + Löschen content.querySelectorAll('[data-adp-status-change]').forEach(sel => { sel.addEventListener('change', async () => { const id = sel.dataset.adpStatusChange; try { await API.patch(`/adoption/community/${id}`, { status: sel.value }); UI.toast.success('Status aktualisiert.'); _loadCommunity(); } catch { UI.toast.error('Status konnte nicht aktualisiert werden.'); } }); }); content.querySelectorAll('[data-adp-delete]').forEach(btn => { btn.addEventListener('click', async () => { if (!window.confirm('Inserat wirklich löschen?')) return; try { await API.del(`/adoption/community/${btn.dataset.adpDelete}`); UI.toast.success('Inserat gelöscht.'); _communityData = null; _myListings = null; _loadCommunity(); } catch { UI.toast.error('Löschen fehlgeschlagen.'); } }); }); } function _communityCard(l) { const foto = l.foto_url ? `${_esc(l.name)}` : '
🐾
'; const isActive = !l.status || l.status === 'active'; const statusLabel = l.status === 'reserved' ? 'Reserviert' : l.status === 'adopted' ? 'Vermittelt' : ''; const alterLabel = l.alter_kategorie === 'welpe' ? 'Welpe <6Mo' : l.alter_kategorie === 'jung' ? 'Jung 6Mo–2J' : l.alter_kategorie === 'adult' ? 'Adult 2–8J' : l.alter_kategorie === 'senior' ? 'Senior >8J' : ''; const genderIcon = l.geschlecht === 'maennlich' ? '♂' : l.geschlecht === 'weiblich' ? '♀' : ''; const distTxt = l.distanz_km != null ? `${l.distanz_km} km` : ''; const ort = [l.plz, l.ort].filter(Boolean).join(' '); const interestBtn = l.user_interested ? `` : ``; return `
${foto} ${!isActive ? `
${_esc(statusLabel)}
` : ''}
${_esc(l.name)}
${l.rasse ? `
${_esc(l.rasse)}
` : ''}
${alterLabel ? ` ${_esc(alterLabel)} ` : ''} ${genderIcon ? ` ${genderIcon} ` : ''} ${distTxt ? ` ${_esc(distTxt)} ` : ''}
${ort ? `
${_esc(ort)}
` : ''} ${l.beschreibung ? `
${_esc(l.beschreibung)}
` : ''} ${l.interesse_count ? `
❤️ ${l.interesse_count} Interessent${l.interesse_count !== 1 ? 'en' : ''}
` : ''}
${interestBtn}
`; } function _myListingRow(l) { const statusOptions = [ { value: 'active', label: 'Aktiv' }, { value: 'reserved', label: 'Reserviert' }, { value: 'adopted', label: 'Vermittelt' }, ]; return `
${_esc(l.name)}
${l.interesse_count || 0} Interessent${(l.interesse_count || 0) !== 1 ? 'en' : ''}
`; } // ------------------------------------------------------------------ // INTERESSE BEKUNDEN / ZURÜCKZIEHEN // ------------------------------------------------------------------ async function _handleInterest(id, isInterested, btn) { if (!_appState?.user) { UI.toast.error('Bitte anmelden um Interesse zu bekunden.'); return; } if (isInterested) { // Interesse zurückziehen try { btn.disabled = true; await API.del(`/adoption/community/${id}/interest`); UI.toast.success('Interesse zurückgezogen.'); _communityData = null; _myListings = null; _loadCommunity(); } catch { UI.toast.error('Fehler beim Zurückziehen des Interesses.'); btn.disabled = false; } return; } // Interesse bekunden — Modal mit optionaler Nachricht const body = `

Du kannst optional eine Nachricht an den Anbieter schicken.

`; const footer = `
`; UI.modal.open({ title: 'Interesse bekunden', body, footer }); document.getElementById('adp-interest-form')?.addEventListener('submit', async e => { e.preventDefault(); const submitBtn = document.getElementById('adp-interest-submit'); const fd = new FormData(e.target); const payload = { nachricht: fd.get('nachricht') || null }; if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = '…'; } try { await API.post(`/adoption/community/${id}/interest`, payload); UI.modal.close(); UI.toast.success('Interesse gemeldet!'); _communityData = null; _myListings = null; _loadCommunity(); } catch { UI.toast.error('Fehler beim Melden des Interesses.'); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = `${UI.icon('heart')} Interesse bekunden`; } } }); } // ------------------------------------------------------------------ // INSERAT ERSTELLEN — Modal // ------------------------------------------------------------------ function _openCreateModal() { if (!_appState?.user) { UI.toast.error('Bitte anmelden um ein Inserat zu erstellen.'); return; } const body = `
Mindestens 80 Zeichen
`; const footer = `
`; UI.modal.open({ title: 'Hund zur Vermittlung anbieten', body, footer }); document.getElementById('adp-create-form')?.addEventListener('submit', async e => { e.preventDefault(); const submitBtn = document.getElementById('adp-create-submit'); const fd = new FormData(e.target); // Mindestlänge Beschreibung manuell prüfen (minlength gilt nur für text) const beschreibung = (fd.get('beschreibung') || '').trim(); if (beschreibung.length < 80) { UI.toast.error('Beschreibung muss mindestens 80 Zeichen lang sein.'); return; } // FormData für multipart aufbauen const postData = new FormData(); postData.append('name', fd.get('name') || ''); postData.append('rasse', fd.get('rasse') || ''); postData.append('alter_kategorie', fd.get('alter_kategorie') || ''); postData.append('geschlecht', fd.get('geschlecht') || ''); postData.append('plz', fd.get('plz') || ''); postData.append('ort', fd.get('ort') || ''); postData.append('beschreibung', beschreibung); postData.append('hintergrund', fd.get('hintergrund') || ''); if (_lat) postData.append('lat', _lat); if (_lon) postData.append('lon', _lon); const fotoFile = document.getElementById('adp-create-foto')?.files?.[0]; if (fotoFile) postData.append('foto', fotoFile); if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = '…'; } try { await API.upload('/adoption/community', postData); UI.modal.close(); UI.toast.success('Inserat erstellt!'); _communityData = null; _myListings = null; _loadCommunity(); } catch (err) { UI.toast.error(err.message || 'Inserat konnte nicht erstellt werden.'); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = `${UI.icon('plus')} Inserat erstellen`; } } }); } // ---------------------------------------------------------- // HILFSFUNKTIONEN // ---------------------------------------------------------- function _formatAlter(jahre) { if (jahre < 0.5) return 'Welpe'; if (jahre < 1) return 'Jungtier'; if (jahre < 2) return `${Math.round(jahre)} Jahr`; if (jahre < 10) return `${Math.round(jahre)} Jahre`; return 'Senior'; } function _esc(s) { if (s == null) return ''; return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } // ---------------------------------------------------------- // PUBLIC API // ---------------------------------------------------------- return { init, refresh }; })();