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
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1103
|
||||
1104
|
||||
328
backend/static/css/lists.css
Normal file
328
backend/static/css/lists.css
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
/* ============================================================
|
||||
BAN YARO — Listen-Komponenten
|
||||
Wiederverwendbare Klassen für Seiten mit Listen+Detail-Pattern:
|
||||
Notes, Expenses, Health, Diary, Behavior-Log, ...
|
||||
|
||||
Verwendung:
|
||||
<div class="list-shell">
|
||||
<div class="list-filter-bar">...</div>
|
||||
<div class="list-group-header">Mai 2026</div>
|
||||
<div class="list-item-card list-item-card--clickable" data-id="...">
|
||||
<div class="list-item-meta-badge" style="--meta-color:#f97316">🍖</div>
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">Titel</div>
|
||||
<div class="list-item-text">Vorschau-Text</div>
|
||||
<div class="list-item-meta-row">
|
||||
<span>10:30</span> · <span>📍 Berlin</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-item-amount">25,50 €</div>
|
||||
</div>
|
||||
</div>
|
||||
============================================================ */
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Shell + Header
|
||||
------------------------------------------------------------ */
|
||||
.list-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.list-filter-bar {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-search-wrap {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.list-search-wrap > input { width: 100%; }
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Group-Header (Monat / Datums-Gruppe)
|
||||
------------------------------------------------------------ */
|
||||
.list-group-header {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 600;
|
||||
color: var(--c-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
padding: var(--space-3) var(--space-4) var(--space-1);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Item-Card (universelle Listen-Karte)
|
||||
------------------------------------------------------------ */
|
||||
.list-item-card {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--c-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--c-border-light);
|
||||
align-items: flex-start;
|
||||
transition: background 0.15s, transform 0.1s;
|
||||
}
|
||||
|
||||
.list-item-card--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.list-item-card--clickable:hover {
|
||||
background: var(--c-surface-2);
|
||||
}
|
||||
.list-item-card--clickable:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.list-item-card--milestone {
|
||||
border-left: 3px solid #f5c518;
|
||||
}
|
||||
|
||||
.list-item-card--inactive {
|
||||
opacity: 0.55;
|
||||
filter: grayscale(0.8);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Linke Spalte: Date-Col oder Meta-Badge
|
||||
------------------------------------------------------------ */
|
||||
|
||||
/* Date-Column (Diary-Style: Wochentag + Tag) */
|
||||
.list-item-date-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
min-width: 44px;
|
||||
text-align: center;
|
||||
}
|
||||
.list-item-date-col-weekday {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 600;
|
||||
color: var(--c-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.list-item-date-col-day {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--c-text);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* Meta-Badge (Expenses/Health-Style: farbiges Icon im Kreis) */
|
||||
.list-item-meta-badge {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--meta-color, var(--c-primary)) 15%, transparent);
|
||||
color: var(--meta-color, var(--c-primary));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.4rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Body (Hauptinhalt mittig)
|
||||
------------------------------------------------------------ */
|
||||
.list-item-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.list-item-title {
|
||||
font-weight: 600;
|
||||
font-size: var(--text-base);
|
||||
color: var(--c-text);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.list-item-text {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--c-text-secondary);
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item-meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--c-text-muted);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Chips + Micro-Badges (in Item-Body)
|
||||
------------------------------------------------------------ */
|
||||
.list-item-chips {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.list-item-chip {
|
||||
padding: 2px var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: color-mix(in srgb, var(--chip-color, var(--c-primary)) 15%, transparent);
|
||||
color: var(--chip-color, var(--c-primary));
|
||||
}
|
||||
|
||||
.list-item-micro-badges {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
flex-wrap: wrap;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.list-item-micro-badge {
|
||||
padding: 1px 6px;
|
||||
background: var(--c-surface-2);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 11px;
|
||||
color: var(--c-text-secondary);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Rechte Spalte: Thumbnail, Amount, Actions
|
||||
------------------------------------------------------------ */
|
||||
.list-item-thumb {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
background: var(--c-surface-2);
|
||||
position: relative;
|
||||
}
|
||||
.list-item-thumb-count {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
background: rgba(0,0,0,0.65);
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 1px 5px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.list-item-amount {
|
||||
font-weight: 700;
|
||||
font-size: var(--text-base);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
.list-item-amount--positive { color: var(--c-success); }
|
||||
.list-item-amount--negative { color: var(--c-danger); }
|
||||
.list-item-amount--neutral { color: var(--c-text); }
|
||||
|
||||
.list-item-actions {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
.list-item-action-btn {
|
||||
padding: 6px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--c-text-muted);
|
||||
cursor: pointer;
|
||||
font-size: var(--text-sm);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.list-item-action-btn:hover {
|
||||
color: var(--c-text);
|
||||
background: var(--c-surface-2);
|
||||
}
|
||||
.list-item-action-btn--danger:hover {
|
||||
color: var(--c-danger);
|
||||
background: color-mix(in srgb, var(--c-danger) 10%, transparent);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Reminder/Hinweis-Banner (Health-Style)
|
||||
------------------------------------------------------------ */
|
||||
.list-reminders-banner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
}
|
||||
|
||||
.list-reminder-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--c-surface);
|
||||
border-radius: var(--radius-md);
|
||||
border-left: 3px solid var(--c-text-muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.list-reminder-item--urgent { border-left-color: var(--c-danger); }
|
||||
.list-reminder-item--warning { border-left-color: var(--c-warning, #f59e0b); }
|
||||
.list-reminder-item--success { border-left-color: var(--c-success); }
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
FAB (Floating Action Button)
|
||||
------------------------------------------------------------ */
|
||||
.list-fab {
|
||||
position: fixed;
|
||||
bottom: calc(env(safe-area-inset-bottom, 16px) + 16px);
|
||||
right: 20px;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 50%;
|
||||
background: var(--c-primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 18px rgba(196,132,58,0.4);
|
||||
font-size: 26px;
|
||||
z-index: 80;
|
||||
transition: transform 0.12s, box-shadow 0.12s;
|
||||
}
|
||||
.list-fab:active {
|
||||
transform: scale(0.92);
|
||||
box-shadow: 0 2px 10px rgba(196,132,58,0.3);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Load-More + Empty-List in Listen-Context
|
||||
------------------------------------------------------------ */
|
||||
.list-load-more {
|
||||
text-align: center;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
|
@ -86,13 +86,14 @@
|
|||
<title>Ban Yaro</title>
|
||||
|
||||
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
|
||||
<script src="/js/boot-early.js?v=1103"></script>
|
||||
<script src="/js/boot-early.js?v=1104"></script>
|
||||
|
||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1103">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1103">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1103">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1103">
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1104">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1104">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1104">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1104">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1104">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -616,11 +617,11 @@
|
|||
<div id="modal-container"></div>
|
||||
|
||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||
<script src="/js/api.js?v=1103"></script>
|
||||
<script src="/js/ui.js?v=1103"></script>
|
||||
<script src="/js/app.js?v=1103"></script>
|
||||
<script src="/js/worlds.js?v=1103"></script>
|
||||
<script src="/js/offline-indicator.js?v=1103"></script>
|
||||
<script src="/js/api.js?v=1104"></script>
|
||||
<script src="/js/ui.js?v=1104"></script>
|
||||
<script src="/js/app.js?v=1104"></script>
|
||||
<script src="/js/worlds.js?v=1104"></script>
|
||||
<script src="/js/offline-indicator.js?v=1104"></script>
|
||||
|
||||
<!-- Feature-Seiten werden lazy geladen -->
|
||||
|
||||
|
|
@ -630,7 +631,7 @@
|
|||
|
||||
|
||||
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
|
||||
<script src="/js/boot.js?v=1103"></script>
|
||||
<script src="/js/boot.js?v=1104"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '1103'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '1104'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
||||
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
|
||||
window.APP_VERSION = APP_VERSION;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ window.Page_notes = (() => {
|
|||
.filter(([, items]) => items.length > 0)
|
||||
.map(([label, items]) => `
|
||||
<div class="notes-group">
|
||||
<div class="notes-group-label">${_esc(label)}</div>
|
||||
<div class="list-group-header">${_esc(label)}</div>
|
||||
${items.map(_noteCard).join('')}
|
||||
</div>
|
||||
`).join('');
|
||||
|
|
@ -243,21 +243,28 @@ window.Page_notes = (() => {
|
|||
|
||||
/* Gruppen */
|
||||
.notes-group { display: flex; flex-direction: column; gap: var(--space-2); }
|
||||
/* TODO nach Migration entfernen: ersetzt durch .list-group-header in lists.css */
|
||||
.notes-group-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); color: var(--c-text-muted); text-transform: uppercase; letter-spacing: .05em; padding: var(--space-1) 0; }
|
||||
|
||||
/* Karten */
|
||||
.notes-card { background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); }
|
||||
.notes-card-top { display: flex; align-items: flex-start; gap: var(--space-2); }
|
||||
.notes-rubrik-chip { display: inline-flex; align-items: center; gap: 4px; font-size: var(--text-xs); font-weight: var(--weight-semibold); padding: 2px var(--space-2); border-radius: 999px; flex-shrink: 0; }
|
||||
/* Karten — Notes-spezifischer Override: vertikales Layout statt horizontalem .list-item-card */
|
||||
.notes-card { flex-direction: column; gap: var(--space-2); }
|
||||
.notes-card-top { display: flex; align-items: flex-start; gap: var(--space-2); width: 100%; }
|
||||
/* TODO nach Migration entfernen: ersetzt durch .list-item-chip */
|
||||
/* .notes-rubrik-chip { display: inline-flex; align-items: center; gap: 4px; font-size: var(--text-xs); font-weight: var(--weight-semibold); padding: 2px var(--space-2); border-radius: 999px; flex-shrink: 0; } */
|
||||
.notes-parent-label { font-size: var(--text-xs); color: var(--c-text-secondary); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; align-self: center; }
|
||||
.notes-card-meta { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--c-text-muted); }
|
||||
.notes-card-actions { display: flex; gap: var(--space-2); margin-left: auto; flex-shrink: 0; }
|
||||
.notes-card-text { font-size: var(--text-sm); color: var(--c-text); line-height: 1.55; white-space: pre-wrap; margin: 0; }
|
||||
.notes-micro-badges { display: flex; flex-wrap: wrap; gap: var(--space-1); }
|
||||
.notes-micro-badge { font-size: var(--text-xs); padding: 2px 6px; border-radius: var(--radius-sm); background: var(--c-surface-2); color: var(--c-text-secondary); }
|
||||
.notes-action-btn { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-md); border: 1px solid var(--c-border); background: var(--c-surface-2); color: var(--c-text-muted); cursor: pointer; font-size: 1rem; transition: background .15s, color .15s; }
|
||||
.notes-action-btn:hover { background: var(--c-surface); color: var(--c-text); }
|
||||
.notes-action-btn--danger:hover { background: #fef2f2; color: var(--c-danger); border-color: var(--c-danger); }
|
||||
/* TODO nach Migration entfernen: ersetzt durch .list-item-meta-row */
|
||||
/* .notes-card-meta { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--c-text-muted); } */
|
||||
/* Notes-Override: Actions in Top-Zeile rechts ausrichten (statt align-self:center bei list-item-actions) */
|
||||
.notes-card-actions { margin-left: auto; align-self: flex-start; }
|
||||
/* Notes-Override: Text ohne -webkit-line-clamp (komplett anzeigen) + pre-wrap */
|
||||
.notes-card-text { line-height: 1.55; white-space: pre-wrap; margin: 0; display: block; -webkit-line-clamp: unset; overflow: visible; color: var(--c-text); }
|
||||
/* TODO nach Migration entfernen: ersetzt durch .list-item-micro-badges / .list-item-micro-badge */
|
||||
/* .notes-micro-badges { display: flex; flex-wrap: wrap; gap: var(--space-1); } */
|
||||
/* .notes-micro-badge { font-size: var(--text-xs); padding: 2px 6px; border-radius: var(--radius-sm); background: var(--c-surface-2); color: var(--c-text-secondary); } */
|
||||
/* TODO nach Migration entfernen: ersetzt durch .list-item-action-btn / --danger */
|
||||
/* .notes-action-btn { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-md); border: 1px solid var(--c-border); background: var(--c-surface-2); color: var(--c-text-muted); cursor: pointer; font-size: 1rem; transition: background .15s, color .15s; } */
|
||||
/* .notes-action-btn:hover { background: var(--c-surface); color: var(--c-text); } */
|
||||
/* .notes-action-btn--danger:hover { background: #fef2f2; color: var(--c-danger); border-color: var(--c-danger); } */
|
||||
|
||||
.notes-list { display: flex; flex-direction: column; gap: var(--space-4); }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
|
@ -314,11 +321,10 @@ window.Page_notes = (() => {
|
|||
const hasLocation = !!note.location_name;
|
||||
|
||||
return `
|
||||
<div class="notes-card" data-id="${note.id}">
|
||||
<div class="list-item-card list-item-card--clickable notes-card" data-id="${note.id}">
|
||||
<!-- Top-Zeile: Rubrik-Chip + parent_label + Zeit + Buttons -->
|
||||
<div class="notes-card-top">
|
||||
<span class="notes-rubrik-chip"
|
||||
style="background:${rb.color}22;color:${rb.color}">
|
||||
<span class="list-item-chip" style="--chip-color:${rb.color}">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${rb.icon}"></use></svg>
|
||||
${_esc(rb.label)}
|
||||
</span>
|
||||
|
|
@ -326,28 +332,28 @@ window.Page_notes = (() => {
|
|||
? `<span class="notes-parent-label" title="${_esc(note.parent_label)}">${_esc(note.parent_label)}</span>`
|
||||
: ''
|
||||
}
|
||||
<div class="notes-card-actions">
|
||||
<button class="notes-action-btn notes-edit-btn" data-id="${note.id}" title="Bearbeiten">
|
||||
<div class="list-item-actions notes-card-actions">
|
||||
<button class="list-item-action-btn notes-edit-btn" data-id="${note.id}" title="Bearbeiten">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#pencil"></use></svg>
|
||||
</button>
|
||||
<button class="notes-action-btn notes-action-btn--danger notes-delete-btn" data-id="${note.id}" title="Löschen">
|
||||
<button class="list-item-action-btn list-item-action-btn--danger notes-delete-btn" data-id="${note.id}" title="Löschen">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notiztext -->
|
||||
<p class="notes-card-text">${_esc(_truncate(note.text))}</p>
|
||||
<p class="list-item-text notes-card-text">${_esc(_truncate(note.text))}</p>
|
||||
|
||||
<!-- Micro-Badges -->
|
||||
${microBadges.length ? `
|
||||
<div class="notes-micro-badges">
|
||||
${microBadges.map(b => `<span class="notes-micro-badge">${_esc(b)}</span>`).join('')}
|
||||
<div class="list-item-micro-badges">
|
||||
${microBadges.map(b => `<span class="list-item-micro-badge">${_esc(b)}</span>`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Meta: Zeit + Ort -->
|
||||
<div class="notes-card-meta">
|
||||
<div class="list-item-meta-row">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#clock"></use></svg>
|
||||
${_esc(_formatTime(note.updated_at || note.created_at))}
|
||||
${hasLocation ? `<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#map-pin"></use></svg> ${_esc(note.location_name)}` : ''}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<script src="/js/landing-init.js?v=1103"></script>
|
||||
<script src="/js/landing-init.js?v=1104"></script>
|
||||
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
|
||||
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
|
||||
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
============================================================ */
|
||||
|
||||
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
||||
const VER = '1103';
|
||||
const VER = '1104';
|
||||
const CACHE_VERSION = `by-v${VER}`;
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue