Feature: Fell-Typ Einstellung am Hundeprofil — personalisierte Wetter-Hinweise (SW by-v693)

- DB-Migration: dogs.fell_typ (kurz|mittel|lang|drahtaar|doppel|nackt)
- Hund-Profil Formular: Dropdown "Felltyp" mit 6 Optionen, wird via PATCH /api/dogs/{id} gespeichert
- Wetter: _dogWeatherLabel(d, felltyp) mit fell-spezifischen Hitze-/Kälteschwellen
- Wetter: Fell-spezifische Hinweise (doppel + Hitze, nackt + Kälte, kurz + Frost)
This commit is contained in:
rene 2026-05-04 20:21:02 +02:00
parent 471633867c
commit af1508c0de
6 changed files with 80 additions and 12 deletions

View file

@ -93,9 +93,9 @@
</script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=690">
<link rel="stylesheet" href="/css/layout.css?v=690">
<link rel="stylesheet" href="/css/components.css?v=690">
<link rel="stylesheet" href="/css/design-system.css?v=693">
<link rel="stylesheet" href="/css/layout.css?v=693">
<link rel="stylesheet" href="/css/components.css?v=693">
</head>
<body>
@ -562,7 +562,7 @@
<script src="/js/api.js?v=94"></script>
<script src="/js/ui.js?v=94"></script>
<script src="/js/app.js?v=94"></script>
<script src="/js/worlds.js?v=690"></script>
<script src="/js/worlds.js?v=693"></script>
<!-- Feature-Seiten werden lazy geladen -->

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '692'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '693'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.3.0'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';

View file

