Feature: Welcome-Chips — Termin nur <60 Tage, Übung des Tages als Fallback, async Gassirunde-Bank — SW by-v476, APP_VER 453

This commit is contained in:
rene 2026-04-29 07:34:22 +02:00
parent db386da2c0
commit 4e1e7ca37e
5 changed files with 102 additions and 15 deletions

View file

@ -327,15 +327,39 @@ window.Page_welcome = (() => {
</div>`;
}
function _chip2HTML(dashData) {
const appt = dashData?.next_appointment;
if (appt) {
const apptLabel = UI.escape(appt.bezeichnung);
const apptDate = _relDate(appt.naechstes) || appt.naechstes || '—';
const apptIcon = _appointmentIcon(appt.typ);
return `
<div class="wc-chip" id="wc-chip-mid" data-nav="health">
<svg class="ph-icon wc-chip-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${apptIcon}"></use></svg>
<span class="wc-chip-label">Nächster Termin</span>
<span class="wc-chip-val">${apptLabel} · ${apptDate}</span>
</div>`;
}
const ex = dashData?.daily_exercise;
if (ex) {
return `
<div class="wc-chip" id="wc-chip-mid" data-nav="uebungen">
<svg class="ph-icon wc-chip-icon" aria-hidden="true"><use href="/icons/phosphor.svg#target"></use></svg>
<span class="wc-chip-label">Übung des Tages</span>
<span class="wc-chip-val">${UI.escape(ex.name)}</span>
</div>`;
}
return `
<div class="wc-chip" id="wc-chip-mid" data-nav="uebungen">
<svg class="ph-icon wc-chip-icon" aria-hidden="true"><use href="/icons/phosphor.svg#target"></use></svg>
<span class="wc-chip-label">Übung des Tages</span>
<span class="wc-chip-val wc-chip-val--empty"></span>
</div>`;
}
function _chipsHTML(dashData) {
const diaryDate = dashData?.last_diary?.datum;
const diaryText = diaryDate ? (_relDate(diaryDate) || diaryDate) : '—';
const appt = dashData?.next_appointment;
const apptLabel = appt ? UI.escape(appt.bezeichnung) : '—';
const apptDate = appt ? (_relDate(appt.naechstes) || appt.naechstes || '—') : '—';
const apptIcon = _appointmentIcon(appt?.typ);
const weight = dashData?.last_weight;
const weightVal = weight ? `${weight.wert} ${weight.einheit}` : '—';
@ -346,11 +370,7 @@ window.Page_welcome = (() => {
<span class="wc-chip-label">Tagebuch</span>
<span class="wc-chip-val${diaryDate ? '' : ' wc-chip-val--empty'}">${diaryText}</span>
</div>
<div class="wc-chip" data-nav="health">
<svg class="ph-icon wc-chip-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${apptIcon}"></use></svg>
<span class="wc-chip-label">Nächster Termin</span>
<span class="wc-chip-val${appt ? '' : ' wc-chip-val--empty'}">${appt ? apptLabel + ' · ' + apptDate : '—'}</span>
</div>
${_chip2HTML(dashData)}
<div class="wc-chip" data-nav="health">
<svg class="ph-icon wc-chip-icon" aria-hidden="true"><use href="/icons/phosphor.svg#scales"></use></svg>
<span class="wc-chip-label">Gewicht</span>
@ -359,6 +379,48 @@ window.Page_welcome = (() => {
</div>`;
}
// Versucht async eine Bank in 2 km Umkreis zu finden und ersetzt Chip 2
async function _tryBenchChip(dashData) {
if (dashData?.next_appointment) return; // Termin hat Vorrang
let loc;
try { loc = await API.getLocation({ timeout: 5000, maximumAge: 300000 }); }
catch { return; }
const d = 0.018; // ~2 km in Grad
let pois;
try {
pois = await API.osm.pois('bank', loc.lat - d, loc.lon - d, loc.lat + d, loc.lon + d);
} catch { return; }
if (!pois || pois.length === 0) return;
// täglich stabile Auswahl, aber täglich andere Bank
const dayIdx = Math.floor(Date.now() / 86400000);
const bench = pois[dayIdx % pois.length];
const distM = Math.round(_haversine(loc.lat, loc.lon, bench.lat, bench.lon));
const distTxt = distM < 1000 ? `${distM} m` : `${(distM / 1000).toFixed(1)} km`;
const name = bench.name || 'Bank';
const chip2 = _container.querySelector('#wc-chip-mid');
if (!chip2) return;
chip2.dataset.nav = 'map';
chip2.dataset.bench = JSON.stringify({ lat: bench.lat, lon: bench.lon });
chip2.innerHTML = `
<svg class="ph-icon wc-chip-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg>
<span class="wc-chip-label">Gassirunde</span>
<span class="wc-chip-val">${UI.escape(name)} · ${distTxt}</span>`;
// Event neu binden (ersetzt existierenden)
chip2.addEventListener('click', () => App.navigate('map'));
}
function _haversine(lat1, lon1, lat2, lon2) {
const R = 6371000;
const f1 = lat1 * Math.PI / 180, f2 = lat2 * Math.PI / 180;
const df = (lat2 - lat1) * Math.PI / 180, dl = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(df/2)**2 + Math.cos(f1)*Math.cos(f2)*Math.sin(dl/2)**2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
}
// ----------------------------------------------------------
// EINGELOGGTE ANSICHT — visueller Überblick
// ----------------------------------------------------------
@ -418,6 +480,7 @@ window.Page_welcome = (() => {
API.dogs.welcomeDashboard(dog.id).then(dash => {
_updateHeroFromDash(dash, dog);
_updateChipsFromDash(dash);
_tryBenchChip(dash); // nach Chips-Update: ggf. mit naher Bank ersetzen
}).catch(() => { /* Skeleton bleibt sichtbar */ });
}
}