Feature: Goldene Gassi-Stunde — täglicher Push mit bestem Wetterfenster (SW by-v693)

- Scheduler-Job täglich 07:00: berechnet bestes 2h-Fenster via Open-Meteo
- Score-System (max. 10 Pkt): Temperatur, Niederschlag, Wind, Tageszeit
- User-Fallback auf letzten bekannten Standort (push_subscriptions.last_lat/lon) oder München
- Nur Push wenn score >= 3 (kein sinnloser Push bei schlechtem Wetter)
- DB-Migration: users.gassi_stunde_push (Boolean, default 0)
- settings.js: Toggle "Goldene Gassi-Stunde täglich" in App-Einstellungen
- PATCH /api/profile + auth.py /me: gassi_stunde_push Feld
This commit is contained in:
rene 2026-05-04 20:22:02 +02:00
parent af1508c0de
commit 6bf088df56
4 changed files with 239 additions and 3 deletions

View file

@ -313,7 +313,7 @@ window.Page_settings = (() => {
</div>
<!-- KI-Notiz-Assistent -->
<div style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4)">
<div style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4);border-bottom:1px solid var(--c-border)">
<svg class="ph-icon" aria-hidden="true" style="width:1.25rem;height:1.25rem"><use href="/icons/phosphor.svg#brain"></use></svg>
<div style="flex:1">
<div style="font-weight:500">KI-Notiz-Assistent</div>
@ -336,6 +336,30 @@ window.Page_settings = (() => {
</label>
</div>
<!-- Goldene Gassi-Stunde -->
<div style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4)">
<svg class="ph-icon" aria-hidden="true" style="width:1.25rem;height:1.25rem"><use href="/icons/phosphor.svg#paw-print"></use></svg>
<div style="flex:1">
<div style="font-weight:500">Goldene Gassi-Stunde täglich</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
Täglich um 07:00 Uhr: bestes Wetterfenster für den Gassi-Gang
</div>
</div>
<label style="position:relative;display:inline-block;width:44px;height:24px;flex-shrink:0">
<input type="checkbox" id="toggle-gassi-stunde"
style="opacity:0;width:0;height:0;position:absolute"
${u.gassi_stunde_push ? 'checked' : ''}>
<span style="position:absolute;cursor:pointer;inset:0;border-radius:12px;
background:${u.gassi_stunde_push ? 'var(--c-primary)' : 'var(--c-border)'};transition:.2s"
id="toggle-gassi-stunde-track"></span>
<span id="toggle-gassi-stunde-thumb"
style="position:absolute;top:2px;left:${u.gassi_stunde_push ? '22px' : '2px'};
width:20px;height:20px;border-radius:50%;
background:#fff;transition:.2s;
box-shadow:0 1px 3px rgba(0,0,0,.3)"></span>
</label>
</div>
</div>
</div>
@ -785,6 +809,25 @@ window.Page_settings = (() => {
}
});
document.getElementById('toggle-gassi-stunde')?.addEventListener('change', async e => {
const enabled = e.target.checked;
const track = document.getElementById('toggle-gassi-stunde-track');
const thumb = document.getElementById('toggle-gassi-stunde-thumb');
if (track) track.style.background = enabled ? 'var(--c-primary)' : 'var(--c-border)';
if (thumb) thumb.style.left = enabled ? '22px' : '2px';
try {
await API.patch('/profile', { gassi_stunde_push: enabled ? 1 : 0 });
_appState.user.gassi_stunde_push = enabled ? 1 : 0;
UI.toast.success(enabled ? 'Goldene Gassi-Stunde aktiviert.' : 'Goldene Gassi-Stunde deaktiviert.');
} catch (err) {
UI.toast.error(err?.message || 'Einstellung konnte nicht gespeichert werden.');
// Revert UI
e.target.checked = !enabled;
if (track) track.style.background = !enabled ? 'var(--c-primary)' : 'var(--c-border)';
if (thumb) thumb.style.left = !enabled ? '22px' : '2px';
}
});
_loadReferral();
_loadBreederCard();
}