Teil 3: Terminvorschläge + KI-Limit-Bypass für Admins/Mods — SW by-v435, APP_VER 414
- timeutils: next_appointment_slot() parst OSM opening_hours, findet Slot - GET /health/terminvorschlaege: fällige/überfällige Einträge (30-Tage-Horizont) Impfung/Tierarzt nutzen Praxis-Öffnungszeiten, Rest nächster Werktag 09:00 - Frontend: Terminvorschlags-Karten, bestätigbares Modal, legt Event an - ki.py: Admins, Moderatoren, Media Manager bypassen CLOUD_WEEKLY_LIMIT
This commit is contained in:
parent
570dcd4e93
commit
c935d3fbd4
7 changed files with 300 additions and 9 deletions
|
|
@ -153,6 +153,7 @@ window.Page_health = (() => {
|
|||
</div>
|
||||
${transponderHtml}
|
||||
<div id="health-ki-berichte"></div>
|
||||
<div id="health-terminvorschlaege"></div>
|
||||
<div id="health-reminders"></div>
|
||||
<div class="by-tabs" id="by-tabs"></div>
|
||||
<div id="by-tab-content"></div>
|
||||
|
|
@ -168,6 +169,7 @@ window.Page_health = (() => {
|
|||
_renderErinnerungen();
|
||||
_renderTab();
|
||||
_loadKiBerichte(dog.id);
|
||||
_loadTerminvorschlaege(dog.id);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -1990,6 +1992,126 @@ window.Page_health = (() => {
|
|||
|
||||
// ----------------------------------------------------------
|
||||
// KI-ZUSAMMENFASSUNG
|
||||
// ----------------------------------------------------------
|
||||
// TERMINVORSCHLÄGE
|
||||
// ----------------------------------------------------------
|
||||
async function _loadTerminvorschlaege(dogId) {
|
||||
const el = _container.querySelector('#health-terminvorschlaege');
|
||||
if (!el) return;
|
||||
try {
|
||||
const vorschlaege = await API.health.terminvorschlaege(dogId);
|
||||
if (!vorschlaege || !vorschlaege.length) return;
|
||||
|
||||
const _fmtDatum = iso => new Date(iso + 'T00:00:00').toLocaleDateString('de-DE', {
|
||||
weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric'
|
||||
});
|
||||
|
||||
el.innerHTML = `
|
||||
<div style="margin-bottom:var(--space-3)">
|
||||
<div style="font-size:var(--text-xs);font-weight:600;color:var(--c-text-muted);
|
||||
text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-2)">
|
||||
Terminvorschläge
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
${vorschlaege.map(v => {
|
||||
const badge = v.ueberfaellig
|
||||
? `<span style="font-size:var(--text-xs);color:var(--c-danger);font-weight:600">Überfällig seit ${_fmtDatum(v.naechstes)}</span>`
|
||||
: `<span style="font-size:var(--text-xs);color:var(--c-warning);font-weight:600">Fällig am ${_fmtDatum(v.naechstes)}</span>`;
|
||||
return `
|
||||
<div class="health-card" style="flex-direction:row;align-items:center;gap:var(--space-3)">
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(v.bezeichnung)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(v.label)}${v.praxis_name ? ' · ' + _esc(v.praxis_name) : ''}</div>
|
||||
${badge}
|
||||
</div>
|
||||
<div style="text-align:right;flex-shrink:0">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Vorschlag</div>
|
||||
<div style="font-size:var(--text-sm);font-weight:600">${_fmtDatum(v.datum_vorschlag)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${v.uhrzeit_vorschlag} Uhr</div>
|
||||
<button class="btn btn-primary btn-sm" style="margin-top:var(--space-1)"
|
||||
data-action="termin-anlegen"
|
||||
data-v='${_esc(JSON.stringify(v))}'>
|
||||
📅 In Kalender
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
el.querySelectorAll('[data-action="termin-anlegen"]').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
let v;
|
||||
try { v = JSON.parse(btn.dataset.v); } catch { return; }
|
||||
await _terminAnlegen(v, btn);
|
||||
});
|
||||
});
|
||||
} catch { /* still show health page if this fails */ }
|
||||
}
|
||||
|
||||
async function _terminAnlegen(v, btn) {
|
||||
const titel = v.beim_tierarzt
|
||||
? `${v.label}: ${v.bezeichnung} (Tierarzt)`
|
||||
: `${v.label}: ${v.bezeichnung}`;
|
||||
const beschreibung = v.praxis_name
|
||||
? `Praxis: ${v.praxis_name}`
|
||||
: v.ueberfaellig
|
||||
? `Überfällig seit ${v.naechstes}`
|
||||
: `Fällig am ${v.naechstes}`;
|
||||
|
||||
UI.modal.open({
|
||||
title: '📅 Termin in Kalender eintragen',
|
||||
body: `
|
||||
<form id="termin-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Bezeichnung</label>
|
||||
<input class="form-control" type="text" name="titel" value="${_esc(titel)}" required>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Datum</label>
|
||||
<input class="form-control" type="date" name="datum" value="${_esc(v.datum_vorschlag)}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Uhrzeit</label>
|
||||
<input class="form-control" type="time" name="uhrzeit" value="${_esc(v.uhrzeit_vorschlag)}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Notiz</label>
|
||||
<input class="form-control" type="text" name="beschreibung" value="${_esc(beschreibung)}">
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
footer: `
|
||||
<button type="button" class="btn btn-secondary flex-1" id="termin-cancel">Abbrechen</button>
|
||||
<button type="submit" form="termin-form" class="btn btn-primary flex-1">Speichern</button>
|
||||
`,
|
||||
});
|
||||
document.getElementById('termin-cancel')?.addEventListener('click', UI.modal.close);
|
||||
document.getElementById('termin-form')?.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const saveBtn = document.querySelector('[form="termin-form"][type="submit"]');
|
||||
const fd = UI.formData(e.target);
|
||||
await UI.asyncButton(saveBtn, async () => {
|
||||
await API.events.create({
|
||||
titel: fd.titel,
|
||||
datum: fd.datum,
|
||||
uhrzeit: fd.uhrzeit || null,
|
||||
beschreibung: fd.beschreibung || null,
|
||||
typ: v.beim_tierarzt ? 'tierarzt' : 'sonstiges',
|
||||
lat: v.praxis_lat ?? null,
|
||||
lon: v.praxis_lon ?? null,
|
||||
ort_name: v.praxis_name ?? null,
|
||||
});
|
||||
UI.modal.close();
|
||||
UI.toast.success('Termin gespeichert — erscheint in deinem Kalender.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
async function _showKiSummary() {
|
||||
const btn = _container.querySelector('#health-ki-btn');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue