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

@ -1688,11 +1688,34 @@ window.Page_map = (() => {
}
function _showRecSaveModal(track, distKm, dauMin) {
const dogs = _appState?.dogs || [];
const activeDogId = _appState?.activeDog?.id;
const dogPickerHtml = dogs.length > 1 ? `
<div class="form-group">
<label class="form-label">Welche Hunde waren dabei?</label>
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2)">
${dogs.map(d => {
const checked = d.id === activeDogId;
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"><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 var(--c-border);border-radius:100px;cursor:pointer;
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="rec-dog-cb">
${av}<span>${UI.escape(d.name)}</span>
</label>`;
}).join('')}
</div>
</div>` : '';
const body = `
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
${track.length} GPS-Punkte · ${distKm.toFixed(2)} km · ca. ${dauMin} min
</p>
<form id="rec-save-form" autocomplete="off">
${dogPickerHtml}
<div class="form-group">
<label class="form-label">Name der Route *</label>
<input class="form-control" type="text" name="name"
@ -1772,10 +1795,23 @@ window.Page_map = (() => {
if (_recMarker) { _recMarker.remove(); _recMarker = null; }
});
// Hund-Checkbox Toggle-Styling
document.querySelectorAll('.rec-dog-cb').forEach(cb => {
const label = cb.closest('label');
const update = () => {
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)' : '';
};
update();
cb.addEventListener('change', update);
});
document.getElementById('rec-save-form')?.addEventListener('submit', async e => {
e.preventDefault();
const btn = document.querySelector('[form="rec-save-form"][type="submit"]');
const fd = UI.formData(e.target);
const dogIds = [...document.querySelectorAll('.rec-dog-cb:checked')].map(c => parseInt(c.value));
await UI.asyncButton(btn, async () => {
const saved = await API.routes.create({
name: fd.name?.trim(),
@ -1789,6 +1825,7 @@ window.Page_map = (() => {
leine_empfohlen: 'leine_empfohlen' in fd,
is_public: 'is_public' in fd,
hunde_tauglichkeit: fd.hunde_tauglichkeit || 'sehr_gut',
dog_ids: dogIds.length ? dogIds : null,
});
UI.modal.close();
if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; }