/* ============================================================ BAN YARO — Notizblock Seiten-Modul: Alle Notizen mit Filter, Suche, Sortierung und KI-Analyse. ============================================================ */ window.Page_notes = (() => { let _container = null; let _appState = null; let _notes = []; // Aktueller Filter-/Such-Zustand let _filterType = ''; // '' = alle let _sortMode = 'newest'; // newest | type | location let _searchQ = ''; let _searchTimer = null; // KI-Panel let _kiOpen = false; let _kiLoading = false; let _kiSuggestions = null; let _kiError = null; // ---------------------------------------------------------- // Rubrik-Konfiguration // ---------------------------------------------------------- const RUBRIKEN = [ { type: '', label: 'Alle', color: 'var(--c-text-muted)', icon: 'note' }, { type: 'health', label: 'Gesundheit', color: '#e74c3c', icon: 'heart' }, { type: 'diary', label: 'Tagebuch', color: '#C4843A', icon: 'book-open' }, { type: 'training_session', label: 'Training', color: '#27ae60', icon: 'target' }, { type: 'route', label: 'Routen', color: '#2980b9', icon: 'path' }, { type: 'event', label: 'Events', color: '#8e44ad', icon: 'calendar' }, { type: 'walk', label: 'Gassi-Treffen',color: '#f39c12', icon: 'paw-print' }, { type: 'sitting', label: 'Sitting', color: '#16a085', icon: 'house-line' }, { type: 'erste_hilfe', label: 'Erste Hilfe', color: '#c0392b', icon: 'first-aid' }, { type: 'trainingsplan', label: 'Trainingsplan',color: '#059669', icon: 'clipboard-text' }, { type: 'friends', label: 'Freunde', color: '#7c3aed', icon: 'users' }, { type: 'poison', label: 'Giftköder', color: '#dc2626', icon: 'warning-octagon' }, { type: 'lost', label: 'Vermisste', color: '#b45309', icon: 'magnifying-glass' }, ]; function _rubrik(type) { return RUBRIKEN.find(r => r.type === type) || { type, label: type, color: 'var(--c-text-muted)', icon: 'note' }; } // ---------------------------------------------------------- // Hilfsfunktionen // ---------------------------------------------------------- function _formatTime(isoStr) { if (!isoStr) return ''; try { const d = new Date(isoStr.replace(' ', 'T') + (isoStr.includes('T') || isoStr.endsWith('Z') ? '' : 'Z')); return d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); } catch (_) { return ''; } } function _dateGroup(isoStr) { if (!isoStr) return 'Älteres'; try { const d = new Date(isoStr.replace(' ', 'T') + (isoStr.includes('T') || isoStr.endsWith('Z') ? '' : 'Z')); const now = new Date(); const diffDays = (now - d) / 86400000; if (diffDays < 1 && d.getDate() === now.getDate()) return 'Heute'; if (diffDays < 7) return 'Diese Woche'; return 'Älteres'; } catch (_) { return 'Älteres'; } } function _truncate(str, max = 150) { if (!str) return ''; return str.length > max ? str.slice(0, max) + '…' : str; } // ---------------------------------------------------------- // Daten laden // ---------------------------------------------------------- async function _load() { const params = {}; if (_filterType) params.parent_type = _filterType; if (_sortMode !== 'newest') params.sort = _sortMode; if (_searchQ) params.q = _searchQ; return await API.notes.getAll(params); } // ---------------------------------------------------------- // Filter/Sortierung anwenden (client-seitig falls API alles zurückgibt) // ---------------------------------------------------------- function _applySort(list) { const copy = [...list]; if (_sortMode === 'newest') { copy.sort((a, b) => new Date(b.updated_at || b.created_at) - new Date(a.updated_at || a.created_at)); } else if (_sortMode === 'type') { copy.sort((a, b) => (a.parent_type || '').localeCompare(b.parent_type || '', 'de')); } else if (_sortMode === 'location') { copy.sort((a, b) => (a.location_name || '').localeCompare(b.location_name || '', 'de')); } return copy; } // ---------------------------------------------------------- // Rendern // ---------------------------------------------------------- function _render() { const kiEnabled = _appState?.user?.notes_ki_enabled !== 0; const sorted = _applySort(_notes); // Gruppen aufbauen const groups = { 'Heute': [], 'Diese Woche': [], 'Älteres': [] }; sorted.forEach(n => { const g = _dateGroup(n.updated_at || n.created_at); groups[g].push(n); }); const groupHtml = Object.entries(groups) .filter(([, items]) => items.length > 0) .map(([label, items]) => `
${UI.escape(label)}
${items.map(_noteCard).join('')}
`).join(''); _container.innerHTML = `

Notizblock

${_notes.length} Notiz${_notes.length !== 1 ? 'en' : ''}
${localStorage.getItem('by_notes_privacy_ack') ? '' : `
Alle Notizen sind privat — nur du kannst sie lesen.
`} ${kiEnabled ? _kiPanelHtml() : ''}
${RUBRIKEN.map(r => ` `).join('')}
${sorted.length === 0 ? UI.emptyState({ icon: 'note', title: 'Keine Notizen', text: 'Füge Notizen zu Trainingseinheiten oder anderen Einträgen hinzu.' }) : groupHtml }
`; _bindEvents(); } // ---------------------------------------------------------- // KI-Panel HTML // ---------------------------------------------------------- function _kiPanelHtml() { return `
Muster-Analyse
${_kiOpen ? `
${_kiError ? `
${UI.escape(_kiError)}
` : ''} ${_kiSuggestions ? `
    ${_kiSuggestions.map(s => `
  • ${UI.escape(s)}
  • `).join('')}
` : ''}
` : ''}
`; } // ---------------------------------------------------------- // Notiz-Karte HTML // ---------------------------------------------------------- function _noteCard(note) { const rb = _rubrik(note.parent_type); const meta = note.meta_json || {}; const microBadges = []; if (meta.erfolgsquote) microBadges.push('🐾'.repeat(meta.erfolgsquote)); if (meta.umgebung) microBadges.push({ zuhause: '🏠 Zuhause', natur: '🌿 Natur', stadt: '🌆 Stadt' }[meta.umgebung] || meta.umgebung); if (meta.hund_stimmung) microBadges.push({ super: '😊 Super', ok: '😐 Ok', mude: '😔 Müde' }[meta.hund_stimmung] || meta.hund_stimmung); const hasLocation = !!note.location_name; return `
${UI.escape(rb.label)} ${note.parent_label ? `${UI.escape(note.parent_label)}` : '' }

${UI.escape(_truncate(note.text))}

${microBadges.length ? `
${microBadges.map(b => `${UI.escape(b)}`).join('')}
` : ''}
${UI.escape(_formatTime(note.updated_at || note.created_at))} ${hasLocation ? ` ${UI.escape(note.location_name)}` : ''}
`; } // ---------------------------------------------------------- // Event-Binding // ---------------------------------------------------------- function _bindEvents() { // Datenschutz-Hinweis wegklicken _container.querySelector('#notes-privacy-notice')?.addEventListener('click', () => { localStorage.setItem('by_notes_privacy_ack', '1'); _container.querySelector('#notes-privacy-notice')?.remove(); }); // Neue Notiz _container.querySelector('#notes-new-btn')?.addEventListener('click', () => { _openCreateModal(_filterType || ''); }); // Filter-Chips _container.querySelectorAll('.notes-chip').forEach(btn => { btn.addEventListener('click', () => { _filterType = btn.dataset.type; _reload(); }); }); // Sortierung _container.querySelectorAll('.notes-sort-btn').forEach(btn => { btn.addEventListener('click', () => { _sortMode = btn.dataset.sort; _render(); // nur neu rendern, keine API-Last }); }); // Suche (debounced) const searchInput = _container.querySelector('#notes-search'); if (searchInput) { searchInput.addEventListener('input', () => { clearTimeout(_searchTimer); _searchTimer = setTimeout(() => { _searchQ = searchInput.value.trim(); _reload(); }, 300); }); } // KI-Toggle const kiToggle = _container.querySelector('#notes-ki-toggle'); if (kiToggle) { kiToggle.addEventListener('click', () => { _kiOpen = !_kiOpen; _render(); }); } // KI-Analyse-Button const kiBtn = _container.querySelector('#notes-ki-analyse-btn'); if (kiBtn) { kiBtn.addEventListener('click', async () => { _kiLoading = true; _kiError = null; _kiSuggestions = null; _render(); try { const res = await API.notes.analyse(); if (res && Array.isArray(res.suggestions)) { _kiSuggestions = res.suggestions; } else if (res && res.text) { _kiSuggestions = res.text.split('\n').filter(Boolean); } else { _kiSuggestions = ['Keine Vorschläge verfügbar.']; } } catch (err) { _kiError = err?.message || 'KI-Analyse nicht verfügbar.'; } finally { _kiLoading = false; _render(); } }); } // Edit-Buttons _container.querySelectorAll('.notes-edit-btn').forEach(btn => { btn.addEventListener('click', e => { e.stopPropagation(); const note = _notes.find(n => n.id === parseInt(btn.dataset.id, 10)); if (note) _openEditModal(note); }); }); // Delete-Buttons _container.querySelectorAll('.notes-delete-btn').forEach(btn => { btn.addEventListener('click', async e => { e.stopPropagation(); const noteId = parseInt(btn.dataset.id, 10); if (!window.confirm('Notiz wirklich löschen?')) return; try { await API.notes.delete(noteId); _notes = _notes.filter(n => n.id !== noteId); _render(); UI.toast.success('Notiz gelöscht.'); } catch (_) { UI.toast.error('Löschen fehlgeschlagen.'); } }); }); } // ---------------------------------------------------------- // Laden + Re-Render // ---------------------------------------------------------- async function _reload() { _container.querySelector('.notes-list')?.classList.add('loading'); try { _notes = await _load(); } catch (_) { _notes = []; } _render(); } // ---------------------------------------------------------- // Create-Modal — neue Notiz mit vorausgewählter Kategorie // ---------------------------------------------------------- function _openCreateModal(preselectedType = '') { const ERSTELL_RUBRIKEN = RUBRIKEN.filter(r => r.type !== ''); // ohne "Alle" let _selType = preselectedType || ERSTELL_RUBRIKEN[0].type; const modalId = 'notes-create-modal'; document.getElementById(modalId)?.remove(); const overlay = document.createElement('div'); overlay.id = modalId; overlay.style.cssText = `position:fixed;inset:0;z-index:9999;display:flex;align-items:flex-end;justify-content:center;background:rgba(0,0,0,0.45)`; const _buildContent = () => { const rb = _rubrik(_selType); return `

Neue Notiz

${ERSTELL_RUBRIKEN.map(r => ` `).join('')}
`; }; overlay.innerHTML = _buildContent(); document.body.appendChild(overlay); const _rebind = () => { overlay.querySelectorAll('.nc-cat').forEach(btn => { btn.addEventListener('click', () => { _selType = btn.dataset.type; overlay.innerHTML = _buildContent(); _rebind(); overlay.querySelector('#nc-text')?.focus(); }); }); overlay.querySelector('#nc-cancel')?.addEventListener('click', () => overlay.remove()); overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); }); overlay.querySelector('#nc-save')?.addEventListener('click', async () => { const text = overlay.querySelector('#nc-text')?.value?.trim(); if (!text) { UI.toast.warning('Bitte einen Text eingeben.'); return; } const btn = overlay.querySelector('#nc-save'); await UI.asyncButton(btn, async () => { const rb = _rubrik(_selType); await API.notes.create(_selType, 'standalone', { text, parent_label: rb.label, }); overlay.remove(); _filterType = _selType; await _reload(); UI.toast.success('Notiz gespeichert.'); }); }); setTimeout(() => overlay.querySelector('#nc-text')?.focus(), 100); }; _rebind(); } // ---------------------------------------------------------- // Edit-Modal (Bottom-Sheet Stil) // ---------------------------------------------------------- function _openEditModal(note) { const meta = note.meta_json || {}; const rb = _rubrik(note.parent_type); const modalId = 'notes-edit-modal'; document.getElementById(modalId)?.remove(); const overlay = document.createElement('div'); overlay.id = modalId; overlay.style.cssText = ` position:fixed;inset:0;z-index:9999; display:flex;align-items:flex-end;justify-content:center; background:rgba(0,0,0,0.45); `; overlay.innerHTML = `
${UI.escape(rb.label)}

Notiz bearbeiten

${note.parent_type === 'training_session' ? `
${[1,2,3,4,5].map(n => ` `).join('')}
${[['🏠','zuhause'],['🌿','natur'],['🌆','stadt']].map(([emoji,val]) => ` `).join('')}
${[['😊','super'],['😐','ok'],['😔','mude']].map(([emoji,val]) => ` `).join('')}
` : ''}
`; document.body.appendChild(overlay); let selErfolgsquote = meta.erfolgsquote || null; let selUmgebung = meta.umgebung || null; let selStimmung = meta.hund_stimmung || null; function _toggleBtn(group, val, getter, setter) { overlay.querySelectorAll(`.notes-${group}`).forEach(b => { const match = (group === 'pfote') ? parseInt(b.dataset.val, 10) === val : b.dataset.val === val; b.style.background = match ? 'var(--c-primary-subtle)' : 'var(--c-surface-2)'; b.style.borderColor = match ? 'var(--c-primary)' : 'var(--c-border)'; }); } overlay.querySelectorAll('.notes-pfote').forEach(btn => { btn.addEventListener('click', () => { const v = parseInt(btn.dataset.val, 10); selErfolgsquote = selErfolgsquote === v ? null : v; _toggleBtn('pfote', selErfolgsquote, null, null); }); }); overlay.querySelectorAll('.notes-umgebung').forEach(btn => { btn.addEventListener('click', () => { selUmgebung = selUmgebung === btn.dataset.val ? null : btn.dataset.val; _toggleBtn('umgebung', selUmgebung, null, null); }); }); overlay.querySelectorAll('.notes-stimmung').forEach(btn => { btn.addEventListener('click', () => { selStimmung = selStimmung === btn.dataset.val ? null : btn.dataset.val; _toggleBtn('stimmung', selStimmung, null, null); }); }); function _close() { overlay.remove(); } overlay.addEventListener('click', e => { if (e.target === overlay) _close(); }); overlay.querySelector('#notes-edit-cancel').addEventListener('click', _close); // Speichern overlay.querySelector('#notes-edit-save').addEventListener('click', async () => { const text = overlay.querySelector('#notes-edit-text').value.trim(); if (!text) { UI.toast.warning('Notiz darf nicht leer sein.'); return; } const saveBtn = overlay.querySelector('#notes-edit-save'); saveBtn.disabled = true; saveBtn.textContent = 'Speichern…'; const metaObj = {}; if (selErfolgsquote) metaObj.erfolgsquote = selErfolgsquote; if (selUmgebung) metaObj.umgebung = selUmgebung; if (selStimmung) metaObj.hund_stimmung = selStimmung; try { const updated = await API.notes.update(note.id, { text, meta_json: Object.keys(metaObj).length > 0 ? metaObj : null, }); const idx = _notes.findIndex(n => n.id === note.id); if (idx >= 0) _notes[idx] = updated; _render(); _close(); UI.toast.success('Notiz aktualisiert.'); } catch (_) { saveBtn.disabled = false; saveBtn.textContent = 'Speichern'; UI.toast.error('Speichern fehlgeschlagen.'); } }); // Löschen overlay.querySelector('#notes-edit-delete').addEventListener('click', async () => { if (!window.confirm('Notiz wirklich löschen?')) return; try { await API.notes.delete(note.id); _notes = _notes.filter(n => n.id !== note.id); _render(); _close(); UI.toast.success('Notiz gelöscht.'); } catch (_) { UI.toast.error('Löschen fehlgeschlagen.'); } }); } // ---------------------------------------------------------- // INIT / REFRESH // ---------------------------------------------------------- async function init(container, appState) { _container = container; _appState = appState; // Zustand zurücksetzen _filterType = ''; _sortMode = 'newest'; _searchQ = ''; _kiOpen = false; _kiLoading = false; _kiSuggestions = null; _kiError = null; _notes = []; _container.innerHTML = UI.skeleton(3); try { _notes = await _load(); } catch (_) { _notes = []; } _render(); } async function refresh() { if (!_container) return; _container.innerHTML = UI.skeleton(3); try { _notes = await _load(); } catch (_) { _notes = []; } _render(); } return { init, refresh }; })();