UX: Würfe-Stats-Chips klickbar (Status-Filter), Wurfbörse Testdaten (SW by-v895)

This commit is contained in:
rene 2026-05-13 17:51:23 +02:00
parent f3308a6a94
commit 0f230b9ddc
5 changed files with 79 additions and 62 deletions

View file

@ -591,10 +591,10 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=894"></script>
<script src="/js/ui.js?v=894"></script>
<script src="/js/app.js?v=894"></script>
<script src="/js/worlds.js?v=894"></script>
<script src="/js/api.js?v=895"></script>
<script src="/js/ui.js?v=895"></script>
<script src="/js/app.js?v=895"></script>
<script src="/js/worlds.js?v=895"></script>
<!-- Feature-Seiten werden lazy geladen -->

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '894'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '895'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';
// Cache-Bust-Parameter nach Update-Reload sofort entfernen

View file

@ -5,10 +5,11 @@
window.Page_litters = (() => {
let _container = null;
let _appState = null;
let _litters = []; // geladene Würfe
let _openId = null; // aufgeklappter Wurf
let _container = null;
let _appState = null;
let _litters = []; // geladene Würfe
let _openId = null; // aufgeklappter Wurf
let _filterStatus = null; // aktiver Status-Filter
// ----------------------------------------------------------
// Hilfsfunktionen
@ -142,22 +143,59 @@ window.Page_litters = (() => {
const welpen = _litters.reduce((s, l) => s + (l.welpen_gesamt || 0), 0);
const verfuegb = _litters.reduce((s, l) => s + (l.welpen_verfuegbar || 0), 0);
const statItems = [
{ icon: 'list-bullets', label: 'Würfe gesamt', val: total },
{ icon: 'baby', label: 'Aktiv', val: aktiv, color: 'var(--c-success)' },
{ icon: 'calendar-dots',label: 'Geplant', val: geplant },
{ icon: 'dog', label: 'Welpen', val: welpen },
{ icon: 'tag', label: 'Verfügbar', val: verfuegb, color: verfuegb > 0 ? 'var(--c-primary)' : undefined },
{ icon: 'list-bullets', label: 'Alle Würfe', val: total, filter: null },
{ icon: 'baby', label: 'Aktiv', val: aktiv, filter: ['verfuegbar','geboren'], color: 'var(--c-success)' },
{ icon: 'calendar-dots',label: 'Geplant', val: geplant, filter: ['geplant'] },
{ icon: 'dog', label: 'Welpen ges.', val: welpen, filter: null },
{ icon: 'tag', label: 'Verfügbar', val: verfuegb,filter: ['verfuegbar'], color: verfuegb > 0 ? 'var(--c-primary)' : undefined },
];
bar.style.display = 'flex';
bar.innerHTML = statItems.map(s => `
<div style="background:var(--c-bg-secondary);border:1px solid var(--c-border);border-radius:var(--radius-md);
padding:var(--space-3) var(--space-4);display:flex;align-items:center;gap:var(--space-2);flex:1;min-width:100px">
<span style="color:${s.color || 'var(--c-text-muted)'};opacity:.8">${UI.icon(s.icon)}</span>
<div>
<div style="font-size:var(--text-lg);font-weight:700;color:${s.color || 'var(--c-text);line-height:1'}">${s.val}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${s.label}</div>
</div>
</div>`).join('');
bar.innerHTML = statItems.map((s, i) => {
const isActive = JSON.stringify(_filterStatus) === JSON.stringify(s.filter);
const clickable = s.filter !== undefined && !(s.label === 'Welpen ges.');
return `
<div data-stat-idx="${i}"
style="background:${isActive ? 'var(--c-primary)' : 'var(--c-bg-secondary)'};
border:1px solid ${isActive ? 'var(--c-primary)' : 'var(--c-border)'};
border-radius:var(--radius-md);padding:var(--space-3) var(--space-4);
display:flex;align-items:center;gap:var(--space-2);flex:1;min-width:90px;
${clickable ? 'cursor:pointer;user-select:none;transition:opacity .15s' : ''}">
<span style="color:${isActive ? 'white' : (s.color || 'var(--c-text-muted)')};opacity:.9">${UI.icon(s.icon)}</span>
<div>
<div style="font-size:var(--text-lg);font-weight:700;
color:${isActive ? 'white' : (s.color || 'var(--c-text)')};line-height:1">${s.val}</div>
<div style="font-size:var(--text-xs);color:${isActive ? 'rgba(255,255,255,.75)' : 'var(--c-text-muted)'}">${s.label}</div>
</div>
</div>`;
}).join('');
bar.querySelectorAll('[data-stat-idx]').forEach(chip => {
const s = statItems[parseInt(chip.dataset.statIdx)];
if (!s.filter && s.label !== 'Alle Würfe') return;
chip.addEventListener('click', () => {
_filterStatus = JSON.stringify(_filterStatus) === JSON.stringify(s.filter) ? null : s.filter;
_renderStats();
_renderFilteredList();
});
});
}
function _renderFilteredList() {
const el = document.getElementById('litters-list');
if (!el) return;
const visible = _filterStatus
? _litters.filter(l => _filterStatus.includes(l.status))
: _litters;
if (!visible.length) {
el.innerHTML = `
<div style="text-align:center;padding:var(--space-8) var(--space-4);
border:1px dashed var(--c-border);border-radius:var(--radius-lg)">
<p style="color:var(--c-text-muted)">Keine Würfe für diesen Filter.</p>
</div>`;
return;
}
el.innerHTML = visible.map(l => _litterCardHTML(l)).join('');
_bindCardEvents(el, visible);
}
function _renderList() {
@ -178,73 +216,52 @@ window.Page_litters = (() => {
}
_renderStats();
_filterStatus = null;
el.innerHTML = _litters.map(l => _litterCardHTML(l)).join('');
_bindCardEvents(el, _litters);
if (_openId) _togglePuppies(_openId, true);
}
// Events
function _bindCardEvents(el, litters) {
el.querySelectorAll('.litters-card-toggle').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
_togglePuppies(id);
});
btn.addEventListener('click', () => _togglePuppies(parseInt(btn.dataset.id)));
});
el.querySelectorAll('.litters-photos-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
const litter = _litters.find(l => l.id === id);
if (litter) _showPhotosModal('litter', litter.id, litter.zwingername || `Wurf #${litter.id}`);
const l = litters.find(x => x.id === id);
if (l) _showPhotosModal('litter', l.id, l.zwingername || `Wurf #${l.id}`);
});
});
el.querySelectorAll('.litters-parent-photos-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
const litter = _litters.find(l => l.id === id);
if (!litter) return;
const label = [litter.vater_name, litter.mutter_name].filter(Boolean).join(' × ') || `Eltern Wurf #${id}`;
_showPhotosModal('parent', litter.id, label);
const l = litters.find(x => x.id === id);
if (!l) return;
_showPhotosModal('parent', l.id, [l.vater_name, l.mutter_name].filter(Boolean).join(' × ') || `Eltern #${id}`);
});
});
el.querySelectorAll('.litters-edit-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
const litter = _litters.find(l => l.id === id);
if (litter) _showLitterForm(litter);
const l = litters.find(x => x.id === parseInt(btn.dataset.id));
if (l) _showLitterForm(l);
});
});
el.querySelectorAll('.litters-delete-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
_deleteLitter(id);
});
btn.addEventListener('click', () => _deleteLitter(parseInt(btn.dataset.id)));
});
el.querySelectorAll('.litters-ki-announce-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
_showKiAnnouncement(id);
});
btn.addEventListener('click', () => _showKiAnnouncement(parseInt(btn.dataset.id)));
});
el.querySelectorAll('.litters-add-puppy-btn').forEach(btn => {
btn.addEventListener('click', () => {
const id = parseInt(btn.dataset.id);
_showPuppyForm(id, null);
});
btn.addEventListener('click', () => _showPuppyForm(parseInt(btn.dataset.id), null));
});
el.querySelectorAll('.litters-waitlist-btn').forEach(btn => {
btn.addEventListener('click', () => _toggleWaitlist(parseInt(btn.dataset.id)));
});
el.querySelectorAll('.litters-add-waitlist-btn').forEach(btn => {
btn.addEventListener('click', () => _showWaitlistForm(parseInt(btn.dataset.id), null));
});
// Aufgeklappten Wurf wiederherstellen
if (_openId) _togglePuppies(_openId, true);
}
function _daysUntil(dateStr) {

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v894';
const CACHE_VERSION = 'by-v895';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache