Frontend Sprint 3+4: Dog-Switcher, Health-Seite, Multi-Dog Tagebuch

- app.js: vollständiger Dog-Switcher (Avatar im Header/Sidebar, Quickpicker
  bei 3+ Hunden, setActiveDog, localStorage-Persistenz), iOS Ghost-Click Fix,
  Loading-Guard, Logout State Reset
- index.html: Dog-Switcher HTML, Favicon-Links, Sidebar "+ Neu erstellen",
  Navigation Tab Karte → Gesundheit
- health.js (neu): vollständiges Health-Frontend mit Tabs (Impfung, Entwurmung,
  Tierarzt, Medikament, Gewicht-Kurve, Allergie, Dokument), Ampel-System,
  KI-Zusammenfassung
- dog-profile.js: "+ Weiteren Hund anlegen" Button + _openCreateModal(),
  Event-Delegation statt direkter Listener (kein Doppelaufruf)
- diary.js: Dog-Picker im Formular, Avatar-Reihe auf Karten, Dog-Chips
  im Detail-Modal, dog_ids im API-Payload
- poison.js: Erledigt-Dialog mit Grundauswahl (beseitigt/fehlerhaft/anderes)
- api.js: health-Endpoints (list, create, update, delete, upload, ki)
- ui.js: confirm() Fix (resolve vor close)
- layout.css: Dog-Switcher Styles, scrollbare Sidebar-Nav, User-Item fix
- components.css: Health-Styles, Diary Dog-Picker, Ampel-Punkte, Gewicht-SVG
- icons/: Favicon-Set (ico, 16px, 32px, 180px, 192px, 512px)
This commit is contained in:
rene 2026-04-13 19:30:03 +02:00
parent 6f48ec581d
commit d8b9561fff
16 changed files with 1597 additions and 91 deletions

View file

@ -14,6 +14,22 @@ window.Page_dog_profile = (() => {
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();
}
@ -77,7 +93,7 @@ window.Page_dog_profile = (() => {
title="Foto ändern">
📷
<input type="file" id="dp-photo-input" accept="image/*"
capture="user" style="display:none">
style="display:none">
</label>
</div>
@ -135,9 +151,14 @@ window.Page_dog_profile = (() => {
</div>
` : ''}
<button class="btn btn-primary w-full" id="dp-edit-btn">
Profil bearbeiten
</button>
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button class="btn btn-primary w-full" id="dp-edit-btn">
Profil bearbeiten
</button>
<button class="btn btn-secondary w-full" id="dp-add-dog-btn">
+ Weiteren Hund anlegen
</button>
</div>
</div>
`;
@ -150,7 +171,6 @@ window.Page_dog_profile = (() => {
const fd = new FormData();
fd.append('file', file);
const result = await API.dogs.uploadPhoto(dog.id, fd);
// State in-place aktualisieren
dog.foto_url = result.foto_url;
_appState.activeDog = { ..._appState.activeDog, foto_url: result.foto_url };
_appState.dogs = _appState.dogs.map(d =>
@ -162,11 +182,7 @@ window.Page_dog_profile = (() => {
UI.toast.error(err.message || 'Fehler beim Hochladen.');
}
});
// Bearbeiten öffnen
document.getElementById('dp-edit-btn')?.addEventListener('click', () => {
_openEditModal(dog);
});
// Edit- und Add-Klicks laufen über Event-Delegation in init() — keine direkten Listener nötig.
}
// ----------------------------------------------------------
@ -190,18 +206,26 @@ window.Page_dog_profile = (() => {
_bindForm(null, false);
}
// ----------------------------------------------------------
// NEUEN HUND ANLEGEN (Modal) — auch aufrufbar via addNew()
// ----------------------------------------------------------
function _openCreateModal() {
UI.modal.open({ title: 'Weiteren Hund anlegen', body: _formHTML(null, true) });
_bindForm(null, true);
}
// ----------------------------------------------------------
// BEARBEITEN (Modal)
// ----------------------------------------------------------
function _openEditModal(dog) {
UI.modal.open({ title: `${dog.name} bearbeiten`, body: _formHTML(dog) });
UI.modal.open({ title: `${dog.name} bearbeiten`, body: _formHTML(dog, true) });
_bindForm(dog, true);
}
// ----------------------------------------------------------
// FORMULAR HTML
// ----------------------------------------------------------
function _formHTML(dog) {
function _formHTML(dog, inModal = false) {
const today = new Date().toISOString().slice(0, 10);
return `
<form id="dp-form" autocomplete="off">
@ -270,9 +294,25 @@ window.Page_dog_profile = (() => {
</label>
</div>
<div class="form-group">
<label class="form-label">Foto</label>
<div style="display:flex;align-items:center;gap:var(--space-3)">
<img id="dp-form-preview"
src="${dog?.foto_url || ''}"
style="width:64px;height:64px;border-radius:50%;object-fit:cover;
background:var(--c-surface-2);border:2px solid var(--c-border);
display:${dog?.foto_url ? 'block' : 'none'}">
<label class="btn btn-secondary btn-sm" style="cursor:pointer;margin:0">
📷 Foto auswählen
<input type="file" name="foto" accept="image/*" style="display:none"
id="dp-form-foto">
</label>
</div>
</div>
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-4)">
${dog ? `<button type="button" class="btn btn-secondary flex-1"
id="dp-form-cancel">Abbrechen</button>` : ''}
${(dog || inModal) ? `<button type="button" class="btn btn-secondary flex-1"
id="dp-form-cancel">Abbrechen</button>` : ''}
<button type="submit" class="btn btn-primary flex-1">
${dog ? 'Speichern' : '🐕 Hund anlegen'}
</button>
@ -299,6 +339,22 @@ window.Page_dog_profile = (() => {
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);
@ -355,8 +411,29 @@ window.Page_dog_profile = (() => {
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();
});
});
@ -390,6 +467,6 @@ window.Page_dog_profile = (() => {
// ----------------------------------------------------------
// PUBLIC
// ----------------------------------------------------------
return { init, refresh, onDogChange };
return { init, refresh, onDogChange, addNew: _openCreateModal };
})();