diff --git a/Chart.tsx b/Chart.tsx new file mode 100644 index 0000000..12fdcbd --- /dev/null +++ b/Chart.tsx @@ -0,0 +1,58 @@ +import * as shape from 'd3-shape'; +import React from 'react'; +import {useColorScheme, View} from 'react-native'; +import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts'; +import {CombinedDarkTheme, CombinedDefaultTheme} from './App'; +import Set from './set'; + +export default function Chart({ + yData, + xFormat, + xData, + yFormat, +}: { + yData: number[]; + xData: Set[]; + xFormat: (value: any, index: number) => string; + yFormat: (value: any) => string; +}) { + const dark = useColorScheme() === 'dark'; + const axesSvg = {fontSize: 10, fill: 'grey'}; + const verticalContentInset = {top: 10, bottom: 10}; + const xAxisHeight = 30; + + return ( + <> + + + + + + + + + + + ); +} diff --git a/ViewBest.tsx b/ViewBest.tsx index a58160d..5504f17 100644 --- a/ViewBest.tsx +++ b/ViewBest.tsx @@ -4,7 +4,6 @@ import { useNavigation, useRoute, } from '@react-navigation/native'; -import * as shape from 'd3-shape'; import React, { useCallback, useContext, @@ -12,14 +11,13 @@ import React, { useRef, useState, } from 'react'; -import {useColorScheme, View} from 'react-native'; import {FileSystem} from 'react-native-file-access'; -import {Text, IconButton} from 'react-native-paper'; +import {IconButton} from 'react-native-paper'; +import RNPickerSelect from 'react-native-picker-select'; import Share from 'react-native-share'; -import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts'; import ViewShot from 'react-native-view-shot'; -import {CombinedDarkTheme, CombinedDefaultTheme} from './App'; import {BestPageParams} from './BestPage'; +import Chart from './Chart'; import {DatabaseContext} from './Routes'; import Set from './set'; import {formatMonth} from './time'; @@ -31,13 +29,25 @@ interface Volume { unit: string; } +enum Metrics { + Weight = 'Best weight per day', + Volume = 'Best volume per day', +} + +enum Periods { + Weekly = 'This week', + Monthly = 'This month', + Yearly = 'This year', +} + export default function ViewBest() { const {params} = useRoute>(); const [weights, setWeights] = useState([]); const [volumes, setVolumes] = useState([]); + const [metric, setMetric] = useState(Metrics.Weight); + const [period, setPeriod] = useState(Periods.Monthly); const db = useContext(DatabaseContext); const navigation = useNavigation(); - const dark = useColorScheme() === 'dark'; const viewShot = useRef(null); useFocusEffect( @@ -69,114 +79,91 @@ export default function ViewBest() { }, [navigation, params.best]), ); - useEffect(() => { - console.log(`${ViewBest.name}.useEffect`); - const selectWeights = ` + const refreshWeight = useCallback(async () => { + const select = ` SELECT max(weight) AS weight, STRFTIME('%Y-%m-%d', created) as created, unit FROM sets WHERE name = ? AND NOT hidden + AND DATE(created) >= DATE('now', 'weekday 0', ?) GROUP BY name, STRFTIME('%Y-%m-%d', created) `; - const selectVolumes = ` + let difference = '-7 days'; + if (period === Periods.Monthly) difference = '-1 months'; + else if (period === Periods.Yearly) difference = '-1 years'; + const [result] = await db.executeSql(select, [ + params.best.name, + difference, + ]); + if (result.rows.length === 0) return; + setWeights(result.rows.raw()); + }, [params.best.name, db, period]); + + const refreshVolume = useCallback(async () => { + const select = ` SELECT sum(weight * reps) AS value, STRFTIME('%Y-%m-%d', created) as created, unit FROM sets WHERE name = ? AND NOT hidden + AND DATE(created) >= DATE('now', 'weekday 0', ?) GROUP BY name, STRFTIME('%Y-%m-%d', created) `; - const refresh = async () => { - const [weightsResult] = await db.executeSql(selectWeights, [ - params.best.name, - ]); - if (weightsResult.rows.length === 0) return; - setWeights(weightsResult.rows.raw()); - const [volumesResult] = await db.executeSql(selectVolumes, [ - params.best.name, - ]); - console.log(volumesResult.rows.raw()); - if (volumesResult.rows.length === 0) return; - setVolumes(volumesResult.rows.raw()); - }; - refresh(); - }, [params.best.name, db]); + let difference = '-7 days'; + if (period === Periods.Monthly) difference = '-1 months'; + else if (period === Periods.Yearly) difference = '-1 years'; + const [result] = await db.executeSql(select, [ + params.best.name, + difference, + ]); + if (result.rows.length === 0) return; + setVolumes(result.rows.raw()); + }, [db, params.best.name, period]); - const axesSvg = {fontSize: 10, fill: 'grey'}; - const verticalContentInset = {top: 10, bottom: 10}; - const xAxisHeight = 30; + useEffect(() => { + if (metric === Metrics.Weight) refreshWeight(); + else if (metric === Metrics.Volume) refreshVolume(); + console.log(`${ViewBest.name}.useEffect`, {metric, period}); + }, [params.best.name, db, metric, period, refreshVolume, refreshWeight]); return ( - Best weight per day - - set.weight)} - style={{marginBottom: xAxisHeight}} - contentInset={verticalContentInset} - svg={axesSvg} - formatLabel={value => `${value}${weights[0].unit}`} - /> - - set.weight)} - contentInset={verticalContentInset} - curve={shape.curveBasis} - svg={{ - stroke: dark - ? CombinedDarkTheme.colors.primary - : CombinedDefaultTheme.colors.primary, - }}> - - - - formatMonth(weights[index].created!) - } - contentInset={{left: 10, right: 10}} - svg={axesSvg} - /> - - - Volume per day - - volume.value)} - style={{marginBottom: xAxisHeight}} - contentInset={verticalContentInset} - svg={axesSvg} - formatLabel={(value: number) => + + + {metric === Metrics.Volume && ( + v.value)} + yFormat={(value: number) => `${value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}${ volumes[0].unit }` } + xData={weights} + xFormat={(_value, index) => formatMonth(weights[index].created!)} /> - - volume.value)} - contentInset={verticalContentInset} - curve={shape.curveBasis} - svg={{ - stroke: dark - ? CombinedDarkTheme.colors.primary - : CombinedDefaultTheme.colors.primary, - }}> - - - - formatMonth(volumes[index]?.created) - } - contentInset={{left: 10, right: 10}} - svg={axesSvg} - /> - - + )} + {metric === Metrics.Weight && ( + set.weight)} + yFormat={value => `${value}${weights[0].unit}`} + xData={weights} + xFormat={(_value, index) => formatMonth(weights[index].created!)} + /> + )} ); } diff --git a/package.json b/package.json index 48c02b6..c59c309 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@babel/preset-env": "^7.1.6", "@react-native-masked-view/masked-view": "^0.2.6", + "@react-native-picker/picker": "^2.4.4", "@react-navigation/drawer": "^6.4.3", "@react-navigation/native": "^6.0.10", "@react-navigation/stack": "^6.2.2", @@ -29,6 +30,7 @@ "react-native-linear-gradient": "^2.6.2", "react-native-pager-view": "^5.4.24", "react-native-paper": "^4.12.2", + "react-native-picker-select": "^8.0.4", "react-native-reanimated": "^2.9.0", "react-native-safe-area-context": "^4.3.1", "react-native-screens": "^3.14.0", diff --git a/yarn.lock b/yarn.lock index 5264eea..a86c43d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1913,6 +1913,26 @@ __metadata: languageName: node linkType: hard +"@react-native-picker/picker@npm:^1.8.3": + version: 1.16.8 + resolution: "@react-native-picker/picker@npm:1.16.8" + peerDependencies: + react: 16 || 17 + react-native: ">=0.57" + checksum: c0176e41ba7486bf0a27ab5471315848cb166330313bd12e29ee65b7d2e1dfde22383de6a89be15770d132b33bca1764e64feee3acf22c67c3733fd877d3e91f + languageName: node + linkType: hard + +"@react-native-picker/picker@npm:^2.4.4": + version: 2.4.4 + resolution: "@react-native-picker/picker@npm:2.4.4" + peerDependencies: + react: ">=16" + react-native: ">=0.57" + checksum: 8a122707e39f3f6a97536bd7800bdb2a326bdcff85d7e03472250454974a14789c8963c0d99397410955a9bbe31a67cef38bccfd0834b161e8a169e25ecc2304 + languageName: node + linkType: hard + "@react-native/assets@npm:1.0.0": version: 1.0.0 resolution: "@react-native/assets@npm:1.0.0" @@ -6001,6 +6021,7 @@ __metadata: "@babel/runtime": ^7.12.5 "@react-native-community/eslint-config": ^2.0.0 "@react-native-masked-view/masked-view": ^0.2.6 + "@react-native-picker/picker": ^2.4.4 "@react-navigation/drawer": ^6.4.3 "@react-navigation/native": ^6.0.10 "@react-navigation/stack": ^6.2.2 @@ -6023,6 +6044,7 @@ __metadata: react-native-linear-gradient: ^2.6.2 react-native-pager-view: ^5.4.24 react-native-paper: ^4.12.2 + react-native-picker-select: ^8.0.4 react-native-reanimated: ^2.9.0 react-native-safe-area-context: ^4.3.1 react-native-screens: ^3.14.0 @@ -7434,6 +7456,16 @@ __metadata: languageName: node linkType: hard +"react-native-picker-select@npm:^8.0.4": + version: 8.0.4 + resolution: "react-native-picker-select@npm:8.0.4" + dependencies: + "@react-native-picker/picker": ^1.8.3 + lodash.isequal: ^4.5.0 + checksum: 9656a2f7b330a9e5a8b6b90393140fc5370762d2a14de4c5d31ce43de8abc677870c7f2ee8a493f48535157ddcbc3311cf3f41dac262bd84e50e913f0d4a24ff + languageName: node + linkType: hard + "react-native-reanimated@npm:^2.9.0": version: 2.9.0 resolution: "react-native-reanimated@npm:2.9.0"