UX: Modal-Rand, Icon-Farben, Adresse aufgeteilt

- Tierarzt-Adresse: strasse / plz / ort statt einzeiligem Freitext
- Modal: Rand in Primärfarbe + kein versehentliches Schließen beim Klick auf Hintergrund
- Nav/Sidebar-Icons: inaktiv gedämpft, aktiv amber-getönt (CSS filter)
- Datums-Kalender-Icon: ebenfalls amber statt Schwarz
- SW-Cache → by-v8
This commit is contained in:
rene 2026-04-13 20:16:36 +02:00
parent fc0f48c6d0
commit dee8d10496
7 changed files with 59 additions and 14 deletions

View file

@ -254,7 +254,9 @@ def init_db():
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL, name TEXT NOT NULL,
adresse TEXT, strasse TEXT,
plz TEXT,
ort TEXT,
telefon TEXT, telefon TEXT,
notfall_telefon TEXT, notfall_telefon TEXT,
email TEXT, email TEXT,
@ -295,6 +297,10 @@ def _migrate(conn_factory):
("health", "datei_url", "TEXT"), ("health", "datei_url", "TEXT"),
("health", "datei_typ", "TEXT"), ("health", "datei_typ", "TEXT"),
("health", "tierarzt_id", "INTEGER"), ("health", "tierarzt_id", "INTEGER"),
# Tierärzte: Adresse aufgeteilt in Strasse/PLZ/Ort
("tieraerzte", "strasse", "TEXT"),
("tieraerzte", "plz", "TEXT"),
("tieraerzte", "ort", "TEXT"),
] ]
with conn_factory() as conn: with conn_factory() as conn:
for table, column, col_type in migrations: for table, column, col_type in migrations:

View file

