From 45b2a2c36e7d5049916958454719ab422e9327e1 Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Sat, 27 Jun 2026 22:40:55 +0200 Subject: [PATCH] feat: add Dashboard with latest card and weight chart --- src/components/LatestMeasurementCard.tsx | 54 ++++++++++++++++++++++++ src/components/WeightChart.tsx | 53 +++++++++++++++++++++++ src/screens/DashboardScreen.tsx | 44 +++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 src/components/LatestMeasurementCard.tsx create mode 100644 src/components/WeightChart.tsx create mode 100644 src/screens/DashboardScreen.tsx diff --git a/src/components/LatestMeasurementCard.tsx b/src/components/LatestMeasurementCard.tsx new file mode 100644 index 0000000..295fbb5 --- /dev/null +++ b/src/components/LatestMeasurementCard.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import { View, Text, StyleSheet } from 'react-native' +import { Measurement } from '../types' + +type Props = { measurement: Measurement | null } + +const rows: { label: string; key: keyof Measurement; suffix: string }[] = [ + { label: 'Gewicht', key: 'weight', suffix: 'kg' }, + { label: 'BMI', key: 'bmi', suffix: '' }, + { label: 'Vetpercentage', key: 'bodyFatPercentage', suffix: '%' }, + { label: 'Vetmassa', key: 'fatMass', suffix: 'kg' }, + { label: 'Spierpercentage', key: 'musclePercentage', suffix: '%' }, + { label: 'Spiermassa', key: 'muscleMass', suffix: 'kg' }, + { label: 'Buikvet', key: 'visceralFat', suffix: '' }, + { label: 'Stofwisseling', key: 'bmr', suffix: 'kcal' }, +] + +export default function LatestMeasurementCard({ measurement }: Props) { + if (!measurement) { + return ( + + Nog geen metingen + + ) + } + + return ( + + Laatste meting + {new Date(measurement.measuredAt).toLocaleDateString('nl-NL')} + {rows.map(r => ( + + {r.label} + + {typeof measurement[r.key] === 'number' + ? (measurement[r.key] as number).toFixed(1) + : measurement[r.key]}{' '} + {r.suffix} + + + ))} + + ) +} + +const styles = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 12, padding: 16, margin: 16, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 8, elevation: 3 }, + title: { fontSize: 18, fontWeight: '700', marginBottom: 4 }, + date: { fontSize: 14, color: '#666', marginBottom: 12 }, + row: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 4 }, + rowLabel: { fontSize: 14, color: '#555' }, + rowValue: { fontSize: 14, fontWeight: '600' }, + emptyText: { fontSize: 16, color: '#999', textAlign: 'center', padding: 20 }, +}) diff --git a/src/components/WeightChart.tsx b/src/components/WeightChart.tsx new file mode 100644 index 0000000..1fc6aa4 --- /dev/null +++ b/src/components/WeightChart.tsx @@ -0,0 +1,53 @@ +import React from 'react' +import { View, Text, StyleSheet, Dimensions } from 'react-native' +import { LineChart } from 'react-native-chart-kit' +import { Measurement } from '../types' + +type Props = { measurements: Measurement[] } + +export default function WeightChart({ measurements }: Props) { + if (measurements.length < 2) { + return null + } + + const sorted = [...measurements].sort( + (a, b) => new Date(a.measuredAt).getTime() - new Date(b.measuredAt).getTime() + ) + + const labels = sorted.map(m => { + const d = new Date(m.measuredAt) + return `${d.getDate()}/${d.getMonth() + 1}` + }) + const data = sorted.map(m => m.weight) + + return ( + + Gewichtsgrafiek + 10 ? labels.filter((_, i) => i % Math.ceil(labels.length / 10) === 0) : labels, + datasets: [{ data }], + }} + width={Dimensions.get('window').width - 32} + height={220} + yAxisSuffix=" kg" + chartConfig={{ + backgroundColor: '#ffffff', + backgroundGradientFrom: '#ffffff', + backgroundGradientTo: '#ffffff', + decimalCount: 1, + color: (opacity = 1) => `rgba(76, 175, 80, ${opacity})`, + labelColor: () => '#666', + }} + bezier + style={styles.chart} + /> + + ) +} + +const styles = StyleSheet.create({ + container: { marginHorizontal: 16, marginBottom: 16 }, + title: { fontSize: 18, fontWeight: '700', marginBottom: 8 }, + chart: { borderRadius: 12 }, +}) diff --git a/src/screens/DashboardScreen.tsx b/src/screens/DashboardScreen.tsx new file mode 100644 index 0000000..fa207c9 --- /dev/null +++ b/src/screens/DashboardScreen.tsx @@ -0,0 +1,44 @@ +import React, { useCallback, useState } from 'react' +import { View, ScrollView, TouchableOpacity, Text, StyleSheet } from 'react-native' +import { useFocusEffect } from '@react-navigation/native' +import { initDatabase, getAllMeasurements, getLatestMeasurement } from '../database/db' +import { Measurement } from '../types' +import LatestMeasurementCard from '../components/LatestMeasurementCard' +import WeightChart from '../components/WeightChart' + +type Props = { navigation: any } + +export default function DashboardScreen({ navigation }: Props) { + const [latest, setLatest] = useState(null) + const [all, setAll] = useState([]) + + useFocusEffect( + useCallback(() => { + const load = async () => { + await initDatabase() + const allM = await getAllMeasurements() + setAll(allM) + const latestM = await getLatestMeasurement() + setLatest(latestM) + } + load() + }, []) + ) + + return ( + + + + + navigation.navigate('NewMeasurement')}> + + Nieuwe meting + + + ) +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#f5f5f5' }, + fab: { backgroundColor: '#4CAF50', padding: 16, borderRadius: 8, margin: 16, alignItems: 'center' }, + fabText: { color: '#fff', fontSize: 16, fontWeight: '700' }, +})