Feature: Schnell-Gassi-Log + Hunde-Visitenkarte mit QR-Code (SW by-v698)

- Worlds-FAB: neuer 'Schnell-Gassi' Button im Gassi-Chip — öffnet schlankes
  Bottom-Sheet mit Dauer-Toggle (15/30/45/60 min), auto-Wetter aus Cache,
  postet direkt als Tagebucheintrag typ='gassi' ohne GPS-Tracking
- dog-profile.js: 'Visitenkarte teilen' Button öffnet Modal mit gestalteter
  Karte (Hundefoto, Name, Rasse/Alter, Wohnort) + QR-Code via qrserver.com,
  Link-kopieren und native Web-Share-API
This commit is contained in:
rene 2026-05-04 20:52:11 +02:00
parent 6e4bf25581
commit a4e97348ed
2 changed files with 356 additions and 62 deletions

View file

@ -195,9 +195,18 @@ window.Page_dog_profile = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#notebook"></use></svg>
Hundepass
</button>` : ''}
${!dog.is_guest ? `<button class="btn btn-secondary w-full" id="dp-vcard-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#identification-card"></use></svg>
Visitenkarte teilen
</button>` : ''}
${!dog.is_guest ? `<button class="btn btn-secondary w-full" id="dp-add-dog-btn">
+ Weiteren Hund anlegen
</button>` : ''}
${!dog.is_guest ? `<button class="btn btn-secondary w-full" id="dp-wrapped-btn"
style="background:linear-gradient(135deg,#1a1a2e,#16213e);color:#e8c96e;
border-color:transparent;font-weight:700">
Jahresrückblick ${new Date().getFullYear()}
</button>` : ''}
</div>
</div>
@ -264,6 +273,14 @@ window.Page_dog_profile = (() => {
_showPassportModal(dog);
});
document.getElementById('dp-vcard-btn')?.addEventListener('click', () => {
_showVcardModal(dog);
});
document.getElementById('dp-wrapped-btn')?.addEventListener('click', () => {
_showWrappedModal(dog);
});
// Edit- und Add-Klicks laufen über Event-Delegation in init() — keine direkten Listener nötig.
}
@ -750,6 +767,138 @@ window.Page_dog_profile = (() => {
// ----------------------------------------------------------
// TEILEN
// ----------------------------------------------------------
// ----------------------------------------------------------
// HUNDE-VISITENKARTE MIT QR-CODE
// ----------------------------------------------------------
function _showVcardModal(dog) {
const passportUrl = `https://banyaro.app/hund/${dog.id}`;
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=140x140&color=ffffff&bgcolor=1a2035&data=${encodeURIComponent(passportUrl)}`;
const user = _appState?.user;
const ownerName = user?.name || '';
const wohnort = user?.wohnort || '';
// Alter errechnen
let alterStr = '';
if (dog.geburtstag) {
const birth = new Date(dog.geburtstag + 'T00:00:00');
const now = new Date();
const years = now.getFullYear() - birth.getFullYear()
- (now < new Date(now.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0);
alterStr = years < 1
? `${Math.max(1, Math.round((now - birth) / (30.5 * 86400000)))} Monate`
: years === 1 ? '1 Jahr' : `${years} Jahre`;
}
const metaLine = [dog.rasse, alterStr].filter(Boolean).join(' · ');
const cardHtml = `
<div id="dp-vcard-canvas" style="
background:linear-gradient(135deg,#0f1a2b 0%,#1a2a45 60%,#0d2137 100%);
border-radius:20px;padding:24px 20px;color:white;
font-family:system-ui,-apple-system,sans-serif;
position:relative;overflow:hidden;max-width:340px;margin:0 auto;
box-shadow:0 8px 32px rgba(0,0,0,0.4)">
<!-- Deko-Kreis -->
<div style="position:absolute;top:-30px;right:-30px;width:120px;height:120px;
border-radius:50%;background:rgba(196,132,58,0.12);pointer-events:none"></div>
<div style="position:absolute;bottom:-40px;left:-20px;width:100px;height:100px;
border-radius:50%;background:rgba(196,132,58,0.07);pointer-events:none"></div>
<!-- Header -->
<div style="display:flex;align-items:center;gap:12px;margin-bottom:18px">
${dog.foto_url
? `<img src="${_esc(dog.foto_url)}" style="width:52px;height:52px;border-radius:50%;object-fit:cover;
border:2px solid rgba(196,132,58,0.6);flex-shrink:0">`
: `<div style="width:52px;height:52px;border-radius:50%;background:rgba(196,132,58,0.2);
display:flex;align-items:center;justify-content:center;font-size:1.6rem;
flex-shrink:0;border:2px solid rgba(196,132,58,0.4)">🐾</div>`}
<div>
<div style="font-size:1.25rem;font-weight:800;color:#fff;line-height:1.2">${_esc(dog.name)}</div>
${metaLine ? `<div style="font-size:0.8rem;color:rgba(255,255,255,0.6);margin-top:2px">${_esc(metaLine)}</div>` : ''}
${wohnort ? `<div style="font-size:0.75rem;color:rgba(196,132,58,0.9);margin-top:3px">📍 ${_esc(wohnort)}</div>` : ''}
</div>
</div>
<!-- Divider -->
<div style="height:1px;background:rgba(255,255,255,0.1);margin-bottom:16px"></div>
<!-- Owner + QR -->
<div style="display:flex;align-items:flex-end;justify-content:space-between;gap:12px">
<div style="flex:1;min-width:0">
${ownerName ? `<div style="font-size:0.7rem;color:rgba(255,255,255,0.4);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px">Besitzer</div>
<div style="font-size:0.9rem;font-weight:600;color:rgba(255,255,255,0.85)">${_esc(ownerName)}</div>` : ''}
<div style="font-size:0.65rem;color:rgba(255,255,255,0.35);margin-top:8px">banyaro.app</div>
</div>
<div style="flex-shrink:0;text-align:center">
<img id="dp-vcard-qr" src="${_esc(qrUrl)}"
style="width:80px;height:80px;border-radius:10px;display:block"
alt="QR-Code">
<div style="font-size:0.6rem;color:rgba(255,255,255,0.35);margin-top:4px">Profil öffnen</div>
</div>
</div>
</div>
`;
UI.modal.open({
title: 'Visitenkarte',
body: `
<div style="margin-bottom:var(--space-4)">${cardHtml}</div>
<p style="font-size:var(--text-xs);color:var(--c-text-secondary);text-align:center;margin-bottom:0">
QR-Code auf NFC-Tag oder Anhänger kleben jeder kann das Profil von ${_esc(dog.name)} sofort öffnen.
</p>
`,
footer: `
<button class="btn btn-secondary" id="dp-vcard-copy-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#link"></use></svg>
Link kopieren
</button>
<button class="btn btn-primary" id="dp-vcard-share-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#share-network"></use></svg>
Teilen
</button>
`,
});
// Link kopieren
document.getElementById('dp-vcard-copy-btn')?.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(passportUrl);
UI.toast.success('Link kopiert!');
} catch {
const inp = document.createElement('input');
inp.value = passportUrl;
document.body.appendChild(inp);
inp.select();
document.execCommand('copy');
inp.remove();
UI.toast.success('Link kopiert!');
}
});
// Native Share API
document.getElementById('dp-vcard-share-btn')?.addEventListener('click', async () => {
if (navigator.share) {
try {
await navigator.share({
title: `${dog.name} auf Ban Yaro`,
text: `Schau dir das Profil von ${dog.name} an!`,
url: passportUrl,
});
} catch {}
} else {
// Fallback: kopieren
try {
await navigator.clipboard.writeText(passportUrl);
UI.toast.success('Link kopiert!');
} catch {
UI.toast.error('Teilen nicht verfügbar.');
}
}
});
}
async function _showShareModal(dog) {
UI.modal.open({
title: `${_esc(dog.name)} teilen`,