| [2e0035f] | 1 | import { useEffect, useState } from 'react'
|
|---|
| 2 | import { api } from '../lib/api.js'
|
|---|
| 3 |
|
|---|
| 4 | export default function Biometrics() {
|
|---|
| 5 | const [rows, setRows] = useState([])
|
|---|
| 6 | const [error, setError] = useState(null)
|
|---|
| 7 | const [form, setForm] = useState({
|
|---|
| 8 | date: new Date().toISOString().slice(0, 10),
|
|---|
| 9 | weight: '', height: '', age: '', muscleFatRatio: ''
|
|---|
| 10 | })
|
|---|
| 11 |
|
|---|
| 12 | const load = () => api.get('/biometrics').then(setRows).catch(e => setError(e.message))
|
|---|
| 13 | useEffect(() => { load() }, [])
|
|---|
| 14 |
|
|---|
| 15 | const set = (k) => (e) => setForm({ ...form, [k]: e.target.value })
|
|---|
| 16 |
|
|---|
| 17 | const save = async () => {
|
|---|
| 18 | setError(null)
|
|---|
| 19 | try {
|
|---|
| 20 | await api.post('/biometrics', {
|
|---|
| 21 | date: form.date,
|
|---|
| 22 | weight: form.weight ? Number(form.weight) : null,
|
|---|
| 23 | height: form.height ? Number(form.height) : null,
|
|---|
| 24 | age: form.age ? Number(form.age) : null,
|
|---|
| 25 | muscleFatRatio: form.muscleFatRatio ? Number(form.muscleFatRatio) : null
|
|---|
| 26 | })
|
|---|
| 27 | load()
|
|---|
| 28 | } catch (e) { setError(e.message) }
|
|---|
| 29 | }
|
|---|
| 30 |
|
|---|
| 31 | return (
|
|---|
| 32 | <div>
|
|---|
| 33 | {error && <div className="error">{error}</div>}
|
|---|
| 34 |
|
|---|
| 35 | <div className="card">
|
|---|
| 36 | <h3>Record a measurement</h3>
|
|---|
| 37 | <p className="muted">One per day. Saving the same date again updates that entry.</p>
|
|---|
| 38 | <div className="row">
|
|---|
| 39 | <input type="date" value={form.date} onChange={set('date')} />
|
|---|
| 40 | <input type="number" step="0.1" placeholder="weight kg" style={{ width: 120 }}
|
|---|
| 41 | value={form.weight} onChange={set('weight')} />
|
|---|
| 42 | <input type="number" placeholder="height cm" style={{ width: 120 }}
|
|---|
| 43 | value={form.height} onChange={set('height')} />
|
|---|
| 44 | <input type="number" placeholder="age" style={{ width: 90 }}
|
|---|
| 45 | value={form.age} onChange={set('age')} />
|
|---|
| 46 | <input type="number" step="0.01" placeholder="muscle/fat" style={{ width: 120 }}
|
|---|
| 47 | value={form.muscleFatRatio} onChange={set('muscleFatRatio')} />
|
|---|
| 48 | <button className="primary" onClick={save}>Save</button>
|
|---|
| 49 | </div>
|
|---|
| 50 | </div>
|
|---|
| 51 |
|
|---|
| 52 | <div className="card">
|
|---|
| 53 | <h3>Progress</h3>
|
|---|
| 54 | <table>
|
|---|
| 55 | <thead>
|
|---|
| 56 | <tr><th>date</th><th>weight</th><th>change</th><th>BMI</th><th>muscle/fat</th><th></th></tr>
|
|---|
| 57 | </thead>
|
|---|
| 58 | <tbody>
|
|---|
| 59 | {rows.map(r => (
|
|---|
| 60 | <tr key={r.id}>
|
|---|
| 61 | <td>{r.date}</td>
|
|---|
| 62 | <td>{r.weight}</td>
|
|---|
| 63 | <td className={r.changeSincePrevious < 0 ? 'kcal' : 'muted'}>
|
|---|
| 64 | {r.changeSincePrevious ?? '—'}
|
|---|
| 65 | </td>
|
|---|
| 66 | <td>{r.bmi ?? '—'}</td>
|
|---|
| 67 | <td>{r.muscleFatRatio ?? '—'}</td>
|
|---|
| 68 | <td style={{ width: 1 }}>
|
|---|
| 69 | <button className="small"
|
|---|
| 70 | onClick={() => api.del(`/biometrics/${r.id}`).then(load)}>delete</button>
|
|---|
| 71 | </td>
|
|---|
| 72 | </tr>
|
|---|
| 73 | ))}
|
|---|
| 74 | </tbody>
|
|---|
| 75 | </table>
|
|---|
| 76 | {rows.length === 0 && <p className="muted">No measurements yet.</p>}
|
|---|
| 77 | </div>
|
|---|
| 78 | </div>
|
|---|
| 79 | )
|
|---|
| 80 | }
|
|---|