Feature: Trauer-Feature, Futter-Verträglichkeit, Multi-Hund-Fixes, Wetter-Ort (Sprint 47)

- dog-profile.js: Verstorben-Button, Gedenkseite, KI-Abschiedstext
- database.py: futter_eintraege/reaktionen, route_dogs, exercise_progress.dog_id
- routes/ernaehrung.py: Futter-Verträglichkeit mit 20 Reaktionstypen + Analyse
- routes/routen.py: route_dogs Many-to-Many, Routen editierbar
- routes/training.py: exercise_progress per dog_id
- routes/ki.py: /ki/abschied Trauer-KI
- weather.py: Nominatim Ortsname parallel geladen
- ui.js: dogChip/bindDogChip, visualViewport-Modal
- api.js: gedenken, gedenkseite, futter-Methoden, route_dogs
- worlds.js: Ortsname im Wetter-Chip
- uebungen.js: _progressLoaded-Flag, dog-spezifischer Fortschritt
- trainingsplaene.js: dog_id Unterstützung
- diary.js/health.js: P-Badge Cleanup
- map.js: Wetter-Ort-Anzeige entfernt
- wetter.js: Ort in Wetter-Detail
This commit is contained in:
rene 2026-05-11 19:28:38 +02:00
parent 1ce802c8dc
commit bda61a0e40
16 changed files with 713 additions and 181 deletions

View file

@ -2156,6 +2156,7 @@ window.Page_routes = (() => {
${_actionBtn('rd-send-friend', 'paper-plane-tilt', 'Senden')}
${track.length >= 4 ? _actionBtn('rd-trim', 'pencil-simple', 'Kürzen') : ''}
${_actionBtn('rd-reverse', 'path', 'Umkehren')}
${(_appState?.dogs?.length > 0) ? _actionBtn('rd-dogs', 'dog', 'Hunde') : ''}
${_actionBtn('rd-del', 'trash', 'Löschen', true)}
</div>` : '';
@ -2244,6 +2245,9 @@ window.Page_routes = (() => {
} catch (err) { UI.toast.error(err.message); }
});
// Hunde bearbeiten
document.getElementById('rd-dogs')?.addEventListener('click', () => _openEditDogsModal(route));
// Löschen
document.getElementById('rd-del')?.addEventListener('click', async () => {
const ok = await UI.modal.confirm({
@ -2304,6 +2308,70 @@ window.Page_routes = (() => {
}
}
// ----------------------------------------------------------
// Hunde einer Route bearbeiten
// ----------------------------------------------------------
function _openEditDogsModal(route) {
const dogs = _appState?.dogs || [];
if (!dogs.length) { UI.toast.info('Keine Hunde im Profil vorhanden.'); return; }
const currentIds = new Set(route.dog_ids || []);
const dogRows = dogs.map(d => {
const checked = currentIds.has(d.id);
const av = d.foto_url
? `<img src="${UI.escape(d.foto_url)}" style="width:20px;height:20px;border-radius:50%;object-fit:cover;flex-shrink:0">`
: `<svg class="ph-icon" style="width:14px;height:14px;flex-shrink:0" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>`;
return `<label style="display:inline-flex;align-items:center;gap:6px;padding:5px 10px;
border:1.5px solid ${checked ? 'var(--c-primary)' : 'var(--c-border)'};
border-radius:100px;cursor:pointer;
background:${checked ? 'var(--c-primary-subtle)' : ''};
color:${checked ? 'var(--c-primary)' : ''};
font-size:var(--text-xs);font-weight:600;user-select:none">
<input type="checkbox" name="dog_ids" value="${d.id}" ${checked ? 'checked' : ''}
style="display:none" class="rd-dog-cb">
${av}<span>${UI.escape(d.name)}</span>
</label>`;
}).join('');
const body = `
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
Welche Hunde waren bei dieser Route dabei?
</p>
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2)" id="rd-dogs-picker">
${dogRows}
</div>
`;
const footer = `
<button type="button" class="btn btn-secondary flex-1" id="rd-dogs-cancel">Abbrechen</button>
<button type="button" class="btn btn-primary flex-1" id="rd-dogs-save">${UI.icon('floppy-disk')} Speichern</button>
`;
UI.modal.open({ title: `${UI.icon('dog')} Hunde bearbeiten`, body, footer });
// Checkbox-Pill Styling
document.querySelectorAll('.rd-dog-cb').forEach(cb => {
const label = cb.closest('label');
cb.addEventListener('change', () => {
label.style.borderColor = cb.checked ? 'var(--c-primary)' : 'var(--c-border)';
label.style.background = cb.checked ? 'var(--c-primary-subtle)' : '';
label.style.color = cb.checked ? 'var(--c-primary)' : '';
});
});
document.getElementById('rd-dogs-cancel')?.addEventListener('click', UI.modal.close);
document.getElementById('rd-dogs-save')?.addEventListener('click', async () => {
const btn = document.getElementById('rd-dogs-save');
await UI.asyncButton(btn, async () => {
const dogIds = [...document.querySelectorAll('.rd-dog-cb:checked')].map(c => parseInt(c.value));
await API.routes.updateDogs(route.id, dogIds);
route.dog_ids = dogIds;
UI.modal.close();
UI.toast.success('Hunde aktualisiert.');
});
});
}
// Richtungspfeile gleichmäßig entlang des Tracks platzieren
function _addRouteArrows(map, track, color = '#fff') {
if (track.length < 2) return;