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
|
|
@ -38,6 +38,11 @@ window.Page_diary = (() => {
|
|||
// ----------------------------------------------------------
|
||||
async function refresh() {
|
||||
if (!_appState.activeDog) return;
|
||||
// Wenn vorher "kein Hund"-Zustand: #diary-list existiert nicht → voll neu rendern
|
||||
if (!_container.querySelector('#diary-list')) {
|
||||
await _render();
|
||||
return;
|
||||
}
|
||||
_offset = 0;
|
||||
_entries = [];
|
||||
await _load();
|
||||
|
|
@ -185,6 +190,9 @@ window.Page_diary = (() => {
|
|||
? `<p class="diary-card-text">${_escape(e.text.slice(0, 140))}${e.text.length > 140 ? '…' : ''}</p>`
|
||||
: '';
|
||||
|
||||
// Mehrere Hunde: kleine Avatare in der Karte
|
||||
const dogAvatars = _dogAvatarRow(e.dog_ids || []);
|
||||
|
||||
return `
|
||||
<div class="diary-card${isMile ? ' diary-card--milestone' : ''}" data-entry-id="${e.id}">
|
||||
${photo}
|
||||
|
|
@ -196,11 +204,24 @@ window.Page_diary = (() => {
|
|||
${e.titel ? `<div class="diary-card-title">${_escape(e.titel)}</div>` : ''}
|
||||
${textPreview}
|
||||
${tagsHtml}
|
||||
${dogAvatars}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function _dogAvatarRow(dogIds) {
|
||||
if (!dogIds || dogIds.length <= 1) return '';
|
||||
const avatars = dogIds.map(did => {
|
||||
const dog = _appState.dogs.find(d => d.id === did);
|
||||
if (!dog) return '';
|
||||
return `<div class="diary-dog-av" title="${_escape(dog.name)}">
|
||||
${dog.foto_url ? `<img src="${_escape(dog.foto_url)}" alt="">` : '<span>🐕</span>'}
|
||||
</div>`;
|
||||
}).join('');
|
||||
return `<div class="diary-dog-row">${avatars}</div>`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// DETAIL-ANSICHT
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -217,6 +238,22 @@ window.Page_diary = (() => {
|
|||
style="width:100%;border-radius:var(--radius-md);margin-bottom:var(--space-4)">`
|
||||
: '';
|
||||
|
||||
// Hunde-Anzeige wenn mehrere beteiligt
|
||||
const dogIds = entry.dog_ids || [entry.dog_id];
|
||||
const dogsHtml = dogIds.length > 1
|
||||
? `<div class="diary-detail-dogs">
|
||||
${dogIds.map(did => {
|
||||
const dog = _appState.dogs.find(d => d.id === did);
|
||||
return dog ? `<div class="diary-dog-chip">
|
||||
<div class="diary-dog-av">
|
||||
${dog.foto_url ? `<img src="${_escape(dog.foto_url)}" alt="">` : '<span>🐕</span>'}
|
||||
</div>
|
||||
<span>${_escape(dog.name)}</span>
|
||||
</div>` : '';
|
||||
}).join('')}
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
const body = `
|
||||
${isMile ? '<div class="diary-detail-milestone-badge">🏆 Meilenstein</div>' : ''}
|
||||
${photo}
|
||||
|
|
@ -226,6 +263,7 @@ window.Page_diary = (() => {
|
|||
${entry.datum ? UI.time.format(entry.datum + 'T00:00:00') : ''}
|
||||
</span>
|
||||
</div>
|
||||
${dogsHtml}
|
||||
${entry.text
|
||||
? `<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${_escape(entry.text)}</p>`
|
||||
: ''}
|
||||
|
|
@ -263,13 +301,33 @@ window.Page_diary = (() => {
|
|||
// FORMULAR — Neu erstellen / Bearbeiten
|
||||
// ----------------------------------------------------------
|
||||
function _showForm(entry) {
|
||||
const isEdit = !!entry;
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const typOpts = Object.entries(TYPEN)
|
||||
const isEdit = !!entry;
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const activeDog = _appState.activeDog;
|
||||
const typOpts = Object.entries(TYPEN)
|
||||
.map(([val, { icon, label }]) =>
|
||||
`<option value="${val}" ${entry?.typ === val ? 'selected' : ''}>${icon} ${label}</option>`)
|
||||
.join('');
|
||||
|
||||
// Weitere Hunde: alle außer dem aktiven
|
||||
const otherDogs = _appState.dogs.filter(d => d.id !== activeDog?.id);
|
||||
const entryDogIds = entry?.dog_ids || [activeDog?.id];
|
||||
const dogPickerHtml = otherDogs.length > 0 ? `
|
||||
<div class="form-group">
|
||||
<label class="form-label">Betrifft auch</label>
|
||||
<div class="diary-dog-picker">
|
||||
${otherDogs.map(d => `
|
||||
<label class="diary-dog-pick-item ${entryDogIds.includes(d.id) ? 'checked' : ''}">
|
||||
<input type="checkbox" name="extra_dog" value="${d.id}"
|
||||
${entryDogIds.includes(d.id) ? 'checked' : ''}>
|
||||
<div class="diary-dog-av">
|
||||
${d.foto_url ? `<img src="${_escape(d.foto_url)}" alt="">` : '<span>🐕</span>'}
|
||||
</div>
|
||||
<span>${_escape(d.name)}</span>
|
||||
</label>`).join('')}
|
||||
</div>
|
||||
</div>` : '';
|
||||
|
||||
const body = `
|
||||
<form id="diary-form" autocomplete="off">
|
||||
<div class="form-group">
|
||||
|
|
@ -291,6 +349,7 @@ window.Page_diary = (() => {
|
|||
<textarea class="form-control" name="text" rows="5"
|
||||
placeholder="Was ist passiert? Besonderheiten, Gedanken…">${_escape(entry?.text || '')}</textarea>
|
||||
</div>
|
||||
${dogPickerHtml}
|
||||
<div class="form-group">
|
||||
<label class="form-label" style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
<input type="checkbox" name="is_milestone" ${entry?.is_milestone ? 'checked' : ''}>
|
||||
|
|
@ -318,6 +377,9 @@ window.Page_diary = (() => {
|
|||
|
||||
const form = document.getElementById('diary-form');
|
||||
|
||||
// Fokus auf Titel-Feld → öffnet Keyboard auf Mobile, zeigt dem User was zu tun ist
|
||||
setTimeout(() => form?.querySelector('[name="titel"]')?.focus(), 150);
|
||||
|
||||
// Foto-Vorschau
|
||||
const photoInput = form.querySelector('[name="photo"]');
|
||||
const photoPreview = document.getElementById('diary-photo-preview');
|
||||
|
|
@ -330,11 +392,25 @@ window.Page_diary = (() => {
|
|||
|
||||
document.getElementById('diary-form-cancel')?.addEventListener('click', UI.modal.close);
|
||||
|
||||
// Checked-Klasse auf Dog-Picker-Items toggeln
|
||||
form.querySelectorAll('.diary-dog-pick-item input').forEach(cb => {
|
||||
cb.addEventListener('change', () => {
|
||||
cb.closest('.diary-dog-pick-item').classList.toggle('checked', cb.checked);
|
||||
});
|
||||
});
|
||||
|
||||
form.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const submitBtn = form.querySelector('[type="submit"]');
|
||||
const fd = UI.formData(form);
|
||||
|
||||
// dog_ids zusammenbauen: aktiver Hund + gewählte weitere
|
||||
const dogIds = [_appState.activeDog.id];
|
||||
form.querySelectorAll('.diary-dog-pick-item input:checked').forEach(cb => {
|
||||
const id = parseInt(cb.value);
|
||||
if (!dogIds.includes(id)) dogIds.push(id);
|
||||
});
|
||||
|
||||
await UI.asyncButton(submitBtn, async () => {
|
||||
const payload = {
|
||||
datum: fd.datum || null,
|
||||
|
|
@ -342,6 +418,7 @@ window.Page_diary = (() => {
|
|||
titel: fd.titel || null,
|
||||
text: fd.text || null,
|
||||
is_milestone: 'is_milestone' in fd,
|
||||
dog_ids: dogIds,
|
||||
};
|
||||
|
||||
if (isEdit) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue