Sprint 15: Suche, Ausweis, Teilen, Widget

- Volltext-Suche im Tagebuch (LIKE über Titel/Text/Tags, Debounce 350ms)
- Digitaler Heimtierausweis als druckbare HTML-Seite (/ausweis/{dog_id})
  Enthält Impfungen, Medikamente, Allergien, Tierärzte, Chip-Nr.
- Hund teilen: Einladungslink-System (dog_shares-Tabelle, /teilen/{token})
  Geteilte Hunde erscheinen in der Hundeliste, Tagebuch/Gesundheit lesbar
- Widget-Seite /#widget: zufälliges Tagebuchbild + nächste Erinnerung
  Als PWA-Shortcut im Manifest verankert
- SW-Cache by-v144, APP_VER 117
This commit is contained in:
rene 2026-04-17 15:51:09 +02:00
parent d5f09cd16b
commit 34f29f9d0a
16 changed files with 917 additions and 35 deletions

View file

@ -9,11 +9,12 @@ window.Page_diary = (() => {
// ----------------------------------------------------------
// MODUL-STATE
// ----------------------------------------------------------
let _container = null;
let _appState = null;
let _entries = [];
let _offset = 0;
const LIMIT = 20;
let _container = null;
let _appState = null;
let _entries = [];
let _offset = 0;
let _searchQuery = '';
const LIMIT = 20;
const TYPEN = {
eintrag: { label: 'Eintrag', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#book-open"></use></svg>' },
@ -53,9 +54,9 @@ window.Page_diary = (() => {
// ON DOG CHANGE — vom Header-Switcher ausgelöst
// ----------------------------------------------------------
async function onDogChange(dog) {
_offset = 0;
_entries = [];
// Direkt Diary laden — Hund wurde bereits extern gewählt
_offset = 0;
_entries = [];
_searchQuery = '';
await _renderDiary();
}
@ -136,9 +137,13 @@ window.Page_diary = (() => {
async function _renderDiary() {
_container.innerHTML = `
<div class="by-toolbar diary-toolbar">
<button class="btn btn-secondary btn-sm" id="diary-import-btn">
<div class="diary-search-wrap" id="diary-search-wrap">
<svg class="ph-icon diary-search-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
<input type="search" class="diary-search-input" id="diary-search-input"
placeholder="Einträge durchsuchen…" autocomplete="off">
</div>
<button class="btn btn-secondary btn-sm" id="diary-import-btn" title="Importieren">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#download-simple"></use></svg>
Importieren
</button>
</div>
<div id="diary-list"></div>
@ -152,6 +157,20 @@ window.Page_diary = (() => {
_container.querySelector('#diary-btn-more')
?.addEventListener('click', () => _loadMore());
// Suche mit Debounce
let _searchTimer = null;
_container.querySelector('#diary-search-input')
?.addEventListener('input', e => {
clearTimeout(_searchTimer);
_searchTimer = setTimeout(async () => {
_offset = 0;
_entries = [];
_searchQuery = e.target.value.trim();
await _load();
_renderList();
}, 350);
});
await _load();
_renderList();
}
@ -163,7 +182,9 @@ window.Page_diary = (() => {
const dog = _appState.activeDog;
if (!dog) return;
try {
const batch = await API.diary.list(dog.id, { limit: LIMIT, offset: _offset });
const params = { limit: LIMIT, offset: _offset };
if (_searchQuery) params.q = _searchQuery;
const batch = await API.diary.list(dog.id, params);
_entries = _entries.concat(batch);
// "Mehr laden" anzeigen wenn volle Page geladen wurde