@ -11,7 +11,9 @@ router = APIRouter()
class TierarztCreate(BaseModel): class TierarztCreate(BaseModel):
name: str name: str
adresse: Optional[str] = None strasse: Optional[str] = None
plz: Optional[str] = None
ort: Optional[str] = None
telefon: Optional[str] = None telefon: Optional[str] = None
notfall_telefon: Optional[str] = None notfall_telefon: Optional[str] = None
email: Optional[str] = None email: Optional[str] = None
@ -22,7 +24,9 @@ class TierarztCreate(BaseModel):
class TierarztUpdate(BaseModel): class TierarztUpdate(BaseModel):
name: Optional[str] = None name: Optional[str] = None
adresse: Optional[str] = None strasse: Optional[str] = None
plz: Optional[str] = None
ort: Optional[str] = None
telefon: Optional[str] = None telefon: Optional[str] = None
notfall_telefon: Optional[str] = None notfall_telefon: Optional[str] = None
email: Optional[str] = None email: Optional[str] = None
@ -48,11 +52,11 @@ async def create_tierarzt(data: TierarztCreate, user=Depends(get_current_user)):
with db() as conn: with db() as conn:
conn.execute( conn.execute(
"""INSERT INTO tieraerzte """INSERT INTO tieraerzte
(user_id, name, adresse, telefon, notfall_telefon, (user_id, name, strasse, plz, ort, telefon, notfall_telefon,
email, website, notizen, ist_notfallpraxis) email, website, notizen, ist_notfallpraxis)
VALUES (?,?,?,?,?,?,?,?,?)""", VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
(user["id"], data.name, data.adresse, data.telefon, (user["id"], data.name, data.strasse, data.plz, data.ort,
data.notfall_telefon, data.email, data.website, data.telefon, data.notfall_telefon, data.email, data.website,
data.notizen, int(data.ist_notfallpraxis)) data.notizen, int(data.ist_notfallpraxis))
) )
row = conn.execute( row = conn.execute(

View file

@ -239,6 +239,14 @@
.form-control::placeholder { color: var(--c-text-muted); } .form-control::placeholder { color: var(--c-text-muted); }
/* Kalender-Icon in Datumseingaben: Amber statt Schwarz */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator {
cursor: pointer;
opacity: 0.85;
filter: sepia(0.7) saturate(1.8) hue-rotate(-8deg) brightness(0.9);
}
.form-control:focus { .form-control:focus {
outline: none; outline: none;
border-color: var(--c-primary); border-color: var(--c-primary);
@ -450,6 +458,7 @@ textarea.form-control {
.modal { .modal {
background: var(--c-surface); background: var(--c-surface);
border-radius: var(--radius-xl) var(--radius-xl) 0 0; border-radius: var(--radius-xl) var(--radius-xl) 0 0;
border: 2px solid var(--c-primary);
width: 100%; width: 100%;
max-width: 480px; max-width: 480px;
max-height: 90vh; max-height: 90vh;

View file

@ -142,6 +142,11 @@
font-size: 22px; font-size: 22px;
line-height: 1; line-height: 1;
position: relative; position: relative;
filter: saturate(0.35) brightness(0.75);
transition: filter var(--transition-fast);
}
.nav-item.active .nav-item-icon {
filter: sepia(0.7) saturate(1.8) hue-rotate(-8deg) brightness(0.9);
} }
/* Ungelesen-Badge auf Nav-Icon */ /* Ungelesen-Badge auf Nav-Icon */
@ -308,6 +313,11 @@
width: 24px; width: 24px;
text-align: center; text-align: center;
flex-shrink: 0; flex-shrink: 0;
filter: saturate(0.35) brightness(0.75);
transition: filter var(--transition-fast);
}
.sidebar-item.active .sidebar-item-icon {
filter: sepia(0.7) saturate(1.8) hue-rotate(-8deg) brightness(0.9);
} }
.sidebar-item-badge { .sidebar-item-badge {
margin-left: auto; margin-left: auto;

View file

@ -812,7 +812,10 @@ window.Page_health = (() => {
${_esc(p.name)} ${_esc(p.name)}
${!p.aktiv ? '<span style="font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:400"> · Ehemalig</span>' : ''} ${!p.aktiv ? '<span style="font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:400"> · Ehemalig</span>' : ''}
</div> </div>
${p.adresse ? `<div class="health-card-meta">${_esc(p.adresse)}</div>` : ''} ${(p.strasse || p.plz || p.ort) ? `
<div class="health-card-meta">
${[p.strasse, [p.plz, p.ort].filter(Boolean).join(' ')].filter(Boolean).map(_esc).join(', ')}
</div>` : ''}
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-2);flex-wrap:wrap"> <div style="display:flex;gap:var(--space-2);margin-top:var(--space-2);flex-wrap:wrap">
${p.telefon ? ` ${p.telefon ? `
<a href="tel:${_esc(p.telefon)}" class="btn btn-secondary btn-sm" <a href="tel:${_esc(p.telefon)}" class="btn btn-secondary btn-sm"
@ -861,9 +864,21 @@ window.Page_health = (() => {
value="${_esc(praxis?.name || '')}" placeholder="Dr. Muster Tierarztpraxis" required> value="${_esc(praxis?.name || '')}" placeholder="Dr. Muster Tierarztpraxis" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">Adresse</label> <label class="form-label">Straße &amp; Hausnummer</label>
<input class="form-control" type="text" name="adresse" <input class="form-control" type="text" name="strasse"
value="${_esc(praxis?.adresse || '')}" placeholder="Musterstraße 1, 12345 Stadt"> value="${_esc(praxis?.strasse || '')}" placeholder="Musterstraße 1">
</div>
<div style="display:grid;grid-template-columns:120px 1fr;gap:var(--space-3)">
<div class="form-group">
<label class="form-label">PLZ</label>
<input class="form-control" type="text" name="plz" inputmode="numeric"
value="${_esc(praxis?.plz || '')}" placeholder="12345">
</div>
<div class="form-group">
<label class="form-label">Ort</label>
<input class="form-control" type="text" name="ort"
value="${_esc(praxis?.ort || '')}" placeholder="Musterstadt">
</div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="form-group"> <div class="form-group">
@ -918,7 +933,9 @@ window.Page_health = (() => {
await UI.asyncButton(btn, async () => { await UI.asyncButton(btn, async () => {
const payload = { const payload = {
name: fd.name?.trim(), name: fd.name?.trim(),
adresse: fd.adresse || null, strasse: fd.strasse || null,
plz: fd.plz || null,
ort: fd.ort || null,
telefon: fd.telefon || null, telefon: fd.telefon || null,
notfall_telefon: fd.notfall_telefon || null, notfall_telefon: fd.notfall_telefon || null,
email: fd.email || null, email: fd.email || null,

View file

@ -68,7 +68,6 @@ const UI = (() => {
`; `;
overlay.querySelector('.modal-close-btn')?.addEventListener('click', close); overlay.querySelector('.modal-close-btn')?.addEventListener('click', close);
overlay.addEventListener('click', e => { if (e.target === overlay) close(); });
document.getElementById('modal-container').appendChild(overlay); document.getElementById('modal-container').appendChild(overlay);
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications Offline-Cache + Push Notifications
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v7'; const CACHE_VERSION = 'by-v8';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
// Diese Dateien werden beim Install gecacht (App Shell) // Diese Dateien werden beim Install gecacht (App Shell)