WP 9c Phase 1 #12

Merged
Lars merged 14 commits from develop into main 2026-03-22 14:14:34 +01:00
Showing only changes of commit b22481d4ce - Show all commits

View File

@ -48,6 +48,17 @@ export default function SleepPage() {
setTimeout(() => setToast(null), 4000) setTimeout(() => setToast(null), 4000)
} }
// Clean data: convert empty strings to null for optional integer fields
const cleanSleepData = (data) => ({
...data,
quality: data.quality === '' ? null : data.quality,
wake_count: data.wake_count === '' ? 0 : data.wake_count,
deep_minutes: data.deep_minutes === '' ? null : data.deep_minutes,
rem_minutes: data.rem_minutes === '' ? null : data.rem_minutes,
light_minutes: data.light_minutes === '' ? null : data.light_minutes,
awake_minutes: data.awake_minutes === '' ? null : data.awake_minutes,
})
const handleImport = async (file) => { const handleImport = async (file) => {
if (!file) return if (!file) return
if (!file.name.endsWith('.csv')) { if (!file.name.endsWith('.csv')) {
@ -248,7 +259,7 @@ export default function SleepPage() {
<NewEntryForm <NewEntryForm
onSave={async (data) => { onSave={async (data) => {
try { try {
await api.createSleep(data) await api.createSleep(cleanSleepData(data))
await load() await load()
setEditingId(null) setEditingId(null)
showToast('Gespeichert') showToast('Gespeichert')
@ -283,7 +294,7 @@ export default function SleepPage() {
onCancelEdit={() => setEditingId(null)} onCancelEdit={() => setEditingId(null)}
onSave={async (data) => { onSave={async (data) => {
try { try {
await api.updateSleep(entry.id, data) await api.updateSleep(entry.id, cleanSleepData(data))
await load() await load()
setEditingId(null) setEditingId(null)
showToast('Gespeichert') showToast('Gespeichert')
@ -291,6 +302,7 @@ export default function SleepPage() {
showToast(err.message, 'error') showToast(err.message, 'error')
} }
}} }}
cleanSleepData={cleanSleepData}
onDelete={() => handleDelete(entry.id, entry.date)} onDelete={() => handleDelete(entry.id, entry.date)}
formatDuration={formatDuration} formatDuration={formatDuration}
getSourceBadge={getSourceBadge} getSourceBadge={getSourceBadge}
@ -322,6 +334,27 @@ function SleepEntry({ entry, expanded, editing, onToggleExpand, onEdit, onCancel
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [plausibilityError, setPlausibilityError] = useState(null) const [plausibilityError, setPlausibilityError] = useState(null)
const [suggestedDuration, setSuggestedDuration] = useState(null)
// Auto-calculate duration from bedtime + wake_time
useEffect(() => {
if (editing && formData.bedtime && formData.wake_time) {
const [bedH, bedM] = formData.bedtime.split(':').map(Number)
const [wakeH, wakeM] = formData.wake_time.split(':').map(Number)
let bedMinutes = bedH * 60 + bedM
let wakeMinutes = wakeH * 60 + wakeM
if (wakeMinutes < bedMinutes) {
wakeMinutes += 24 * 60
}
const duration = wakeMinutes - bedMinutes
setSuggestedDuration(duration)
} else {
setSuggestedDuration(null)
}
}, [editing, formData.bedtime, formData.wake_time])
// Live plausibility check // Live plausibility check
useEffect(() => { useEffect(() => {
@ -386,6 +419,12 @@ function SleepEntry({ entry, expanded, editing, onToggleExpand, onEdit, onCancel
/> />
<div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 2 }}> <div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 2 }}>
= {formatDuration(formData.duration_minutes)} = {formatDuration(formData.duration_minutes)}
{suggestedDuration && suggestedDuration !== formData.duration_minutes && (
<span style={{ marginLeft: 8, color: 'var(--accent)', cursor: 'pointer' }}
onClick={() => setFormData({ ...formData, duration_minutes: suggestedDuration })}>
💡 Vorschlag: {formatDuration(suggestedDuration)} (übernehmen?)
</span>
)}
</div> </div>
</div> </div>
@ -690,6 +729,28 @@ function NewEntryForm({ onSave, onCancel, formatDuration }) {
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [plausibilityError, setPlausibilityError] = useState(null) const [plausibilityError, setPlausibilityError] = useState(null)
const [showDetail, setShowDetail] = useState(false) const [showDetail, setShowDetail] = useState(false)
const [suggestedDuration, setSuggestedDuration] = useState(null)
// Auto-calculate duration from bedtime + wake_time
useEffect(() => {
if (formData.bedtime && formData.wake_time) {
const [bedH, bedM] = formData.bedtime.split(':').map(Number)
const [wakeH, wakeM] = formData.wake_time.split(':').map(Number)
let bedMinutes = bedH * 60 + bedM
let wakeMinutes = wakeH * 60 + wakeM
// If wake time < bed time, add 24 hours (crossed midnight)
if (wakeMinutes < bedMinutes) {
wakeMinutes += 24 * 60
}
const duration = wakeMinutes - bedMinutes
setSuggestedDuration(duration)
} else {
setSuggestedDuration(null)
}
}, [formData.bedtime, formData.wake_time])
// Live plausibility check // Live plausibility check
useEffect(() => { useEffect(() => {
@ -750,6 +811,12 @@ function NewEntryForm({ onSave, onCancel, formatDuration }) {
/> />
<div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 4 }}> <div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 4 }}>
= {formatDuration(formData.duration_minutes)} = {formatDuration(formData.duration_minutes)}
{suggestedDuration && suggestedDuration !== formData.duration_minutes && (
<span style={{ marginLeft: 8, color: 'var(--accent)', cursor: 'pointer' }}
onClick={() => setFormData({ ...formData, duration_minutes: suggestedDuration })}>
💡 Vorschlag: {formatDuration(suggestedDuration)} (übernehmen?)
</span>
)}
</div> </div>
</div> </div>