UX: Tierarztbesuch-Tab — Praxis-Verknüpfung als Kernfunktion

- Formular: Praxis-Dropdown ersetzt Freitext wenn Praxen vorhanden
- Kein redundantes Freitext-Feld mehr, Ort in der Option angezeigt
- "Praxis anlegen"-Link navigiert direkt zum Praxen-Tab
- tierarzt_name wird zusätzlich als Sicherheitskopie gespeichert
- Karte: zeigt Praxisname + Ort unter dem Besuchsgrund
- Detail-Modal: Praxis mit Adresse und anklickbarer Telefonnummer
- SW-Cache → by-v12
This commit is contained in:
rene 2026-04-13 20:30:36 +02:00
parent d9f2e85263
commit e9587d4ecd
2 changed files with 69 additions and 45 deletions

View file

@ -245,23 +245,32 @@ window.Page_health = (() => {
function _renderTierarzt(entries) {
const addBtn = `<button class="btn btn-primary btn-sm" data-action="add-entry">+ Besuch eintragen</button>`;
if (!entries.length) return UI.emptyState({
icon: '🏥', title: 'Noch keine Tierarztbesuche', text: 'Halte alle Tierarztbesuche fest.', action: addBtn
icon: '🩺', title: 'Noch keine Tierarztbesuche', text: 'Halte alle Tierarztbesuche fest.', action: addBtn
});
const items = entries.map(e => `
<div class="health-card" data-id="${e.id}" data-action="open-entry">
<div class="health-card-body">
<div class="health-card-title">${_esc(e.bezeichnung)}</div>
<div class="health-card-meta">
${UI.time.format(e.datum + 'T00:00:00')}
${e.tierarzt_name ? ` · ${_esc(e.tierarzt_name)}` : ''}
${e.kosten != null ? ` · ${Number(e.kosten).toFixed(2)}` : ''}
const items = entries.map(e => {
const praxis = _praxen.find(p => p.id === e.tierarzt_id);
const praxisName = praxis?.name || e.tierarzt_name || '';
const praxisOrt = praxis ? [praxis.plz, praxis.ort].filter(Boolean).join(' ') : '';
return `
<div class="health-card" data-id="${e.id}" data-action="open-entry">
<div class="health-card-body">
<div class="health-card-title">${_esc(e.bezeichnung)}</div>
<div class="health-card-meta">
${UI.time.format(e.datum + 'T00:00:00')}
${e.kosten != null ? ` · ${Number(e.kosten).toFixed(2)}` : ''}
</div>
${praxisName ? `
<div style="display:flex;align-items:center;gap:var(--space-1);
margin-top:var(--space-1);font-size:var(--text-sm);color:var(--c-text-secondary)">
🏥 ${_esc(praxisName)}${praxisOrt ? ` · ${_esc(praxisOrt)}` : ''}
</div>` : ''}
${e.diagnose ? `<div class="health-card-note"><b>Diagnose:</b> ${_esc(e.diagnose)}</div>` : ''}
${e.notiz ? `<div class="health-card-note">${_esc(e.notiz)}</div>` : ''}
</div>
${e.diagnose ? `<div class="health-card-note"><b>Diagnose:</b> ${_esc(e.diagnose)}</div>` : ''}
${e.notiz ? `<div class="health-card-note">${_esc(e.notiz)}</div>` : ''}
</div>
</div>
`).join('');
`;
}).join('');
return `<div class="health-list">${items}</div>
<div style="text-align:center;padding:var(--space-4)">${addBtn}</div>`;
@ -574,7 +583,16 @@ window.Page_health = (() => {
const rows = [];
if (e.datum) rows.push(['Datum', UI.time.format(e.datum + 'T00:00:00')]);
if (e.naechstes) rows.push(['Nächstes', UI.time.format(e.naechstes + 'T00:00:00')]);
if (e.tierarzt_name) rows.push(['Tierarzt', _esc(e.tierarzt_name)]);
if (e.tierarzt_id) {
const praxis = _praxen.find(p => p.id === e.tierarzt_id);
if (praxis) {
const adresse = [praxis.strasse, [praxis.plz, praxis.ort].filter(Boolean).join(' ')].filter(Boolean).join(', ');
const tel = praxis.telefon ? ` · <a href="tel:${_esc(praxis.telefon)}">${_esc(praxis.telefon)}</a>` : '';
rows.push(['Praxis', `🏥 ${_esc(praxis.name)}${adresse ? `<br><small style="color:var(--c-text-secondary)">${_esc(adresse)}${tel}</small>` : tel}`]);
}
} else if (e.tierarzt_name) {
rows.push(['Tierarzt', _esc(e.tierarzt_name)]);
}
if (e.charge_nr) rows.push(['Charge-Nr.', _esc(e.charge_nr)]);
if (e.kosten != null) rows.push(['Kosten', `${Number(e.kosten).toFixed(2)}`]);
if (e.diagnose) rows.push(['Diagnose', _esc(e.diagnose)]);
@ -648,17 +666,12 @@ window.Page_health = (() => {
const form = document.getElementById('health-form');
setTimeout(() => {
form?.querySelector('[name="bezeichnung"]')?.focus();
// Praxis-Dropdown: Name auto-befüllen
const praxisSelect = document.getElementById('health-praxis-select');
const nameInput = document.getElementById('health-tierarzt-name-input');
if (praxisSelect && nameInput) {
praxisSelect.addEventListener('change', () => {
const selected = praxisSelect.options[praxisSelect.selectedIndex];
if (selected.value) {
nameInput.value = selected.dataset.name || selected.textContent.trim();
}
});
}
// "Praxis anlegen" Button im Formular
form?.querySelector('[data-action="goto-praxen"]')?.addEventListener('click', () => {
UI.modal.close();
_activeTab = 'praxen';
_renderTab();
});
}, 150);
document.getElementById('health-form-cancel')?.addEventListener('click', UI.modal.close);
@ -743,25 +756,31 @@ window.Page_health = (() => {
`;
case 'tierarzt': {
const aktivePraxen = _praxen.filter(p => p.aktiv);
const praxisDropdown = aktivePraxen.length ? `
<div class="form-group">
<label class="form-label">Praxis auswählen</label>
<select class="form-control" id="health-praxis-select" name="tierarzt_id">
<option value=""> Praxis wählen </option>
${aktivePraxen.map(p => `
<option value="${p.id}" data-name="${_esc(p.name)}"
${entry?.tierarzt_id === p.id ? 'selected' : ''}>
${_esc(p.name)}
</option>`).join('')}
</select>
</div>` : '';
const praxisField = aktivePraxen.length
? `<div class="form-group">
<label class="form-label">Behandelnde Praxis</label>
<select class="form-control" id="health-praxis-select" name="tierarzt_id">
<option value=""> Praxis wählen </option>
${aktivePraxen.map(p => `
<option value="${p.id}"
${entry?.tierarzt_id === p.id ? 'selected' : ''}>
${_esc(p.name)}${p.ort ? ` · ${_esc(p.ort)}` : ''}
</option>`).join('')}
</select>
</div>`
: `<div class="form-group">
<div style="padding:var(--space-3);background:var(--c-bg);border-radius:var(--radius-md);
font-size:var(--text-sm);color:var(--c-text-secondary)">
🏥 Noch keine Praxis angelegt
<button type="button" class="btn btn-ghost btn-sm" style="padding:0;font-size:inherit"
data-action="goto-praxen">Praxis im Tab Praxen anlegen</button>
</div>
<label class="form-label" style="margin-top:var(--space-2)">Tierarzt / Praxis (Freitext)</label>
<input class="form-control" name="tierarzt_name"
value="${_esc(entry?.tierarzt_name || '')}" placeholder="Dr. Muster">
</div>`;
return `
${praxisDropdown}
<div class="form-group">
<label class="form-label">Tierarzt / Praxis (Freitext)</label>
<input class="form-control" type="text" id="health-tierarzt-name-input"
name="tierarzt_name" value="${_esc(entry?.tierarzt_name || '')}">
</div>
${praxisField}
<div class="form-group">
<label class="form-label">Diagnose</label>
<textarea class="form-control" name="diagnose" rows="2">${_esc(entry?.diagnose || '')}</textarea>
@ -845,7 +864,12 @@ window.Page_health = (() => {
if (typ === 'gewicht') p.bezeichnung = `${p.wert} kg`;
}
if (fd.kosten) p.kosten = parseFloat(fd.kosten.toString().replace(',', '.'));
if (fd.tierarzt_id) p.tierarzt_id = parseInt(fd.tierarzt_id);
if (fd.tierarzt_id) {
p.tierarzt_id = parseInt(fd.tierarzt_id);
// Praxisname auch als tierarzt_name sichern (bleibt lesbar wenn Praxis inaktiv/gelöscht)
const praxis = _praxen.find(x => x.id === p.tierarzt_id);
if (praxis) p.tierarzt_name = praxis.name;
}
if (typ === 'medikament') {
p.aktiv = 'aktiv' in fd ? 1 : 0;
}

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications
============================================================ */
const CACHE_VERSION = 'by-v11';
const CACHE_VERSION = 'by-v12';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
// Diese Dateien werden beim Install gecacht (App Shell)