feat: VitalsPage mobile-optimized with inline editing & smart upsert
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s

- Full-width fields with section headers (mobile-friendly)
- Inline editing for all measurements (edit mode per row)
- Smart upsert: date change loads existing entry → update instead of duplicate
- Units integrated into labels (no overflow)
- Baseline: auto-detects existing entry and switches to update mode
- Blood Pressure: inline editing with all fields (date, time, BP, context, flags)
- Edit/Save/Cancel buttons with lucide-react icons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-23 16:19:53 +01:00
parent 7f10286e02
commit 10772d1f80

View File

@ -1,12 +1,12 @@
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef } from 'react'
import { Pencil, Trash2, X, TrendingUp, TrendingDown, Minus, Upload } from 'lucide-react' import { Pencil, Trash2, X, Save, TrendingUp, TrendingDown, Minus, Upload } from 'lucide-react'
import { api } from '../utils/api' import { api } from '../utils/api'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import 'dayjs/locale/de' import 'dayjs/locale/de'
dayjs.locale('de') dayjs.locale('de')
/** /**
* VitalsPage - Refactored v9d Phase 2d * VitalsPage - Refactored v9d Phase 2d (Mobile-optimized, Inline Editing, Smart Upsert)
* *
* Separated vitals tracking: * Separated vitals tracking:
* - Baseline Vitals: Once daily (morning, fasted) - RHR, HRV, VO2 Max, SpO2 * - Baseline Vitals: Once daily (morning, fasted) - RHR, HRV, VO2 Max, SpO2
@ -22,6 +22,7 @@ function BaselineTab() {
const [entries, setEntries] = useState([]) const [entries, setEntries] = useState([])
const [stats, setStats] = useState(null) const [stats, setStats] = useState(null)
const [form, setForm] = useState({ const [form, setForm] = useState({
id: null, // Für Update
date: dayjs().format('YYYY-MM-DD'), date: dayjs().format('YYYY-MM-DD'),
resting_hr: '', resting_hr: '',
hrv: '', hrv: '',
@ -30,7 +31,8 @@ function BaselineTab() {
respiratory_rate: '', respiratory_rate: '',
note: '' note: ''
}) })
const [editing, setEditing] = useState(null) const [editingId, setEditingId] = useState(null)
const [editForm, setEditForm] = useState({})
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [error, setError] = useState(null) const [error, setError] = useState(null)
const [success, setSuccess] = useState(false) const [success, setSuccess] = useState(false)
@ -50,6 +52,47 @@ function BaselineTab() {
useEffect(() => { load() }, []) useEffect(() => { load() }, [])
// Smart Upsert: Beim Datum-Wechsel existierenden Eintrag laden
useEffect(() => {
const loadExisting = async () => {
if (!form.date) return
try {
const existing = await api.getBaselineByDate(form.date)
if (existing && existing.id) {
// Eintrag gefunden Formular vorausfüllen
setForm({
id: existing.id,
date: existing.date,
resting_hr: existing.resting_hr || '',
hrv: existing.hrv || '',
vo2_max: existing.vo2_max || '',
spo2: existing.spo2 || '',
respiratory_rate: existing.respiratory_rate || '',
note: existing.note || ''
})
} else {
// Kein Eintrag leeres Formular (nur Datum behalten)
setForm(f => ({
id: null,
date: f.date,
resting_hr: '',
hrv: '',
vo2_max: '',
spo2: '',
respiratory_rate: '',
note: ''
}))
}
} catch (err) {
// 404 ist ok (kein Eintrag vorhanden)
if (!err.message.includes('404')) {
console.error('Fehler beim Laden:', err)
}
}
}
loadExisting()
}, [form.date])
const handleSave = async () => { const handleSave = async () => {
setSaving(true) setSaving(true)
setError(null) setError(null)
@ -68,12 +111,17 @@ function BaselineTab() {
return return
} }
await api.createBaseline(payload) if (form.id) {
await api.updateBaseline(form.id, payload)
} else {
await api.createBaseline(payload)
}
setSuccess(true) setSuccess(true)
await load() await load()
setTimeout(() => { setTimeout(() => {
setSuccess(false) setSuccess(false)
setForm({ date: dayjs().format('YYYY-MM-DD'), resting_hr: '', hrv: '', vo2_max: '', spo2: '', respiratory_rate: '', note: '' }) setForm({ id: null, date: dayjs().format('YYYY-MM-DD'), resting_hr: '', hrv: '', vo2_max: '', spo2: '', respiratory_rate: '', note: '' })
}, 1500) }, 1500)
} catch (err) { } catch (err) {
setError(err.message) setError(err.message)
@ -92,10 +140,41 @@ function BaselineTab() {
} }
} }
const getTrendIcon = (trend) => { const startEdit = (entry) => {
if (trend === 'increasing') return <TrendingUp size={14} style={{ color: '#D85A30' }} /> setEditingId(entry.id)
if (trend === 'decreasing') return <TrendingDown size={14} style={{ color: '#1D9E75' }} /> setEditForm({
return <Minus size={14} style={{ color: 'var(--text3)' }} /> resting_hr: entry.resting_hr || '',
hrv: entry.hrv || '',
vo2_max: entry.vo2_max || '',
spo2: entry.spo2 || '',
respiratory_rate: entry.respiratory_rate || '',
note: entry.note || ''
})
}
const cancelEdit = () => {
setEditingId(null)
setEditForm({})
}
const saveEdit = async (id) => {
try {
const entry = entries.find(e => e.id === id)
const payload = { date: entry.date }
if (editForm.resting_hr) payload.resting_hr = parseInt(editForm.resting_hr)
if (editForm.hrv) payload.hrv = parseInt(editForm.hrv)
if (editForm.vo2_max) payload.vo2_max = parseFloat(editForm.vo2_max)
if (editForm.spo2) payload.spo2 = parseInt(editForm.spo2)
if (editForm.respiratory_rate) payload.respiratory_rate = parseFloat(editForm.respiratory_rate)
if (editForm.note) payload.note = editForm.note
await api.updateBaseline(id, payload)
setEditingId(null)
setEditForm({})
await load()
} catch (err) {
setError(err.message)
}
} }
return ( return (
@ -129,56 +208,112 @@ function BaselineTab() {
{/* Form */} {/* Form */}
<div className="card" style={{ marginBottom: 12 }}> <div className="card" style={{ marginBottom: 12 }}>
<div className="card-title">Morgenmessung erfassen</div> <div className="card-title">Morgenmessung erfassen</div>
<p style={{ fontSize: 12, color: 'var(--text3)', marginBottom: 12 }}> <p style={{ fontSize: 12, color: 'var(--text3)', marginBottom: 16 }}>
Einmal täglich, morgens vor dem Aufstehen (nüchtern) Einmal täglich, morgens vor dem Aufstehen (nüchtern). {form.id && <strong style={{ color: 'var(--accent)' }}>Eintrag wird aktualisiert.</strong>}
</p> </p>
{error && <div style={{ padding: 10, background: '#FCEBEB', border: '1px solid #D85A30', borderRadius: 8, fontSize: 13, color: '#D85A30', marginBottom: 8 }}>{error}</div>} {error && <div style={{ padding: 10, background: '#FCEBEB', border: '1px solid #D85A30', borderRadius: 8, fontSize: 13, color: '#D85A30', marginBottom: 12 }}>{error}</div>}
<div className="form-row"> {/* Datum - volle Breite */}
<label className="form-label">Datum</label> <div style={{ marginBottom: 16 }}>
<input type="date" className="form-input" style={{ width: 140 }} value={form.date} onChange={e => setForm(f => ({ ...f, date: e.target.value }))} /> <label style={{ display: 'block', fontSize: 13, fontWeight: 600, marginBottom: 4, color: 'var(--text2)' }}>Datum</label>
<span className="form-unit" /> <input
type="date"
className="form-input"
style={{ width: '100%' }}
value={form.date}
onChange={e => setForm(f => ({ ...f, date: e.target.value }))}
/>
</div> </div>
<div className="form-row"> {/* Sektion: Herzfunktion */}
<label className="form-label">Ruhepuls</label> <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, color: 'var(--text1)' }}> Herzfunktion</div>
<input type="number" className="form-input" min={30} max={120} placeholder="z.B. 58" value={form.resting_hr} onChange={e => setForm(f => ({ ...f, resting_hr: e.target.value }))} /> <div style={{ marginBottom: 16 }}>
<span className="form-unit">bpm</span> <label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Ruhepuls (bpm)</label>
<input
type="number"
className="form-input"
style={{ width: '100%' }}
min={30}
max={120}
placeholder="z.B. 58"
value={form.resting_hr}
onChange={e => setForm(f => ({ ...f, resting_hr: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 16 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>HRV (ms)</label>
<input
type="number"
className="form-input"
style={{ width: '100%' }}
min={1}
max={300}
placeholder="optional"
value={form.hrv}
onChange={e => setForm(f => ({ ...f, hrv: e.target.value }))}
/>
</div> </div>
<div className="form-row"> {/* Sektion: Fitness & Atmung */}
<label className="form-label">HRV</label> <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, marginTop: 20, color: 'var(--text1)' }}>🏃 Fitness & Atmung</div>
<input type="number" className="form-input" min={1} max={300} placeholder="optional" value={form.hrv} onChange={e => setForm(f => ({ ...f, hrv: e.target.value }))} /> <div style={{ marginBottom: 16 }}>
<span className="form-unit">ms</span> <label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>VO2 Max (ml/kg/min)</label>
<input
type="number"
className="form-input"
style={{ width: '100%' }}
min={10}
max={90}
step={0.1}
placeholder="optional"
value={form.vo2_max}
onChange={e => setForm(f => ({ ...f, vo2_max: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 16 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>SpO2 (%)</label>
<input
type="number"
className="form-input"
style={{ width: '100%' }}
min={70}
max={100}
placeholder="optional"
value={form.spo2}
onChange={e => setForm(f => ({ ...f, spo2: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 16 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Atemfrequenz (/min)</label>
<input
type="number"
className="form-input"
style={{ width: '100%' }}
min={1}
max={60}
step={0.1}
placeholder="optional"
value={form.respiratory_rate}
onChange={e => setForm(f => ({ ...f, respiratory_rate: e.target.value }))}
/>
</div> </div>
<div className="form-row"> {/* Notiz */}
<label className="form-label">VO2 Max</label> <div style={{ marginBottom: 16 }}>
<input type="number" className="form-input" min={10} max={90} step={0.1} placeholder="optional" value={form.vo2_max} onChange={e => setForm(f => ({ ...f, vo2_max: e.target.value }))} /> <label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Notiz</label>
<span className="form-unit">ml/kg/min</span> <input
</div> type="text"
className="form-input"
<div className="form-row"> style={{ width: '100%' }}
<label className="form-label">SpO2</label> placeholder="optional"
<input type="number" className="form-input" min={70} max={100} placeholder="optional" value={form.spo2} onChange={e => setForm(f => ({ ...f, spo2: e.target.value }))} /> value={form.note}
<span className="form-unit">%</span> onChange={e => setForm(f => ({ ...f, note: e.target.value }))}
</div> />
<div className="form-row">
<label className="form-label">Atemfrequenz</label>
<input type="number" className="form-input" min={1} max={60} step={0.1} placeholder="optional" value={form.respiratory_rate} onChange={e => setForm(f => ({ ...f, respiratory_rate: e.target.value }))} />
<span className="form-unit">/min</span>
</div>
<div className="form-row">
<label className="form-label">Notiz</label>
<input type="text" className="form-input" placeholder="optional" value={form.note} onChange={e => setForm(f => ({ ...f, note: e.target.value }))} />
<span className="form-unit" />
</div> </div>
<button className="btn btn-primary btn-full" onClick={handleSave} disabled={saving}> <button className="btn btn-primary btn-full" onClick={handleSave} disabled={saving}>
{success ? '✓ Gespeichert!' : saving ? 'Speichere...' : 'Speichern'} {success ? '✓ Gespeichert!' : saving ? 'Speichere...' : form.id ? 'Aktualisieren' : 'Speichern'}
</button> </button>
</div> </div>
@ -188,24 +323,109 @@ function BaselineTab() {
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8 }}>Letzte Messungen ({entries.length})</div> <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8 }}>Letzte Messungen ({entries.length})</div>
{entries.map(e => ( {entries.map(e => (
<div key={e.id} className="card" style={{ marginBottom: 8, padding: 12 }}> <div key={e.id} className="card" style={{ marginBottom: 8, padding: 12 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}> {editingId === e.id ? (
<div style={{ flex: 1 }}> // Edit Mode
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}> <div>
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8, color: 'var(--text2)' }}>
{dayjs(e.date).format('dd, DD. MMM YYYY')} {dayjs(e.date).format('dd, DD. MMM YYYY')}
</div> </div>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', fontSize: 12, color: 'var(--text2)' }}> <div style={{ marginBottom: 8 }}>
{e.resting_hr && <span> {e.resting_hr} bpm</span>} <label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Ruhepuls (bpm)</label>
{e.hrv && <span>📊 HRV {e.hrv} ms</span>} <input
{e.vo2_max && <span style={{ color: '#1D9E75', fontWeight: 600 }}>🏃 VO2 {e.vo2_max}</span>} type="number"
{e.spo2 && <span>🫁 SpO2 {e.spo2}%</span>} className="form-input"
{e.respiratory_rate && <span>💨 {e.respiratory_rate}/min</span>} style={{ width: '100%', fontSize: 13 }}
value={editForm.resting_hr}
onChange={e => setEditForm(f => ({ ...f, resting_hr: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>HRV (ms)</label>
<input
type="number"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.hrv}
onChange={e => setEditForm(f => ({ ...f, hrv: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>VO2 Max</label>
<input
type="number"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
step={0.1}
value={editForm.vo2_max}
onChange={e => setEditForm(f => ({ ...f, vo2_max: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>SpO2 (%)</label>
<input
type="number"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.spo2}
onChange={e => setEditForm(f => ({ ...f, spo2: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Atemfrequenz (/min)</label>
<input
type="number"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
step={0.1}
value={editForm.respiratory_rate}
onChange={e => setEditForm(f => ({ ...f, respiratory_rate: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 12 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Notiz</label>
<input
type="text"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.note}
onChange={e => setEditForm(f => ({ ...f, note: e.target.value }))}
/>
</div>
<div style={{ display: 'flex', gap: 6 }}>
<button className="btn btn-primary" style={{ flex: 1, fontSize: 13 }} onClick={() => saveEdit(e.id)}>
<Save size={13} style={{ marginRight: 4 }} /> Speichern
</button>
<button className="btn btn-secondary" style={{ fontSize: 13 }} onClick={cancelEdit}>
<X size={13} />
</button>
</div> </div>
{e.note && <p style={{ fontSize: 12, color: 'var(--text3)', fontStyle: 'italic', marginTop: 4 }}>"{e.note}"</p>}
</div> </div>
<button className="btn btn-danger" style={{ padding: '5px 8px' }} onClick={() => handleDelete(e.id)}> ) : (
<Trash2 size={13} /> // View Mode
</button> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
</div> <div style={{ flex: 1 }}>
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}>
{dayjs(e.date).format('dd, DD. MMM YYYY')}
</div>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', fontSize: 12, color: 'var(--text2)' }}>
{e.resting_hr && <span> {e.resting_hr} bpm</span>}
{e.hrv && <span>📊 HRV {e.hrv} ms</span>}
{e.vo2_max && <span style={{ color: '#1D9E75', fontWeight: 600 }}>🏃 VO2 {e.vo2_max}</span>}
{e.spo2 && <span>🫁 SpO2 {e.spo2}%</span>}
{e.respiratory_rate && <span>💨 {e.respiratory_rate}/min</span>}
</div>
{e.note && <p style={{ fontSize: 12, color: 'var(--text3)', fontStyle: 'italic', marginTop: 4 }}>"{e.note}"</p>}
</div>
<div style={{ display: 'flex', gap: 4 }}>
<button className="btn btn-secondary" style={{ padding: '5px 8px' }} onClick={() => startEdit(e)}>
<Pencil size={13} />
</button>
<button className="btn btn-danger" style={{ padding: '5px 8px' }} onClick={() => handleDelete(e.id)}>
<Trash2 size={13} />
</button>
</div>
</div>
)}
</div> </div>
))} ))}
</div> </div>
@ -243,6 +463,8 @@ function BloodPressureTab() {
possible_afib: false, possible_afib: false,
note: '' note: ''
}) })
const [editingId, setEditingId] = useState(null)
const [editForm, setEditForm] = useState({})
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [error, setError] = useState(null) const [error, setError] = useState(null)
const [success, setSuccess] = useState(false) const [success, setSuccess] = useState(false)
@ -317,6 +539,50 @@ function BloodPressureTab() {
} }
} }
const startEdit = (entry) => {
const dt = dayjs(entry.measured_at)
setEditingId(entry.id)
setEditForm({
date: dt.format('YYYY-MM-DD'),
time: dt.format('HH:mm'),
systolic: entry.systolic,
diastolic: entry.diastolic,
pulse: entry.pulse || '',
context: entry.context,
irregular_heartbeat: entry.irregular_heartbeat,
possible_afib: entry.possible_afib,
note: entry.note || ''
})
}
const cancelEdit = () => {
setEditingId(null)
setEditForm({})
}
const saveEdit = async (id) => {
try {
const measured_at = `${editForm.date} ${editForm.time}:00`
const payload = {
measured_at,
systolic: parseInt(editForm.systolic),
diastolic: parseInt(editForm.diastolic),
pulse: editForm.pulse ? parseInt(editForm.pulse) : null,
context: editForm.context,
irregular_heartbeat: editForm.irregular_heartbeat,
possible_afib: editForm.possible_afib,
note: editForm.note || null
}
await api.updateBloodPressure(id, payload)
setEditingId(null)
setEditForm({})
await load()
} catch (err) {
setError(err.message)
}
}
const getBPCategory = (sys, dia) => { const getBPCategory = (sys, dia) => {
if (sys < 120 && dia < 80) return { label: 'Optimal', color: '#1D9E75' } if (sys < 120 && dia < 80) return { label: 'Optimal', color: '#1D9E75' }
if (sys < 130 && dia < 85) return { label: 'Normal', color: '#1D9E75' } if (sys < 130 && dia < 85) return { label: 'Normal', color: '#1D9E75' }
@ -359,51 +625,92 @@ function BloodPressureTab() {
{/* Form */} {/* Form */}
<div className="card" style={{ marginBottom: 12 }}> <div className="card" style={{ marginBottom: 12 }}>
<div className="card-title">Blutdruck messen</div> <div className="card-title">Blutdruck messen</div>
<p style={{ fontSize: 12, color: 'var(--text3)', marginBottom: 12 }}> <p style={{ fontSize: 12, color: 'var(--text3)', marginBottom: 16 }}>
Mehrfach täglich mit Kontext-Tagging Mehrfach täglich mit Kontext-Tagging
</p> </p>
{error && <div style={{ padding: 10, background: '#FCEBEB', border: '1px solid #D85A30', borderRadius: 8, fontSize: 13, color: '#D85A30', marginBottom: 8 }}>{error}</div>} {error && <div style={{ padding: 10, background: '#FCEBEB', border: '1px solid #D85A30', borderRadius: 8, fontSize: 13, color: '#D85A30', marginBottom: 12 }}>{error}</div>}
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}> {/* Datum + Uhrzeit - volle Breite */}
<div style={{ flex: 1 }}> <div style={{ marginBottom: 16 }}>
<label className="form-label">Datum</label> <label style={{ display: 'block', fontSize: 13, fontWeight: 600, marginBottom: 4, color: 'var(--text2)' }}>Datum</label>
<input type="date" className="form-input" value={form.date} onChange={e => setForm(f => ({ ...f, date: e.target.value }))} /> <input
</div> type="date"
<div style={{ flex: 1 }}> className="form-input"
<label className="form-label">Uhrzeit</label> style={{ width: '100%' }}
<input type="time" className="form-input" value={form.time} onChange={e => setForm(f => ({ ...f, time: e.target.value }))} /> value={form.date}
</div> onChange={e => setForm(f => ({ ...f, date: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 16 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 600, marginBottom: 4, color: 'var(--text2)' }}>Uhrzeit</label>
<input
type="time"
className="form-input"
style={{ width: '100%' }}
value={form.time}
onChange={e => setForm(f => ({ ...f, time: e.target.value }))}
/>
</div> </div>
<div className="form-row"> {/* Sektion: Blutdruck */}
<label className="form-label">Systolisch</label> <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, color: 'var(--text1)' }}>🩸 Blutdruck</div>
<input type="number" className="form-input" min={50} max={250} placeholder="z.B. 125" value={form.systolic} onChange={e => setForm(f => ({ ...f, systolic: e.target.value }))} /> <div style={{ marginBottom: 16 }}>
<span className="form-unit">mmHg</span> <label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Systolisch (mmHg)</label>
<input
type="number"
className="form-input"
style={{ width: '100%' }}
min={50}
max={250}
placeholder="z.B. 125"
value={form.systolic}
onChange={e => setForm(f => ({ ...f, systolic: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 16 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Diastolisch (mmHg)</label>
<input
type="number"
className="form-input"
style={{ width: '100%' }}
min={30}
max={150}
placeholder="z.B. 83"
value={form.diastolic}
onChange={e => setForm(f => ({ ...f, diastolic: e.target.value }))}
/>
</div>
<div style={{ marginBottom: 16 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Puls (bpm)</label>
<input
type="number"
className="form-input"
style={{ width: '100%' }}
min={30}
max={200}
placeholder="optional"
value={form.pulse}
onChange={e => setForm(f => ({ ...f, pulse: e.target.value }))}
/>
</div> </div>
<div className="form-row"> {/* Kontext */}
<label className="form-label">Diastolisch</label> <div style={{ marginBottom: 16 }}>
<input type="number" className="form-input" min={30} max={150} placeholder="z.B. 83" value={form.diastolic} onChange={e => setForm(f => ({ ...f, diastolic: e.target.value }))} /> <label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Kontext</label>
<span className="form-unit">mmHg</span> <select
</div> className="form-input"
style={{ width: '100%' }}
<div className="form-row"> value={form.context}
<label className="form-label">Puls</label> onChange={e => setForm(f => ({ ...f, context: e.target.value }))}
<input type="number" className="form-input" min={30} max={200} placeholder="optional" value={form.pulse} onChange={e => setForm(f => ({ ...f, pulse: e.target.value }))} /> >
<span className="form-unit">bpm</span>
</div>
<div className="form-row">
<label className="form-label">Kontext</label>
<select className="form-input" value={form.context} onChange={e => setForm(f => ({ ...f, context: e.target.value }))}>
{CONTEXT_OPTIONS.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)} {CONTEXT_OPTIONS.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
</select> </select>
<span className="form-unit" />
</div> </div>
<div style={{ marginBottom: 8 }}> {/* Checkboxen */}
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, marginBottom: 4 }}> <div style={{ marginBottom: 16 }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, marginBottom: 6 }}>
<input type="checkbox" checked={form.irregular_heartbeat} onChange={e => setForm(f => ({ ...f, irregular_heartbeat: e.target.checked }))} /> <input type="checkbox" checked={form.irregular_heartbeat} onChange={e => setForm(f => ({ ...f, irregular_heartbeat: e.target.checked }))} />
Unregelmäßiger Herzschlag Unregelmäßiger Herzschlag
</label> </label>
@ -413,10 +720,17 @@ function BloodPressureTab() {
</label> </label>
</div> </div>
<div className="form-row"> {/* Notiz */}
<label className="form-label">Notiz</label> <div style={{ marginBottom: 16 }}>
<input type="text" className="form-input" placeholder="optional" value={form.note} onChange={e => setForm(f => ({ ...f, note: e.target.value }))} /> <label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Notiz</label>
<span className="form-unit" /> <input
type="text"
className="form-input"
style={{ width: '100%' }}
placeholder="optional"
value={form.note}
onChange={e => setForm(f => ({ ...f, note: e.target.value }))}
/>
</div> </div>
<button className="btn btn-primary btn-full" onClick={handleSave} disabled={saving}> <button className="btn btn-primary btn-full" onClick={handleSave} disabled={saving}>
@ -433,25 +747,134 @@ function BloodPressureTab() {
const ctx = CONTEXT_OPTIONS.find(o => o.value === e.context) const ctx = CONTEXT_OPTIONS.find(o => o.value === e.context)
return ( return (
<div key={e.id} className="card" style={{ marginBottom: 8, padding: 12 }}> <div key={e.id} className="card" style={{ marginBottom: 8, padding: 12 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}> {editingId === e.id ? (
<div style={{ flex: 1 }}> // Edit Mode
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 2 }}> <div>
{dayjs(e.measured_at).format('dd, DD. MMM • HH:mm')} Uhr <div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Datum</label>
<input
type="date"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.date}
onChange={ev => setEditForm(f => ({ ...f, date: ev.target.value }))}
/>
</div> </div>
<div style={{ fontSize: 16, fontWeight: 700, color: cat.color, marginBottom: 4 }}> <div style={{ marginBottom: 8 }}>
{e.systolic}/{e.diastolic} mmHg <label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Uhrzeit</label>
{e.pulse && <span style={{ fontSize: 12, fontWeight: 400, marginLeft: 8 }}>💓 {e.pulse} bpm</span>} <input
type="time"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.time}
onChange={ev => setEditForm(f => ({ ...f, time: ev.target.value }))}
/>
</div> </div>
<div style={{ fontSize: 11, color: 'var(--text3)' }}> <div style={{ marginBottom: 8 }}>
{ctx?.label} · {cat.label} <label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Systolisch (mmHg)</label>
{(e.irregular_heartbeat || e.possible_afib) && <span style={{ color: '#D85A30', marginLeft: 8 }}> {e.irregular_heartbeat ? 'Unregelmäßig' : ''} {e.possible_afib ? 'AFib?' : ''}</span>} <input
type="number"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.systolic}
onChange={ev => setEditForm(f => ({ ...f, systolic: ev.target.value }))}
/>
</div>
<div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Diastolisch (mmHg)</label>
<input
type="number"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.diastolic}
onChange={ev => setEditForm(f => ({ ...f, diastolic: ev.target.value }))}
/>
</div>
<div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Puls (bpm)</label>
<input
type="number"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.pulse}
onChange={ev => setEditForm(f => ({ ...f, pulse: ev.target.value }))}
/>
</div>
<div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Kontext</label>
<select
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.context}
onChange={ev => setEditForm(f => ({ ...f, context: ev.target.value }))}
>
{CONTEXT_OPTIONS.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
</select>
</div>
<div style={{ marginBottom: 8 }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, marginBottom: 4 }}>
<input
type="checkbox"
checked={editForm.irregular_heartbeat}
onChange={ev => setEditForm(f => ({ ...f, irregular_heartbeat: ev.target.checked }))}
/>
Unregelmäßiger Herzschlag
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }}>
<input
type="checkbox"
checked={editForm.possible_afib}
onChange={ev => setEditForm(f => ({ ...f, possible_afib: ev.target.checked }))}
/>
Mögliches Vorhofflimmern
</label>
</div>
<div style={{ marginBottom: 12 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Notiz</label>
<input
type="text"
className="form-input"
style={{ width: '100%', fontSize: 13 }}
value={editForm.note}
onChange={ev => setEditForm(f => ({ ...f, note: ev.target.value }))}
/>
</div>
<div style={{ display: 'flex', gap: 6 }}>
<button className="btn btn-primary" style={{ flex: 1, fontSize: 13 }} onClick={() => saveEdit(e.id)}>
<Save size={13} style={{ marginRight: 4 }} /> Speichern
</button>
<button className="btn btn-secondary" style={{ fontSize: 13 }} onClick={cancelEdit}>
<X size={13} />
</button>
</div> </div>
{e.note && <p style={{ fontSize: 12, color: 'var(--text3)', fontStyle: 'italic', marginTop: 4 }}>"{e.note}"</p>}
</div> </div>
<button className="btn btn-danger" style={{ padding: '5px 8px' }} onClick={() => handleDelete(e.id)}> ) : (
<Trash2 size={13} /> // View Mode
</button> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
</div> <div style={{ flex: 1 }}>
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 2 }}>
{dayjs(e.measured_at).format('dd, DD. MMM • HH:mm')} Uhr
</div>
<div style={{ fontSize: 16, fontWeight: 700, color: cat.color, marginBottom: 4 }}>
{e.systolic}/{e.diastolic} mmHg
{e.pulse && <span style={{ fontSize: 12, fontWeight: 400, marginLeft: 8 }}>💓 {e.pulse} bpm</span>}
</div>
<div style={{ fontSize: 11, color: 'var(--text3)' }}>
{ctx?.label} · {cat.label}
{(e.irregular_heartbeat || e.possible_afib) && <span style={{ color: '#D85A30', marginLeft: 8 }}> {e.irregular_heartbeat ? 'Unregelmäßig' : ''} {e.possible_afib ? 'AFib?' : ''}</span>}
</div>
{e.note && <p style={{ fontSize: 12, color: 'var(--text3)', fontStyle: 'italic', marginTop: 4 }}>"{e.note}"</p>}
</div>
<div style={{ display: 'flex', gap: 4 }}>
<button className="btn btn-secondary" style={{ padding: '5px 8px' }} onClick={() => startEdit(e)}>
<Pencil size={13} />
</button>
<button className="btn btn-danger" style={{ padding: '5px 8px' }} onClick={() => handleDelete(e.id)}>
<Trash2 size={13} />
</button>
</div>
</div>
)}
</div> </div>
) )
})} })}