feat: add Dashboard with latest card and weight chart
This commit is contained in:
parent
212bc700c3
commit
45b2a2c36e
3 changed files with 151 additions and 0 deletions
54
src/components/LatestMeasurementCard.tsx
Normal file
54
src/components/LatestMeasurementCard.tsx
Normal file
|
|
@ -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 (
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.emptyText}>Nog geen metingen</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.title}>Laatste meting</Text>
|
||||
<Text style={styles.date}>{new Date(measurement.measuredAt).toLocaleDateString('nl-NL')}</Text>
|
||||
{rows.map(r => (
|
||||
<View key={r.key} style={styles.row}>
|
||||
<Text style={styles.rowLabel}>{r.label}</Text>
|
||||
<Text style={styles.rowValue}>
|
||||
{typeof measurement[r.key] === 'number'
|
||||
? (measurement[r.key] as number).toFixed(1)
|
||||
: measurement[r.key]}{' '}
|
||||
{r.suffix}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
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 },
|
||||
})
|
||||
53
src/components/WeightChart.tsx
Normal file
53
src/components/WeightChart.tsx
Normal file
|
|
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Gewichtsgrafiek</Text>
|
||||
<LineChart
|
||||
data={{
|
||||
labels: labels.length > 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}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { marginHorizontal: 16, marginBottom: 16 },
|
||||
title: { fontSize: 18, fontWeight: '700', marginBottom: 8 },
|
||||
chart: { borderRadius: 12 },
|
||||
})
|
||||
44
src/screens/DashboardScreen.tsx
Normal file
44
src/screens/DashboardScreen.tsx
Normal file
|
|
@ -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<Measurement | null>(null)
|
||||
const [all, setAll] = useState<Measurement[]>([])
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const load = async () => {
|
||||
await initDatabase()
|
||||
const allM = await getAllMeasurements()
|
||||
setAll(allM)
|
||||
const latestM = await getLatestMeasurement()
|
||||
setLatest(latestM)
|
||||
}
|
||||
load()
|
||||
}, [])
|
||||
)
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container}>
|
||||
<LatestMeasurementCard measurement={latest} />
|
||||
<WeightChart measurements={all} />
|
||||
|
||||
<TouchableOpacity style={styles.fab} onPress={() => navigation.navigate('NewMeasurement')}>
|
||||
<Text style={styles.fabText}>+ Nieuwe meting</Text>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
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' },
|
||||
})
|
||||
Loading…
Reference in a new issue