Feature: KI-Jahresberichte speichern + Archiv + Download — SW by-v505, APP_VER 482

This commit is contained in:
rene 2026-04-29 17:03:49 +02:00
parent b4de0aa27c
commit 9832cd24d8
6 changed files with 146 additions and 17 deletions

View file

@ -692,6 +692,8 @@ const API = (() => {
},
hundBeschreibung(hundId) { return post('/zucht-ki/hund-beschreibung', { hund_id: hundId }); },
jahresbericht() { return post('/zucht-ki/jahresbericht', {}); },
jahresberichtList() { return get('/zucht-ki/jahresbericht'); },
jahresberichtGet(id) { return get(`/zucht-ki/jahresbericht/${id}`); },
};
const osm = {

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '481'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '482'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {

View file

@ -117,6 +117,9 @@ window.Page_zuchthunde = (() => {
${_appState?.user?.ki_zucht_jahresbericht !== 0 ? `
<a class="btn btn-ghost btn-sm" id="zh-jahresbericht-btn">
${UI.icon('chart-bar')} Jahresbericht
</a>
<a class="btn btn-ghost btn-sm" id="zh-jahresbericht-archiv-btn" title="Frühere Berichte">
${UI.icon('archive')}
</a>` : ''}
</div>
<div style="padding:0 0 var(--space-3)">
@ -132,6 +135,7 @@ window.Page_zuchthunde = (() => {
document.getElementById('zh-new-btn')?.addEventListener('click', () => _showHundForm(null));
document.getElementById('zh-trial-btn')?.addEventListener('click', () => _showTrialMatingModal());
document.getElementById('zh-jahresbericht-btn')?.addEventListener('click', () => _showJahresbericht());
document.getElementById('zh-jahresbericht-archiv-btn')?.addEventListener('click', () => _showJahresberichtArchiv());
document.getElementById('zh-search')?.addEventListener('input', e => {
_query = e.target.value.toLowerCase().trim();
@ -1285,7 +1289,7 @@ window.Page_zuchthunde = (() => {
}
// ----------------------------------------------------------
// KI: Jahresbericht
// KI: Jahresbericht generieren
// ----------------------------------------------------------
async function _showJahresbericht() {
UI.modal.open({
@ -1294,10 +1298,12 @@ window.Page_zuchthunde = (() => {
footer: '',
});
let text = '';
let text = '', savedId = null, jahr = new Date().getFullYear();
try {
const result = await API.zuchtKi.jahresbericht();
text = result.text || result.content || result.bericht || JSON.stringify(result);
text = result.text || result.content || result.bericht || JSON.stringify(result);
savedId = result.saved_id;
jahr = result.jahr || jahr;
} catch (err) {
UI.modal.open({
title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
@ -1307,23 +1313,93 @@ window.Page_zuchthunde = (() => {
return;
}
_renderBerichtModal(text, jahr, savedId);
}
function _renderBerichtModal(text, jahr, savedId) {
UI.modal.open({
title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
body: `<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>`,
title: `${UI.icon('chart-bar')} KI-Jahresbericht ${jahr}`,
body: `
${savedId ? `<p style="font-size:var(--text-xs);color:var(--c-success);margin:0 0 var(--space-3);display:flex;align-items:center;gap:4px">
${UI.icon('check-circle')} Automatisch gespeichert</p>` : ''}
<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>`,
footer: `
<button class="btn btn-secondary flex-1" id="ki-bericht-copy">
<button class="btn btn-ghost btn-sm" id="ki-bericht-copy">
${UI.icon('clipboard-text')} Kopieren
</button>
<button class="btn btn-primary flex-1" data-modal-close>Schließen</button>`,
<button class="btn btn-ghost btn-sm" id="ki-bericht-download">
${UI.icon('download-simple')} Herunterladen
</button>
<button class="btn btn-primary" data-modal-close>Schließen</button>`,
});
document.getElementById('ki-bericht-copy')?.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(text);
UI.toast.success('Bericht kopiert.');
} catch {
UI.toast.error('Kopieren nicht möglich.');
}
try { await navigator.clipboard.writeText(text); UI.toast.success('Bericht kopiert.'); }
catch { UI.toast.error('Kopieren nicht möglich.'); }
});
document.getElementById('ki-bericht-download')?.addEventListener('click', () => {
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = `ban-yaro-jahresbericht-${jahr}.txt`;
a.click(); URL.revokeObjectURL(url);
});
}
// KI: Archiv früherer Berichte
// ----------------------------------------------------------
async function _showJahresberichtArchiv() {
UI.modal.open({
title: `${UI.icon('archive')} Gespeicherte Jahresberichte`,
body: `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-4)">Lädt…</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
let berichte = [];
try { berichte = await API.zuchtKi.jahresberichtList(); }
catch { UI.toast.error('Berichte konnten nicht geladen werden.'); return; }
if (!berichte.length) {
UI.modal.open({
title: `${UI.icon('archive')} Gespeicherte Jahresberichte`,
body: `<p style="color:var(--c-text-muted);text-align:center;padding:var(--space-6)">Noch keine Berichte gespeichert.</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;
}
const listHtml = berichte.map(b => `
<div style="display:flex;align-items:center;justify-content:space-between;
padding:var(--space-3) 0;border-bottom:1px solid var(--c-border-light)">
<div>
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">
Jahresbericht ${b.jahr}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
${new Date(b.created_at).toLocaleDateString('de', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}
</div>
</div>
<button class="btn btn-ghost btn-sm" data-bericht-id="${b.id}" data-bericht-jahr="${b.jahr}">
${UI.icon('eye')} Lesen
</button>
</div>`).join('');
UI.modal.open({
title: `${UI.icon('archive')} Gespeicherte Jahresberichte`,
body: `<div style="padding:0 var(--space-1)">${listHtml}</div>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
document.querySelectorAll('[data-bericht-id]').forEach(btn => {
btn.addEventListener('click', async () => {
const id = Number(btn.dataset.berichtId);
const jahr = Number(btn.dataset.berichtJahr);
try {
const r = await API.zuchtKi.jahresberichtGet(id);
_renderBerichtModal(r.text, r.jahr || jahr, r.id);
} catch { UI.toast.error('Bericht konnte nicht geladen werden.'); }
});
});
}