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:
parent
1de39536af
commit
9a066cb24c
9 changed files with 484 additions and 157 deletions
|
|
@ -376,11 +376,11 @@ window.Page_health = (() => {
|
|||
const praxis = _praxen.find(p => p.id === e.tierarzt_id);
|
||||
const vetName = praxis?.name || e.tierarzt_name || '';
|
||||
return `
|
||||
<div class="health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="health-card-ampel ampel-${ampel.color}" title="${ampel.label}"></div>
|
||||
<div class="health-card-body">
|
||||
<div class="health-card-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="health-card-meta">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${UI.time.format(e.datum + 'T00:00:00')}
|
||||
${e.charge_nr ? ` · Ch.-Nr: ${_esc(e.charge_nr)}` : ''}
|
||||
</div>
|
||||
|
|
@ -388,7 +388,7 @@ window.Page_health = (() => {
|
|||
${e.naechstes ? `<div class="health-card-next ampel-text-${ampel.color}">
|
||||
Nächste Impfung: ${UI.time.format(e.naechstes + 'T00:00:00')} ${ampel.icon}
|
||||
</div>` : ''}
|
||||
${e.notiz ? `<div class="health-card-note">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
|
|
@ -424,10 +424,10 @@ window.Page_health = (() => {
|
|||
const praxisName = praxis?.name || e.tierarzt_name || '';
|
||||
const praxisOrt = praxis ? [praxis.plz, praxis.ort].filter(Boolean).join(' ') : '';
|
||||
return `
|
||||
<div class="health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="health-card-body">
|
||||
<div class="health-card-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="health-card-meta">
|
||||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${UI.time.format(e.datum + 'T00:00:00')}
|
||||
${e.kosten != null ? ` · ${Number(e.kosten).toFixed(2)} €` : ''}
|
||||
</div>
|
||||
|
|
@ -436,8 +436,8 @@ window.Page_health = (() => {
|
|||
margin-top:var(--space-1);font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${_esc(praxisName)}${praxisOrt ? ` · ${_esc(praxisOrt)}` : ''}
|
||||
</div>` : ''}
|
||||
${e.diagnose ? `<div class="health-card-note"><b>Diagnose:</b> ${_esc(e.diagnose)}</div>` : ''}
|
||||
${e.notiz ? `<div class="health-card-note">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.diagnose ? `<div class="list-item-text"><b>Diagnose:</b> ${_esc(e.diagnose)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
|
|
@ -479,21 +479,22 @@ window.Page_health = (() => {
|
|||
const chart = _renderWeightChart(chartEntries);
|
||||
|
||||
const items = sorted.slice().reverse().map(e => `
|
||||
<div class="health-card" data-id="${e.id}" data-action="open-entry"
|
||||
style="padding:var(--space-3) var(--space-4)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;width:100%">
|
||||
<span class="text-sm-secondary">
|
||||
${UI.time.format(e.datum + 'T00:00:00')}
|
||||
</span>
|
||||
<span style="font-weight:var(--weight-bold);font-size:var(--text-lg)">
|
||||
${e.wert} <span class="text-sm-secondary">${e.einheit || 'kg'}</span>
|
||||
</span>
|
||||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-body">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;width:100%">
|
||||
<span class="list-item-meta-row" style="margin:0">
|
||||
${UI.time.format(e.datum + 'T00:00:00')}
|
||||
</span>
|
||||
<span class="list-item-amount">
|
||||
${e.wert} <span class="text-sm-secondary">${e.einheit || 'kg'}</span>
|
||||
</span>
|
||||
</div>
|
||||
${e.notiz ? `<div class="list-item-text" style="padding-top:var(--space-1)">${_esc(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-1);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="Gewicht ${_esc(e.datum)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
</div>
|
||||
${e.notiz ? `<div class="health-card-note" style="padding-top:var(--space-1)">${_esc(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-1);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="Gewicht ${_esc(e.datum)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
|
|
@ -714,19 +715,19 @@ window.Page_health = (() => {
|
|||
? Math.round((new Date(e.datum) - new Date(prev.datum)) / 86400000)
|
||||
: null;
|
||||
return `
|
||||
<div class="health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div style="width:36px;height:36px;border-radius:50%;background:var(--c-primary);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
flex-shrink:0;color:var(--c-text-inverse)">
|
||||
${UI.icon('gender-female')}
|
||||
</div>
|
||||
<div class="health-card-body">
|
||||
<div class="health-card-title">Läufigkeit · ${UI.time.format(e.datum + 'T00:00:00')}</div>
|
||||
<div class="health-card-meta">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">Läufigkeit · ${UI.time.format(e.datum + 'T00:00:00')}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${e.wert ? `Dauer: ${e.wert} Tage` : 'Dauer nicht angegeben'}
|
||||
${interval ? ` · Abstand zur Vorherigen: ${interval} Tage` : ''}
|
||||
</div>
|
||||
${e.notiz ? `<div class="health-card-note">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="Läufigkeit ${_esc(e.datum)}"
|
||||
|
|
@ -754,17 +755,17 @@ window.Page_health = (() => {
|
|||
const inaktive = entries.filter(e => !e.aktiv);
|
||||
|
||||
const renderGroup = (items, label) => items.length ? `
|
||||
<div class="by-section-label">${label}</div>
|
||||
<div class="list-group-header by-section-label">${label}</div>
|
||||
${items.map(e => `
|
||||
<div class="health-card${e.aktiv ? '' : ' health-card--inactive'}" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="health-card-body">
|
||||
<div class="health-card-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="health-card-meta">
|
||||
<div class="list-item-card list-item-card--clickable health-card${e.aktiv ? '' : ' list-item-card--inactive health-card--inactive'}" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${e.dosierung ? _esc(e.dosierung) : ''}
|
||||
${e.haeufigkeit ? ` · ${_esc(e.haeufigkeit)}` : ''}
|
||||
${e.bis_datum ? ` · bis ${UI.time.format(e.bis_datum + 'T00:00:00')}` : ''}
|
||||
</div>
|
||||
${e.notiz ? `<div class="health-card-note">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
|
|
@ -795,17 +796,17 @@ window.Page_health = (() => {
|
|||
const SCHWEREGRAD = { leicht: '🟡', mittel: '🟠', schwer: '🔴' };
|
||||
|
||||
const items = entries.map(e => `
|
||||
<div class="health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="health-card-body">
|
||||
<div class="health-card-title">
|
||||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">
|
||||
${e.schweregrad ? SCHWEREGRAD[e.schweregrad] || '' : ''} ${_esc(e.bezeichnung)}
|
||||
</div>
|
||||
<div class="health-card-meta">
|
||||
<div class="list-item-meta-row">
|
||||
Erstmals: ${UI.time.format(e.datum + 'T00:00:00')}
|
||||
${e.schweregrad ? ` · Schweregrad: ${_esc(e.schweregrad)}` : ''}
|
||||
</div>
|
||||
${e.reaktion ? `<div class="health-card-note"><b>Reaktion:</b> ${_esc(e.reaktion)}</div>` : ''}
|
||||
${e.notiz ? `<div class="health-card-note">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.reaktion ? `<div class="list-item-text"><b>Reaktion:</b> ${_esc(e.reaktion)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
|
|
@ -837,19 +838,18 @@ window.Page_health = (() => {
|
|||
const count = mediaList.length;
|
||||
|
||||
return `
|
||||
<div class="health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
${firstImg
|
||||
? `<img src="${_esc(firstImg.url)}" class="health-doc-thumb" alt="Vorschau"
|
||||
style="width:64px;height:64px;object-fit:cover;border-radius:var(--radius-md);flex-shrink:0">`
|
||||
? `<img src="${_esc(firstImg.url)}" class="list-item-thumb health-doc-thumb" alt="Vorschau">`
|
||||
: `<div style="width:48px;height:48px;display:flex;align-items:center;justify-content:center;
|
||||
font-size:2rem;flex-shrink:0"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg></div>`}
|
||||
<div class="health-card-body">
|
||||
<div class="health-card-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="health-card-meta">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${UI.time.format(e.datum + 'T00:00:00')}
|
||||
${count > 1 ? ` · ${count} Dateien` : ''}
|
||||
</div>
|
||||
${e.notiz ? `<div class="health-card-note">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
|
|
@ -1627,20 +1627,20 @@ window.Page_health = (() => {
|
|||
</div>`
|
||||
: `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-1)">Noch keine Bewertungen</div>`;
|
||||
return `
|
||||
<div class="health-card praxis-card${!p.aktiv ? ' health-card--inactive' : ''}"
|
||||
<div class="list-item-card list-item-card--clickable health-card praxis-card${!p.aktiv ? ' list-item-card--inactive health-card--inactive' : ''}"
|
||||
data-praxis-id="${p.id}" data-action="open-praxis">
|
||||
<div style="font-size:1.5rem"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${p.ist_notfallpraxis ? 'warning' : 'first-aid'}"></use></svg></div>
|
||||
<div class="health-card-body">
|
||||
<div class="health-card-title">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">
|
||||
${_esc(p.name)}
|
||||
${!p.aktiv ? '<span style="font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:400"> · Ehemalig</span>' : ''}
|
||||
</div>
|
||||
${(p.strasse || p.plz || p.ort) ? `
|
||||
<div class="health-card-meta">
|
||||
<div class="list-item-meta-row">
|
||||
${[p.strasse, [p.plz, p.ort].filter(Boolean).join(' ')].filter(Boolean).map(_esc).join(', ')}
|
||||
</div>` : ''}
|
||||
${p.opening_hours ? `
|
||||
<div class="health-card-meta" style="margin-top:var(--space-1)">
|
||||
<div class="list-item-meta-row" style="margin-top:var(--space-1)">
|
||||
<svg class="ph-icon" aria-hidden="true" style="font-size:0.9em"><use href="/icons/phosphor.svg#clock"></use></svg>
|
||||
${_esc(_fmtOeffnungszeiten(p.opening_hours))}
|
||||
</div>` : ''}
|
||||
|
|
@ -2484,14 +2484,14 @@ window.Page_health = (() => {
|
|||
text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-2)">
|
||||
Mein Tierarzt
|
||||
</div>
|
||||
<div class="health-card" style="align-items:flex-start">
|
||||
<div class="list-item-card health-card" style="align-items:flex-start">
|
||||
<div style="font-size:1.6rem;flex-shrink:0">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg>
|
||||
</div>
|
||||
<div class="health-card-body flex-1-min">
|
||||
<div class="list-item-body flex-1-min">
|
||||
${vet ? `
|
||||
<div class="health-card-title">${_esc(vet.name)}</div>
|
||||
${adresse ? `<div class="health-card-meta">${_esc(adresse)}</div>` : ''}
|
||||
<div class="list-item-title">${_esc(vet.name)}</div>
|
||||
${adresse ? `<div class="list-item-meta-row">${_esc(adresse)}</div>` : ''}
|
||||
${vet.telefon ? `
|
||||
<div class="mt-2">
|
||||
<a href="tel:${_esc(vet.telefon)}" class="btn btn-secondary btn-sm"
|
||||
|
|
@ -2579,17 +2579,17 @@ window.Page_health = (() => {
|
|||
const isImg = !['pdf'].includes(doc.file_type);
|
||||
const datum = doc.datum ? UI.time.format(doc.datum + 'T00:00:00') : '';
|
||||
return `
|
||||
<div class="health-card" style="align-items:flex-start">
|
||||
<div class="list-item-card health-card" style="align-items:flex-start">
|
||||
<div style="font-size:1.4rem;flex-shrink:0;color:var(--c-primary)">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${_esc(icon)}"></use></svg>
|
||||
</div>
|
||||
<div class="health-card-body flex-1-min">
|
||||
<div class="health-card-title">${_esc(doc.titel)}</div>
|
||||
<div class="health-card-meta">
|
||||
<div class="list-item-body flex-1-min">
|
||||
<div class="list-item-title">${_esc(doc.titel)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${_esc(label)}${datum ? ' · ' + datum : ''}
|
||||
${doc.vet_name ? ' · <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ' + _esc(doc.vet_name) : ''}
|
||||
</div>
|
||||
${doc.beschreibung ? `<div class="health-card-note">${_esc(doc.beschreibung)}</div>` : ''}
|
||||
${doc.beschreibung ? `<div class="list-item-text">${_esc(doc.beschreibung)}</div>` : ''}
|
||||
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-2);flex-wrap:wrap">
|
||||
<a href="${_esc(doc.file_path)}" target="_blank" rel="noopener"
|
||||
class="btn btn-secondary btn-sm" onclick="event.stopPropagation()">
|
||||
|
|
@ -3076,16 +3076,18 @@ window.Page_health = (() => {
|
|||
const fmt = d => { try { const p = d.split('-'); return `${parseInt(p[2])}.${parseInt(p[1])}.${p[0]}`; } catch { return d; } };
|
||||
|
||||
wrap.style.display = '';
|
||||
wrap.classList.add('list-reminders-banner');
|
||||
wrap.innerHTML = items.slice(0, 3).map(r => {
|
||||
const overdue = r.ueberfaellig;
|
||||
const modifier = overdue ? 'list-reminder-item--urgent'
|
||||
: r.delta_tage <= 3 ? 'list-reminder-item--warning'
|
||||
: '';
|
||||
const color = overdue ? 'var(--c-danger,#ef4444)' : r.delta_tage <= 3 ? '#f59e0b' : 'var(--c-primary)';
|
||||
const bg = overdue ? 'rgba(239,68,68,0.08)' : r.delta_tage <= 3 ? 'rgba(245,158,11,0.08)' : 'var(--c-primary-subtle)';
|
||||
const label = overdue ? `Überfällig seit ${Math.abs(r.delta_tage)} Tag${Math.abs(r.delta_tage)!==1?'en':''}` :
|
||||
r.delta_tage === 0 ? 'Heute fällig' :
|
||||
`in ${r.delta_tage} Tag${r.delta_tage!==1?'en':''}`;
|
||||
return `
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);
|
||||
background:${bg};border-radius:var(--radius-md);border-left:3px solid ${color}">
|
||||
<div class="list-reminder-item ${modifier}">
|
||||
<svg class="ph-icon" style="width:16px;height:16px;color:${color};flex-shrink:0" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#bell-ringing"></use>
|
||||
</svg>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue