2026-03-16 13:35:11 +01:00
|
|
|
|
import { useState } from 'react'
|
|
|
|
|
|
import { ChevronRight, ChevronLeft, Check, X, BookOpen } from 'lucide-react'
|
|
|
|
|
|
import { useNavigate } from 'react-router-dom'
|
|
|
|
|
|
import { api } from '../utils/api'
|
|
|
|
|
|
import { calcBodyFat, getBfCategory, METHOD_POINTS } from '../utils/calc'
|
|
|
|
|
|
import { CIRCUMFERENCE_POINTS, CALIPER_POINTS, CALIPER_METHODS } from '../utils/guideData'
|
|
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
|
|
|
|
|
|
// ── Circumference Wizard ──────────────────────────────────────────────────────
|
|
|
|
|
|
function CircumWizard({ onDone, onCancel }) {
|
|
|
|
|
|
const [step, setStep] = useState(0)
|
|
|
|
|
|
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'))
|
|
|
|
|
|
const [values, setValues] = useState({})
|
|
|
|
|
|
const [saving, setSaving] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
const points = CIRCUMFERENCE_POINTS
|
|
|
|
|
|
const current = points[step]
|
|
|
|
|
|
const totalSteps = points.length
|
|
|
|
|
|
|
|
|
|
|
|
const handleNext = () => {
|
|
|
|
|
|
if (step < totalSteps - 1) setStep(s => s + 1)
|
|
|
|
|
|
else handleSave()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
|
setSaving(true)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload = { date }
|
|
|
|
|
|
Object.entries(values).forEach(([k,v]) => { if(v) payload[k] = parseFloat(v) })
|
|
|
|
|
|
await api.upsertCirc(payload)
|
|
|
|
|
|
onDone()
|
|
|
|
|
|
} finally { setSaving(false) }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const progress = ((step + 1) / totalSteps) * 100
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{display:'flex',flexDirection:'column',minHeight:'calc(100vh - 120px)'}}>
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div style={{marginBottom:20}}>
|
|
|
|
|
|
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
|
|
|
|
|
|
<span style={{fontSize:12,color:'var(--text3)'}}>Schritt {step+1} von {totalSteps}</span>
|
|
|
|
|
|
<button onClick={onCancel} style={{background:'none',border:'none',cursor:'pointer',color:'var(--text3)'}}>
|
|
|
|
|
|
<X size={18}/>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{height:4,background:'var(--border)',borderRadius:2}}>
|
|
|
|
|
|
<div style={{height:'100%',background:'var(--accent)',borderRadius:2,width:`${progress}%`,transition:'width 0.3s'}}/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Datum (nur erster Schritt) */}
|
|
|
|
|
|
{step === 0 && (
|
|
|
|
|
|
<div className="card" style={{marginBottom:16}}>
|
|
|
|
|
|
<div className="form-row">
|
|
|
|
|
|
<label className="form-label">Datum</label>
|
|
|
|
|
|
<input type="date" className="form-input" style={{width:140}}
|
|
|
|
|
|
value={date} onChange={e=>setDate(e.target.value)}/>
|
|
|
|
|
|
<span className="form-unit"/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Messpunkt */}
|
|
|
|
|
|
<div className="card" style={{flex:1,marginBottom:16}}>
|
|
|
|
|
|
{/* Punkt-Indikator */}
|
|
|
|
|
|
<div style={{display:'flex',alignItems:'center',gap:10,marginBottom:16}}>
|
|
|
|
|
|
<div style={{width:40,height:40,borderRadius:'50%',background:current.color,
|
|
|
|
|
|
display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}>
|
|
|
|
|
|
<span style={{fontSize:16,fontWeight:700,color:'white'}}>{step+1}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{fontSize:18,fontWeight:700}}>{current.label}</div>
|
|
|
|
|
|
<div style={{fontSize:11,color:'var(--text3)'}}>Umfang messen</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Anleitung */}
|
|
|
|
|
|
<div style={{display:'flex',flexDirection:'column',gap:10,marginBottom:20}}>
|
|
|
|
|
|
{[
|
|
|
|
|
|
['📍 Wo', current.where],
|
|
|
|
|
|
['🧍 Haltung', current.posture],
|
|
|
|
|
|
['📏 Maßband', current.how],
|
|
|
|
|
|
['💡 Tipp', current.tip],
|
|
|
|
|
|
].map(([label, text]) => (
|
|
|
|
|
|
<div key={label} style={{background:'var(--surface2)',borderRadius:8,padding:'10px 12px'}}>
|
|
|
|
|
|
<div style={{fontSize:11,fontWeight:600,color:'var(--text3)',marginBottom:3}}>{label}</div>
|
|
|
|
|
|
<div style={{fontSize:13,color:'var(--text1)',lineHeight:1.55}}>{text}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Eingabe */}
|
|
|
|
|
|
<div style={{display:'flex',gap:10,alignItems:'center'}}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number" min={10} max={200} step={0.1}
|
|
|
|
|
|
className="form-input"
|
|
|
|
|
|
style={{flex:1,fontSize:24,fontWeight:700,textAlign:'center',height:56}}
|
|
|
|
|
|
placeholder="–"
|
|
|
|
|
|
value={values[current.id] || ''}
|
|
|
|
|
|
onChange={e => setValues(v => ({...v, [current.id]: e.target.value}))}
|
|
|
|
|
|
onKeyDown={e => e.key==='Enter' && handleNext()}
|
|
|
|
|
|
autoFocus
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span style={{fontSize:18,color:'var(--text3)',fontWeight:500}}>cm</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{values[current.id] && (
|
|
|
|
|
|
<div style={{textAlign:'center',marginTop:8,fontSize:12,color:'var(--accent)'}}>
|
|
|
|
|
|
✓ {values[current.id]} cm erfasst
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Navigation */}
|
|
|
|
|
|
<div style={{display:'flex',gap:8}}>
|
|
|
|
|
|
<button className="btn btn-secondary" style={{flex:1}} onClick={()=>setStep(s=>s-1)} disabled={step===0}>
|
|
|
|
|
|
<ChevronLeft size={16}/> Zurück
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button className="btn btn-primary" style={{flex:2}} onClick={handleNext} disabled={saving}>
|
|
|
|
|
|
{saving ? <div className="spinner" style={{width:14,height:14}}/> :
|
|
|
|
|
|
step === totalSteps-1
|
|
|
|
|
|
? <><Check size={16}/> Speichern</>
|
|
|
|
|
|
: <>Weiter <ChevronRight size={16}/></>}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Übersicht bereits erfasster Werte */}
|
|
|
|
|
|
{Object.keys(values).length > 0 && (
|
|
|
|
|
|
<div style={{marginTop:14,padding:'10px 12px',background:'var(--surface2)',borderRadius:8}}>
|
|
|
|
|
|
<div style={{fontSize:11,fontWeight:600,color:'var(--text3)',marginBottom:6}}>Bisher erfasst:</div>
|
|
|
|
|
|
<div style={{display:'flex',flexWrap:'wrap',gap:6}}>
|
|
|
|
|
|
{points.slice(0, step+1).map(p => values[p.id] ? (
|
|
|
|
|
|
<span key={p.id} onClick={()=>setStep(points.indexOf(p))}
|
|
|
|
|
|
style={{fontSize:12,background:'var(--accent-light)',color:'var(--accent-dark)',
|
|
|
|
|
|
padding:'2px 8px',borderRadius:6,cursor:'pointer'}}>
|
|
|
|
|
|
{p.label}: {values[p.id]}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
) : null)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Caliper Wizard ────────────────────────────────────────────────────────────
|
|
|
|
|
|
function CaliperWizard({ onDone, onCancel, profile }) {
|
|
|
|
|
|
const [method, setMethod] = useState('jackson3')
|
|
|
|
|
|
const [step, setStep] = useState(-1) // -1 = method select
|
|
|
|
|
|
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'))
|
|
|
|
|
|
const [values, setValues] = useState({})
|
|
|
|
|
|
const [saving, setSaving] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
const sex = profile?.sex || 'm'
|
|
|
|
|
|
const age = profile?.dob ? Math.floor((Date.now()-new Date(profile.dob))/(365.25*24*3600*1000)) : 30
|
|
|
|
|
|
const sfPoints = METHOD_POINTS[method]?.[sex] || []
|
|
|
|
|
|
const current = step >= 0 ? CALIPER_POINTS[sfPoints[step]] : null
|
|
|
|
|
|
const totalSteps = sfPoints.length
|
|
|
|
|
|
|
|
|
|
|
|
// Live BF calculation
|
|
|
|
|
|
const sfVals = {}
|
|
|
|
|
|
sfPoints.forEach(k => { const v=values[`sf_${k}`]; if(v) sfVals[k]=parseFloat(v) })
|
|
|
|
|
|
const bfPct = Object.keys(sfVals).length === sfPoints.length && sfPoints.length > 0
|
|
|
|
|
|
? Math.round(calcBodyFat(method, sfVals, sex, age)*10)/10 : null
|
|
|
|
|
|
const bfCat = bfPct ? getBfCategory(bfPct, sex) : null
|
|
|
|
|
|
|
|
|
|
|
|
const handleNext = () => {
|
|
|
|
|
|
if (step < totalSteps - 1) setStep(s => s+1)
|
|
|
|
|
|
else handleSave()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
|
setSaving(true)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload = { date, sf_method: method }
|
|
|
|
|
|
Object.entries(values).forEach(([k,v]) => { if(v) payload[k]=parseFloat(v) })
|
|
|
|
|
|
if (bfPct) {
|
|
|
|
|
|
payload.body_fat_pct = bfPct
|
|
|
|
|
|
if (profile?.weight || values.weight) {
|
|
|
|
|
|
const w = parseFloat(profile?.weight || values.weight)
|
|
|
|
|
|
payload.lean_mass = Math.round(w*(1-bfPct/100)*10)/10
|
|
|
|
|
|
payload.fat_mass = Math.round(w*(bfPct/100)*10)/10
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
await api.upsertCaliper(payload)
|
|
|
|
|
|
onDone()
|
|
|
|
|
|
} finally { setSaving(false) }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const progress = step >= 0 ? ((step+1)/totalSteps)*100 : 0
|
|
|
|
|
|
|
|
|
|
|
|
// Method selection screen
|
|
|
|
|
|
if (step === -1) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:20}}>
|
|
|
|
|
|
<h2 style={{fontSize:18,fontWeight:700,margin:0}}>Caliper-Methode</h2>
|
|
|
|
|
|
<button onClick={onCancel} style={{background:'none',border:'none',cursor:'pointer',color:'var(--text3)'}}>
|
|
|
|
|
|
<X size={18}/>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="form-row" style={{marginBottom:16}}>
|
|
|
|
|
|
<label className="form-label">Datum</label>
|
|
|
|
|
|
<input type="date" className="form-input" style={{width:140}}
|
|
|
|
|
|
value={date} onChange={e=>setDate(e.target.value)}/>
|
|
|
|
|
|
<span className="form-unit"/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{display:'flex',flexDirection:'column',gap:10,marginBottom:20}}>
|
|
|
|
|
|
{Object.entries(CALIPER_METHODS).map(([k,m]) => (
|
|
|
|
|
|
<button key={k} onClick={()=>setMethod(k)}
|
|
|
|
|
|
style={{padding:'14px 16px',borderRadius:12,border:`2px solid ${method===k?'var(--accent)':'var(--border2)'}`,
|
|
|
|
|
|
background:method===k?'var(--accent-light)':'var(--surface)',cursor:'pointer',
|
|
|
|
|
|
fontFamily:'var(--font)',textAlign:'left',transition:'all 0.15s'}}>
|
|
|
|
|
|
<div style={{fontSize:15,fontWeight:600,color:method===k?'var(--accent-dark)':'var(--text1)'}}>{m.label}</div>
|
|
|
|
|
|
<div style={{fontSize:12,color:'var(--text3)',marginTop:2}}>{m.points_m.length} Messpunkte</div>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{padding:'10px 12px',background:'var(--warn-bg)',borderRadius:8,marginBottom:16,fontSize:12,color:'var(--warn-text)'}}>
|
|
|
|
|
|
Immer rechte Körperseite · Falte 1 cm abheben · Caliper 2 Sek. · 3× messen, Mittelwert
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button className="btn btn-primary btn-full" onClick={()=>setStep(0)}>
|
|
|
|
|
|
Weiter mit {CALIPER_METHODS[method]?.label} <ChevronRight size={16}/>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{display:'flex',flexDirection:'column',minHeight:'calc(100vh - 120px)'}}>
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div style={{marginBottom:20}}>
|
|
|
|
|
|
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
|
|
|
|
|
|
<span style={{fontSize:12,color:'var(--text3)'}}>Punkt {step+1} von {totalSteps} · {CALIPER_METHODS[method]?.label}</span>
|
|
|
|
|
|
<button onClick={onCancel} style={{background:'none',border:'none',cursor:'pointer',color:'var(--text3)'}}>
|
|
|
|
|
|
<X size={18}/>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{height:4,background:'var(--border)',borderRadius:2}}>
|
|
|
|
|
|
<div style={{height:'100%',background:'#D85A30',borderRadius:2,width:`${progress}%`,transition:'width 0.3s'}}/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Live BF Preview */}
|
|
|
|
|
|
{bfPct && (
|
|
|
|
|
|
<div style={{padding:'10px 14px',background:'var(--accent-light)',borderRadius:10,marginBottom:14,
|
|
|
|
|
|
display:'flex',alignItems:'center',gap:10}}>
|
|
|
|
|
|
<div style={{fontSize:22,fontWeight:700,color:bfCat?.color||'var(--accent)'}}>{bfPct}%</div>
|
|
|
|
|
|
{bfCat && <div style={{fontSize:12,color:'var(--accent-dark)'}}>{bfCat.label}</div>}
|
|
|
|
|
|
<div style={{flex:1,fontSize:11,color:'var(--accent-dark)',textAlign:'right'}}>Körperfett (live)</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Messpunkt */}
|
|
|
|
|
|
{current && (
|
|
|
|
|
|
<div className="card" style={{flex:1,marginBottom:16}}>
|
|
|
|
|
|
<div style={{display:'flex',alignItems:'center',gap:10,marginBottom:16}}>
|
|
|
|
|
|
<div style={{width:40,height:40,borderRadius:'50%',background:current.color,
|
|
|
|
|
|
display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}>
|
|
|
|
|
|
<span style={{fontSize:16,fontWeight:700,color:'white'}}>{step+1}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{fontSize:18,fontWeight:700}}>{current.label}</div>
|
|
|
|
|
|
<div style={{fontSize:11,color:'var(--text3)'}}>Hautfalte messen</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{display:'flex',flexDirection:'column',gap:10,marginBottom:20}}>
|
|
|
|
|
|
{[
|
|
|
|
|
|
['📍 Wo', current.where],
|
|
|
|
|
|
['🧍 Haltung', current.posture],
|
|
|
|
|
|
['🔧 Technik', current.how],
|
|
|
|
|
|
['💡 Tipp', current.tip],
|
|
|
|
|
|
].map(([label, text]) => (
|
|
|
|
|
|
<div key={label} style={{background:'var(--surface2)',borderRadius:8,padding:'10px 12px'}}>
|
|
|
|
|
|
<div style={{fontSize:11,fontWeight:600,color:'var(--text3)',marginBottom:3}}>{label}</div>
|
|
|
|
|
|
<div style={{fontSize:13,color:'var(--text1)',lineHeight:1.55}}>{text}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{display:'flex',gap:10,alignItems:'center'}}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number" min={2} max={80} step={0.5}
|
|
|
|
|
|
className="form-input"
|
|
|
|
|
|
style={{flex:1,fontSize:24,fontWeight:700,textAlign:'center',height:56}}
|
|
|
|
|
|
placeholder="–"
|
|
|
|
|
|
value={values[`sf_${sfPoints[step]}`] || ''}
|
|
|
|
|
|
onChange={e => setValues(v => ({...v, [`sf_${sfPoints[step]}`]: e.target.value}))}
|
|
|
|
|
|
onKeyDown={e => e.key==='Enter' && handleNext()}
|
|
|
|
|
|
autoFocus
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span style={{fontSize:18,color:'var(--text3)',fontWeight:500}}>mm</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{values[`sf_${sfPoints[step]}`] && (
|
|
|
|
|
|
<div style={{textAlign:'center',marginTop:8,fontSize:12,color:'var(--accent)'}}>
|
|
|
|
|
|
✓ {values[`sf_${sfPoints[step]}`]} mm erfasst
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Navigation */}
|
|
|
|
|
|
<div style={{display:'flex',gap:8}}>
|
|
|
|
|
|
<button className="btn btn-secondary" style={{flex:1}}
|
|
|
|
|
|
onClick={()=>setStep(s=>s<=0?-1:s-1)}>
|
|
|
|
|
|
<ChevronLeft size={16}/> Zurück
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button className="btn btn-primary" style={{flex:2}} onClick={handleNext} disabled={saving}>
|
|
|
|
|
|
{saving ? <div className="spinner" style={{width:14,height:14}}/> :
|
|
|
|
|
|
step === totalSteps-1
|
|
|
|
|
|
? <><Check size={16}/> Speichern</>
|
|
|
|
|
|
: <>Weiter <ChevronRight size={16}/></>}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Übersicht */}
|
|
|
|
|
|
{Object.keys(values).length > 0 && (
|
|
|
|
|
|
<div style={{marginTop:14,padding:'10px 12px',background:'var(--surface2)',borderRadius:8}}>
|
|
|
|
|
|
<div style={{fontSize:11,fontWeight:600,color:'var(--text3)',marginBottom:6}}>Bisher erfasst:</div>
|
|
|
|
|
|
<div style={{display:'flex',flexWrap:'wrap',gap:6}}>
|
|
|
|
|
|
{sfPoints.slice(0,step+1).map((k,idx) => values[`sf_${k}`] ? (
|
|
|
|
|
|
<span key={k} onClick={()=>setStep(idx)}
|
|
|
|
|
|
style={{fontSize:12,background:'#D85A3022',color:'#D85A30',
|
|
|
|
|
|
padding:'2px 8px',borderRadius:6,cursor:'pointer'}}>
|
|
|
|
|
|
{CALIPER_POINTS[k]?.label}: {values[`sf_${k}`]}mm
|
|
|
|
|
|
</span>
|
|
|
|
|
|
) : null)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Main Wizard Page ──────────────────────────────────────────────────────────
|
|
|
|
|
|
export default function MeasureWizard() {
|
|
|
|
|
|
const [mode, setMode] = useState(null) // null | 'circum' | 'caliper'
|
|
|
|
|
|
const [done, setDone] = useState(false)
|
|
|
|
|
|
const [profile, setProfile] = useState(null)
|
|
|
|
|
|
const nav = useNavigate()
|
|
|
|
|
|
|
|
|
|
|
|
useState(() => { api.getProfile().then(setProfile) })
|
|
|
|
|
|
|
|
|
|
|
|
if (done) {
|
|
|
|
|
|
return (
|
2026-04-05 10:14:07 +02:00
|
|
|
|
<div className="capture-page" style={{display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',
|
2026-03-16 13:35:11 +01:00
|
|
|
|
minHeight:'60vh',gap:16,textAlign:'center'}}>
|
|
|
|
|
|
<div style={{fontSize:48}}>✅</div>
|
|
|
|
|
|
<h2 style={{fontSize:20,fontWeight:700}}>Gespeichert!</h2>
|
|
|
|
|
|
<p style={{color:'var(--text2)',fontSize:14}}>Deine Messung wurde erfolgreich gespeichert.</p>
|
|
|
|
|
|
<div style={{display:'flex',gap:8}}>
|
|
|
|
|
|
<button className="btn btn-primary" onClick={()=>{ setDone(false); setMode(null) }}>
|
|
|
|
|
|
Weitere Messung
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button className="btn btn-secondary" onClick={()=>nav('/')}>
|
|
|
|
|
|
Zum Dashboard
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 10:14:07 +02:00
|
|
|
|
if (mode === 'circum') return (
|
|
|
|
|
|
<div className="capture-page">
|
|
|
|
|
|
<CircumWizard onDone={()=>setDone(true)} onCancel={()=>setMode(null)}/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
if (mode === 'caliper') return (
|
|
|
|
|
|
<div className="capture-page">
|
|
|
|
|
|
<CaliperWizard onDone={()=>setDone(true)} onCancel={()=>setMode(null)} profile={profile}/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
2026-03-16 13:35:11 +01:00
|
|
|
|
|
|
|
|
|
|
return (
|
2026-04-05 10:14:07 +02:00
|
|
|
|
<div className="capture-page">
|
2026-03-16 13:35:11 +01:00
|
|
|
|
<h1 className="page-title">Assistent</h1>
|
|
|
|
|
|
<p style={{fontSize:13,color:'var(--text2)',marginBottom:20,lineHeight:1.6}}>
|
|
|
|
|
|
Der Assistent führt dich Schritt für Schritt durch die Messung – mit Anleitung für jeden Messpunkt.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div style={{display:'flex',flexDirection:'column',gap:12}}>
|
|
|
|
|
|
<button onClick={()=>setMode('circum')}
|
|
|
|
|
|
style={{padding:'20px 16px',borderRadius:14,border:'1.5px solid var(--border2)',
|
|
|
|
|
|
background:'var(--surface)',cursor:'pointer',fontFamily:'var(--font)',textAlign:'left',
|
|
|
|
|
|
display:'flex',alignItems:'center',gap:14}}>
|
|
|
|
|
|
<div style={{fontSize:32}}>📏</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{fontSize:16,fontWeight:600}}>Umfänge messen</div>
|
|
|
|
|
|
<div style={{fontSize:12,color:'var(--text3)',marginTop:2}}>
|
|
|
|
|
|
8 Messpunkte · Hals, Brust, Taille, Bauch, Hüfte, Oberschenkel, Wade, Oberarm
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<ChevronRight size={20} style={{marginLeft:'auto',color:'var(--text3)'}}/>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button onClick={()=>setMode('caliper')}
|
|
|
|
|
|
style={{padding:'20px 16px',borderRadius:14,border:'1.5px solid var(--border2)',
|
|
|
|
|
|
background:'var(--surface)',cursor:'pointer',fontFamily:'var(--font)',textAlign:'left',
|
|
|
|
|
|
display:'flex',alignItems:'center',gap:14}}>
|
|
|
|
|
|
<div style={{fontSize:32}}>📐</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{fontSize:16,fontWeight:600}}>Caliper Körperfett</div>
|
|
|
|
|
|
<div style={{fontSize:12,color:'var(--text3)',marginTop:2}}>
|
|
|
|
|
|
3–9 Messpunkte · Jackson/Pollock, Durnin oder Parrillo
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<ChevronRight size={20} style={{marginLeft:'auto',color:'var(--text3)'}}/>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{marginTop:8,padding:'12px 14px',background:'var(--surface2)',borderRadius:10,
|
|
|
|
|
|
fontSize:12,color:'var(--text3)',lineHeight:1.6}}>
|
|
|
|
|
|
💡 Für schnelle Einzeleingaben oder Bearbeitung bestehender Werte nutze die direkten Screens
|
|
|
|
|
|
unter <strong>Gewicht</strong>, <strong>Umfänge</strong> und <strong>Caliper</strong>.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|