Sprint C: Listen-Familie konsolidiert (Notes/Expenses/Health), SW by-v1104

Neue zentrale CSS-Datei lists.css (~280 Zeilen) mit Listen-Komponenten:
- .list-shell, .list-filter-bar, .list-search-wrap
- .list-group-header
- .list-item-card + Modifier: --clickable, --milestone, --inactive
- .list-item-date-col + sub-elements (für Diary-Style)
- .list-item-meta-badge mit --meta-color (für Expenses/Health Icons)
- .list-item-body, .list-item-title, .list-item-text, .list-item-meta-row
- .list-item-chips + .list-item-chip mit --chip-color
- .list-item-micro-badges + .list-item-micro-badge
- .list-item-thumb (+ .list-item-thumb-count Overlay)
- .list-item-amount (+ --positive/--negative/--neutral)
- .list-item-actions + .list-item-action-btn (+ --danger)
- .list-reminders-banner + .list-reminder-item (+ --urgent/--warning/--success)
- .list-fab (FAB mit safe-area-inset)

MIGRATIONEN:

notes.js — 10+ Klassen ersetzt:
- .notes-card → .list-item-card list-item-card--clickable
- .notes-rubrik-chip → .list-item-chip mit --chip-color
- .notes-card-meta → .list-item-meta-row
- .notes-action-btn → .list-item-action-btn
- .notes-group-label → .list-group-header
- Notes-spezifische Klassen als Modifier behalten (vertikales Layout,
  pre-wrap text, Top-Zeile mit Actions rechts oben)
- Alte CSS-Definitionen im Inline-<style> als TODO markiert

expenses.js — komplette Item-Card-Migration:
- .exp-entry → .list-item-card list-item-card--clickable
- .exp-entry-icon-badge mit --kat-color → .list-item-meta-badge --meta-color
- .exp-entry-betrag → .list-item-amount list-item-amount--negative
- .exp-entry-del → .list-item-action-btn list-item-action-btn--danger
- .exp-recurring-card--inaktiv → .list-item-card--inactive
- .exp-fab → .list-fab
- UI.moneyInput + UI.parseMoney in beide Forms integriert (€-Prefix,
  Komma-Dezimal)
- Hero-Card + Statistik/Kacheln behalten (spezifisch)

health.js — 9 Card-Renderings migriert:
- Impfungen/Tierarzt/Gewicht/Läufigkeit/Medikamente/Allergien/
  Dokumente/Tierarztpraxis/Befunde
- .health-card → .list-item-card list-item-card--clickable
- Health-Ampel parallel behalten (.health-card-ampel + Linie links)
- Reminder-Banner: .health-reminder-* → .list-reminders-banner +
  .list-reminder-item--urgent/--warning
- Gewicht-Wert: .list-item-amount für kg-Anzeigen
- Form-Modals + KI-Buttons + Transponder-Chip unangetastet (anderer
  Scope)

Tests 19/19 grün. Kein visueller Diff erwartet — Modifier-Klassen
bewahren spezifische Layouts.
This commit is contained in:
rene 2026-05-27 07:31:21 +02:00
parent 1de39536af
commit 9a066cb24c
9 changed files with 484 additions and 157 deletions

View file

