/* ============================================================
BAN YARO — Hunde-Profil
Seiten-Modul: Profil anlegen / anzeigen / bearbeiten.
============================================================ */
window.Page_dog_profile = (() => {
let _container = null;
let _appState = null;
// ----------------------------------------------------------
// INIT / REFRESH / LIFECYCLE
// ----------------------------------------------------------
async function init(container, appState) {
_container = container;
_appState = appState;
// Event-Delegation auf dem persistenten Container — überlebt innerHTML-Ersatz
_container.addEventListener('click', e => {
if (e.target.closest('#dp-add-dog-btn')) {
_openCreateModal();
return;
}
if (e.target.closest('#dp-edit-btn')) {
if (_appState.activeDog) _openEditModal(_appState.activeDog);
return;
}
if (e.target.closest('#profile-goto-login')) {
App.navigate('settings');
}
});
await _render();
}
async function refresh() {
await _render();
}
async function onDogChange(dog) {
await _render();
}
// ----------------------------------------------------------
// HAUPTRENDER
// ----------------------------------------------------------
async function _render() {
if (!_appState.user) {
_container.innerHTML = UI.emptyState({
icon : ' ',
title : 'Anmelden erforderlich',
text : 'Melde dich an, um ein Hundeprofil anzulegen.',
action: `Anmelden `,
});
_container.querySelector('#profile-goto-login')
?.addEventListener('click', () => App.navigate('settings'));
return;
}
if (!_appState.activeDog) {
_renderCreateForm();
} else {
_renderProfile(_appState.activeDog);
}
}
// ----------------------------------------------------------
// PROFIL-ANSICHT
// ----------------------------------------------------------
function _renderProfile(dog) {
const geburtstag = dog.geburtstag
? new Date(dog.geburtstag + 'T00:00:00')
.toLocaleDateString('de-DE', { day: 'numeric', month: 'long', year: 'numeric' })
: null;
_container.innerHTML = `
${dog.foto_url
? `
`
: `
${UI.icon('dog')}
`}
${_esc(dog.name)}
${dog.rasse
? `
${_esc(dog.rasse)}
`
: `
`}
${geburtstag ? `
Geburtstag
${geburtstag}
${_calcAlter(dog.geburtstag)}
` : ''}
${dog.geschlecht ? `
${dog.geschlecht === 'm' ? ' ' : ' '} Geschlecht
${dog.geschlecht === 'm' ? 'Rüde' : 'Hündin'}
` : ''}
${dog.gewicht_kg ? `
Gewicht
${dog.gewicht_kg} kg
` : ''}
Transponder
${dog.chip_nr
? `
${_esc(dog.chip_nr)}
`
: `
nicht eingetragen
Eintragen
`
}
${dog.bio ? `
` : ''}
${dog.is_public ? `
NFC-Link
banyaro.app/hund/${dog.id}
Kopieren
Dieser Link kann auf ein NFC-Tag gebrannt werden
` : ''}
Profil bearbeiten
Ausweis
Teilen
+ Weiteren Hund anlegen
`;
// Foto hochladen
document.getElementById('dp-photo-input')?.addEventListener('change', async e => {
const file = e.target.files[0];
if (!file) return;
try {
const fd = new FormData();
fd.append('file', file);
const result = await API.dogs.uploadPhoto(dog.id, fd);
dog.foto_url = result.foto_url;
_appState.activeDog = { ..._appState.activeDog, foto_url: result.foto_url };
_appState.dogs = _appState.dogs.map(d =>
d.id === dog.id ? _appState.activeDog : d
);
UI.toast.success('Foto gespeichert.');
_renderProfile(_appState.activeDog);
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Hochladen.');
}
});
// NFC-Link kopieren
document.getElementById('dp-copy-link-btn')?.addEventListener('click', async () => {
const url = `https://banyaro.app/hund/${dog.id}`;
try {
await navigator.clipboard.writeText(url);
UI.toast.success('Link kopiert!');
} catch {
// Fallback für ältere Browser
const el = document.getElementById('dp-nfc-link');
const range = document.createRange();
range.selectNodeContents(el);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
document.execCommand('copy');
sel.removeAllRanges();
UI.toast.success('Link kopiert!');
}
});
// Transponder "Eintragen"-Button
document.getElementById('dp-chip-edit-btn')?.addEventListener('click', () => {
_showChipEdit(dog);
});
document.getElementById('dp-ausweis-btn')?.addEventListener('click', () => {
window.open(`/ausweis/${dog.id}`, '_blank');
});
document.getElementById('dp-share-btn')?.addEventListener('click', () => {
_showShareModal(dog);
});
// Edit- und Add-Klicks laufen über Event-Delegation in init() — keine direkten Listener nötig.
}
function _showChipEdit(dog) {
UI.modal.open({
title: 'Transpondernummer',
body: `
Chip-Nummer (15-stellig)
`,
footer: `
Abbrechen
Speichern `,
});
document.getElementById('chip-edit-save-btn').addEventListener('click', async () => {
const nr = document.getElementById('chip-edit-input').value.trim() || null;
const btn = document.getElementById('chip-edit-save-btn');
UI.setLoading(btn, true);
try {
await API.dogs.update(dog.id, { chip_nr: nr });
dog.chip_nr = nr;
_appState.activeDog = { ..._appState.activeDog, chip_nr: nr };
_appState.dogs = _appState.dogs.map(d => d.id === dog.id ? _appState.activeDog : d);
UI.modal.close();
UI.toast.success('Transpondernummer gespeichert.');
_renderProfile(_appState.activeDog);
} catch (e) {
UI.setLoading(btn, false);
UI.toast.error('Fehler beim Speichern.');
}
});
}
// ----------------------------------------------------------
// TEILEN
// ----------------------------------------------------------
async function _showShareModal(dog) {
UI.modal.open({
title: `${_esc(dog.name)} teilen`,
body: `
Erstelle einen Einladungslink, den du per WhatsApp, Signal oder E-Mail teilen kannst.
Die eingeladene Person sieht Tagebuch und Gesundheitsakte nach dem Annehmen.
Berechtigung
Mitschreiben (Tagebuch & Gesundheit bearbeiten)
Nur lesen
`,
footer: `
Schließen
Link erstellen `,
});
_loadShareList(dog.id);
document.getElementById('share-create-btn').addEventListener('click', async () => {
const role = document.getElementById('share-role-select').value;
const btn = document.getElementById('share-create-btn');
UI.setLoading(btn, true);
try {
const res = await API.sharing.create(dog.id, role);
const link = `${location.origin}${res.invite_path}`;
const inp = document.getElementById('share-link-input');
inp.value = link;
document.getElementById('share-link-result').style.display = 'block';
document.getElementById('share-link-copy').onclick = async () => {
await navigator.clipboard.writeText(link).catch(() => {});
UI.toast.success('Link kopiert!');
};
UI.setLoading(btn, false);
_loadShareList(dog.id);
} catch (e) {
UI.setLoading(btn, false);
UI.toast.error(e.message || 'Fehler');
}
});
}
async function _loadShareList(dogId) {
const wrap = document.getElementById('share-list-wrap');
if (!wrap) return;
try {
const shares = await API.sharing.list(dogId);
if (!shares.length) { wrap.innerHTML = ''; return; }
wrap.innerHTML = `
Aktive Einladungen
` +
shares.map(s => `
${s.shared_with_name
? `${_esc(s.shared_with_name)} · ${s.role}`
: `Ausstehend · ${s.role}`}
`).join('');
wrap.querySelectorAll('.share-revoke-btn').forEach(btn => {
btn.addEventListener('click', async () => {
await API.sharing.revoke(dogId, parseInt(btn.dataset.shareId));
_loadShareList(dogId);
});
});
} catch (e) { /* ignore */ }
}
// ----------------------------------------------------------
// NEU ANLEGEN (direkt auf der Seite, kein Modal)
// ----------------------------------------------------------
function _renderCreateForm() {
_container.innerHTML = `
${UI.icon('dog')}
Hund anlegen
Erstelle das Profil für deinen Hund.
${_formHTML(null)}
`;
_bindForm(null, false);
}
// ----------------------------------------------------------
// NEUEN HUND ANLEGEN (Modal) — auch aufrufbar via addNew()
// ----------------------------------------------------------
function _openCreateModal() {
UI.modal.open({
title: 'Weiteren Hund anlegen',
body: _formHTML(null, true),
footer: `
Abbrechen
${UI.icon('dog')} Hund anlegen
`,
});
_bindForm(null, true);
}
// ----------------------------------------------------------
// BEARBEITEN (Modal)
// ----------------------------------------------------------
function _openEditModal(dog) {
UI.modal.open({
title: `${dog.name} bearbeiten`,
body: _formHTML(dog, true),
footer: `
Abbrechen
Speichern
`,
});
_bindForm(dog, true);
}
// ----------------------------------------------------------
// FORMULAR HTML
// ----------------------------------------------------------
function _formHTML(dog, inModal = false) {
const today = new Date().toISOString().slice(0, 10);
return `
`;
}
// ----------------------------------------------------------
// FORMULAR EVENTS
// ----------------------------------------------------------
function _bindForm(dog, inModal) {
const form = document.getElementById('dp-form');
if (!form) return;
// Foto-Vorschau
const fotoInput = document.getElementById('dp-form-foto');
const fotoPreview = document.getElementById('dp-form-preview');
if (fotoInput && fotoPreview) {
fotoInput.addEventListener('change', () => {
const file = fotoInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
fotoPreview.src = e.target.result;
fotoPreview.style.display = 'block';
};
reader.readAsDataURL(file);
});
}
document.getElementById('dp-form-cancel')
?.addEventListener('click', UI.modal.close);
document.getElementById('dp-delete-btn')?.addEventListener('click', async () => {
const ok = await UI.modal.confirm({
title : `${dog.name} löschen?`,
message: 'Tagebuch-Einträge und Gesundheitsdaten werden ebenfalls gelöscht. Nicht rückgängig.',
confirmText: 'Löschen',
danger : true,
});
if (!ok) return;
try {
await API.dogs.delete(dog.id);
_appState.dogs = _appState.dogs.filter(d => d.id !== dog.id);
_appState.activeDog = _appState.dogs[0] || null;
if (inModal) UI.modal.close();
UI.toast.success(`${dog.name} wurde gelöscht.`);
await _render();
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Löschen.');
}
});
form.addEventListener('submit', async e => {
e.preventDefault();
const btn = document.querySelector('[form="dp-form"][type="submit"]') || form.querySelector('[type="submit"]');
const fd = UI.formData(form);
if (!fd.name?.trim()) {
UI.toast.warning('Bitte einen Namen eingeben.');
return;
}
await UI.asyncButton(btn, async () => {
const payload = {
name: fd.name.trim(),
rasse: fd.rasse || null,
geburtstag: fd.geburtstag || null,
geschlecht: fd.geschlecht || null,
gewicht_kg: fd.gewicht_kg ? parseFloat(fd.gewicht_kg) : null,
chip_nr: fd.chip_nr || null,
bio: fd.bio || null,
is_public: 'is_public' in fd,
};
let saved;
if (dog) {
saved = await API.dogs.update(dog.id, payload);
_appState.dogs = _appState.dogs.map(d => d.id === dog.id ? saved : d);
_appState.activeDog = saved;
if (inModal) UI.modal.close();
UI.toast.success('Profil gespeichert.');
} else {
saved = await API.dogs.create(payload);
_appState.dogs.push(saved);
_appState.activeDog = saved;
localStorage.setItem('by_active_dog', String(saved.id));
if (inModal) UI.modal.close();
UI.toast.success(`${saved.name} wurde angelegt! 🎉`);
}
// Foto hochladen wenn gewählt
const fotoFile = document.getElementById('dp-form-foto')?.files[0];
if (fotoFile) {
try {
const fd = new FormData();
fd.append('file', fotoFile);
const result = await API.dogs.uploadPhoto(saved.id, fd);
saved.foto_url = result.foto_url;
_appState.activeDog = { ...saved };
_appState.dogs = _appState.dogs.map(d => d.id === saved.id ? _appState.activeDog : d);
} catch {
UI.toast.warning('Profil gespeichert, Foto konnte nicht hochgeladen werden.');
}
}
// Dog Switcher in Header + Sidebar aktualisieren
App.renderDogSwitcher?.();
await _render();
});
});
}
// ----------------------------------------------------------
// HELPER
// ----------------------------------------------------------
function _calcAlter(geburtstag) {
const born = new Date(geburtstag + 'T00:00:00');
const tage = Math.floor((Date.now() - born) / 86400000);
if (tage < 0) return '';
if (tage < 30) return `${tage} Tag${tage !== 1 ? 'e' : ''} alt`;
if (tage < 365) {
const m = Math.floor(tage / 30);
return `${m} Monat${m !== 1 ? 'e' : ''} alt`;
}
const j = Math.floor(tage / 365);
const m = Math.floor((tage % 365) / 30);
return m > 0
? `${j} Jahr${j !== 1 ? 'e' : ''}, ${m} Monat${m !== 1 ? 'e' : ''} alt`
: `${j} Jahr${j !== 1 ? 'e' : ''} alt`;
}
function _esc(str) {
if (!str) return '';
return str.replace(/&/g, '&').replace(//g, '>')
.replace(/"/g, '"');
}
// ----------------------------------------------------------
// PUBLIC
// ----------------------------------------------------------
return { init, refresh, onDogChange, addNew: _openCreateModal };
})();