Pflege-System: Pflegetipps im Hundeprofil + Rassen-Autocomplete

- GET /api/dogs/{id}/pflege: rassenspezifische Pflegetipps
- pflege_tipps DB-Tabelle (43 Tipps, 10 Kategorien) geseedet
- dogs.rasse_id für Wiki-Verknüpfung (Migration)
- Hundeprofil: Tipp des Tages + alle Tipps aufklappbar
- Hundeprofil-Edit: Rassen-Autocomplete mit Wiki-Match-Badge
- Social: Post-Bestätigung (Gepostet!-Button, Quick-Mark, Pending-Banner)
- Social: Pflegetipp-Button (allg. + rassenspezifisch)
- Social: Diversitäts-Check, Kategorie-Tagging
- Social: 104 Übungen, Übungsübersicht-Modal
- Admin: Social-Media-Tracking-Sektion
- SW by-v356, APP_VER 343
This commit is contained in:
rene 2026-04-24 20:56:47 +02:00
parent 75615140c4
commit ba5547f993
7 changed files with 797 additions and 9 deletions

View file

@ -149,6 +149,7 @@ window.Page_dog_profile = (() => {
` : ''}
<div id="dp-skills" style="margin-bottom:var(--space-5);text-align:left"></div>
<div id="dp-pflege" style="margin-bottom:var(--space-5);text-align:left"></div>
${dog.is_public ? `
<div style="background:var(--c-primary-subtle);border:1px solid var(--c-primary-light);
@ -220,6 +221,9 @@ window.Page_dog_profile = (() => {
// Skills laden
_loadSkills(dog);
// Pflegetipps laden
_loadPflegeTipps(dog);
// Sitter-Zugang laden (nur für Besitzer)
if (dog.user_id === _appState.user?.id) {
_loadSittingAccess(dog.id);
@ -324,6 +328,123 @@ window.Page_dog_profile = (() => {
</div>`;
}
// ----------------------------------------------------------
// PFLEGETIPPS
// ----------------------------------------------------------
async function _loadPflegeTipps(dog) {
const el = document.getElementById('dp-pflege');
if (!el) return;
let data;
try {
data = await API.get(`/dogs/${dog.id}/pflege`);
} catch { return; }
if (!data?.tipps?.length) return;
const t = data.tipp_des_tages;
const kat_icons = {
'Fell':'✂️','Krallen':'💅','Zähne':'🦷','Ohren':'👂',
'Augen':'👁','Pfoten':'🐾','Parasiten':'🦟',
'Saisonal':'🌸','Gesundheitsvorsorge':'❤️','Welpen-Pflege':'🐶',
};
el.innerHTML = `
<div class="card" style="padding:var(--space-4)">
<div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-3)">
<span style="font-size:1.1em">🛁</span>
<span style="font-size:var(--text-sm);font-weight:600">
Pflegetipps${data.rasse_name ? ` für ${_esc(data.rasse_name)}` : ''}
</span>
</div>
${t ? `
<!-- Tipp des Tages -->
<div style="background:var(--c-surface-2);border-radius:10px;padding:12px;
margin-bottom:var(--space-3);border-left:3px solid #a78bfa">
<div style="font-size:10px;font-weight:700;color:#a78bfa;text-transform:uppercase;
letter-spacing:.5px;margin-bottom:4px">
${t.saisonal_aktuell ? '🌸 Aktuell & Saisonal' : '💡 Tipp des Tages'}
</div>
<div style="font-weight:600;font-size:var(--text-sm);margin-bottom:4px">
${kat_icons[t.kategorie]||'🐾'} ${_esc(t.titel)}
</div>
<div style="font-size:12px;color:var(--c-text-secondary);margin-bottom:8px;
line-height:1.5">${_esc(t.beschreibung||'')}</div>
${t.haeufigkeit ? `<div style="font-size:11px;color:var(--c-text-muted)">
🔄 ${_esc(t.haeufigkeit)}</div>` : ''}
${t.materialien ? `<div style="font-size:11px;color:var(--c-text-muted)">
🛒 ${_esc(t.materialien)}</div>` : ''}
${t.schritte?.length ? `
<details style="margin-top:8px">
<summary style="font-size:12px;cursor:pointer;color:var(--c-primary);
font-weight:600">Anleitung anzeigen</summary>
<ol style="margin:8px 0 0 16px;padding:0;font-size:12px;
color:var(--c-text);line-height:1.6">
${t.schritte.map(s=>`<li style="margin-bottom:3px">${_esc(s)}</li>`).join('')}
</ol>
${t.tipp ? `<div style="margin-top:8px;font-size:11px;color:#a78bfa;
font-style:italic">💜 ${_esc(t.tipp)}</div>` : ''}
</details>` : ''}
</div>` : ''}
<!-- Alle Tipps Button -->
<button id="dp-pflege-alle" class="btn btn-secondary btn-sm"
style="width:100%;font-size:12px">
Alle ${data.tipps.length} Pflegetipps anzeigen
</button>
<div id="dp-pflege-liste" style="display:none;margin-top:var(--space-3)">
${data.kategorien.map(kat => {
const katTipps = data.tipps.filter(t=>t.kategorie===kat);
return `
<div style="margin-bottom:var(--space-3)">
<div style="font-size:11px;font-weight:700;color:var(--c-text-muted);
text-transform:uppercase;margin-bottom:8px">
${kat_icons[kat]||'🐾'} ${_esc(kat)}</div>
${katTipps.map(tip => `
<details style="background:var(--c-surface-2);border-radius:8px;
padding:10px;margin-bottom:6px">
<summary style="font-size:var(--text-sm);font-weight:600;cursor:pointer;
list-style:none;display:flex;justify-content:space-between;
align-items:center">
${_esc(tip.titel)}
${tip.saisonal_aktuell ? '<span style="font-size:10px;color:#10b981">● Aktuell</span>' : ''}
</summary>
<div style="margin-top:8px;font-size:12px;color:var(--c-text-secondary);
line-height:1.5">${_esc(tip.beschreibung||'')}</div>
${tip.haeufigkeit ? `<div style="font-size:11px;color:var(--c-text-muted);
margin-top:4px">🔄 ${_esc(tip.haeufigkeit)}</div>` : ''}
${tip.schritte?.length ? `
<ol style="margin:8px 0 0 16px;padding:0;font-size:12px;line-height:1.6">
${tip.schritte.map(s=>`<li style="margin-bottom:3px">${_esc(s)}</li>`).join('')}
</ol>` : ''}
${tip.tipp ? `<div style="margin-top:6px;font-size:11px;color:#a78bfa;
font-style:italic">💜 ${_esc(tip.tipp)}</div>` : ''}
</details>`).join('')}
</div>`;
}).join('')}
</div>
</div>`;
el.querySelector('#dp-pflege-alle')?.addEventListener('click', e => {
const liste = el.querySelector('#dp-pflege-liste');
const btn = e.currentTarget;
if (liste.style.display === 'none') {
liste.style.display = '';
btn.textContent = 'Pflegetipps einklappen ▲';
} else {
liste.style.display = 'none';
btn.textContent = `Alle ${data.tipps.length} Pflegetipps anzeigen`;
}
});
}
function _esc(s) {
if (!s) return '';
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;')
.replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// ----------------------------------------------------------
// SITTER-ZUGANG
// ----------------------------------------------------------
@ -788,11 +909,21 @@ window.Page_dog_profile = (() => {
<label class="form-label">
Rasse
<span style="color:var(--c-text-secondary)">(optional)</span>
${UI.help('Die Rasse wird für Rasseninformationen und Statistiken verwendet.')}
${UI.help('Verknüpfe deine Rasse mit unserem Wiki für personalisierte Pflegetipps.')}
</label>
<input class="form-control" type="text" name="rasse"
id="dp-rasse-input"
value="${_esc(dog?.rasse || '')}"
list="dp-rasse-list"
autocomplete="off"
placeholder="z. B. Mischling, Golden Retriever…">
<datalist id="dp-rasse-list"></datalist>
<input type="hidden" name="rasse_id" id="dp-rasse-id"
value="${dog?.rasse_id || ''}">
<div id="dp-rasse-match" style="display:none;margin-top:4px;font-size:11px;
color:var(--c-success);font-weight:600">
Mit Wiki verknüpft Pflegetipps werden auf diese Rasse angepasst
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
@ -882,6 +1013,45 @@ window.Page_dog_profile = (() => {
const form = document.getElementById('dp-form');
if (!form) return;
// Rassen-Autocomplete aus Wiki laden
let _wikiBreeds = [];
API.get('/wiki/rassen?limit=1000&offset=0').then(data => {
_wikiBreeds = data.rassen || [];
const list = document.getElementById('dp-rasse-list');
if (list) {
list.innerHTML = _wikiBreeds.map(r =>
`<option value="${r.name.replace(/"/g,'&quot;')}" data-id="${r.id}">`
).join('');
}
// Vorhandene Rasse: Match prüfen und Badge zeigen
const rasseInput = document.getElementById('dp-rasse-input');
const rasseIdInput = document.getElementById('dp-rasse-id');
const matchBadge = document.getElementById('dp-rasse-match');
if (rasseInput?.value) {
const match = _wikiBreeds.find(r =>
r.name.toLowerCase() === rasseInput.value.toLowerCase());
if (match && matchBadge) {
if (!rasseIdInput.value) rasseIdInput.value = match.id;
matchBadge.style.display = '';
}
}
}).catch(() => {});
// Rassen-Input: bei Änderung ID nachschlagen
document.getElementById('dp-rasse-input')?.addEventListener('input', e => {
const val = e.target.value.trim().toLowerCase();
const rasseIdInput = document.getElementById('dp-rasse-id');
const matchBadge = document.getElementById('dp-rasse-match');
const match = _wikiBreeds.find(r => r.name.toLowerCase() === val);
if (match) {
rasseIdInput.value = match.id;
if (matchBadge) matchBadge.style.display = '';
} else {
rasseIdInput.value = '';
if (matchBadge) matchBadge.style.display = 'none';
}
});
// Foto-Vorschau
const fotoInput = document.getElementById('dp-form-foto');
const fotoPreview = document.getElementById('dp-form-preview');
@ -935,6 +1105,7 @@ window.Page_dog_profile = (() => {
const payload = {
name: fd.name.trim(),
rasse: fd.rasse || null,
rasse_id: fd.rasse_id ? parseInt(fd.rasse_id) : null,
geburtstag: fd.geburtstag || null,
geschlecht: fd.geschlecht || null,
gewicht_kg: fd.gewicht_kg ? parseFloat(fd.gewicht_kg) : null,