@ -87,7 +87,7 @@ window.Page_expenses = (() => {
</div>
${_dogSelectorHtml()}
<div id="exp-content"></div>
<button class="exp-fab" id="exp-fab" title="Neue Ausgabe">
<button class="list-fab" id="exp-fab" title="Neue Ausgabe">
${UI.icon('plus')}
</button>
`;
@ -283,28 +283,28 @@ window.Page_expenses = (() => {
const datum = new Date(e.datum + 'T00:00:00')
.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
const dogBadge = e.dog_name
? `<span class="exp-dog-badge">${UI.icon('paw-print')} ${_esc(e.dog_name)}</span>`
? `<span>${UI.icon('paw-print')} ${_esc(e.dog_name)}</span>`
: '';
const notiz = e.notiz
? `<span class="exp-entry-notiz">${_esc(e.notiz)}</span>`
? `<div class="list-item-text">${_esc(e.notiz)}</div>`
: '';
return `
<div class="exp-entry" data-id="${e.id}">
<div class="exp-entry-icon-badge" style="--kat-color:${k.color}">
<div class="list-item-card list-item-card--clickable exp-entry" data-id="${e.id}">
<div class="list-item-meta-badge" style="--meta-color:${k.color}">
${UI.icon(k.icon)}
</div>
<div class="exp-entry-body">
<div class="exp-entry-head">
<span class="exp-entry-datum">${datum}</span>
<span class="exp-entry-kat">${k.label}</span>
${dogBadge}
</div>
<div class="list-item-body">
<div class="list-item-title">${k.label}</div>
${notiz}
<div class="list-item-meta-row">
<span>${datum}</span>
${dogBadge ? `· ${dogBadge}` : ''}
</div>
</div>
<div class="exp-entry-right">
<div class="exp-entry-betrag">${_fmt(e.betrag)}</div>
<button class="exp-entry-del" data-del="${e.id}" title="Löschen"
aria-label="Eintrag löschen">
<div class="list-item-amount list-item-amount--negative">${_fmt(e.betrag)}</div>
<div class="list-item-actions">
<button class="list-item-action-btn list-item-action-btn--danger exp-entry-del"
data-del="${e.id}" title="Löschen" aria-label="Eintrag löschen">
${UI.icon('trash')}
</button>
</div>
@ -313,15 +313,15 @@ window.Page_expenses = (() => {
return `
<div class="exp-month-group">
<div class="exp-month-header">
<span class="exp-month-title">${titel}</span>
<span class="exp-month-summe">${_fmt(summe)}</span>
<div class="list-group-header" style="display:flex;justify-content:space-between;align-items:baseline">
<span>${titel}</span>
<span style="text-transform:none;font-weight:700;color:var(--c-text)">${_fmt(summe)}</span>
</div>
${rows}
</div>`;
}).join('');
el.innerHTML = `<div class="exp-list">${html}</div><div style="height:80px"></div>`;
el.innerHTML = `<div class="list-shell">${html}</div><div style="height:80px"></div>`;
// Klick auf Zeile → Bearbeiten (nur wenn nicht Löschen-Button)
el.querySelectorAll('.exp-entry').forEach(row => {
@ -372,30 +372,26 @@ window.Page_expenses = (() => {
.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
: '—';
return `
<div class="exp-recurring-card${r.aktiv ? '' : ' exp-recurring-card--inaktiv'}" data-rid="${r.id}">
<div class="exp-entry-icon-badge" style="--kat-color:${k.color}">${UI.icon(k.icon)}</div>
<div class="exp-entry-body">
<div class="exp-entry-head">
<span class="exp-entry-kat">${k.label}</span>
<span class="exp-recurring-freq">${HAEUFIGKEIT_LABEL[r.haeufigkeit] || r.haeufigkeit}</span>
${r.dog_name ? `<span class="exp-dog-badge">${UI.icon('paw-print')} ${_esc(r.dog_name)}</span>` : ''}
</div>
${r.notiz ? `<div class="exp-entry-notiz">${_esc(r.notiz)}</div>` : ''}
<div class="exp-recurring-next">
${UI.icon('calendar')} Nächste Buchung: <strong>${naechste}</strong>
${!r.aktiv ? '<span class="exp-badge-inaktiv">Pausiert</span>' : ''}
<div class="list-item-card${r.aktiv ? '' : ' list-item-card--inactive'}" data-rid="${r.id}">
<div class="list-item-meta-badge" style="--meta-color:${k.color}">${UI.icon(k.icon)}</div>
<div class="list-item-body">
<div class="list-item-title">${k.label}</div>
${r.notiz ? `<div class="list-item-text">${_esc(r.notiz)}</div>` : ''}
<div class="list-item-meta-row">
<span>${HAEUFIGKEIT_LABEL[r.haeufigkeit] || r.haeufigkeit}</span>
· <span>${UI.icon('calendar')} ${naechste}</span>
${r.dog_name ? `· <span>${UI.icon('paw-print')} ${_esc(r.dog_name)}</span>` : ''}
${!r.aktiv ? '· <span>Pausiert</span>' : ''}
</div>
</div>
<div class="exp-entry-right">
<div class="exp-entry-betrag">${_fmt(r.betrag)}</div>
<div style="display:flex;gap:var(--space-1);margin-top:var(--space-1)">
<button class="exp-icon-btn exp-recurring-toggle" data-rid="${r.id}" data-aktiv="${r.aktiv}"
title="${r.aktiv ? 'Pausieren' : 'Aktivieren'}">
${UI.icon(r.aktiv ? 'pause' : 'play')}
</button>
<button class="exp-icon-btn exp-icon-btn--danger exp-recurring-del" data-rid="${r.id}"
title="Löschen">${UI.icon('trash')}</button>
</div>
<div class="list-item-amount list-item-amount--negative">${_fmt(r.betrag)}</div>
<div class="list-item-actions">
<button class="list-item-action-btn exp-recurring-toggle" data-rid="${r.id}" data-aktiv="${r.aktiv}"
title="${r.aktiv ? 'Pausieren' : 'Aktivieren'}">
${UI.icon(r.aktiv ? 'pause' : 'play')}
</button>
<button class="list-item-action-btn list-item-action-btn--danger exp-recurring-del" data-rid="${r.id}"
title="Löschen">${UI.icon('trash')}</button>
</div>
</div>`;
}).join('');
@ -407,7 +403,7 @@ window.Page_expenses = (() => {
</button>
</div>
${recurring.length
? `<div class="exp-list">${cards}</div>`
? `<div class="list-shell">${cards}</div>`
: UI.emptyState({ icon: UI.icon('arrows-clockwise'),
title: 'Keine Daueraufträge',
text: 'Erfasse regelmäßige Ausgaben wie Hundesteuer oder Versicherung.' })}
@ -458,9 +454,8 @@ window.Page_expenses = (() => {
<select class="form-control" name="kategorie">${katOptions}</select>
</div>
<div class="form-group">
<label class="form-label">Betrag ()</label>
<input class="form-control" type="number" name="betrag" step="0.01" min="0.01"
value="${r?.betrag || ''}" placeholder="0,00" required>
<label class="form-label">Betrag</label>
${UI.moneyInput({ name: 'betrag', value: r?.betrag ?? '', required: true })}
</div>
<div class="form-group">
<label class="form-label">Häufigkeit</label>
@ -501,7 +496,7 @@ window.Page_expenses = (() => {
const fd = UI.formData(e.target);
const payload = {
kategorie: fd.kategorie,
betrag: parseFloat(fd.betrag),
betrag: UI.parseMoney(fd.betrag),
haeufigkeit: fd.haeufigkeit,
startdatum: fd.startdatum,
notiz: fd.notiz || null,
@ -710,12 +705,7 @@ window.Page_expenses = (() => {
<div class="grid-2">
<div class="form-group" style="margin-bottom:0">
<label class="form-label">Betrag</label>
<div class="exp-betrag-wrap">
<span class="exp-betrag-prefix"></span>
<input type="number" name="betrag" class="form-control exp-betrag-input"
value="${entry?.betrag || ''}" min="0.01" step="0.01"
placeholder="0,00" required>
</div>
${UI.moneyInput({ name: 'betrag', value: entry?.betrag ?? '', required: true })}
</div>
<div class="form-group" style="margin-bottom:0">
<label class="form-label">Datum</label>
@ -810,7 +800,7 @@ window.Page_expenses = (() => {
const fd = UI.formData(ev.target);
const payload = {
kategorie: fd.kategorie,
betrag: parseFloat(fd.betrag),
betrag: UI.parseMoney(fd.betrag),
datum: fd.datum,
notiz: fd.notiz || null,
dog_id: fd.dog_id ? parseInt(fd.dog_id) : null,