@ -970,6 +970,23 @@ window.Page_dog_profile = (() => {
</div>
</div>
<div class="form-group">
<label class="form-label">
Felltyp
<span style="color:var(--c-text-secondary)">(optional)</span>
${UI.help('Der Felltyp wird für personalisierte Wetter-Hinweise genutzt.')}
</label>
<select class="form-control" name="fell_typ">
<option value="" ${!dog?.fell_typ ? 'selected' : ''}> nicht angegeben </option>
<option value="kurz" ${dog?.fell_typ === 'kurz' ? 'selected' : ''}>Kurzhaar (Labrador, Boxer)</option>
<option value="mittel" ${dog?.fell_typ === 'mittel' ? 'selected' : ''}>Mittellang (Spaniel, Husky)</option>
<option value="lang" ${dog?.fell_typ === 'lang' ? 'selected' : ''}>Langhaar (Collie, Berner Senne)</option>
<option value="drahtaar" ${dog?.fell_typ === 'drahtaar' ? 'selected' : ''}>Drahthaar (Terrier, Schnauzer)</option>
<option value="doppel" ${dog?.fell_typ === 'doppel' ? 'selected' : ''}>Doppeltes Unterfell (Husky, Malamute, Samojede)</option>
<option value="nackt" ${dog?.fell_typ === 'nackt' ? 'selected' : ''}>Nackthund (Chinese Crested, Xolo)</option>
</select>
</div>
<div class="form-group">
<label class="form-label">
Bio / Steckbrief
@ -1136,6 +1153,7 @@ window.Page_dog_profile = (() => {
chip_nr: fd.chip_nr || null,
bio: fd.bio || null,
is_public: 'is_public' in fd,
fell_typ: fd.fell_typ || null,
};
let saved;

View file

@ -531,7 +531,8 @@ window.Page_wetter = (() => {
if (!d) return;
const _POLLEN_NAMES = { erle:'Erle', birke:'Birke', graeser:'Gräser', beifuss:'Beifuß', ambrosia:'Ambrosia' };
const _wl = _dogWeatherLabel(d);
const felltyp = (_appState?.activeDog ?? _appState?.dogs?.[0])?.fell_typ || null;
const _wl = _dogWeatherLabel(d, felltyp);
let html = `
<div style="border-radius:var(--radius);padding:var(--space-4);
background:${_wl.color}18;border:1px solid ${_wl.color}44;
@ -653,6 +654,35 @@ window.Page_wetter = (() => {
`;
}
// Fell-spezifische Hinweise
if (felltyp) {
const tempNow = d.temp_max ?? 20;
let fellHint = null;
if (felltyp === 'doppel' && tempNow > 20) {
fellHint = { icon: 'thermometer-hot', color: '#F97316',
text: 'Doppeltes Fell — heute besonders auf Überhitzung achten.' };
} else if (felltyp === 'nackt' && tempNow < 15) {
fellHint = { icon: 'coat-hanger', color: '#60A5FA',
text: 'Nackthund braucht heute eine Hundejacke oder einen -pullover.' };
} else if (felltyp === 'kurz' && tempNow < 5) {
fellHint = { icon: 'snowflake', color: '#38BDF8',
text: 'Kurzhaar friert schnell — Hundemantel empfohlen.' };
}
if (fellHint) {
html += `
<div style="display:flex;align-items:center;gap:var(--space-3);
padding:var(--space-3);border-radius:var(--radius);
background:${fellHint.color}1a;border:1px solid ${fellHint.color}55;
margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:${fellHint.color}">
<use href="/icons/phosphor.svg#${fellHint.icon}"></use>
</svg>
<div style="font-size:var(--text-sm)">${_esc(fellHint.text)}</div>
</div>
`;
}
}
// Schnüffel-Index + Hunde-Alter Chips
const ageYears = _dogAgeYears();
html += _dogAgeChip(ageYears);
@ -874,7 +904,7 @@ window.Page_wetter = (() => {
return '#F44336'; // level 4+
}
function _dogWeatherLabel(d) {
function _dogWeatherLabel(d, felltyp) {
const temp = d.temp_max ?? 20;
const tempMin = d.temp_min ?? temp;
const precip = d.precip_prob ?? 0;
@ -882,6 +912,15 @@ window.Page_wetter = (() => {
const asphalt = d.asphalt_temp ?? 0;
const wcode = d.weathercode ?? 0;
const isSnow = wcode >= 71 && wcode <= 77;
// Fell-spezifische Temperaturschwellen
const heatLimit = {
kurz: 25, mittel: 27, lang: 22, drahtaar: 26, doppel: 30, nackt: 20
}[felltyp] ?? 28;
const coldLimit = {
kurz: 8, mittel: 5, lang: 3, drahtaar: 5, doppel: -5, nackt: 15
}[felltyp] ?? 5;
if (d.thunderstorm)
return { label:'Gewitterangst-Wetter', sub:'Angsthasen lieber zu Hause lassen', emoji:'⛈️', color:'#7C3AED' };
if (isSnow && temp < 3)
@ -890,13 +929,13 @@ window.Page_wetter = (() => {
return { label:'Matschpfoten-Wetter', sub:'Pfoten nach der Runde gut abtrocknen', emoji:'🌨️', color:'#60A5FA' };
if (tempMin < 0 && precip < 30)
return { label:'Kristallklare Nasenluft', sub:'Kalt aber herrlich — Schnüffeln auf Maximum', emoji:'🌡️', color:'#60A5FA' };
if (temp < 5 && precip > 50)
if (temp < coldLimit && precip > 50)
return { label:'Kuschelwetter', sub:'Kurze Runde, dann ab auf das Sofa', emoji:'🏠', color:'#6B7280' };
if (temp < 5)
if (temp < coldLimit)
return { label:'Fellkuschelwetter', sub:'Frisch und klar — ideal für aktive Rassen', emoji:'🧣', color:'#93C5FD' };
if (temp > 28 && asphalt > 50)
if (temp > heatLimit && asphalt > 50)
return { label:'Pfoten-Alarm!', sub:'Asphalt zu heiß — früh morgens oder abends raus', emoji:'🔥', color:'#EF4444' };
if (temp > 28)
if (temp > heatLimit)
return { label:'Schwimm-Wetter', sub:'Bach oder See suchen — Hunde überhitzen schnell', emoji:'🏊', color:'#F97316' };
if (precip > 70 && temp < 15)
return { label:'Nass-Hund-Wetter', sub:'Handtuch bereit? Der Geruch kommt garantiert', emoji:'💧', color:'#3B82F6' };

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v692';
const CACHE_VERSION = 'by-v693';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache