diff --git a/backend/database.py b/backend/database.py
index 0bb98d6..3559537 100644
--- a/backend/database.py
+++ b/backend/database.py
@@ -301,6 +301,8 @@ def _migrate(conn_factory):
("tieraerzte", "strasse", "TEXT"),
("tieraerzte", "plz", "TEXT"),
("tieraerzte", "ort", "TEXT"),
+ # Gesundheit: Erinnerungsintervall für wiederkehrende Einträge
+ ("health", "intervall_tage", "INTEGER"),
]
with conn_factory() as conn:
for table, column, col_type in migrations:
diff --git a/backend/routes/health.py b/backend/routes/health.py
index e3eba24..6bb2592 100644
--- a/backend/routes/health.py
+++ b/backend/routes/health.py
@@ -42,6 +42,7 @@ class HealthCreate(BaseModel):
schweregrad: Optional[str] = None # leicht | mittel | schwer
reaktion: Optional[str] = None
erinnerung: Optional[int] = 1
+ intervall_tage: Optional[int] = None # Wiederkehrend alle X Tage
# Tierarzt-Verknüpfung
tierarzt_id: Optional[int] = None
@@ -64,6 +65,7 @@ class HealthUpdate(BaseModel):
schweregrad: Optional[str] = None
reaktion: Optional[str] = None
erinnerung: Optional[int] = None
+ intervall_tage: Optional[int] = None
tierarzt_id: Optional[int] = None
diff --git a/backend/static/js/pages/health.js b/backend/static/js/pages/health.js
index 094ffa9..89b1d11 100644
--- a/backend/static/js/pages/health.js
+++ b/backend/static/js/pages/health.js
@@ -122,6 +122,7 @@ window.Page_health = (() => {
✨ KI-Zusammenfassung
+
`;
@@ -131,9 +132,126 @@ window.Page_health = (() => {
.addEventListener('click', _showKiSummary);
await _loadAll();
+ _renderErinnerungen();
_renderTab();
}
+ // ----------------------------------------------------------
+ // ERINNERUNGEN — Banner über den Tabs
+ // ----------------------------------------------------------
+ function _getErinnerungen() {
+ const REMINDER_TABS = ['impfung', 'entwurmung', 'medikament'];
+ const now = Date.now();
+ const items = [];
+ REMINDER_TABS.forEach(typ => {
+ (_data[typ] || []).forEach(e => {
+ if (!e.naechstes) return;
+ const tage = Math.ceil((new Date(e.naechstes).getTime() - now) / 86400000);
+ if (tage <= 60) items.push({ ...e, _tage: tage, _typ: typ });
+ });
+ });
+ return items.sort((a, b) => a._tage - b._tage);
+ }
+
+ function _renderErinnerungen() {
+ const el = _container.querySelector('#health-reminders');
+ if (!el) return;
+
+ const items = _getErinnerungen();
+
+ // Nav-Badge aktualisieren (Anzahl überfälliger/bald fälliger Einträge)
+ const overdueCount = items.filter(e => e._tage < 0).length;
+ _updateHealthBadge(overdueCount || (items.length ? items.length : 0));
+
+ if (!items.length) { el.innerHTML = ''; return; }
+
+ const ICONS = { impfung: '💉', entwurmung: '🪱', medikament: '💊' };
+
+ el.innerHTML = `
+
+
+ 📅 Anstehende Erinnerungen
+
+ ${items.map(e => {
+ const ampel = _impfAmpel(e.naechstes);
+ const dateStr = UI.time.format(e.naechstes + 'T00:00:00');
+ const ageLabel = e._tage < 0
+ ? `Überfällig seit ${Math.abs(e._tage)} Tagen`
+ : e._tage === 0 ? 'Heute fällig'
+ : `In ${e._tage} Tagen`;
+ return `
+
+
${ICONS[e._typ] || '📋'}
+
+
+ ${_esc(e.bezeichnung)}
+
+
+ ${ageLabel} · ${dateStr}
+
+
+
+
`;
+ }).join('')}
+
+ `;
+
+ el.querySelectorAll('[data-action="reminder-erledigt"]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const id = parseInt(btn.dataset.entryId);
+ const typ = btn.dataset.entryTyp;
+ const entry = (_data[typ] || []).find(e => e.id === id);
+ if (!entry) return;
+ // Neues Formular ohne id → Neu-Eintrag, vorausgefüllt
+ const today = new Date().toISOString().slice(0, 10);
+ let naechstes = '';
+ if (entry.intervall_tage) {
+ const next = new Date();
+ next.setDate(next.getDate() + entry.intervall_tage);
+ naechstes = next.toISOString().slice(0, 10);
+ }
+ _showForm({
+ bezeichnung: entry.bezeichnung,
+ datum: today,
+ naechstes,
+ intervall_tage: entry.intervall_tage,
+ tierarzt_id: entry.tierarzt_id,
+ tierarzt_name: entry.tierarzt_name,
+ charge_nr: entry.charge_nr,
+ }, typ);
+ });
+ });
+ }
+
+ function _updateHealthBadge(count) {
+ ['[data-page="health"] .nav-item-icon',
+ '[data-page="health"] .sidebar-item-icon'].forEach(sel => {
+ document.querySelectorAll(sel).forEach(el => {
+ let badge = el.querySelector('.nav-badge');
+ if (count > 0) {
+ if (!badge) {
+ badge = document.createElement('span');
+ badge.className = 'nav-badge';
+ el.appendChild(badge);
+ }
+ badge.textContent = count > 9 ? '9+' : count;
+ } else if (badge) {
+ badge.remove();
+ }
+ });
+ });
+ }
+
function _renderTabBar() {
const tabsEl = _container.querySelector('#health-tabs');
tabsEl.innerHTML = TABS.map(t => `
@@ -624,7 +742,7 @@ window.Page_health = (() => {
// FORMULAR — Neu / Bearbeiten
// ----------------------------------------------------------
function _showForm(entry, typ) {
- const isEdit = !!entry;
+ const isEdit = !!(entry?.id);
const today = new Date().toISOString().slice(0, 10);
const t = typ || _activeTab;
@@ -739,6 +857,28 @@ window.Page_health = (() => {
return ph[typ] || '';
}
+ // Intervall-Auswahl für wiederkehrende Einträge
+ function _intervallField(entry) {
+ const v = entry?.intervall_tage;
+ const opts = [
+ [null, 'Einmalig'],
+ [30, 'Monatlich (30 Tage)'],
+ [60, 'Alle 2 Monate'],
+ [90, 'Vierteljährlich (90 Tage)'],
+ [180, 'Halbjährlich'],
+ [365, 'Jährlich'],
+ ];
+ return `
+
+
+
+
`;
+ }
+
// Wiederverwendbares Praxis-Dropdown für alle Formulare
function _praxisSelectField(entry) {
const aktivePraxen = _praxen.filter(p => p.aktiv);
@@ -759,9 +899,12 @@ window.Page_health = (() => {
function _extraFormFields(entry, typ) {
switch (typ) {
case 'impfung': return `
-