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:
parent
6f48ec581d
commit
d8b9561fff
16 changed files with 1597 additions and 91 deletions
|
|
@ -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 };
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue