feat: VitalsPage mobile-optimized with inline editing & smart upsert
- 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:
parent
7f10286e02
commit
10772d1f80
|
|
@ -1,12 +1,12 @@
|
|||
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 dayjs from 'dayjs'
|
||||
import '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:
|
||||
* - Baseline Vitals: Once daily (morning, fasted) - RHR, HRV, VO2 Max, SpO2
|
||||
|
|
@ -22,6 +22,7 @@ function BaselineTab() {
|
|||
const [entries, setEntries] = useState([])
|
||||
const [stats, setStats] = useState(null)
|
||||
const [form, setForm] = useState({
|
||||
id: null, // Für Update
|
||||
date: dayjs().format('YYYY-MM-DD'),
|
||||
resting_hr: '',
|
||||
hrv: '',
|
||||
|
|
@ -30,7 +31,8 @@ function BaselineTab() {
|
|||
respiratory_rate: '',
|
||||
note: ''
|
||||
})
|
||||
const [editing, setEditing] = useState(null)
|
||||
const [editingId, setEditingId] = useState(null)
|
||||
const [editForm, setEditForm] = useState({})
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
const [success, setSuccess] = useState(false)
|
||||
|
|
@ -50,6 +52,47 @@ function BaselineTab() {
|
|||
|
||||
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 () => {
|
||||
setSaving(true)
|
||||
setError(null)
|
||||
|
|
@ -68,12 +111,17 @@ function BaselineTab() {
|
|||
return
|
||||
}
|
||||
|
||||
if (form.id) {
|
||||
await api.updateBaseline(form.id, payload)
|
||||
} else {
|
||||
await api.createBaseline(payload)
|
||||
}
|
||||
|
||||
setSuccess(true)
|
||||
await load()
|
||||
setTimeout(() => {
|
||||
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)
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
|
|
@ -92,10 +140,41 @@ function BaselineTab() {
|
|||
}
|
||||
}
|
||||
|
||||
const getTrendIcon = (trend) => {
|
||||
if (trend === 'increasing') return <TrendingUp size={14} style={{ color: '#D85A30' }} />
|
||||
if (trend === 'decreasing') return <TrendingDown size={14} style={{ color: '#1D9E75' }} />
|
||||
return <Minus size={14} style={{ color: 'var(--text3)' }} />
|
||||
const startEdit = (entry) => {
|
||||
setEditingId(entry.id)
|
||||
setEditForm({
|
||||
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 (
|
||||
|
|
@ -129,56 +208,112 @@ function BaselineTab() {
|
|||
{/* Form */}
|
||||
<div className="card" style={{ marginBottom: 12 }}>
|
||||
<div className="card-title">Morgenmessung erfassen</div>
|
||||
<p style={{ fontSize: 12, color: 'var(--text3)', marginBottom: 12 }}>
|
||||
Einmal täglich, morgens vor dem Aufstehen (nüchtern)
|
||||
<p style={{ fontSize: 12, color: 'var(--text3)', marginBottom: 16 }}>
|
||||
Einmal täglich, morgens vor dem Aufstehen (nüchtern). {form.id && <strong style={{ color: 'var(--accent)' }}>Eintrag wird aktualisiert.</strong>}
|
||||
</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">
|
||||
<label className="form-label">Datum</label>
|
||||
<input type="date" className="form-input" style={{ width: 140 }} value={form.date} onChange={e => setForm(f => ({ ...f, date: e.target.value }))} />
|
||||
<span className="form-unit" />
|
||||
{/* Datum - volle Breite */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 600, marginBottom: 4, color: 'var(--text2)' }}>Datum</label>
|
||||
<input
|
||||
type="date"
|
||||
className="form-input"
|
||||
style={{ width: '100%' }}
|
||||
value={form.date}
|
||||
onChange={e => setForm(f => ({ ...f, date: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-label">Ruhepuls</label>
|
||||
<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 }))} />
|
||||
<span className="form-unit">bpm</span>
|
||||
{/* Sektion: Herzfunktion */}
|
||||
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, color: 'var(--text1)' }}>❤️ Herzfunktion</div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<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 className="form-row">
|
||||
<label className="form-label">HRV</label>
|
||||
<input type="number" className="form-input" min={1} max={300} placeholder="optional" value={form.hrv} onChange={e => setForm(f => ({ ...f, hrv: e.target.value }))} />
|
||||
<span className="form-unit">ms</span>
|
||||
{/* Sektion: Fitness & Atmung */}
|
||||
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, marginTop: 20, color: 'var(--text1)' }}>🏃 Fitness & Atmung</div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<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 className="form-row">
|
||||
<label className="form-label">VO2 Max</label>
|
||||
<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 }))} />
|
||||
<span className="form-unit">ml/kg/min</span>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-label">SpO2</label>
|
||||
<input type="number" className="form-input" min={70} max={100} placeholder="optional" value={form.spo2} onChange={e => setForm(f => ({ ...f, spo2: e.target.value }))} />
|
||||
<span className="form-unit">%</span>
|
||||
</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" />
|
||||
{/* Notiz */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Notiz</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-input"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="optional"
|
||||
value={form.note}
|
||||
onChange={e => setForm(f => ({ ...f, note: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
|
@ -188,6 +323,85 @@ function BaselineTab() {
|
|||
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8 }}>Letzte Messungen ({entries.length})</div>
|
||||
{entries.map(e => (
|
||||
<div key={e.id} className="card" style={{ marginBottom: 8, padding: 12 }}>
|
||||
{editingId === e.id ? (
|
||||
// Edit Mode
|
||||
<div>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8, color: 'var(--text2)' }}>
|
||||
{dayjs(e.date).format('dd, DD. MMM YYYY')}
|
||||
</div>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Ruhepuls (bpm)</label>
|
||||
<input
|
||||
type="number"
|
||||
className="form-input"
|
||||
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>
|
||||
) : (
|
||||
// View Mode
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}>
|
||||
|
|
@ -202,11 +416,17 @@ function BaselineTab() {
|
|||
</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>
|
||||
)}
|
||||
|
|
@ -243,6 +463,8 @@ function BloodPressureTab() {
|
|||
possible_afib: false,
|
||||
note: ''
|
||||
})
|
||||
const [editingId, setEditingId] = useState(null)
|
||||
const [editForm, setEditForm] = useState({})
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
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) => {
|
||||
if (sys < 120 && dia < 80) return { label: 'Optimal', color: '#1D9E75' }
|
||||
if (sys < 130 && dia < 85) return { label: 'Normal', color: '#1D9E75' }
|
||||
|
|
@ -359,51 +625,92 @@ function BloodPressureTab() {
|
|||
{/* Form */}
|
||||
<div className="card" style={{ marginBottom: 12 }}>
|
||||
<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
|
||||
</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 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label className="form-label">Datum</label>
|
||||
<input type="date" className="form-input" value={form.date} onChange={e => setForm(f => ({ ...f, date: e.target.value }))} />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label className="form-label">Uhrzeit</label>
|
||||
<input type="time" className="form-input" value={form.time} onChange={e => setForm(f => ({ ...f, time: e.target.value }))} />
|
||||
{/* Datum + Uhrzeit - volle Breite */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 600, marginBottom: 4, color: 'var(--text2)' }}>Datum</label>
|
||||
<input
|
||||
type="date"
|
||||
className="form-input"
|
||||
style={{ width: '100%' }}
|
||||
value={form.date}
|
||||
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 className="form-row">
|
||||
<label className="form-label">Systolisch</label>
|
||||
<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 }))} />
|
||||
<span className="form-unit">mmHg</span>
|
||||
{/* Sektion: Blutdruck */}
|
||||
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, color: 'var(--text1)' }}>🩸 Blutdruck</div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<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 className="form-row">
|
||||
<label className="form-label">Diastolisch</label>
|
||||
<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 }))} />
|
||||
<span className="form-unit">mmHg</span>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-label">Puls</label>
|
||||
<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 }))}>
|
||||
{/* Kontext */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Kontext</label>
|
||||
<select
|
||||
className="form-input"
|
||||
style={{ width: '100%' }}
|
||||
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>)}
|
||||
</select>
|
||||
<span className="form-unit" />
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, marginBottom: 4 }}>
|
||||
{/* Checkboxen */}
|
||||
<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 }))} />
|
||||
Unregelmäßiger Herzschlag
|
||||
</label>
|
||||
|
|
@ -413,10 +720,17 @@ function BloodPressureTab() {
|
|||
</label>
|
||||
</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" />
|
||||
{/* Notiz */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4, color: 'var(--text2)' }}>Notiz</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-input"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="optional"
|
||||
value={form.note}
|
||||
onChange={e => setForm(f => ({ ...f, note: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-primary btn-full" onClick={handleSave} disabled={saving}>
|
||||
|
|
@ -433,6 +747,109 @@ function BloodPressureTab() {
|
|||
const ctx = CONTEXT_OPTIONS.find(o => o.value === e.context)
|
||||
return (
|
||||
<div key={e.id} className="card" style={{ marginBottom: 8, padding: 12 }}>
|
||||
{editingId === e.id ? (
|
||||
// Edit Mode
|
||||
<div>
|
||||
<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 style={{ marginBottom: 8 }}>
|
||||
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Uhrzeit</label>
|
||||
<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 style={{ marginBottom: 8 }}>
|
||||
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>Systolisch (mmHg)</label>
|
||||
<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>
|
||||
) : (
|
||||
// View Mode
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 2 }}>
|
||||
|
|
@ -448,11 +865,17 @@ function BloodPressureTab() {
|
|||
</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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user