banyaro/backend/static/js/pages/recalls.js

188 lines
7.1 KiB
JavaScript

/* ============================================================
BAN YARO — Tierfutter-Rückrufe
Seiten-Modul: RASFF EU Rückruf-Alarm für Heimtierfutter.
============================================================ */
window.Page_recalls = (() => {
// ----------------------------------------------------------
// MODUL-STATE
// ----------------------------------------------------------
let _container = null;
let _appState = null;
let _recalls = [];
let _query = '';
// ----------------------------------------------------------
// INIT
// ----------------------------------------------------------
async function init(container, appState) {
_container = container;
_appState = appState;
_query = '';
await _render();
}
// ----------------------------------------------------------
// REFRESH
// ----------------------------------------------------------
async function refresh() {
_recalls = [];
_query = '';
await _render();
}
// ----------------------------------------------------------
// RENDER
// ----------------------------------------------------------
async function _render() {
_container.innerHTML = `
<!-- Warnbanner -->
<div class="recalls-warning-banner">
<svg class="ph-icon recalls-warning-icon" aria-hidden="true">
<use href="/icons/phosphor.svg#warning"></use>
</svg>
<p class="recalls-warning-text">
<strong>Hinweis:</strong> Prüfe immer das Mindesthaltbarkeitsdatum und die Chargen-Nummer
bevor du ein gemeldetes Produkt entsorgst oder zurückgibst.
</p>
</div>
<!-- Suchfeld -->
<div style="position:relative;margin-bottom:var(--space-4)">
<svg class="ph-icon" aria-hidden="true"
style="position:absolute;left:var(--space-3);top:50%;transform:translateY(-50%);
color:var(--c-text-muted);pointer-events:none">
<use href="/icons/phosphor.svg#magnifying-glass"></use>
</svg>
<input type="search" id="recalls-search" placeholder="Produkt, Gefahr oder Herkunft suchen…"
value="${UI.escape(_query)}"
style="width:100%;padding:var(--space-2) var(--space-3) var(--space-2) calc(var(--space-3)*2 + 1.2rem);
border:1px solid var(--c-border);border-radius:var(--radius-md);
font-size:var(--text-sm);background:var(--c-surface);color:var(--c-text);
box-sizing:border-box">
</div>
<!-- Ergebnis-Liste -->
<div id="recalls-list">${UI.skeleton(4)}</div>
`;
// Suchfeld-Handler
_container.querySelector('#recalls-search').addEventListener('input', (e) => {
_query = e.target.value.trim();
_renderList();
});
await _loadRecalls();
}
// ----------------------------------------------------------
// DATEN LADEN
// ----------------------------------------------------------
async function _loadRecalls() {
try {
const url = _query ? `/recalls?q=${encodeURIComponent(_query)}` : '/recalls';
_recalls = await API.get(url);
} catch {
_container.querySelector('#recalls-list').innerHTML = UI.emptyState({
icon: 'warning-circle',
title: 'Rückrufe konnten nicht geladen werden',
text: 'Bitte versuche es später erneut.',
});
return;
}
_renderList();
}
// ----------------------------------------------------------
// LISTE RENDERN
// ----------------------------------------------------------
function _renderList() {
const listEl = _container.querySelector('#recalls-list');
if (!listEl) return;
const filtered = _query
? _recalls.filter(r => {
const q = _query.toLowerCase();
return (r.titel || '').toLowerCase().includes(q)
|| (r.produkt || '').toLowerCase().includes(q)
|| (r.gefahr || '').toLowerCase().includes(q)
|| (r.herkunft || '').toLowerCase().includes(q);
})
: _recalls;
if (!filtered.length) {
const today = new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
listEl.innerHTML = UI.emptyState({
icon: UI.icon('check-circle'),
title: 'Aktuell keine Rückrufe',
text: `Letzte Prüfung: ${today}`,
});
return;
}
listEl.innerHTML = filtered.map(r => _cardHtml(r)).join('');
}
// ----------------------------------------------------------
// EINZELNE KARTE
// ----------------------------------------------------------
function _cardHtml(r) {
const datum = r.datum
? new Date(r.datum).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
: '';
const meta = [
r.herkunft ? `<span>${UI.icon('globe-hemisphere-west')} ${UI.escape(r.herkunft)}</span>` : '',
datum ? `<span>${UI.icon('calendar-blank')} ${datum}</span>` : '',
r.quelle ? `<span style="text-transform:uppercase;font-size:var(--text-xs);color:var(--c-text-muted)">${UI.escape(r.quelle)}</span>` : '',
].filter(Boolean).join('<span style="color:var(--c-border)"> · </span>');
const linkHtml = r.url
? `<a href="${UI.escape(r.url)}" target="_blank" rel="noopener"
style="display:inline-flex;align-items:center;gap:4px;font-size:var(--text-sm);
color:var(--c-primary);text-decoration:none;margin-top:var(--space-1)">
${UI.icon('arrow-square-out')} Details auf RASFF
</a>`
: '';
return `
<div style="background:var(--c-surface);border:1px solid var(--c-border);
border-left:4px solid #dc2626;border-radius:var(--radius-md);
padding:var(--space-3) var(--space-4);margin-bottom:var(--space-3)">
<!-- Titel -->
<div style="display:flex;align-items:flex-start;gap:var(--space-2);margin-bottom:var(--space-1)">
<svg class="ph-icon" aria-hidden="true" style="color:#dc2626;flex-shrink:0;margin-top:2px">
<use href="/icons/phosphor.svg#warning-octagon"></use>
</svg>
<strong style="font-size:var(--text-base);color:var(--c-text);line-height:1.4">
${UI.escape(r.produkt || r.titel)}
</strong>
</div>
<!-- Gefahr -->
${r.gefahr ? `
<p style="margin:0 0 var(--space-2) 0;font-size:var(--text-sm);color:var(--c-text-muted);
padding-left:calc(var(--space-2) + 1.2rem)">
${UI.escape(r.gefahr)}
</p>` : ''}
<!-- Meta-Zeile -->
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);
font-size:var(--text-sm);color:var(--c-text-muted);
padding-left:calc(var(--space-2) + 1.2rem)">
${meta}
</div>
<!-- Link -->
${linkHtml ? `<div style="padding-left:calc(var(--space-2) + 1.2rem)">${linkHtml}</div>` : ''}
</div>
`;
}
// ----------------------------------------------------------
// PUBLIC API
// ----------------------------------------------------------
return { init, refresh };
})();