From 326d16a6591f501be0d9eb7a365a05bc99b68903 Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Sat, 27 Jun 2026 23:03:53 +0200 Subject: [PATCH] feat: auto-calculate fatMass and muscleMass from weight + percentages --- src/components/MeasurementForm.tsx | 79 +++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/src/components/MeasurementForm.tsx b/src/components/MeasurementForm.tsx index f6a11d2..7305670 100644 --- a/src/components/MeasurementForm.tsx +++ b/src/components/MeasurementForm.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState, useMemo } from 'react' import { View, Text, TextInput, TouchableOpacity, StyleSheet, KeyboardAvoidingView, Platform, ScrollView } from 'react-native' import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker' import { Measurement } from '../types' @@ -9,17 +9,27 @@ type Props = { initialValues?: Partial } -const fields: { key: keyof Measurement; label: string; placeholder: string; keyboardType: 'decimal-pad' | 'number-pad' }[] = [ +type FieldDef = { + key: keyof Measurement + label: string + placeholder: string + keyboardType: 'decimal-pad' | 'number-pad' + computed?: boolean +} + +const fields: FieldDef[] = [ { key: 'weight', label: 'Gewicht (kg)', placeholder: '75.5', keyboardType: 'decimal-pad' }, { key: 'bmi', label: 'BMI', placeholder: '22.3', keyboardType: 'decimal-pad' }, { key: 'bodyFatPercentage', label: 'Vetpercentage (%)', placeholder: '15.2', keyboardType: 'decimal-pad' }, - { key: 'fatMass', label: 'Vetmassa (kg)', placeholder: '11.5', keyboardType: 'decimal-pad' }, + { key: 'fatMass', label: 'Vetmassa (kg)', placeholder: '11.5', keyboardType: 'decimal-pad', computed: true }, { key: 'musclePercentage', label: 'Spierpercentage (%)', placeholder: '40.1', keyboardType: 'decimal-pad' }, - { key: 'muscleMass', label: 'Spiermassa (kg)', placeholder: '30.3', keyboardType: 'decimal-pad' }, + { key: 'muscleMass', label: 'Spiermassa (kg)', placeholder: '30.3', keyboardType: 'decimal-pad', computed: true }, { key: 'visceralFat', label: 'Buikvet (index)', placeholder: '8', keyboardType: 'number-pad' }, { key: 'bmr', label: 'Stofwisseling (kcal)', placeholder: '1650', keyboardType: 'number-pad' }, ] +const computedKeys = new Set(['fatMass', 'muscleMass']) + export default function MeasurementForm({ onSubmit, initialValues }: Props) { const [pickerOpen, setPickerOpen] = useState(false) const [measurementDate, setMeasurementDate] = useState(() => { @@ -29,21 +39,31 @@ export default function MeasurementForm({ onSubmit, initialValues }: Props) { const [values, setValues] = useState>(() => { const init: Record = {} for (const f of fields) { + if (f.computed) continue init[f.key] = initialValues?.[f.key]?.toString() ?? '' } return init }) + const weight = parseFloat(values.weight) || 0 + const bodyFatPct = parseFloat(values.bodyFatPercentage) || 0 + const musclePct = parseFloat(values.musclePercentage) || 0 + + const computed = useMemo(() => ({ + fatMass: weight > 0 && bodyFatPct > 0 ? ((weight * bodyFatPct) / 100).toFixed(1) : '', + muscleMass: weight > 0 && musclePct > 0 ? ((weight * musclePct) / 100).toFixed(1) : '', + }), [weight, bodyFatPct, musclePct]) + const handleSubmit = () => { const now = new Date().toISOString() const m: Measurement = { id: generateId(), - weight: parseFloat(values.weight) || 0, + weight, bmi: parseFloat(values.bmi) || 0, - bodyFatPercentage: parseFloat(values.bodyFatPercentage) || 0, - fatMass: parseFloat(values.fatMass) || 0, - musclePercentage: parseFloat(values.musclePercentage) || 0, - muscleMass: parseFloat(values.muscleMass) || 0, + bodyFatPercentage: bodyFatPct, + fatMass: parseFloat(computed.fatMass) || 0, + musclePercentage: musclePct, + muscleMass: parseFloat(computed.muscleMass) || 0, visceralFat: parseInt(values.visceralFat, 10) || 0, bmr: parseFloat(values.bmr) || 0, measuredAt: measurementDate.toISOString(), @@ -91,19 +111,32 @@ export default function MeasurementForm({ onSubmit, initialValues }: Props) { /> )} - {fields.map(f => ( - - {f.label} - setField(f.key, v)} - placeholderTextColor="#999" - /> - - ))} + {fields.map(f => { + if (f.computed) { + const calcValue = f.key === 'fatMass' ? computed.fatMass : computed.muscleMass + return ( + + {f.label} + + {calcValue || '—'} + + + ) + } + return ( + + {f.label} + setField(f.key, v)} + placeholderTextColor="#999" + /> + + ) + })} Opslaan @@ -118,6 +151,8 @@ const styles = StyleSheet.create({ field: { marginBottom: 12 }, label: { fontSize: 14, fontWeight: '600', color: '#333', marginBottom: 4 }, input: { backgroundColor: '#fff', borderRadius: 8, padding: 12, fontSize: 16, borderWidth: 1, borderColor: '#ddd' }, + computedInput: { backgroundColor: '#f0f0f0' }, + computedText: { fontSize: 16, color: '#666' }, dateButton: { backgroundColor: '#fff', borderRadius: 8, padding: 12, borderWidth: 1, borderColor: '#ddd' }, dateButtonText: { fontSize: 16, color: '#333' }, button: { backgroundColor: '#4CAF50', padding: 16, borderRadius: 8, alignItems: 'center', marginTop: 8 },