From 3f864c8922cbf7c2b5d5489ae5dc5263ad4bbbc5 Mon Sep 17 00:00:00 2001 From: carol Date: Tue, 2 Jan 2024 16:31:34 +0100 Subject: [PATCH 01/37] circle --- components/common/loadingSymbol.tsx | 58 +++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/components/common/loadingSymbol.tsx b/components/common/loadingSymbol.tsx index 17fb29a..b7e1a03 100644 --- a/components/common/loadingSymbol.tsx +++ b/components/common/loadingSymbol.tsx @@ -1,23 +1,49 @@ -import { StyleSheet, View } from "react-native"; +import React, { useEffect, useRef } from "react"; +import { StyleSheet, View, Animated, Easing, ActivityIndicator } from "react-native"; const LoadingSymbol = () => { + const color = ["blue", "red", "purple", "green", "yellow", "orange"]; + const random = Math.floor(Math.random() * color.length); - const color = ["blue", "red", "purple", "green", "yellow", "orange"]; - const random = Math.floor(Math.random() * color.length); + const spinValue = useRef(new Animated.Value(0)).current; - const styles = StyleSheet.create({ - container: { - backgroundColor: color[random], - width: "100%", - height: "100%", - position: "absolute", - } - }); + const spin = spinValue.interpolate({ + inputRange: [0, 1], + outputRange: ["0deg", "360deg"], + }); - return ( - + useEffect(() => { + Animated.loop( + Animated.timing(spinValue, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + useNativeDriver: true, + }) + ).start(); + }, [spinValue]); + + const styles = StyleSheet.create({ + container: { + backgroundColor: color[random], + width: "100%", + height: "100%", + position: "absolute", + justifyContent: "center", + alignItems: "center", + }, + loader: { + width: 100, + height: 100, + transform: [{ rotate: spin }], + }, + }); + + return ( + + + ); -} +}; - -export default LoadingSymbol; \ No newline at end of file +export default LoadingSymbol; From 7606c5d1b83c734f03bd97e106b3bf4170e68066 Mon Sep 17 00:00:00 2001 From: thschleicher Date: Fri, 5 Jan 2024 10:52:02 +0100 Subject: [PATCH 02/37] fixed the budget element and implemented onPress --- app/(tabs)/budget/index.tsx | 14 +++++++- components/budget/budgetHeader.tsx | 8 +++-- components/budget/categoryItem.tsx | 54 +++++++++++++++++------------- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx index 3e2052c..b2a2b29 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/budget/index.tsx @@ -37,6 +37,10 @@ export default function Page() { } }; + const handleCategoryPress = (item: {[column: string]: any;}) => { + console.log(item.category_name); + } + return ( @@ -48,7 +52,15 @@ export default function Page() { {isLoading ? () : ( } + renderItem = {({item}) => { + handleCategoryPress(item); + }}/>} keyExtractor={item => item.category_guid} ItemSeparatorComponent={() => { return (); diff --git a/components/budget/budgetHeader.tsx b/components/budget/budgetHeader.tsx index 7fe8f01..bca2e9b 100644 --- a/components/budget/budgetHeader.tsx +++ b/components/budget/budgetHeader.tsx @@ -1,6 +1,6 @@ import { StyleSheet, Text, TouchableHighlight, View } from "react-native"; -import TextInputBar from "../common/TextInputBar"; import { useTheme } from "../../app/contexts/ThemeContext"; +import TextInputBar from "../common/TextInputBar"; type BudgetHeaderProperties = { selectedPage: string, @@ -34,7 +34,7 @@ const BudgetHeader = (properties: BudgetHeaderProperties) => { }} /> - + ); } @@ -81,4 +81,8 @@ const styles = StyleSheet.create({ marginBottom: 20, marginTop: 10, }, + searchBarStyle: { + marginBottom: 20, + marginHorizontal: 10, + } }); \ No newline at end of file diff --git a/components/budget/categoryItem.tsx b/components/budget/categoryItem.tsx index e651caf..6c29e14 100644 --- a/components/budget/categoryItem.tsx +++ b/components/budget/categoryItem.tsx @@ -1,4 +1,5 @@ import { ColorValue, StyleSheet, Text, View } from "react-native"; +import { TouchableOpacity } from "react-native-gesture-handler"; import { useTheme } from "../../app/contexts/ThemeContext"; import CustomCard from "../common/CustomCard"; @@ -8,6 +9,7 @@ export type CategoryItemProps = { allocated_amount: number, total_expenses: number, category_guid: string, + onPress?: () => void, } const CategoryItem = (properties: CategoryItemProps) => { @@ -17,40 +19,46 @@ const CategoryItem = (properties: CategoryItemProps) => { const subText = `${properties.total_expenses} / ${properties.allocated_amount} €`; return ( - - - - - {properties.category} - - - {subText} - - - + + + + + + {properties.category} + + + {subText} + + + + ); }; export default CategoryItem; const styles = StyleSheet.create({ + customCardStyle: { + flexDirection: "row", + justifyContent: "space-between", + }, colorTipStyle: { - width: 25, - borderTopLeftRadius: 10, - borderBottomLeftRadius: 10, + width: 25, + borderTopLeftRadius: 10, + borderBottomLeftRadius: 10, }, textViewStyle: { - flex: 2, - flexDirection: "column", - paddingVertical: 5, - paddingHorizontal: 10, - alignSelf: "stretch", + flex: 2, + flexDirection: "column", + paddingVertical: 5, + paddingHorizontal: 10, + alignSelf: "stretch", }, categoryNameStyle: { - fontSize: 30, - fontWeight: "bold", + fontSize: 30, + fontWeight: "bold", }, subTextStyle: { - fontSize: 17.5, + fontSize: 17.5, } - }) +}); From 412e88d007d98ee0da66b0f2cc72c4f891e086b9 Mon Sep 17 00:00:00 2001 From: Thomas Schleicher Date: Fri, 5 Jan 2024 12:27:56 +0000 Subject: [PATCH 03/37] Resolve "Categories, detailed view on Press" --- app/(tabs)/budget/_layout.tsx | 1 + app/(tabs)/budget/addCategory.tsx | 8 +- app/(tabs)/budget/category.tsx | 95 ++++++++++++++++++++ app/(tabs)/budget/index.tsx | 4 +- app/(tabs)/stats/index.tsx | 8 +- components/stats/BudgetRemaining.tsx | 2 +- components/stats/CategoryProgressBarList.tsx | 1 - components/stats/Widget.tsx | 2 +- 8 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 app/(tabs)/budget/category.tsx diff --git a/app/(tabs)/budget/_layout.tsx b/app/(tabs)/budget/_layout.tsx index 238abc6..ce907f3 100644 --- a/app/(tabs)/budget/_layout.tsx +++ b/app/(tabs)/budget/_layout.tsx @@ -5,6 +5,7 @@ export default function _Layout() { + ); } \ No newline at end of file diff --git a/app/(tabs)/budget/addCategory.tsx b/app/(tabs)/budget/addCategory.tsx index c211406..904f37c 100644 --- a/app/(tabs)/budget/addCategory.tsx +++ b/app/(tabs)/budget/addCategory.tsx @@ -9,8 +9,8 @@ const addCategory = () => { const parameters = useLocalSearchParams(); - const [categoryName, setCartegoryName] = useState("Enter Category Name..."); - const [categoryColor, setCartegoryColor] = useState(null); + const [categoryName, setCategoryName] = useState("Enter Category Name..."); + const [categoryColor, setCategoryColor] = useState(null); const [selectedType, setSelectedType] = useState("expense"); return ( @@ -20,7 +20,7 @@ const addCategory = () => { { - setCartegoryName(newName); + setCategoryName(newName); }}/> @@ -33,7 +33,7 @@ const addCategory = () => { { - setCartegoryColor(color); + setCategoryColor(color); }}/> diff --git a/app/(tabs)/budget/category.tsx b/app/(tabs)/budget/category.tsx new file mode 100644 index 0000000..dc283bf --- /dev/null +++ b/app/(tabs)/budget/category.tsx @@ -0,0 +1,95 @@ +import { FontAwesome } from "@expo/vector-icons"; +import { router, useLocalSearchParams } from "expo-router"; +import { FlatList, SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from "react-native"; +import { ExpenseItem, LoadingSymbol, TextInputBar } from "../../../components"; +import useFetch from "../../../hooks/useFetch"; +import { useTheme } from "../../contexts/ThemeContext"; + +export default function Page() { + const {colors} = useTheme(); + const {category_guid, category_name} = useLocalSearchParams(); + + const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]}); + + const handleEditCategory = () => { + console.log("edit category"); + } + + const handleBackButton = () => { + router.back(); + } + + return ( + + + + Back + + + + {category_name} + + + + + + {isLoading ? () : ( + } + keyExtractor={item => item.guid} + ItemSeparatorComponent={() => { + return (); + }} + /> + )} + + ); +} + +const styles = StyleSheet.create({ + safeAreaView: { + flex: 1, + }, + itemSeperator: { + margin: 5, + }, + categoryEditTouchableOpacity: { + borderRadius: 10, + flexDirection: "row", + justifyContent: "space-around", + margin: 10, + }, + editButtonText: { + fontSize: 30, + padding: 10, + fontWeight: "bold", + }, + iconEdit: { + paddingVertical: 10, + paddingRight: 10, + }, + searchBar: { + marginHorizontal: 10, + marginBottom:20, + }, + iconBack: { + paddingLeft: 10, + paddingRight: 5, + }, + backText: { + fontSize: 20, + padding: 5, + }, + backContainer: { + flexDirection: "row", + justifyContent: "flex-start", + } +}); diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx index b2a2b29..9131dc7 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/budget/index.tsx @@ -38,7 +38,7 @@ export default function Page() { }; const handleCategoryPress = (item: {[column: string]: any;}) => { - console.log(item.category_name); + router.push({pathname: "/(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) } return ( @@ -46,7 +46,7 @@ export default function Page() { { - router.push({pathname: '/(tabs)/budget/addCategory', params: { guid: '123'}}); //This needs to be changed to a regular push, without parameters + router.push({pathname: '/(tabs)/budget/addCategory'}); }}/> {isLoading ? () : ( diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index 9a2033a..b7e477d 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -53,16 +53,10 @@ export default function Page() { - + - - - - - - ); diff --git a/components/stats/BudgetRemaining.tsx b/components/stats/BudgetRemaining.tsx index 1eb038e..6207ead 100644 --- a/components/stats/BudgetRemaining.tsx +++ b/components/stats/BudgetRemaining.tsx @@ -8,7 +8,7 @@ interface BudgetRemainingProps { } const BudgetRemaining: React.FC = ({ budget, spent }) => { - const { colors, theme } = useTheme(); + const { colors } = useTheme(); const remaining = budget - spent; diff --git a/components/stats/CategoryProgressBarList.tsx b/components/stats/CategoryProgressBarList.tsx index 566f352..42cfcf7 100644 --- a/components/stats/CategoryProgressBarList.tsx +++ b/components/stats/CategoryProgressBarList.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { View, Text, Button, StyleSheet, TouchableOpacity } from 'react-native'; import CategoryProgressBar from './CategoryProgressBar'; -import { useTheme } from '../../app/contexts/ThemeContext'; interface CategoryItem { name: string; diff --git a/components/stats/Widget.tsx b/components/stats/Widget.tsx index 153ed65..f4e3e0b 100644 --- a/components/stats/Widget.tsx +++ b/components/stats/Widget.tsx @@ -44,7 +44,7 @@ const Widget: React.FC = ({ title, text, children, image }) => { // }, imageStyle: { width: '100%', - height: 200, + height: 500, borderRadius: 5, marginBottom: 8, }, From cec3a4d2382c01d308fbaf0137e8a25b573ebfc9 Mon Sep 17 00:00:00 2001 From: Walcher Date: Fri, 5 Jan 2024 14:23:47 +0100 Subject: [PATCH 04/37] add savings mock data to DEV_populateDatabase --- services/database.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/database.ts b/services/database.ts index fbb9b6c..1fe01dd 100644 --- a/services/database.ts +++ b/services/database.ts @@ -144,6 +144,7 @@ export const DEV_populateDatabase = async () => { for(let i=0; i < 5; i++){ let random = Math.floor(Math.random() * colors.length); await addCategory(`Category ${i}`, colors[random], "budget", 50) + await addCategory(`Category ${i+6}`, colors[random], "budget", 50) } const result = await executeQuery({sql:"SELECT * from category", args:[]}) let categories: {[column: string]: any}[]; @@ -160,7 +161,7 @@ export const DEV_populateDatabase = async () => { let randomDay = Math.floor(Math.random() * 20) date.setDate(randomDay) let string = new SimpleDate(date).toISOString() - await addExpense(`Expense ${i}`, categories[random].guid, string, 15) + await addExpense(`Expense ${i}`, categories[random].guid, string, 30) } } catch(e){ console.error(e) From 9544b3eabb314aa59f5c164c3481dbcd4d0c063d Mon Sep 17 00:00:00 2001 From: Walcher Date: Fri, 5 Jan 2024 14:32:30 +0100 Subject: [PATCH 05/37] added database connection --- app/(tabs)/stats/index.tsx | 8 +++- components/stats/BudgetOverview.tsx | 48 ++++++++++++++++------ components/stats/BudgetRemaining.tsx | 61 ++++++++++++++++++---------- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index b7e477d..0870688 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -6,6 +6,7 @@ import Widget from '../../../components/stats/Widget'; import CategoryProgressBarList from '../../../components/stats/CategoryProgressBarList'; import BudgetRemaining from '../../../components/stats/BudgetRemaining'; import DebugMenu from '../../../services/DebugMenu'; +import SavingsOverview from '../../../components/stats/SavingsOverview'; export default function Page() { const { colors } = useTheme(); @@ -51,11 +52,14 @@ export default function Page() { - + - + + + + diff --git a/components/stats/BudgetOverview.tsx b/components/stats/BudgetOverview.tsx index 5339fb3..05e89c4 100644 --- a/components/stats/BudgetOverview.tsx +++ b/components/stats/BudgetOverview.tsx @@ -1,31 +1,55 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; +import useFetch from '../../hooks/useFetch'; -interface StatsBudgetProps { - spent: number; - budget: number; -} - -const BudgetOverview: React.FC = ({ spent, budget }) => { +const BudgetOverview = () => { const { colors } = useTheme(); + const [spent, setSpent] = useState(0); + const [budget, setBudget] = useState(0); + + const spentQuery = { + sql: "SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = 'budget'", + args: [] + }; + + const budgetQuery = { + sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'", + args: [] + }; + + const { data: spentData, isLoading: spentLoading } = useFetch(spentQuery); + const { data: budgetData, isLoading: budgetLoading } = useFetch(budgetQuery); + + useEffect(() => { + if (spentData) { + setSpent(spentData[0]?.total || 0); + } + if (budgetData) { + setBudget(budgetData[0]?.total || 0); + } + }, [spentData, budgetData]); const styles = StyleSheet.create({ container: { margin: 10, borderRadius: 5, alignItems: 'center', - justifyContent: 'center', + justifyContent: 'center' }, text: { fontSize: 26, - color: colors.primaryText, + color: colors.primaryText }, boldText: { - fontWeight: 'bold', - }, + fontWeight: 'bold' + } }); + if (spentLoading || budgetLoading) { + return Loading...; + } + return ( @@ -35,4 +59,4 @@ const BudgetOverview: React.FC = ({ spent, budget }) => { ); }; -export default BudgetOverview +export default BudgetOverview; diff --git a/components/stats/BudgetRemaining.tsx b/components/stats/BudgetRemaining.tsx index 6207ead..37aa555 100644 --- a/components/stats/BudgetRemaining.tsx +++ b/components/stats/BudgetRemaining.tsx @@ -1,35 +1,52 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; +import useFetch from '../../hooks/useFetch'; -interface BudgetRemainingProps { - budget: number; - spent: number; -} +const styles = StyleSheet.create({ + text: { + fontSize: 26, + }, + boldText: { + fontWeight: 'bold', + }, +}); -const BudgetRemaining: React.FC = ({ budget, spent }) => { +const BudgetRemaining = () => { const { colors } = useTheme(); + const [spent, setSpent] = useState(0); + const [budget, setBudget] = useState(0); + + const spentQuery = { + sql: "SELECT SUM(amount) as total FROM expense", + args: [] + }; + + const budgetQuery = { + sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'", + args: [] + }; + + const { data: spentData, isLoading: spentLoading } = useFetch(spentQuery); + const { data: budgetData, isLoading: budgetLoading } = useFetch(budgetQuery); + + useEffect(() => { + if (spentData) { + setSpent(spentData[0]?.total || 0); + } + if (budgetData) { + setBudget(budgetData[0]?.total || 0); + } + }, [spentData, budgetData]); const remaining = budget - spent; - const styles = StyleSheet.create({ - container: { - margin: 10, - borderRadius: 5, - alignItems: 'center', - justifyContent: 'center', - }, - text: { - fontSize: 26, - color: colors.primaryText, - }, - boldText: { - fontWeight: 'bold', - }, - }); + if (spentLoading || budgetLoading) { + return Loading...; + } return ( - + You have {remaining.toFixed(2)}€ left. ); From d9db03af613915eddeb2f80183904051cc80f041 Mon Sep 17 00:00:00 2001 From: Walcher Date: Fri, 5 Jan 2024 14:32:41 +0100 Subject: [PATCH 06/37] added SavingsOverview --- components/stats/SavingsOverview.tsx | 62 ++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 components/stats/SavingsOverview.tsx diff --git a/components/stats/SavingsOverview.tsx b/components/stats/SavingsOverview.tsx new file mode 100644 index 0000000..a79234a --- /dev/null +++ b/components/stats/SavingsOverview.tsx @@ -0,0 +1,62 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import useFetch from '../../hooks/useFetch'; + +const SavingsOverview = () => { + const { colors } = useTheme(); + const [saved, setSaved] = useState(0); + const [goal, setGoal] = useState(0); + + const savedQuery = { + sql: "SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = 'savings'", + args: [] + }; + + const goalQuery = { + sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'", + args: [] + }; + + const { data: savedData, isLoading: savedLoading } = useFetch(savedQuery); + const { data: goalData, isLoading: goalLoading } = useFetch(goalQuery); + + useEffect(() => { + if (savedData) { + setSaved(savedData[0]?.total || 0); + } + if (goalData) { + setGoal(goalData[0]?.total || 0); + } + }, [savedData, goalData]); + + const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center' + }, + text: { + fontSize: 26, + color: colors.primaryText + }, + boldText: { + fontWeight: 'bold' + } + }); + + if (savedLoading || goalLoading) { + return Loading...; + } + + return ( + + + You have saved {saved.toFixed(2)}€ out of your goal of {goal.toFixed(2)}€. + + + ); +}; + +export default SavingsOverview; \ No newline at end of file From 7e29e4f0169ed241c95cfc26309ffe44ea718a2e Mon Sep 17 00:00:00 2001 From: Walcher Date: Fri, 5 Jan 2024 14:43:15 +0100 Subject: [PATCH 07/37] added "Financial" Advice --- app/(tabs)/stats/index.tsx | 4 ++ components/stats/FinancialAdvice.tsx | 59 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 components/stats/FinancialAdvice.tsx diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index 0870688..11f17fc 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -7,6 +7,7 @@ import CategoryProgressBarList from '../../../components/stats/CategoryProgressB import BudgetRemaining from '../../../components/stats/BudgetRemaining'; import DebugMenu from '../../../services/DebugMenu'; import SavingsOverview from '../../../components/stats/SavingsOverview'; +import FinancialAdvice from '../../../components/stats/FinancialAdvice'; export default function Page() { const { colors } = useTheme(); @@ -58,6 +59,9 @@ export default function Page() { + + + diff --git a/components/stats/FinancialAdvice.tsx b/components/stats/FinancialAdvice.tsx new file mode 100644 index 0000000..8f136ac --- /dev/null +++ b/components/stats/FinancialAdvice.tsx @@ -0,0 +1,59 @@ +import React, { useState, useEffect } from 'react'; +import { StyleSheet, View, Text } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; + +const FinancialAdvice = () => { + const tips = [ + "Maybe you shouldn't have bought that full-price video game. But who needs savings when you have high scores, right?", + "That daily gourmet coffee is essential, isn't it? Who needs a retirement fund when you've got caffeine!", + "Oh, another pair of designer shoes? Because the other twenty pairs just aren't enough.", + "A luxury car to drive two blocks? Obviously, walking is for peasants.", + "Sure, subscribe to all streaming services. Who needs to socialize outside when you can binge-watch shows alone forever?", + "A gym membership you never use? At least your wallet's getting a workout.", + "Booking another expensive vacation? It's not like you need to save for a rainy day or anything.", + "Another impulse purchase online? Because 'limited time offer' is definitely not a marketing tactic.", + "Eating out for every meal? Clearly, cooking at home is way too mainstream.", + "Upgrading to the latest phone model again? It must be tough having a phone that's 6 months old." + ]; + + const { colors } = useTheme(); + + const [tip, setTip] = useState(''); + + useEffect(() => { + // Change the tip every 10 seconds + const intervalId = setInterval(() => { + const randomTip = tips[Math.floor(Math.random() * tips.length)]; + setTip(randomTip); + }, 10000); + + // Clear the interval on component unmount + return () => clearInterval(intervalId); + }, [tips]); + + const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center' + }, + text: { + fontSize: 26, + color: colors.primaryText + }, + boldText: { + fontWeight: 'bold' + } + }); + + return ( + + + {tip} + + + ); +}; + +export default FinancialAdvice; From 8b68a7b7b0e5b42977ce00c03ab43c63a88964cc Mon Sep 17 00:00:00 2001 From: Walcher Date: Fri, 5 Jan 2024 14:50:33 +0100 Subject: [PATCH 08/37] made it possible to pass an override backgroundcolor to widget --- components/stats/Widget.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/stats/Widget.tsx b/components/stats/Widget.tsx index f4e3e0b..182def4 100644 --- a/components/stats/Widget.tsx +++ b/components/stats/Widget.tsx @@ -1,5 +1,5 @@ import React, { ReactNode } from 'react'; -import { View, StyleSheet, Text, Image } from 'react-native'; // Add the missing import statement for Image +import { View, StyleSheet, Text, Image } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; interface WidgetProps { @@ -7,14 +7,17 @@ interface WidgetProps { text?: string; children?: ReactNode; image?: any; + backgroundColor?: string; } -const Widget: React.FC = ({ title, text, children, image }) => { // Add the 'image' prop to the destructuring +const Widget: React.FC = ({ title, text, children, image, backgroundColor }) => { const { colors } = useTheme(); + const actualBackgroundColor = backgroundColor ? backgroundColor : colors.widgetBackgroundColor; + const styles = StyleSheet.create({ widgetContainer: { - backgroundColor: colors.widgetBackgroundColor, + backgroundColor: actualBackgroundColor, borderColor: colors.widgetBorderColor, borderRadius: 5, padding: 16, From c0a70a0ed5c50d9ea7e2b0bb45bd2fec74b29a44 Mon Sep 17 00:00:00 2001 From: Walcher Date: Fri, 5 Jan 2024 14:52:20 +0100 Subject: [PATCH 09/37] finishing up --- app/(tabs)/stats/index.tsx | 26 ++++---------------------- components/stats/Widget.tsx | 1 + 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index 11f17fc..cac1e04 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -18,25 +18,7 @@ export default function Page() { const spent = 120.75; const budget = 696.96; - const BudgetData = [ - { name: 'Utilities', color: '#20B2AA', maxValue: 80, currentValue: 46 }, - { name: 'Food', color: '#FF6347', maxValue: 88, currentValue: 31 }, - ]; - - const SavingsData = [ - { name: 'Education', color: '#FF6347', maxValue: 135, currentValue: 0 }, - { name: 'Rent', color: '#DA70D6', maxValue: 140, currentValue: 96 }, - { name: 'Food', color: '#F08080', maxValue: 84, currentValue: 78 }, - { name: 'Healthcare', color: '#20B2AA', maxValue: 134, currentValue: 48 }, - { name: 'Healthcare', color: '#32CD32', maxValue: 119, currentValue: 69 }, - { name: 'Clothing', color: '#32CD32', maxValue: 115, currentValue: 99 }, - ]; - - const categoryData = [ - { category: 'Food', value: 50 }, - { category: 'Rent', value: 300 }, - { category: 'Utilities', value: 100 }, - ]; + const styles = StyleSheet.create({ container: { @@ -51,6 +33,9 @@ export default function Page() { + + + @@ -59,9 +44,6 @@ export default function Page() { - - - diff --git a/components/stats/Widget.tsx b/components/stats/Widget.tsx index 182def4..3162d62 100644 --- a/components/stats/Widget.tsx +++ b/components/stats/Widget.tsx @@ -13,6 +13,7 @@ interface WidgetProps { const Widget: React.FC = ({ title, text, children, image, backgroundColor }) => { const { colors } = useTheme(); + const actualBackgroundColor = backgroundColor ? backgroundColor : colors.widgetBackgroundColor; const styles = StyleSheet.create({ From 5b71fa74b1d1d9e08fd16128801655df3c05a9cb Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Fri, 5 Jan 2024 15:26:09 +0100 Subject: [PATCH 10/37] feat: edit expense --- app/(tabs)/home/_layout.tsx | 4 +- app/(tabs)/home/expense/[expense].tsx | 137 ++++++++++++++++++ app/(tabs)/home/expense/_layout.tsx | 39 +++++ .../home/{addItem.tsx => expense/new.tsx} | 14 +- app/(tabs)/home/index.tsx | 4 +- components/common/AutoDecimalInput.tsx | 16 +- components/common/TextInputBar.tsx | 7 +- components/home/expenseItem.tsx | 64 +++++--- hooks/useFetch.ts | 6 +- services/database.ts | 15 ++ types/dbItems.ts | 8 + 11 files changed, 273 insertions(+), 41 deletions(-) create mode 100644 app/(tabs)/home/expense/[expense].tsx create mode 100644 app/(tabs)/home/expense/_layout.tsx rename app/(tabs)/home/{addItem.tsx => expense/new.tsx} (90%) diff --git a/app/(tabs)/home/_layout.tsx b/app/(tabs)/home/_layout.tsx index abe83bf..ba7964c 100644 --- a/app/(tabs)/home/_layout.tsx +++ b/app/(tabs)/home/_layout.tsx @@ -24,8 +24,8 @@ export default function _Layout() { title: "test", headerShown: false, }}/> - (); + const [selectedCategory, setSelectedCategory] = useState(); + const [formatedValue, setFormatedValue] = useState(""); + const [initialValue, setInitialValue] = useState(); + const [selectorModalVisible, setSelecorModalVisible] = useState(false); + const [expenseName, setExpenseName] = useState(""); + const [datePickerShown, setDatePickerShown] = useState(false); + const [selectedDate, setSelectedDate] = useState(new Date()); + + useEffect(()=>{ + const entry = data[0]; + console.log(entry) + if(entry){ + console.log(entry) + const extractedExpense: Expense = {name: entry["e_name"], amount: entry["e_amount"], dateTime: entry["e_datetime"], guid: entry["e_guid"]} + const extractedCategory: Category = {name: entry["c_name"], color: entry["c_color"], guid: entry["c_guid"]} + + console.log(extractedCategory.color) + + setSelectedExpense(extractedExpense); + setSelectedCategory(extractedCategory); + setInitialValue(extractedExpense.amount) + setExpenseName(extractedExpense.name ?? "") + setSelectedDate(extractedExpense.dateTime? new Date(extractedExpense.dateTime) : new Date()); + } + + + }, [data]) + + const handleValueChange = (formatedValue: string) => { + setFormatedValue(formatedValue); + } + + const handleCategorySelect = (category : Category) => { + setSelecorModalVisible(false); + setSelectedCategory(category); + } + + const validateInput = ():boolean => { + if(formatedValue == "" || expenseName == "" || selectedCategory === undefined || selectedDate === null){ + return false; + } + return true; + } + + const submit = () => { + console.log(selectedExpense?.guid) + const insert = async () => { + await updateExpense(selectedExpense!.guid!, expenseName, selectedCategory!.guid!, new SimpleDate(selectedDate).format("YYYY-MM-DD"), Number(formatedValue)) + } + if(validateInput()){ + insert(); + router.back(); + }else { + Alert.alert("Invalid input", "One of the Props is not properly defined") + } + + } + + const handleDelete = () => { + const del = async () => { + await deleteExpense(selectedExpense!.guid!) + router.back(); + } + del(); + } + + return ( + + {setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}> + + {setSelecorModalVisible(true)}} selectedCategory={selectedCategory}/> + setExpenseName(text)} value={expenseName}/> + {setDatePickerShown(true)}}/> + {datePickerShown && + { + setDatePickerShown(false); + if(date){ + setSelectedDate(date); + } + }} + />} + + Delete Expense + + + Save + + + ) +} + +const styles = StyleSheet.create({ + notFound: { + alignItems: "center", + justifyContent: "center" + }, + + notFoundMessageContainer: { + backgroundColor: "#e37b7b", + padding: 50, + }, + save: { + padding: 10, + }, + container: { + margin: SIZES.normal, + display: "flex", + gap: 10 + }, + + submitText: { + fontSize: SIZES.large + } + +}) \ No newline at end of file diff --git a/app/(tabs)/home/expense/_layout.tsx b/app/(tabs)/home/expense/_layout.tsx new file mode 100644 index 0000000..211509f --- /dev/null +++ b/app/(tabs)/home/expense/_layout.tsx @@ -0,0 +1,39 @@ +import { StyleSheet, Text, View } from 'react-native' +import { Stack } from 'expo-router' +import React from 'react' +import { useTheme } from '../../../contexts/ThemeContext' + +const _layout = () => { + const {colors} = useTheme(); + return ( + + + String(Date.now())} + /> + + ) +} + +export default _layout + +const styles = StyleSheet.create({}) \ No newline at end of file diff --git a/app/(tabs)/home/addItem.tsx b/app/(tabs)/home/expense/new.tsx similarity index 90% rename from app/(tabs)/home/addItem.tsx rename to app/(tabs)/home/expense/new.tsx index d7d24f2..f2525c2 100644 --- a/app/(tabs)/home/addItem.tsx +++ b/app/(tabs)/home/expense/new.tsx @@ -1,12 +1,12 @@ import { View, Text, StyleSheet, Alert } from 'react-native' import React, { useRef, useState } from 'react' -import { SIZES } from '../../../constants/theme' -import { useTheme } from '../../contexts/ThemeContext' -import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../../components' -import { Category } from '../../../types/dbItems' +import { SIZES } from '../../../../constants/theme' +import { useTheme } from '../../../contexts/ThemeContext' +import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../../../components' +import { Category } from '../../../../types/dbItems' import DateTimePicker from '@react-native-community/datetimepicker'; -import { addExpense } from '../../../services/database' -import { SimpleDate } from '../../../util/SimpleDate' +import { addExpense } from '../../../../services/database' +import { SimpleDate } from '../../../../util/SimpleDate' import { useRouter } from 'expo-router' export default function AddItem() { @@ -52,7 +52,7 @@ export default function AddItem() { return ( {setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}> - + {setSelecorModalVisible(true)}} selectedCategory={selectedCategory}/> setExpenseName(text)}/> {setDatePickerShown(true)}}/> diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index 5a0a5a8..d57614f 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -74,7 +74,7 @@ export default function Page() { return ( {plusShow && { - router.push("/(tabs)/home/addItem"); + router.push("/(tabs)/home/expense/new"); // executeQuery({sql: "SELECT guid FROM category", args: []}).then((result) => { // if("rows" in result[0]) { @@ -109,7 +109,7 @@ export default function Page() { } - renderItem = {({item}) => } + renderItem = {({item}) => {router.push(`/(tabs)/home/expense/${guid}`)}}/>} keyExtractor={item => item.expense_guid} ItemSeparatorComponent={() => { return (); diff --git a/components/common/AutoDecimalInput.tsx b/components/common/AutoDecimalInput.tsx index 52821fc..260b1ba 100644 --- a/components/common/AutoDecimalInput.tsx +++ b/components/common/AutoDecimalInput.tsx @@ -1,5 +1,5 @@ import { View, Text, TouchableOpacity, TextInput, StyleSheet, NativeSyntheticEvent, TextInputKeyPressEventData } from 'react-native' -import React, {LegacyRef, MutableRefObject, useRef, useState} from 'react' +import React, {LegacyRef, MutableRefObject, useEffect, useRef, useState} from 'react' import { SIZES } from '../../constants/theme'; import { useTheme } from '../../app/contexts/ThemeContext'; @@ -19,13 +19,21 @@ const formatDecimal = (value: string)=>{ interface AutoDecimalInputProps{ onValueChange?: (formattedValue: string) => void | undefined label: string, + initialValue? : number } -const AutoDecimalInput: React.FC = ({onValueChange, label}) => { +const AutoDecimalInput: React.FC = ({onValueChange, label, initialValue}) => { const { colors } = useTheme(); const inputRef = useRef(null); const [pressedNumbers, setPressedNumbers] = useState(""); + const init = () => { + if(initialValue){ + const pressedNumber = initialValue.toFixed(2).replace(".", "") + update(pressedNumber); + } + } + const update = (newValues : string) => { if(onValueChange){ onValueChange(formatDecimal(newValues)) @@ -33,6 +41,10 @@ const AutoDecimalInput: React.FC = ({onValueChange, label setPressedNumbers(newValues); } + useEffect(() => { + init() + }, [initialValue]) + const handleInput = (e: NativeSyntheticEvent)=>{ const pressedKey:string = e.nativeEvent.key if(Number.isInteger(Number.parseInt(pressedKey))){ diff --git a/components/common/TextInputBar.tsx b/components/common/TextInputBar.tsx index a2ffa84..7b0242b 100644 --- a/components/common/TextInputBar.tsx +++ b/components/common/TextInputBar.tsx @@ -7,14 +7,13 @@ import { useTheme } from '../../app/contexts/ThemeContext'; interface SearchBarProps extends ViewProps { placeholder? : string; onChangeText? : (text: string) => void | undefined + value?: string } export default function TextInputBar(props: SearchBarProps) { const [isActive, setIsactive] = React.useState(false); const { colors } = useTheme(); - const [text, setText] = useState(""); - const textColor = colors const backgroundColor = colors.elementDefaultColor; const handleChange = (text:string) : void => { @@ -30,7 +29,7 @@ export default function TextInputBar(props: SearchBarProps) { if(props.onChangeText){ props.onChangeText(text) } - setText(text) + } // cant apply the background color otherwise @@ -43,7 +42,7 @@ export default function TextInputBar(props: SearchBarProps) { // changed styles.container to containerStyle return ( - handleChange(text)} onEndEditing={()=>setIsactive(false)}/> + setIsactive(false)}/> {isActive && {handleChange("")}}> diff --git a/components/home/expenseItem.tsx b/components/home/expenseItem.tsx index ade5ccd..ff6eb2a 100644 --- a/components/home/expenseItem.tsx +++ b/components/home/expenseItem.tsx @@ -3,35 +3,53 @@ import { ColorValue, StyleSheet, Text, View } from 'react-native'; import { SIZES } from '../../constants/theme'; import CustomCard from "../common/CustomCard"; import { useTheme } from '../../app/contexts/ThemeContext'; +import { TouchableOpacity } from 'react-native-gesture-handler'; + +//export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} +interface ExpenseItemProps { + color: ColorValue; + category: string; + title: string; + date: string; + value : string; + guid? : string; + onPress? : (guid?: string) => void +} -export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} export default function ExpenseItem(itemProps : ExpenseItemProps) { const { colors } = useTheme(); + const handlePress = ()=>{ + if(itemProps.onPress){ + itemProps.onPress(itemProps.guid); + } + } return ( - - - - {itemProps.category} - {itemProps.title} - {itemProps.date} + + + + + {itemProps.category} + {itemProps.title} + {itemProps.date} + + + {itemProps.value} + - - {itemProps.value} - - + ) diff --git a/hooks/useFetch.ts b/hooks/useFetch.ts index 496beba..cec238d 100644 --- a/hooks/useFetch.ts +++ b/hooks/useFetch.ts @@ -6,12 +6,16 @@ const useFetch = (query: Query) => { const [isLoading, setIsLoading] = useState(false); const [data, setData] = useState<{[column: string]: any;}[]>([]); + const [isEmptyResult, setIsEmptyResult] = useState(undefined); const reFetch = () => { setIsLoading(true); executeQuery(query).then((result) => { if("rows" in result[0]) { setData(result[0]["rows"]); + if(result[0]["rows"].length == 0){ + setIsEmptyResult(true); + } } }).catch((error: any) => { console.error("Fetching data from database has failed: ", error); @@ -24,7 +28,7 @@ const useFetch = (query: Query) => { reFetch(); }, []) - return {data, isLoading, reFetch}; + return {data, isLoading, reFetch, isEmptyResult}; } export default useFetch; \ No newline at end of file diff --git a/services/database.ts b/services/database.ts index fbb9b6c..016e635 100644 --- a/services/database.ts +++ b/services/database.ts @@ -42,6 +42,21 @@ export const addCategory = async (name: string, color: string, type: string, all } } +export const updateExpense = async (guid: string, name: string, category_guid: string, datetime: string, amount: number) => { + + //needs user input validation for type and color (should be validated by this function) + + try { + await db.transactionAsync(async (tx) => { + await tx.executeSqlAsync("UPDATE expense SET name = ?, category_guid = ?, datetime = ?, amount = ? WHERE guid = ?", [name, category_guid, datetime, amount, guid] + ); + }); + } catch (error) { + console.log("Error updating expense: ", error); + throw error; + } +}; + export const addExpense = async (name: string, category_guid: string, datetime: string, amount: number) => { //needs user input validation for type and color (should be validated by this function) diff --git a/types/dbItems.ts b/types/dbItems.ts index c425095..c2df2dc 100644 --- a/types/dbItems.ts +++ b/types/dbItems.ts @@ -4,4 +4,12 @@ export interface Category { color? :string; type? : "expense" | "saving" allocatedAmount? : number +} + +export interface Expense { + guid? : string; + name? : string; + dateTime? : string; + amount?: number; + category_guid?: string; } \ No newline at end of file From d3b7c61c67a9dc3de0abf3fb80c516b518f31b70 Mon Sep 17 00:00:00 2001 From: Thomas Schleicher Date: Fri, 5 Jan 2024 16:45:25 +0000 Subject: [PATCH 11/37] Resolve "Implement and Split edit Category from add Category" --- .gitlab-ci.yml | 5 +- app/(tabs)/budget/_layout.tsx | 1 + app/(tabs)/budget/addCategory.tsx | 26 ++++--- app/(tabs)/budget/category.tsx | 25 ++++++- app/(tabs)/budget/editCategory.tsx | 96 +++++++++++++++++++++++++ app/(tabs)/budget/index.tsx | 2 +- components/budget/categoryItem.tsx | 4 +- components/budget/customColorPicker.tsx | 8 +-- 8 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 app/(tabs)/budget/editCategory.tsx diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9889a4f..8a1e9c1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,13 +25,14 @@ stages: lint_job: stage: lint - image: node + image: "reactnativecommunity/react-native-android:latest" rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" when: manual - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH script: - - npm install + - yarn install + - npx expo install - npx tsc cache: policy: pull-push diff --git a/app/(tabs)/budget/_layout.tsx b/app/(tabs)/budget/_layout.tsx index ce907f3..c0835eb 100644 --- a/app/(tabs)/budget/_layout.tsx +++ b/app/(tabs)/budget/_layout.tsx @@ -5,6 +5,7 @@ export default function _Layout() { + ); diff --git a/app/(tabs)/budget/addCategory.tsx b/app/(tabs)/budget/addCategory.tsx index 904f37c..984d98e 100644 --- a/app/(tabs)/budget/addCategory.tsx +++ b/app/(tabs)/budget/addCategory.tsx @@ -1,21 +1,23 @@ import { router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native"; -import { CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; +import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; +import { addCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; -const addCategory = () => { +export default function Page() { const {colors} = useTheme(); const parameters = useLocalSearchParams(); const [categoryName, setCategoryName] = useState("Enter Category Name..."); - const [categoryColor, setCategoryColor] = useState(null); + const [categoryColor, setCategoryColor] = useState('#' + Math.floor(Math.random()*16777215).toString(16)); const [selectedType, setSelectedType] = useState("expense"); + const [amount, setAmount] = useState(0); return ( - Category Editor + Add Category @@ -24,6 +26,12 @@ const addCategory = () => { }}/> + + { + setAmount(!Number.isNaN(Number.parseFloat(value)) ? Number.parseFloat(value) : 0); + }}/> + + { @@ -32,7 +40,7 @@ const addCategory = () => { /> - { + { setCategoryColor(color); }}/> @@ -42,7 +50,7 @@ const addCategory = () => { router.back(); }}/> { - console.log("Implement Saving here!"); + addCategory(categoryName, categoryColor, selectedType, amount); router.back(); }}/> @@ -51,8 +59,6 @@ const addCategory = () => { ); } -export default addCategory; - const styles = StyleSheet.create({ containerStyle: { flex: 1, @@ -82,5 +88,9 @@ const styles = StyleSheet.create({ textInputStyle: { paddingHorizontal: 10, fontSize: 25, + }, + budgetInput: { + marginBottom: 10, + marginHorizontal: 10, } }); \ No newline at end of file diff --git a/app/(tabs)/budget/category.tsx b/app/(tabs)/budget/category.tsx index dc283bf..a58d02b 100644 --- a/app/(tabs)/budget/category.tsx +++ b/app/(tabs)/budget/category.tsx @@ -7,12 +7,14 @@ import { useTheme } from "../../contexts/ThemeContext"; export default function Page() { const {colors} = useTheme(); - const {category_guid, category_name} = useLocalSearchParams(); + const {category_guid} = useLocalSearchParams(); + + const {category_amount, category_color, category_name, category_type} = fetchCategoryInformation(category_guid.toString()); const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]}); const handleEditCategory = () => { - console.log("edit category"); + router.push({pathname: "./(tabs)/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); } const handleBackButton = () => { @@ -54,6 +56,25 @@ export default function Page() { ); } +const fetchCategoryInformation = (guid: string) => { + + const {data} = useFetch({sql: "SELECT * FROM category WHERE guid = ?", args: [guid]}); + + let category_name = ""; + let category_color = ""; + let category_amount = 0; + let category_type = ""; + + if (data && data[0]) { + if ("name" in data[0]) category_name = data[0].name as string; + if ("color" in data[0]) category_color = data[0].color as string; + if ("allocated_amount" in data[0]) category_amount = data[0].allocated_amount as number; + if ("type" in data[0]) category_type = data[0].type as string; + } + + return {category_name, category_color, category_amount, category_type}; +} + const styles = StyleSheet.create({ safeAreaView: { flex: 1, diff --git a/app/(tabs)/budget/editCategory.tsx b/app/(tabs)/budget/editCategory.tsx new file mode 100644 index 0000000..b3b8ec7 --- /dev/null +++ b/app/(tabs)/budget/editCategory.tsx @@ -0,0 +1,96 @@ +import { router, useLocalSearchParams } from "expo-router"; +import { useState } from "react"; +import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native"; +import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; +import { useTheme } from "../../contexts/ThemeContext"; + +const addCategory = () => { + const {colors} = useTheme(); + const {category_guid, category_amount, category_color, category_name, category_type} = useLocalSearchParams(); + + const [categoryName, setCategoryName] = useState(category_name.toString()); + const [categoryColor, setCategoryColor] = useState(category_color.toString()); + const [selectedType, setSelectedType] = useState(category_type.toString()); + const [amount, setAmount] = useState(Number.parseFloat(category_amount.toString())); + + return ( + + Edit Category + + + + { + setCategoryName(newName); + }}/> + + + + { + setAmount(!Number.isNaN(Number.parseFloat(value)) ? Number.parseFloat(value) : 0); + }}/> + + + { + setSelectedType(type); + }} + /> + + + { + setCategoryColor(color); + }}/> + + + + { + router.back(); + }}/> + { + console.log("Implement Saving here!"); + router.back(); + }}/> + + + + ); +} + +export default addCategory; + +const styles = StyleSheet.create({ + containerStyle: { + flex: 1, + margin: 10, + borderRadius: 10, + }, + safeAreaViewStyle: { + flex: 1, + flexDirection: "column" + }, + headingTextStyle: { + fontSize: 40, + fontWeight: "bold", + alignSelf: "center", + marginVertical: 10, + }, + navigationButtonViewStyle: { + flexDirection: "row", + justifyContent: "center", + marginBottom: 10, + }, + textInputViewStyle: { + borderRadius: 10, + paddingVertical: 10, + margin: 10, + }, + textInputStyle: { + paddingHorizontal: 10, + fontSize: 25, + }, + budgetInput: { + marginBottom: 10, + marginHorizontal: 10, + } +}); \ No newline at end of file diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx index 9131dc7..3f2aa97 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/budget/index.tsx @@ -38,7 +38,7 @@ export default function Page() { }; const handleCategoryPress = (item: {[column: string]: any;}) => { - router.push({pathname: "/(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) + router.push({pathname: "./(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) } return ( diff --git a/components/budget/categoryItem.tsx b/components/budget/categoryItem.tsx index 6c29e14..d64a29b 100644 --- a/components/budget/categoryItem.tsx +++ b/components/budget/categoryItem.tsx @@ -16,12 +16,12 @@ const CategoryItem = (properties: CategoryItemProps) => { const { colors } = useTheme(); - const subText = `${properties.total_expenses} / ${properties.allocated_amount} €`; + const subText = `${properties.total_expenses.toFixed(2)} / ${properties.allocated_amount} €`; return ( - + {properties.category} diff --git a/components/budget/customColorPicker.tsx b/components/budget/customColorPicker.tsx index b78e1b4..f601f12 100644 --- a/components/budget/customColorPicker.tsx +++ b/components/budget/customColorPicker.tsx @@ -2,14 +2,14 @@ import { StyleSheet } from "react-native"; import ColorPicker, { BrightnessSlider, HueSlider, Preview, SaturationSlider, } from "reanimated-color-picker"; export type CustomColorPickerProperties = { - currentColor?: string | null, + color: string, handleColorChange: (color: string) => void | undefined, } const CustomColorPicker = (properties: CustomColorPickerProperties) => { return ( { properties.handleColorChange(color["hex"]) }} @@ -30,10 +30,6 @@ const CustomColorPicker = (properties: CustomColorPickerProperties) => { ); } -const generateRandomColor = (): string => { - return '#' + Math.floor(Math.random()*16777215).toString(16); -} - export default CustomColorPicker; const styles = StyleSheet.create({ From 67cc3d3c29ae4bf74ea8c7fd8e49050bcd14ba12 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Fri, 5 Jan 2024 19:20:54 +0100 Subject: [PATCH 12/37] update ci --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a1e9c1..bb4e29f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,8 +31,9 @@ lint_job: when: manual - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH script: - - yarn install + - npm install - npx expo install + - npx expo customize tsconfig.json - npx tsc cache: policy: pull-push From 0ddfe3e606be7103ffbd76419456b1e1e3575c9c Mon Sep 17 00:00:00 2001 From: jastornig Date: Fri, 5 Jan 2024 18:41:42 +0000 Subject: [PATCH 13/37] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bb4e29f..f05e1ba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,7 +51,9 @@ build_job: when: manual script: - echo BUILD_JOB_ID=$CI_JOB_ID >> environment.env - - yarn add expo + - npm install -g sharp-cli + - npm install expo + - npx expo customize tsconfig.json - npx expo prebuild --platform android - cd android && ./gradlew assembleRelease - cd $CI_PROJECT_DIR From 497c556a8741896167c5a0480f3e9963ede5d0f3 Mon Sep 17 00:00:00 2001 From: thschleicher Date: Fri, 5 Jan 2024 20:53:47 +0100 Subject: [PATCH 14/37] removed "entered" log, fixed loading symbol, implemented category update with editing --- app/(tabs)/budget/category.tsx | 2 +- app/(tabs)/budget/editCategory.tsx | 3 ++- app/(tabs)/budget/index.tsx | 2 +- app/(tabs)/home/index.tsx | 6 ++---- components/common/loadingSymbol.tsx | 14 +++++++------- services/database.ts | 12 ++++++++++++ 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/(tabs)/budget/category.tsx b/app/(tabs)/budget/category.tsx index a58d02b..ca96d05 100644 --- a/app/(tabs)/budget/category.tsx +++ b/app/(tabs)/budget/category.tsx @@ -14,7 +14,7 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]}); const handleEditCategory = () => { - router.push({pathname: "./(tabs)/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); + router.push({pathname: "/(tabs)/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); } const handleBackButton = () => { diff --git a/app/(tabs)/budget/editCategory.tsx b/app/(tabs)/budget/editCategory.tsx index b3b8ec7..d45958d 100644 --- a/app/(tabs)/budget/editCategory.tsx +++ b/app/(tabs)/budget/editCategory.tsx @@ -2,6 +2,7 @@ import { router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native"; import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; +import { updateCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; const addCategory = () => { @@ -48,7 +49,7 @@ const addCategory = () => { router.back(); }}/> { - console.log("Implement Saving here!"); + updateCategory(category_guid.toString(), categoryName, categoryColor, selectedType, amount); router.back(); }}/> diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx index 3f2aa97..9131dc7 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/budget/index.tsx @@ -38,7 +38,7 @@ export default function Page() { }; const handleCategoryPress = (item: {[column: string]: any;}) => { - router.push({pathname: "./(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) + router.push({pathname: "/(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) } return ( diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index d57614f..8f78e74 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState, useMemo } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native'; import { Calendar } from 'react-native-calendars'; import { FlatList, RefreshControl } from 'react-native-gesture-handler'; @@ -6,9 +6,8 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { ExpenseItem, LoadingSymbol, Plus, TextInputBar, Welcome } from '../../../components'; import useFetch from '../../../hooks/useFetch'; -import { addExpense, executeQuery } from "../../../services/database"; -import { useAuth } from '../../contexts/AuthContext'; import { useRouter } from "expo-router"; +import { addExpense } from "../../../services/database"; import { SimpleDate } from '../../../util/SimpleDate'; import { useTheme } from '../../contexts/ThemeContext'; @@ -22,7 +21,6 @@ type MarkedDates = { } const constructMarkedDates = (data : {[column: string]: any}) => { - console.log("entered") let markedDates: MarkedDates = {}; data.forEach((value: any) => { const dateKey: string = String(value["expense_datetime"]).split(" ")[0] diff --git a/components/common/loadingSymbol.tsx b/components/common/loadingSymbol.tsx index b7e1a03..2769dcb 100644 --- a/components/common/loadingSymbol.tsx +++ b/components/common/loadingSymbol.tsx @@ -1,15 +1,15 @@ import React, { useEffect, useRef } from "react"; -import { StyleSheet, View, Animated, Easing, ActivityIndicator } from "react-native"; +import { ActivityIndicator, Animated, Easing, StyleSheet, View } from "react-native"; +import { useTheme } from "../../app/contexts/ThemeContext"; const LoadingSymbol = () => { - const color = ["blue", "red", "purple", "green", "yellow", "orange"]; - const random = Math.floor(Math.random() * color.length); + const {colors} = useTheme(); const spinValue = useRef(new Animated.Value(0)).current; const spin = spinValue.interpolate({ inputRange: [0, 1], - outputRange: ["0deg", "360deg"], + outputRange: [0, 360], }); useEffect(() => { @@ -25,7 +25,6 @@ const LoadingSymbol = () => { const styles = StyleSheet.create({ container: { - backgroundColor: color[random], width: "100%", height: "100%", position: "absolute", @@ -35,13 +34,14 @@ const LoadingSymbol = () => { loader: { width: 100, height: 100, - transform: [{ rotate: spin }], + transform: [{ rotate: spin + "deg"}], + zIndex: 999, }, }); return ( - + ); }; diff --git a/services/database.ts b/services/database.ts index cbee2b1..7fe3921 100644 --- a/services/database.ts +++ b/services/database.ts @@ -24,6 +24,18 @@ export const initDatabase = async () => { } }; +export const updateCategory = async (guid: string, name: string, color: string, type: string, allocated_amount: number) => { + + try { + await db.transactionAsync(async (tx) => { + await tx.executeSqlAsync("UPDATE category SET name = ?, color = ?, type = ?, allocated_amount = ? WHERE guid = ?", [name, color, type, allocated_amount, guid]); + }); + } catch (error) { + console.log("Error updating category: ", error); + throw error; + } +}; + export const addCategory = async (name: string, color: string, type: string, allocated_amount: number) => { //needs user input validation for type and color (should be validated by this function) From d7bb318c24b482d2818bab7e2963e75c1f0f2f6e Mon Sep 17 00:00:00 2001 From: Thomas Schleicher Date: Fri, 5 Jan 2024 19:56:55 +0000 Subject: [PATCH 15/37] Resolve "implement updating a category" --- app/(tabs)/budget/category.tsx | 2 +- app/(tabs)/budget/editCategory.tsx | 3 ++- app/(tabs)/budget/index.tsx | 2 +- app/(tabs)/home/index.tsx | 6 ++---- components/common/loadingSymbol.tsx | 14 +++++++------- services/database.ts | 12 ++++++++++++ 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/(tabs)/budget/category.tsx b/app/(tabs)/budget/category.tsx index a58d02b..ca96d05 100644 --- a/app/(tabs)/budget/category.tsx +++ b/app/(tabs)/budget/category.tsx @@ -14,7 +14,7 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]}); const handleEditCategory = () => { - router.push({pathname: "./(tabs)/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); + router.push({pathname: "/(tabs)/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); } const handleBackButton = () => { diff --git a/app/(tabs)/budget/editCategory.tsx b/app/(tabs)/budget/editCategory.tsx index b3b8ec7..d45958d 100644 --- a/app/(tabs)/budget/editCategory.tsx +++ b/app/(tabs)/budget/editCategory.tsx @@ -2,6 +2,7 @@ import { router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native"; import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; +import { updateCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; const addCategory = () => { @@ -48,7 +49,7 @@ const addCategory = () => { router.back(); }}/> { - console.log("Implement Saving here!"); + updateCategory(category_guid.toString(), categoryName, categoryColor, selectedType, amount); router.back(); }}/> diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx index 3f2aa97..9131dc7 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/budget/index.tsx @@ -38,7 +38,7 @@ export default function Page() { }; const handleCategoryPress = (item: {[column: string]: any;}) => { - router.push({pathname: "./(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) + router.push({pathname: "/(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) } return ( diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index d57614f..8f78e74 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState, useMemo } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native'; import { Calendar } from 'react-native-calendars'; import { FlatList, RefreshControl } from 'react-native-gesture-handler'; @@ -6,9 +6,8 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { ExpenseItem, LoadingSymbol, Plus, TextInputBar, Welcome } from '../../../components'; import useFetch from '../../../hooks/useFetch'; -import { addExpense, executeQuery } from "../../../services/database"; -import { useAuth } from '../../contexts/AuthContext'; import { useRouter } from "expo-router"; +import { addExpense } from "../../../services/database"; import { SimpleDate } from '../../../util/SimpleDate'; import { useTheme } from '../../contexts/ThemeContext'; @@ -22,7 +21,6 @@ type MarkedDates = { } const constructMarkedDates = (data : {[column: string]: any}) => { - console.log("entered") let markedDates: MarkedDates = {}; data.forEach((value: any) => { const dateKey: string = String(value["expense_datetime"]).split(" ")[0] diff --git a/components/common/loadingSymbol.tsx b/components/common/loadingSymbol.tsx index b7e1a03..2769dcb 100644 --- a/components/common/loadingSymbol.tsx +++ b/components/common/loadingSymbol.tsx @@ -1,15 +1,15 @@ import React, { useEffect, useRef } from "react"; -import { StyleSheet, View, Animated, Easing, ActivityIndicator } from "react-native"; +import { ActivityIndicator, Animated, Easing, StyleSheet, View } from "react-native"; +import { useTheme } from "../../app/contexts/ThemeContext"; const LoadingSymbol = () => { - const color = ["blue", "red", "purple", "green", "yellow", "orange"]; - const random = Math.floor(Math.random() * color.length); + const {colors} = useTheme(); const spinValue = useRef(new Animated.Value(0)).current; const spin = spinValue.interpolate({ inputRange: [0, 1], - outputRange: ["0deg", "360deg"], + outputRange: [0, 360], }); useEffect(() => { @@ -25,7 +25,6 @@ const LoadingSymbol = () => { const styles = StyleSheet.create({ container: { - backgroundColor: color[random], width: "100%", height: "100%", position: "absolute", @@ -35,13 +34,14 @@ const LoadingSymbol = () => { loader: { width: 100, height: 100, - transform: [{ rotate: spin }], + transform: [{ rotate: spin + "deg"}], + zIndex: 999, }, }); return ( - + ); }; diff --git a/services/database.ts b/services/database.ts index cbee2b1..7fe3921 100644 --- a/services/database.ts +++ b/services/database.ts @@ -24,6 +24,18 @@ export const initDatabase = async () => { } }; +export const updateCategory = async (guid: string, name: string, color: string, type: string, allocated_amount: number) => { + + try { + await db.transactionAsync(async (tx) => { + await tx.executeSqlAsync("UPDATE category SET name = ?, color = ?, type = ?, allocated_amount = ? WHERE guid = ?", [name, color, type, allocated_amount, guid]); + }); + } catch (error) { + console.log("Error updating category: ", error); + throw error; + } +}; + export const addCategory = async (name: string, color: string, type: string, allocated_amount: number) => { //needs user input validation for type and color (should be validated by this function) From 7dd17798c44f33a53a89971589d148f1d34bab41 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Fri, 5 Jan 2024 21:33:13 +0100 Subject: [PATCH 16/37] fix last issues --- app/(tabs)/budget/addCategory.tsx | 9 +++++---- app/(tabs)/budget/category.tsx | 10 +++++++--- app/(tabs)/budget/editCategory.tsx | 2 +- app/(tabs)/budget/index.tsx | 2 +- app/(tabs)/home/expense/[expense].tsx | 5 ++++- app/(tabs)/home/expense/_layout.tsx | 3 ++- components/common/loadingSymbol.tsx | 6 ------ 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/app/(tabs)/budget/addCategory.tsx b/app/(tabs)/budget/addCategory.tsx index 984d98e..bdc1a86 100644 --- a/app/(tabs)/budget/addCategory.tsx +++ b/app/(tabs)/budget/addCategory.tsx @@ -1,6 +1,7 @@ import { router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; -import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native"; +import { StyleSheet, Text, TextInput, View } from "react-native"; +import { SafeAreaView } from 'react-native-safe-area-context'; import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; import { addCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; @@ -10,18 +11,18 @@ export default function Page() { const parameters = useLocalSearchParams(); - const [categoryName, setCategoryName] = useState("Enter Category Name..."); + const [categoryName, setCategoryName] = useState(""); const [categoryColor, setCategoryColor] = useState('#' + Math.floor(Math.random()*16777215).toString(16)); const [selectedType, setSelectedType] = useState("expense"); const [amount, setAmount] = useState(0); return ( - + Add Category - { + { setCategoryName(newName); }}/> diff --git a/app/(tabs)/budget/category.tsx b/app/(tabs)/budget/category.tsx index ca96d05..3a88f32 100644 --- a/app/(tabs)/budget/category.tsx +++ b/app/(tabs)/budget/category.tsx @@ -1,11 +1,14 @@ import { FontAwesome } from "@expo/vector-icons"; -import { router, useLocalSearchParams } from "expo-router"; -import { FlatList, SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from "react-native"; +import { useRouter, useLocalSearchParams } from "expo-router"; +import { FlatList, StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { ExpenseItem, LoadingSymbol, TextInputBar } from "../../../components"; + import useFetch from "../../../hooks/useFetch"; import { useTheme } from "../../contexts/ThemeContext"; +import { SafeAreaView } from "react-native-safe-area-context"; export default function Page() { + const router = useRouter(); const {colors} = useTheme(); const {category_guid} = useLocalSearchParams(); @@ -14,7 +17,7 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]}); const handleEditCategory = () => { - router.push({pathname: "/(tabs)/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); + router.push({pathname: "/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); } const handleBackButton = () => { @@ -45,6 +48,7 @@ export default function Page() { title={item.expense_name} date={item.expense_datetime} value={item.expense_amount} + onPress={()=>router.push(`/home/expense/${item.guid}`)} />} keyExtractor={item => item.guid} ItemSeparatorComponent={() => { diff --git a/app/(tabs)/budget/editCategory.tsx b/app/(tabs)/budget/editCategory.tsx index d45958d..60705fa 100644 --- a/app/(tabs)/budget/editCategory.tsx +++ b/app/(tabs)/budget/editCategory.tsx @@ -20,7 +20,7 @@ const addCategory = () => { - { + { setCategoryName(newName); }}/> diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx index 9131dc7..6615367 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/budget/index.tsx @@ -24,7 +24,7 @@ export default function Page() { }) }, []); - const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM expense e RIGHT JOIN category c ON e.category_guid = c.guid WHERE c.type = ? GROUP BY c.guid", args: selectedPage === "expenses" ? ["expense"] : selectedPage === "savings" ? ["saving"] : []}); + const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM category c LEFT JOIN expense e ON e.category_guid = c.guid WHERE c.type = ? GROUP BY c.guid", args: selectedPage === "expenses" ? ["expense"] : selectedPage === "savings" ? ["saving"] : []}); useEffect(() => { reFetch(); diff --git a/app/(tabs)/home/expense/[expense].tsx b/app/(tabs)/home/expense/[expense].tsx index 99a6e73..53e38ae 100644 --- a/app/(tabs)/home/expense/[expense].tsx +++ b/app/(tabs)/home/expense/[expense].tsx @@ -1,6 +1,6 @@ import { View, Text, Alert, StyleSheet } from 'react-native' import React, { useEffect, useState } from 'react' -import { router, useLocalSearchParams } from 'expo-router' +import { router, useLocalSearchParams, useRouter } from 'expo-router' import useFetch from '../../../../hooks/useFetch'; import { Category, Expense } from '../../../../types/dbItems'; import { CategorySelectorModal, AutoDecimalInput, CategorySelector, TextInputBar, DateSelectorButton, RoundedButton } from '../../../../components'; @@ -12,6 +12,7 @@ import { SIZES } from '../../../../constants/theme'; import { useTheme } from '../../../contexts/ThemeContext'; export default function Page() { + const router = useRouter(); const {colors} = useTheme(); const {expense} = useLocalSearchParams(); const {data, isEmptyResult} = useFetch({sql: "SELECT e.guid as e_guid, e.name as e_name, e.datetime as e_datetime, e.amount as e_amount, c.guid as c_guid, c.name as c_name, c.color as c_color FROM expense e INNER JOIN category c on e.category_guid = c.guid WHERE e.guid = ?", args: [expense]}); @@ -24,6 +25,8 @@ export default function Page() { const [datePickerShown, setDatePickerShown] = useState(false); const [selectedDate, setSelectedDate] = useState(new Date()); + + console.log(router.canGoBack()) useEffect(()=>{ const entry = data[0]; console.log(entry) diff --git a/app/(tabs)/home/expense/_layout.tsx b/app/(tabs)/home/expense/_layout.tsx index 211509f..ef1ee81 100644 --- a/app/(tabs)/home/expense/_layout.tsx +++ b/app/(tabs)/home/expense/_layout.tsx @@ -21,11 +21,12 @@ const _layout = () => { String(Date.now())} diff --git a/components/common/loadingSymbol.tsx b/components/common/loadingSymbol.tsx index 2769dcb..757749c 100644 --- a/components/common/loadingSymbol.tsx +++ b/components/common/loadingSymbol.tsx @@ -7,11 +7,6 @@ const LoadingSymbol = () => { const spinValue = useRef(new Animated.Value(0)).current; - const spin = spinValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, 360], - }); - useEffect(() => { Animated.loop( Animated.timing(spinValue, { @@ -34,7 +29,6 @@ const LoadingSymbol = () => { loader: { width: 100, height: 100, - transform: [{ rotate: spin + "deg"}], zIndex: 999, }, }); From 457b0988834ba17820a55dcfae1fde43fbebb74b Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Fri, 5 Jan 2024 22:44:21 +0100 Subject: [PATCH 17/37] fix: category search input cannot be cleared --- components/home/addItem/CategorySelectorModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/home/addItem/CategorySelectorModal.tsx b/components/home/addItem/CategorySelectorModal.tsx index d758669..26f3a94 100644 --- a/components/home/addItem/CategorySelectorModal.tsx +++ b/components/home/addItem/CategorySelectorModal.tsx @@ -49,7 +49,7 @@ const CategorySelectorModal: React.FC = (props : Cat {selectMulitple ? "Categories" : "Category"} - + item.guid!} From 1beee68bffe6719623cfd07be14051966de4576a Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Sat, 20 Jan 2024 11:54:25 +0100 Subject: [PATCH 18/37] Major refactoring: navigation does not break anymore. the user can now navigate between the tabs without loosing context --- app/(tabs)/{budget => (budget)}/_layout.tsx | 0 .../{budget => (budget)}/addCategory.tsx | 0 app/(tabs)/{budget => (budget)}/category.tsx | 19 ++++++++--- .../{budget => (budget)}/editCategory.tsx | 0 app/(tabs)/{budget => (budget)}/index.tsx | 13 +++++--- app/(tabs)/{home => (home)}/_layout.tsx | 3 -- app/(tabs)/{home => (home)}/index.tsx | 32 ++++++++++++++----- app/(tabs)/{home => (home)}/userSettings.tsx | 0 app/(tabs)/{stats => (stats)}/index.tsx | 0 app/(tabs)/_layout.tsx | 11 ++----- app/_layout.tsx | 31 ++++++++++++++---- app/{(tabs)/home => }/expense/[expense].tsx | 21 ++++++------ app/{(tabs)/home => }/expense/_layout.tsx | 2 +- app/{(tabs)/home => }/expense/new.tsx | 17 +++++----- app/index.tsx | 2 +- components/budget/budgetHeader.tsx | 3 +- components/common/CustomCard.tsx | 1 - .../addItem => expense}/CategorySelector.tsx | 6 ++-- .../CategorySelectorModal.tsx | 12 +++---- .../DateSelectorButton.tsx | 6 ++-- components/index.tsx | 6 ++-- hooks/useFetch.ts | 29 ++++++++++++++--- services/database.ts | 3 +- 23 files changed, 137 insertions(+), 80 deletions(-) rename app/(tabs)/{budget => (budget)}/_layout.tsx (100%) rename app/(tabs)/{budget => (budget)}/addCategory.tsx (100%) rename app/(tabs)/{budget => (budget)}/category.tsx (84%) rename app/(tabs)/{budget => (budget)}/editCategory.tsx (100%) rename app/(tabs)/{budget => (budget)}/index.tsx (88%) rename app/(tabs)/{home => (home)}/_layout.tsx (90%) rename app/(tabs)/{home => (home)}/index.tsx (83%) rename app/(tabs)/{home => (home)}/userSettings.tsx (100%) rename app/(tabs)/{stats => (stats)}/index.tsx (100%) rename app/{(tabs)/home => }/expense/[expense].tsx (91%) rename app/{(tabs)/home => }/expense/_layout.tsx (93%) rename app/{(tabs)/home => }/expense/new.tsx (89%) rename components/{home/addItem => expense}/CategorySelector.tsx (89%) rename components/{home/addItem => expense}/CategorySelectorModal.tsx (89%) rename components/{home/addItem => expense}/DateSelectorButton.tsx (87%) diff --git a/app/(tabs)/budget/_layout.tsx b/app/(tabs)/(budget)/_layout.tsx similarity index 100% rename from app/(tabs)/budget/_layout.tsx rename to app/(tabs)/(budget)/_layout.tsx diff --git a/app/(tabs)/budget/addCategory.tsx b/app/(tabs)/(budget)/addCategory.tsx similarity index 100% rename from app/(tabs)/budget/addCategory.tsx rename to app/(tabs)/(budget)/addCategory.tsx diff --git a/app/(tabs)/budget/category.tsx b/app/(tabs)/(budget)/category.tsx similarity index 84% rename from app/(tabs)/budget/category.tsx rename to app/(tabs)/(budget)/category.tsx index 3a88f32..c2e3ea9 100644 --- a/app/(tabs)/budget/category.tsx +++ b/app/(tabs)/(budget)/category.tsx @@ -6,9 +6,12 @@ import { ExpenseItem, LoadingSymbol, TextInputBar } from "../../../components"; import useFetch from "../../../hooks/useFetch"; import { useTheme } from "../../contexts/ThemeContext"; import { SafeAreaView } from "react-native-safe-area-context"; +import { useNavigation } from "expo-router/src/useNavigation"; +import { useEffect } from "react"; export default function Page() { const router = useRouter(); + const navigation = useNavigation(); const {colors} = useTheme(); const {category_guid} = useLocalSearchParams(); @@ -17,13 +20,21 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]}); const handleEditCategory = () => { - router.push({pathname: "/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); + router.push({pathname: "/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); } const handleBackButton = () => { router.back(); } + useEffect(() => { + console.log("useEffect called") + const unsubscribe = navigation.addListener("focus", () => { + reFetch(); + }) + return unsubscribe; + }, [navigation]) + return ( @@ -40,7 +51,7 @@ export default function Page() { {isLoading ? () : ( - router.push(`/home/expense/${item.guid}`)} + onPress={()=>router.push(`/expense/${item.expense_guid}`)} />} - keyExtractor={item => item.guid} + keyExtractor={item => item.expense_guid} ItemSeparatorComponent={() => { return (); }} diff --git a/app/(tabs)/budget/editCategory.tsx b/app/(tabs)/(budget)/editCategory.tsx similarity index 100% rename from app/(tabs)/budget/editCategory.tsx rename to app/(tabs)/(budget)/editCategory.tsx diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/(budget)/index.tsx similarity index 88% rename from app/(tabs)/budget/index.tsx rename to app/(tabs)/(budget)/index.tsx index 6615367..b03fc12 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/(budget)/index.tsx @@ -1,5 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import { router } from 'expo-router'; +import { router, useNavigation } from 'expo-router'; import { useEffect, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; @@ -11,7 +11,7 @@ import { useTheme } from '../../contexts/ThemeContext'; export default function Page() { const {colors} = useTheme() const containerColor = colors.containerColor; - + const navigation = useNavigation(); const [selectedPage, setSelectedPage] = useState("noPageLoaded"); useEffect(() => { @@ -27,9 +27,10 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM category c LEFT JOIN expense e ON e.category_guid = c.guid WHERE c.type = ? GROUP BY c.guid", args: selectedPage === "expenses" ? ["expense"] : selectedPage === "savings" ? ["saving"] : []}); useEffect(() => { - reFetch(); + reFetch() }, [selectedPage]); + const handlePageSelection = (page: string) => { if(page !== selectedPage) { setSelectedPage(page); @@ -38,15 +39,16 @@ export default function Page() { }; const handleCategoryPress = (item: {[column: string]: any;}) => { - router.push({pathname: "/(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) + router.push({pathname: "/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) } return ( + { - router.push({pathname: '/(tabs)/budget/addCategory'}); + router.push({pathname: '/addCategory'}); }}/> {isLoading ? () : ( @@ -67,6 +69,7 @@ export default function Page() { }} /> )} + ); } diff --git a/app/(tabs)/home/_layout.tsx b/app/(tabs)/(home)/_layout.tsx similarity index 90% rename from app/(tabs)/home/_layout.tsx rename to app/(tabs)/(home)/_layout.tsx index ba7964c..8e16222 100644 --- a/app/(tabs)/home/_layout.tsx +++ b/app/(tabs)/(home)/_layout.tsx @@ -24,9 +24,6 @@ export default function _Layout() { title: "test", headerShown: false, }}/> - { export default function Page() { const { colors, theme } = useTheme() - - + const navigation = useNavigation(); const router = useRouter(); const [plusShow, setPlusShow] = useState(true); const prevOffset = useRef(0); @@ -62,17 +62,24 @@ export default function Page() { } const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, c.guid AS category_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color, c.type AS category_type FROM expense e JOIN category c ON e.category_guid = c.guid ORDER BY expense_datetime desc;", args: []}); - + const expenseDates = useMemo(()=> constructMarkedDates(data) , [data]) + useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + console.log("focus event triggered") + reFetch(); + }); + return unsubscribe; + }, [navigation]); return ( {plusShow && { - router.push("/(tabs)/home/expense/new"); + router.push("/expense/new"); // executeQuery({sql: "SELECT guid FROM category", args: []}).then((result) => { // if("rows" in result[0]) { @@ -89,7 +96,7 @@ export default function Page() { data={data} ListHeaderComponent={ <> - {router.push("/home/userSettings")}}/> + {router.push("/userSettings")}}/> } - renderItem = {({item}) => {router.push(`/(tabs)/home/expense/${guid}`)}}/>} - keyExtractor={item => item.expense_guid} + renderItem = {({item}) => + {router.push(`/expense/${guid}`)}} + />} + keyExtractor={item => item.expense_guid } ItemSeparatorComponent={() => { return (); }} diff --git a/app/(tabs)/home/userSettings.tsx b/app/(tabs)/(home)/userSettings.tsx similarity index 100% rename from app/(tabs)/home/userSettings.tsx rename to app/(tabs)/(home)/userSettings.tsx diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/(stats)/index.tsx similarity index 100% rename from app/(tabs)/stats/index.tsx rename to app/(tabs)/(stats)/index.tsx diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 79ff582..4c74a39 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -35,30 +35,25 @@ export default function Layout() { return ( - ( ), - unmountOnBlur: true, - href: "(tabs)/budget" } }/> - ( ), - unmountOnBlur: true, - href: "(tabs)/home/" } }/> - ( ), - unmountOnBlur: true, } }/> diff --git a/app/_layout.tsx b/app/_layout.tsx index f34a4ea..e99a541 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,20 +1,37 @@ -import { Slot } from 'expo-router'; +import { SplashScreen, Stack } from 'expo-router'; import React, { useEffect } from 'react'; import { addCategory, initDatabase } from '../services/database'; import { AuthProvider } from './contexts/AuthContext'; import { ThemeProvider } from './contexts/ThemeContext'; +import { useTheme } from './contexts/ThemeContext'; + export default function _layout() { - + const {colors} = useTheme(); useEffect(() => { initDatabase(); + }, []); + console.log(colors.backgroundColor) return ( - - - - - + + + + + + + + ) } \ No newline at end of file diff --git a/app/(tabs)/home/expense/[expense].tsx b/app/expense/[expense].tsx similarity index 91% rename from app/(tabs)/home/expense/[expense].tsx rename to app/expense/[expense].tsx index 53e38ae..1eb15ff 100644 --- a/app/(tabs)/home/expense/[expense].tsx +++ b/app/expense/[expense].tsx @@ -1,15 +1,15 @@ import { View, Text, Alert, StyleSheet } from 'react-native' import React, { useEffect, useState } from 'react' import { router, useLocalSearchParams, useRouter } from 'expo-router' -import useFetch from '../../../../hooks/useFetch'; -import { Category, Expense } from '../../../../types/dbItems'; -import { CategorySelectorModal, AutoDecimalInput, CategorySelector, TextInputBar, DateSelectorButton, RoundedButton } from '../../../../components'; -import colors from '../../../../constants/colors'; -import { addExpense, deleteExpense, executeQuery, updateExpense } from '../../../../services/database'; -import { SimpleDate } from '../../../../util/SimpleDate'; +import useFetch from '../../hooks/useFetch'; +import { Category, Expense } from '../../types/dbItems'; +import { CategorySelectorModal, AutoDecimalInput, CategorySelector, TextInputBar, DateSelectorButton, RoundedButton } from '../../components'; +import colors from '../../constants/colors'; +import { addExpense, deleteExpense, executeQuery, updateExpense } from '../../services/database'; +import { SimpleDate } from '../../util/SimpleDate'; import DateTimePicker from '@react-native-community/datetimepicker'; -import { SIZES } from '../../../../constants/theme'; -import { useTheme } from '../../../contexts/ThemeContext'; +import { SIZES } from '../../constants/theme'; +import { useTheme } from '../contexts/ThemeContext'; export default function Page() { const router = useRouter(); @@ -25,8 +25,6 @@ export default function Page() { const [datePickerShown, setDatePickerShown] = useState(false); const [selectedDate, setSelectedDate] = useState(new Date()); - - console.log(router.canGoBack()) useEffect(()=>{ const entry = data[0]; console.log(entry) @@ -69,8 +67,7 @@ export default function Page() { await updateExpense(selectedExpense!.guid!, expenseName, selectedCategory!.guid!, new SimpleDate(selectedDate).format("YYYY-MM-DD"), Number(formatedValue)) } if(validateInput()){ - insert(); - router.back(); + insert().then( () => router.back()) }else { Alert.alert("Invalid input", "One of the Props is not properly defined") } diff --git a/app/(tabs)/home/expense/_layout.tsx b/app/expense/_layout.tsx similarity index 93% rename from app/(tabs)/home/expense/_layout.tsx rename to app/expense/_layout.tsx index ef1ee81..f867c8a 100644 --- a/app/(tabs)/home/expense/_layout.tsx +++ b/app/expense/_layout.tsx @@ -1,7 +1,7 @@ import { StyleSheet, Text, View } from 'react-native' import { Stack } from 'expo-router' import React from 'react' -import { useTheme } from '../../../contexts/ThemeContext' +import { useTheme } from '../contexts/ThemeContext' const _layout = () => { const {colors} = useTheme(); diff --git a/app/(tabs)/home/expense/new.tsx b/app/expense/new.tsx similarity index 89% rename from app/(tabs)/home/expense/new.tsx rename to app/expense/new.tsx index f2525c2..7619489 100644 --- a/app/(tabs)/home/expense/new.tsx +++ b/app/expense/new.tsx @@ -1,12 +1,12 @@ import { View, Text, StyleSheet, Alert } from 'react-native' import React, { useRef, useState } from 'react' -import { SIZES } from '../../../../constants/theme' -import { useTheme } from '../../../contexts/ThemeContext' -import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../../../components' -import { Category } from '../../../../types/dbItems' +import { SIZES } from '../../constants/theme' +import { useTheme } from '../contexts/ThemeContext' +import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../components' +import { Category } from '../../types/dbItems' import DateTimePicker from '@react-native-community/datetimepicker'; -import { addExpense } from '../../../../services/database' -import { SimpleDate } from '../../../../util/SimpleDate' +import { addExpense } from '../../services/database' +import { SimpleDate } from '../../util/SimpleDate' import { useRouter } from 'expo-router' export default function AddItem() { @@ -41,8 +41,9 @@ export default function AddItem() { await addExpense(expenseName, selectedCategory?.guid!, new SimpleDate(selectedDate).format("YYYY-MM-DD"), Number(formatedValue)) } if(validateInput()){ - insert(); - router.back(); + insert().then(() => { + router.back(); + }) }else { Alert.alert("Invalid input", "One of the Props is not properly defined") } diff --git a/app/index.tsx b/app/index.tsx index 265d68b..6b489fe 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -7,7 +7,7 @@ export default function index() { const {authState} = useAuth() return ( - + ) } \ No newline at end of file diff --git a/components/budget/budgetHeader.tsx b/components/budget/budgetHeader.tsx index bca2e9b..54df035 100644 --- a/components/budget/budgetHeader.tsx +++ b/components/budget/budgetHeader.tsx @@ -77,12 +77,11 @@ const styles = StyleSheet.create({ containerStyle: { flexDirection: "row", justifyContent: "space-evenly", - marginHorizontal: 20, marginBottom: 20, + marginHorizontal: 10, marginTop: 10, }, searchBarStyle: { marginBottom: 20, - marginHorizontal: 10, } }); \ No newline at end of file diff --git a/components/common/CustomCard.tsx b/components/common/CustomCard.tsx index a866d8c..87aae81 100644 --- a/components/common/CustomCard.tsx +++ b/components/common/CustomCard.tsx @@ -40,7 +40,6 @@ export default function CustomCard(props : ViewProps) { const styles = StyleSheet.create({ container:{ borderRadius: 20, - marginHorizontal: 10, }, boxShadow: {}, }) \ No newline at end of file diff --git a/components/home/addItem/CategorySelector.tsx b/components/expense/CategorySelector.tsx similarity index 89% rename from components/home/addItem/CategorySelector.tsx rename to components/expense/CategorySelector.tsx index 27d5cfe..59e5edf 100644 --- a/components/home/addItem/CategorySelector.tsx +++ b/components/expense/CategorySelector.tsx @@ -1,8 +1,8 @@ import { StyleSheet, Text, View, TouchableOpacity } from 'react-native' import React, { useState } from 'react' -import { useTheme } from '../../../app/contexts/ThemeContext' -import { SIZES } from '../../../constants/theme'; -import { Category } from '../../../types/dbItems'; +import { useTheme } from '../../app/contexts/ThemeContext' +import { SIZES } from '../../constants/theme'; +import { Category } from '../../types/dbItems'; import CategorySelectorModal from './CategorySelectorModal'; interface CategorySelectorProps { diff --git a/components/home/addItem/CategorySelectorModal.tsx b/components/expense/CategorySelectorModal.tsx similarity index 89% rename from components/home/addItem/CategorySelectorModal.tsx rename to components/expense/CategorySelectorModal.tsx index 26f3a94..56ba20f 100644 --- a/components/home/addItem/CategorySelectorModal.tsx +++ b/components/expense/CategorySelectorModal.tsx @@ -1,11 +1,11 @@ import { Modal, NativeSyntheticEvent, StyleSheet, Text, View, FlatList } from 'react-native' import React, { useEffect, useMemo, useState } from 'react' -import { Category } from '../../../types/dbItems'; -import { useTheme } from '../../../app/contexts/ThemeContext'; -import CategoryListItem from '../../common/CategoryListItem'; -import { SIZES } from '../../../constants/theme'; -import useFetch from '../../../hooks/useFetch'; -import TextInputBar from '../../common/TextInputBar'; +import { Category } from '../../types/dbItems'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import CategoryListItem from '../common/CategoryListItem'; +import { SIZES } from '../../constants/theme'; +import useFetch from '../../hooks/useFetch'; +import TextInputBar from '../common/TextInputBar'; interface CategorySelectorModalProps{ diff --git a/components/home/addItem/DateSelectorButton.tsx b/components/expense/DateSelectorButton.tsx similarity index 87% rename from components/home/addItem/DateSelectorButton.tsx rename to components/expense/DateSelectorButton.tsx index 4f07297..ec6916d 100644 --- a/components/home/addItem/DateSelectorButton.tsx +++ b/components/expense/DateSelectorButton.tsx @@ -1,8 +1,8 @@ import { View, Text, StyleSheet, TouchableOpacity, TouchableOpacityProps } from 'react-native' import React, { useState } from 'react' -import { useTheme } from '../../../app/contexts/ThemeContext'; -import { SIZES } from '../../../constants/theme'; -import { SimpleDate } from '../../../util/SimpleDate'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import { SIZES } from '../../constants/theme'; +import { SimpleDate } from '../../util/SimpleDate'; import { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes'; interface DateSelectorProps extends ViewProps { diff --git a/components/index.tsx b/components/index.tsx index b20c5fd..29167eb 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -4,9 +4,9 @@ import Welcome from "./home/Welcome" import ExpenseItem from "./home/expenseItem" //home/addItem -import CategorySelector from "./home/addItem/CategorySelector" -import CategorySelectorModal from "./home/addItem/CategorySelectorModal" -import DateSelectorButton from "./home/addItem/DateSelectorButton" +import CategorySelector from "./expense/CategorySelector" +import CategorySelectorModal from "./expense/CategorySelectorModal" +import DateSelectorButton from "./expense/DateSelectorButton" //common import CustomCard from "./common/CustomCard" diff --git a/hooks/useFetch.ts b/hooks/useFetch.ts index cec238d..e089750 100644 --- a/hooks/useFetch.ts +++ b/hooks/useFetch.ts @@ -4,11 +4,31 @@ import { executeQuery } from "../services/database"; const useFetch = (query: Query) => { - const [isLoading, setIsLoading] = useState(false); - const [data, setData] = useState<{[column: string]: any;}[]>([]); - const [isEmptyResult, setIsEmptyResult] = useState(undefined); + const [fetchState, setFetchState] = useState<{ + data: {[column: string]: any;}[]; + isLoading: boolean; + isEmptyResult: boolean | undefined; + }>({ + data: [], + isLoading: false, + isEmptyResult: undefined + }); + + const setIsLoading = (isLoading: boolean) => { + setFetchState((prevState) => ( {...prevState, isLoading} )); + } + + const setData = (data: {[column: string]: any;}[]) => { + setFetchState((prevState) => ( {...prevState, data} )); + } + + const setIsEmptyResult = (isEmptyResult: boolean) => { + setFetchState((prevState) => ( {...prevState, isEmptyResult} )); + } + const reFetch = () => { + console.log("refetch called") setIsLoading(true); executeQuery(query).then((result) => { if("rows" in result[0]) { @@ -16,6 +36,7 @@ const useFetch = (query: Query) => { if(result[0]["rows"].length == 0){ setIsEmptyResult(true); } + console.log("len", result[0]["rows"].length) } }).catch((error: any) => { console.error("Fetching data from database has failed: ", error); @@ -28,7 +49,7 @@ const useFetch = (query: Query) => { reFetch(); }, []) - return {data, isLoading, reFetch, isEmptyResult}; + return {...fetchState, reFetch}; } export default useFetch; \ No newline at end of file diff --git a/services/database.ts b/services/database.ts index 7fe3921..ec5f656 100644 --- a/services/database.ts +++ b/services/database.ts @@ -57,7 +57,7 @@ export const addCategory = async (name: string, color: string, type: string, all export const updateExpense = async (guid: string, name: string, category_guid: string, datetime: string, amount: number) => { //needs user input validation for type and color (should be validated by this function) - + console.log("update expense called") try { await db.transactionAsync(async (tx) => { await tx.executeSqlAsync("UPDATE expense SET name = ?, category_guid = ?, datetime = ?, amount = ? WHERE guid = ?", [name, category_guid, datetime, amount, guid] @@ -67,6 +67,7 @@ export const updateExpense = async (guid: string, name: string, category_guid: s console.log("Error updating expense: ", error); throw error; } + console.log("update expense finished") }; export const addExpense = async (name: string, category_guid: string, datetime: string, amount: number) => { From bc3d243ffcfb2c4752b38aeee0719d41cecdf9df Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Sat, 20 Jan 2024 17:01:08 +0100 Subject: [PATCH 19/37] feat: filter functionality on Home screen Long press on any day to remove filter --- app/(tabs)/(home)/index.tsx | 138 +++++++++++++++++++++-------- app/expense/new.tsx | 2 +- components/common/TextInputBar.tsx | 4 +- 3 files changed, 107 insertions(+), 37 deletions(-) diff --git a/app/(tabs)/(home)/index.tsx b/app/(tabs)/(home)/index.tsx index 72bbe6c..84f0c97 100644 --- a/app/(tabs)/(home)/index.tsx +++ b/app/(tabs)/(home)/index.tsx @@ -11,16 +11,30 @@ import { addExpense } from "../../../services/database"; import { SimpleDate } from '../../../util/SimpleDate'; import { useTheme } from '../../contexts/ThemeContext'; import { useNavigation } from 'expo-router'; +import { Category, Expense } from '../../../types/dbItems'; interface MarkingProps { dots?:{color:string, selectedColor?:string, key?:string}[]; + selected?: boolean; + selectedColor?: string; } type MarkedDates = { [key: string]: MarkingProps; } +interface ExpenseEntry extends Expense { + category_name?: string; + color?: string; +} + +type Filters = { + search?: string; + month?: string; + day?: string; +} + const constructMarkedDates = (data : {[column: string]: any}) => { let markedDates: MarkedDates = {}; data.forEach((value: any) => { @@ -41,7 +55,8 @@ export default function Page() { const router = useRouter(); const [plusShow, setPlusShow] = useState(true); const prevOffset = useRef(0); - + + const [filter, setFilter] = useState({}) const profile = require("../../../assets/images/profile.jpg") @@ -53,20 +68,55 @@ export default function Page() { setPlusShow(isScrollingUp || isTop) } - const newExpense = async (title: string, category_guid: string, date: string, amount: number) => { - try { - await addExpense(title, category_guid, date, amount); - } catch (error: any) { - console.error("Adding new expense has failed: ", error); - } - } - const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, c.guid AS category_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color, c.type AS category_type FROM expense e JOIN category c ON e.category_guid = c.guid ORDER BY expense_datetime desc;", args: []}); - const expenseDates = useMemo(()=> - constructMarkedDates(data) - , [data]) - + const expenses = useMemo( + () => { + console.log("expenses updated") + return data.map((elem) => { + return { + guid: elem["expense_guid"], + name: elem["expense_name"], + amount: elem["expense_amount"], + dateTime: elem["expense_datetime"], + category_name: elem["category_name"], + color: elem["category_color"]} + }) + }, [data]) + + const filteredExpenses = useMemo( + () => { + console.log("filter called") + return expenses.filter((elem) => { + if(filter.month && filter.month.substring(5, 7) !== elem.dateTime?.substring(5, 7)){ + return false; + } + if(filter.day && filter.day.substring(8, 10) !== elem.dateTime?.substring(8, 10)){ + return false; + } + if(filter.search && !elem.name?.toLowerCase().includes(filter.search.toLowerCase())){ + return false; + } + return true; + }) + }, [expenses, filter] + ) + + const expenseDates = useMemo( + () => { + let markedDates = constructMarkedDates(data) + if(filter.day) { + const dateKey: string = String(filter.day).split(" ")[0] + if(markedDates[dateKey] === undefined){ + markedDates[dateKey] = {} + } + markedDates[dateKey].selected = true; + markedDates[dateKey].selectedColor = colors.accentColor; + } + return markedDates; + } + , [data, filter.day]) + useEffect(() => { const unsubscribe = navigation.addListener('focus', () => { console.log("focus event triggered") @@ -75,7 +125,17 @@ export default function Page() { return unsubscribe; }, [navigation]); + const hanldeDaySelect = (date: string | undefined) => { + setFilter({...filter, day: date}); + } + const handleMonthSelect = (date: string | undefined) => { + setFilter({...filter, month: date, day: undefined}); + } + const handleSearch = (text: string) => { + setFilter({...filter, search: text}); + } + console.log("rendered") return ( {plusShow && { @@ -90,41 +150,49 @@ export default function Page() { // }) }}/>} - {isLoading && } + {/* {isLoading && } */} {router.push("/userSettings")}}/> - + }} + markingType='multi-dot' + markedDates={expenseDates} + onDayPress={(date) => { + hanldeDaySelect(date.dateString) + }} + onDayLongPress={(date) => { + hanldeDaySelect(undefined) + }} + onMonthChange={(date) => { + handleMonthSelect(date.dateString) + }} + /> - - + handleSearch(text)} style={{marginBottom: 20}}> } renderItem = {({item}) => {router.push(`/expense/${guid}`)}} />} - keyExtractor={item => item.expense_guid } + keyExtractor={item => item.guid!} ItemSeparatorComponent={() => { return (); }} diff --git a/app/expense/new.tsx b/app/expense/new.tsx index 7619489..007258e 100644 --- a/app/expense/new.tsx +++ b/app/expense/new.tsx @@ -55,7 +55,7 @@ export default function AddItem() { {setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}> {setSelecorModalVisible(true)}} selectedCategory={selectedCategory}/> - setExpenseName(text)}/> + setExpenseName(text)}/> {setDatePickerShown(true)}}/> {datePickerShown && setIsactive(false)}/> {isActive && - {handleChange("")}}> + { + console.log("cancel") + handleChange("")}}> } From 4ddc76faa4f084337719a4ab2eed57f1eb3827dd Mon Sep 17 00:00:00 2001 From: thschleicher Date: Thu, 25 Jan 2024 15:38:50 +0100 Subject: [PATCH 20/37] fixed euro sign on expense item (dont judge me I know this isnt the correct branch) and fixed the async storage so it acctually loads on the info --- app/(tabs)/(budget)/index.tsx | 7 ++++--- components/home/expenseItem.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/(tabs)/(budget)/index.tsx b/app/(tabs)/(budget)/index.tsx index b03fc12..22f61dd 100644 --- a/app/(tabs)/(budget)/index.tsx +++ b/app/(tabs)/(budget)/index.tsx @@ -1,4 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import { useIsFocused } from '@react-navigation/native'; import { router, useNavigation } from 'expo-router'; import { useEffect, useState } from 'react'; import { StyleSheet, View } from 'react-native'; @@ -13,6 +14,7 @@ export default function Page() { const containerColor = colors.containerColor; const navigation = useNavigation(); const [selectedPage, setSelectedPage] = useState("noPageLoaded"); + const isFocused = useIsFocused(); useEffect(() => { AsyncStorage.getItem("currentBudgetPage").then((page) => { @@ -22,15 +24,14 @@ export default function Page() { }).catch((error) => { console.log("Error fetching previous page from Async Storage:", error); }) - }, []); + }, [isFocused]); const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM category c LEFT JOIN expense e ON e.category_guid = c.guid WHERE c.type = ? GROUP BY c.guid", args: selectedPage === "expenses" ? ["expense"] : selectedPage === "savings" ? ["saving"] : []}); useEffect(() => { - reFetch() + reFetch(); }, [selectedPage]); - const handlePageSelection = (page: string) => { if(page !== selectedPage) { setSelectedPage(page); diff --git a/components/home/expenseItem.tsx b/components/home/expenseItem.tsx index ff6eb2a..fd00c7f 100644 --- a/components/home/expenseItem.tsx +++ b/components/home/expenseItem.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { ColorValue, StyleSheet, Text, View } from 'react-native'; +import { TouchableOpacity } from 'react-native-gesture-handler'; +import { useTheme } from '../../app/contexts/ThemeContext'; import { SIZES } from '../../constants/theme'; import CustomCard from "../common/CustomCard"; -import { useTheme } from '../../app/contexts/ThemeContext'; -import { TouchableOpacity } from 'react-native-gesture-handler'; //export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} interface ExpenseItemProps { @@ -44,9 +44,10 @@ export default function ExpenseItem(itemProps : ExpenseItemProps) { {itemProps.value} + }} numberOfLines={1}>{itemProps.value + " €"} From 7fecf8c5d95b7fb3e4fb3289a85b5683a886ebce Mon Sep 17 00:00:00 2001 From: thschleicher Date: Thu, 25 Jan 2024 16:06:52 +0100 Subject: [PATCH 21/37] implemented delete button and fixed routing interaction with db --- app/(tabs)/(budget)/editCategory.tsx | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/app/(tabs)/(budget)/editCategory.tsx b/app/(tabs)/(budget)/editCategory.tsx index 60705fa..369667b 100644 --- a/app/(tabs)/(budget)/editCategory.tsx +++ b/app/(tabs)/(budget)/editCategory.tsx @@ -2,7 +2,8 @@ import { router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native"; import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; -import { updateCategory } from "../../../services/database"; +import useFetch from "../../../hooks/useFetch"; +import { deleteCategory, deleteExpense, updateCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; const addCategory = () => { @@ -14,6 +15,8 @@ const addCategory = () => { const [selectedType, setSelectedType] = useState(category_type.toString()); const [amount, setAmount] = useState(Number.parseFloat(category_amount.toString())); + const {data} = useFetch({sql: "SELECT * FROM expense WHERE category_guid = ?", args: [category_guid.toString()]}); + return ( Edit Category @@ -44,13 +47,27 @@ const addCategory = () => { }}/> + + { + for (let i = 0; i < data.length;) { + deleteExpense(data[i].guid).then(() => { + i++ + }); + } + deleteCategory(category_guid.toString()).then(() => { + router.push("/(tabs)/(budget)"); + }); + }}/> + + { router.back(); }}/> { - updateCategory(category_guid.toString(), categoryName, categoryColor, selectedType, amount); - router.back(); + updateCategory(category_guid.toString(), categoryName, categoryColor, selectedType, amount).then(() => { + router.back(); + }); }}/> @@ -61,6 +78,9 @@ const addCategory = () => { export default addCategory; const styles = StyleSheet.create({ + deleteButtonView: { + + }, containerStyle: { flex: 1, margin: 10, From c46eec06a588700de1dde29a644a516c590f46fc Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Thu, 25 Jan 2024 16:11:04 +0100 Subject: [PATCH 22/37] Fix: refresh --- app/(tabs)/(budget)/addCategory.tsx | 3 +-- app/(tabs)/(budget)/index.tsx | 18 +++++++++++++----- app/(tabs)/_layout.tsx | 2 +- hooks/useFetch.ts | 2 -- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/(tabs)/(budget)/addCategory.tsx b/app/(tabs)/(budget)/addCategory.tsx index bdc1a86..a6c53b6 100644 --- a/app/(tabs)/(budget)/addCategory.tsx +++ b/app/(tabs)/(budget)/addCategory.tsx @@ -51,8 +51,7 @@ export default function Page() { router.back(); }}/> { - addCategory(categoryName, categoryColor, selectedType, amount); - router.back(); + addCategory(categoryName, categoryColor, selectedType, amount).then(() => router.back()); }}/> diff --git a/app/(tabs)/(budget)/index.tsx b/app/(tabs)/(budget)/index.tsx index 22f61dd..0e42958 100644 --- a/app/(tabs)/(budget)/index.tsx +++ b/app/(tabs)/(budget)/index.tsx @@ -1,20 +1,19 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import { useIsFocused } from '@react-navigation/native'; import { router, useNavigation } from 'expo-router'; -import { useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { SafeAreaView } from 'react-native-safe-area-context'; import { BudgetHeader, CategoryItem, LoadingSymbol, Plus } from '../../../components'; import useFetch from '../../../hooks/useFetch'; import { useTheme } from '../../contexts/ThemeContext'; +import { useFocusEffect } from 'expo-router/src/useFocusEffect'; export default function Page() { const {colors} = useTheme() const containerColor = colors.containerColor; const navigation = useNavigation(); const [selectedPage, setSelectedPage] = useState("noPageLoaded"); - const isFocused = useIsFocused(); useEffect(() => { AsyncStorage.getItem("currentBudgetPage").then((page) => { @@ -24,14 +23,23 @@ export default function Page() { }).catch((error) => { console.log("Error fetching previous page from Async Storage:", error); }) - }, [isFocused]); + }, []); const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM category c LEFT JOIN expense e ON e.category_guid = c.guid WHERE c.type = ? GROUP BY c.guid", args: selectedPage === "expenses" ? ["expense"] : selectedPage === "savings" ? ["saving"] : []}); useEffect(() => { - reFetch(); + reFetch() }, [selectedPage]); + useEffect(() => { + const unsubscribe = navigation.addListener("focus", () => { + reFetch(); + }) + return unsubscribe; + + }, [navigation]) + + const handlePageSelection = (page: string) => { if(page !== selectedPage) { setSelectedPage(page); diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 4c74a39..103e5ac 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -34,7 +34,7 @@ export default function Layout() { } return ( - + { const reFetch = () => { - console.log("refetch called") setIsLoading(true); executeQuery(query).then((result) => { if("rows" in result[0]) { @@ -36,7 +35,6 @@ const useFetch = (query: Query) => { if(result[0]["rows"].length == 0){ setIsEmptyResult(true); } - console.log("len", result[0]["rows"].length) } }).catch((error: any) => { console.error("Fetching data from database has failed: ", error); From 716a41cffd22f5bf3abbe7f5f98b5916247502db Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Thu, 25 Jan 2024 16:42:43 +0100 Subject: [PATCH 23/37] feat: filter by name category --- app/(tabs)/(budget)/index.tsx | 18 ++++++++++++++---- components/budget/budgetHeader.tsx | 1 - 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/(tabs)/(budget)/index.tsx b/app/(tabs)/(budget)/index.tsx index 0e42958..e2da8ed 100644 --- a/app/(tabs)/(budget)/index.tsx +++ b/app/(tabs)/(budget)/index.tsx @@ -1,10 +1,10 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { router, useNavigation } from 'expo-router'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { BudgetHeader, CategoryItem, LoadingSymbol, Plus } from '../../../components'; +import { BudgetHeader, CategoryItem, LoadingSymbol, Plus, TextInputBar } from '../../../components'; import useFetch from '../../../hooks/useFetch'; import { useTheme } from '../../contexts/ThemeContext'; import { useFocusEffect } from 'expo-router/src/useFocusEffect'; @@ -14,8 +14,12 @@ export default function Page() { const containerColor = colors.containerColor; const navigation = useNavigation(); const [selectedPage, setSelectedPage] = useState("noPageLoaded"); + const [searchString, setSearchString] = useState(""); + + useEffect(() => { + console.log("initial effect called") AsyncStorage.getItem("currentBudgetPage").then((page) => { if(page === "expenses" || page === "savings") { setSelectedPage(page); @@ -36,7 +40,6 @@ export default function Page() { reFetch(); }) return unsubscribe; - }, [navigation]) @@ -51,10 +54,17 @@ export default function Page() { router.push({pathname: "/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) } + const filteredData = useMemo(() => { + return data.filter((item) => { + return item.category_name.toLowerCase().includes(searchString.toLowerCase()); + }) + }, [data, searchString]); + return ( + { router.push({pathname: '/addCategory'}); @@ -62,7 +72,7 @@ export default function Page() { {isLoading ? () : ( { }} /> - ); } From ab2ef25615380b85e9f2d558cdea3c24c515e3ee Mon Sep 17 00:00:00 2001 From: thschleicher Date: Thu, 25 Jan 2024 17:20:56 +0100 Subject: [PATCH 24/37] first attempt at graph --- app/(tabs)/(stats)/index.tsx | 18 ++-- components/index.tsx | 33 +++---- components/stats/Graph.tsx | 65 ++++++++++++++ components/stats/PieChart.tsx | 1 - package-lock.json | 164 +++++++++++++++++++++++++++++++++- package.json | 5 +- 6 files changed, 250 insertions(+), 36 deletions(-) create mode 100644 components/stats/Graph.tsx delete mode 100644 components/stats/PieChart.tsx diff --git a/app/(tabs)/(stats)/index.tsx b/app/(tabs)/(stats)/index.tsx index cac1e04..d92ebf4 100644 --- a/app/(tabs)/(stats)/index.tsx +++ b/app/(tabs)/(stats)/index.tsx @@ -1,13 +1,7 @@ import React from 'react'; -import { StyleSheet, View, ScrollView } from 'react-native'; -import BudgetOverview from '../../../components/stats/BudgetOverview'; +import { ScrollView, StyleSheet, View } from 'react-native'; +import { Graph } from '../../../components'; import { useTheme } from '../../contexts/ThemeContext'; -import Widget from '../../../components/stats/Widget'; -import CategoryProgressBarList from '../../../components/stats/CategoryProgressBarList'; -import BudgetRemaining from '../../../components/stats/BudgetRemaining'; -import DebugMenu from '../../../services/DebugMenu'; -import SavingsOverview from '../../../components/stats/SavingsOverview'; -import FinancialAdvice from '../../../components/stats/FinancialAdvice'; export default function Page() { const { colors } = useTheme(); @@ -32,7 +26,11 @@ export default function Page() { return ( - + + + + + {/* @@ -46,7 +44,7 @@ export default function Page() { - + */} ); diff --git a/components/index.tsx b/components/index.tsx index 29167eb..a2b1a50 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -9,13 +9,13 @@ import CategorySelectorModal from "./expense/CategorySelectorModal" import DateSelectorButton from "./expense/DateSelectorButton" //common +import AutoDecimalInput from "./common/AutoDecimalInput" import CustomCard from "./common/CustomCard" +import RoundedButton from "./common/RoundedButton" +import TextInputBar from "./common/TextInputBar" import NavigationButton from "./common/button" import LoadingSymbol from "./common/loadingSymbol" import Plus from "./common/plus" -import TextInputBar from "./common/TextInputBar" -import AutoDecimalInput from "./common/AutoDecimalInput" -import RoundedButton from "./common/RoundedButton" //login import BudgetHeader from "./budget/budgetHeader" @@ -26,25 +26,14 @@ import TypeSelectorSwitch from "./budget/typeSelectorSwitch" //login import Input from "./login/input" +//stats +import Graph from "./stats/Graph" + export { - BudgetHeader, - ButtonSetting, - CustomCard, - ExpenseItem, - Input, - LoadingSymbol, - Plus, - TextInputBar, - ToggleSetting, - Welcome, - AutoDecimalInput, - CategorySelector, - CategorySelectorModal, - DateSelectorButton, - RoundedButton, - CategoryItem, - TypeSelectorSwitch, - NavigationButton, - CustomColorPicker + AutoDecimalInput, BudgetHeader, + ButtonSetting, CategoryItem, CategorySelector, + CategorySelectorModal, CustomCard, CustomColorPicker, DateSelectorButton, ExpenseItem, Graph, Input, + LoadingSymbol, NavigationButton, Plus, RoundedButton, TextInputBar, + ToggleSetting, TypeSelectorSwitch, Welcome } diff --git a/components/stats/Graph.tsx b/components/stats/Graph.tsx new file mode 100644 index 0000000..42e083f --- /dev/null +++ b/components/stats/Graph.tsx @@ -0,0 +1,65 @@ +import { View } from "react-native"; +import { PieChart } from "react-native-chart-kit"; + +const Graph = () => { + + // const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.name AS name, c.color AS color, SUM(e.amount) as total FROM category c LEFT JOIN expense e ON e.category_guid = c.guid GROUP BY c.guid", args: []}); + + // useEffect(() => { + // console.log(data); + // }, [data]) + + const data = [ + { + name: "Seoul", + population: 21500000, + color: "rgba(131, 167, 234, 1)", + legendFontColor: "#7F7F7F", + legendFontSize: 15 + }, + { + name: "Toronto", + population: 2800000, + color: "#F00", + legendFontColor: "#7F7F7F", + legendFontSize: 15 + }, + { + name: "Beijing", + population: 527612, + color: "red", + legendFontColor: "#7F7F7F", + legendFontSize: 15 + }, + { + name: "New York", + population: 8538000, + color: "#ffffff", + legendFontColor: "#7F7F7F", + legendFontSize: 15 + }, + { + name: "Moscow", + population: 11920000, + color: "rgb(0, 0, 255)", + legendFontColor: "#7F7F7F", + legendFontSize: 15 + } + ]; + + + return ( + + + + ); +} + +export default Graph; \ No newline at end of file diff --git a/components/stats/PieChart.tsx b/components/stats/PieChart.tsx deleted file mode 100644 index f3fe67e..0000000 --- a/components/stats/PieChart.tsx +++ /dev/null @@ -1 +0,0 @@ -//honestly just fuck graphs \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aab706f..b72a41b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "react-dom": "18.2.0", "react-native": "0.72.6", "react-native-calendars": "^1.1303.0", + "react-native-chart-kit": "^6.12.0", "react-native-gesture-handler": "~2.12.0", "react-native-reanimated": "^3.3.0", "react-native-safe-area-context": "4.6.3", @@ -7410,6 +7411,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "peer": true + }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -8233,6 +8240,47 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "peer": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "peer": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -8522,6 +8570,32 @@ "node": ">=8" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "peer": true + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -8535,6 +8609,35 @@ "node": ">=12" } }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "peer": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -8598,7 +8701,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "engines": { "node": ">=0.12" }, @@ -13484,6 +13586,12 @@ "resolved": "https://registry.npmjs.org/md5hex/-/md5hex-1.0.0.tgz", "integrity": "sha512-c2YOUbp33+6thdCUi34xIyOU/a7bvGKj/3DB1iaPMTuPHf/Q2d5s4sn1FaCOO43XkXggnb08y5W2PU8UNYNLKQ==" }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "peer": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -14687,6 +14795,18 @@ "node": ">=4" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -15048,6 +15168,14 @@ "node": ">=8" } }, + "node_modules/paths-js": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/paths-js/-/paths-js-0.4.11.tgz", + "integrity": "sha512-3mqcLomDBXOo7Fo+UlaenG6f71bk1ZezPQy2JCmYHy2W2k5VKpP+Jbin9H0bjXynelTbglCqdFhSEkeIkKTYUA==", + "engines": { + "node": ">=0.11.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -15248,6 +15376,11 @@ "node": ">=4.0.0" } }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -15770,6 +15903,21 @@ "moment": "^2.29.4" } }, + "node_modules/react-native-chart-kit": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/react-native-chart-kit/-/react-native-chart-kit-6.12.0.tgz", + "integrity": "sha512-nZLGyCFzZ7zmX0KjYeeSV1HKuPhl1wOMlTAqa0JhlyW62qV/1ZPXHgT8o9s8mkFaGxdqbspOeuaa6I9jUQDgnA==", + "dependencies": { + "lodash": "^4.17.13", + "paths-js": "^0.4.10", + "point-in-polygon": "^1.0.1" + }, + "peerDependencies": { + "react": "> 16.7.0", + "react-native": ">= 0.50.0", + "react-native-svg": "> 6.4.1" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.1.tgz", @@ -15829,6 +15977,20 @@ "react-native": "*" } }, + "node_modules/react-native-svg": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-14.1.0.tgz", + "integrity": "sha512-HeseElmEk+AXGwFZl3h56s0LtYD9HyGdrpg8yd9QM26X+d7kjETrRQ9vCjtxuT5dCZEIQ5uggU1dQhzasnsCWA==", + "peer": true, + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-swipe-gestures": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz", diff --git a/package.json b/package.json index 2e627b2..6203ad9 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@expo/vector-icons": "^13.0.0", "@react-native-async-storage/async-storage": "1.18.2", + "@react-native-community/datetimepicker": "7.2.0", "@react-navigation/native": "^6.0.2", "expo": "~49.0.15", "expo-font": "~11.4.0", @@ -35,14 +36,14 @@ "react-dom": "18.2.0", "react-native": "0.72.6", "react-native-calendars": "^1.1303.0", + "react-native-chart-kit": "^6.12.0", "react-native-gesture-handler": "~2.12.0", "react-native-reanimated": "^3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", "react-native-uuid": "^2.0.1", "react-native-web": "~0.19.6", - "reanimated-color-picker": "^2.4.2", - "@react-native-community/datetimepicker": "7.2.0" + "reanimated-color-picker": "^2.4.2" }, "devDependencies": { "@babel/core": "^7.20.0", From 84dbae49c1a2a99d5e1280e8cdb5c71dfc7ea130 Mon Sep 17 00:00:00 2001 From: thschleicher Date: Thu, 25 Jan 2024 17:33:02 +0100 Subject: [PATCH 25/37] graph working kinda --- components/stats/Graph.tsx | 77 ++++++++++++-------------------------- 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/components/stats/Graph.tsx b/components/stats/Graph.tsx index 42e083f..33ebd2d 100644 --- a/components/stats/Graph.tsx +++ b/components/stats/Graph.tsx @@ -1,64 +1,35 @@ -import { View } from "react-native"; +import { useEffect } from "react"; import { PieChart } from "react-native-chart-kit"; +import useFetch from "../../hooks/useFetch"; const Graph = () => { - // const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.name AS name, c.color AS color, SUM(e.amount) as total FROM category c LEFT JOIN expense e ON e.category_guid = c.guid GROUP BY c.guid", args: []}); - - // useEffect(() => { - // console.log(data); - // }, [data]) - - const data = [ - { - name: "Seoul", - population: 21500000, - color: "rgba(131, 167, 234, 1)", - legendFontColor: "#7F7F7F", - legendFontSize: 15 - }, - { - name: "Toronto", - population: 2800000, - color: "#F00", - legendFontColor: "#7F7F7F", - legendFontSize: 15 - }, - { - name: "Beijing", - population: 527612, - color: "red", - legendFontColor: "#7F7F7F", - legendFontSize: 15 - }, - { - name: "New York", - population: 8538000, - color: "#ffffff", - legendFontColor: "#7F7F7F", - legendFontSize: 15 - }, - { - name: "Moscow", - population: 11920000, - color: "rgb(0, 0, 255)", - legendFontColor: "#7F7F7F", - legendFontSize: 15 - } - ]; - + const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.name AS name, c.color AS color, SUM(e.amount) as total FROM category c LEFT JOIN expense e ON e.category_guid = c.guid GROUP BY c.guid", args: []}); + useEffect(() => { + console.log(data); + }, [data]) + return ( - `rgba(255, 255, 255, ${opacity})`, + style: { + borderRadius: 16 + } + }} + accessor="total" + backgroundColor="red" + paddingLeft="15" + absolute /> - ); } From 4b06a217c52f07a02409f664b18333628517cfc6 Mon Sep 17 00:00:00 2001 From: thschleicher Date: Thu, 25 Jan 2024 18:39:16 +0100 Subject: [PATCH 26/37] graph works --- app/(tabs)/(stats)/index.tsx | 29 ++++------------------------- components/stats/Graph.tsx | 30 ++++++++++++------------------ 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/app/(tabs)/(stats)/index.tsx b/app/(tabs)/(stats)/index.tsx index d92ebf4..fc96d1a 100644 --- a/app/(tabs)/(stats)/index.tsx +++ b/app/(tabs)/(stats)/index.tsx @@ -1,33 +1,13 @@ import React from 'react'; -import { ScrollView, StyleSheet, View } from 'react-native'; +import { SafeAreaView } from 'react-native'; import { Graph } from '../../../components'; -import { useTheme } from '../../contexts/ThemeContext'; export default function Page() { - const { colors } = useTheme(); - - // Mock data #TODO Database einbinden - // what to do when amount too small? - - const spent = 120.75; - const budget = 696.96; - - - - const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.backgroundColor, - marginTop: 50, - alignItems: 'center', - }, - }); return ( - - + - + {/* @@ -45,7 +25,6 @@ export default function Page() { */} - - + ); } diff --git a/components/stats/Graph.tsx b/components/stats/Graph.tsx index 33ebd2d..6d737d8 100644 --- a/components/stats/Graph.tsx +++ b/components/stats/Graph.tsx @@ -1,35 +1,29 @@ -import { useEffect } from "react"; +import { Dimensions, View } from "react-native"; import { PieChart } from "react-native-chart-kit"; +import { useTheme } from "../../app/contexts/ThemeContext"; import useFetch from "../../hooks/useFetch"; const Graph = () => { + const {colors} = useTheme(); - const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.name AS name, c.color AS color, SUM(e.amount) as total FROM category c LEFT JOIN expense e ON e.category_guid = c.guid GROUP BY c.guid", args: []}); - - useEffect(() => { - console.log(data); - }, [data]) + const {data} = useFetch({sql: "SELECT c.name AS name, c.color AS color, SUM(e.amount) as total FROM category c LEFT JOIN expense e ON e.category_guid = c.guid GROUP BY c.guid", args: []}); + const acctual_data = data.map(item => ({...item, legendFontColor: colors.primaryText, legendFontSize: 14})); return ( + `rgba(255, 255, 255, ${opacity})`, - style: { - borderRadius: 16 - } }} + backgroundColor="transparent" accessor="total" - backgroundColor="red" paddingLeft="15" - absolute + avoidFalseZero={true} /> + ); } From 0e22e63ce945863f0f3f05adb431998d66ce9576 Mon Sep 17 00:00:00 2001 From: thschleicher Date: Thu, 25 Jan 2024 18:49:24 +0100 Subject: [PATCH 27/37] graph works + unmount on blur --- app/(tabs)/(stats)/index.tsx | 30 ++++++++++++------------------ app/(tabs)/_layout.tsx | 5 +++-- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/app/(tabs)/(stats)/index.tsx b/app/(tabs)/(stats)/index.tsx index fc96d1a..6a6c0c9 100644 --- a/app/(tabs)/(stats)/index.tsx +++ b/app/(tabs)/(stats)/index.tsx @@ -1,30 +1,24 @@ import React from 'react'; import { SafeAreaView } from 'react-native'; import { Graph } from '../../../components'; +import BudgetOverview from '../../../components/stats/BudgetOverview'; +import BudgetRemaining from '../../../components/stats/BudgetRemaining'; +import SavingsOverview from '../../../components/stats/SavingsOverview'; +import Widget from '../../../components/stats/Widget'; export default function Page() { return ( - + - - - {/* - - - - - - - - - - - - - - */} + + + + + + + ); } diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 103e5ac..62d5907 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -3,9 +3,9 @@ import { StyleSheet } from "react-native"; import { FontAwesome } from "@expo/vector-icons"; import { Redirect } from "expo-router"; -import React, { useEffect } from "react"; -import { useTheme } from "../contexts/ThemeContext"; +import React from "react"; import { useAuth } from "../contexts/AuthContext"; +import { useTheme } from "../contexts/ThemeContext"; export default function Layout() { const {authState} = useAuth() @@ -52,6 +52,7 @@ export default function Layout() { ( ), } From 8149ec234f06eea369bb1586e2d74d2f02be3d08 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Thu, 25 Jan 2024 19:18:15 +0100 Subject: [PATCH 28/37] A few changes: Auto fill amount category edit Empty list component calendar filtering in category screen, expenses can be added directly --- app/(tabs)/(budget)/category.tsx | 5 +- app/(tabs)/(budget)/editCategory.tsx | 8 ++-- app/(tabs)/(budget)/index.tsx | 49 ++++++++++++-------- app/(tabs)/(home)/index.tsx | 11 +++-- app/expense/new.tsx | 22 +++++++-- components/budget/budgetHeader.tsx | 10 ++-- components/common/EmptyListCompenent.tsx | 24 ++++++++++ components/common/TextInputBar.tsx | 2 +- components/expense/CategorySelectorModal.tsx | 4 +- components/index.tsx | 4 +- 10 files changed, 97 insertions(+), 42 deletions(-) create mode 100644 components/common/EmptyListCompenent.tsx diff --git a/app/(tabs)/(budget)/category.tsx b/app/(tabs)/(budget)/category.tsx index c2e3ea9..93d0374 100644 --- a/app/(tabs)/(budget)/category.tsx +++ b/app/(tabs)/(budget)/category.tsx @@ -1,8 +1,7 @@ import { FontAwesome } from "@expo/vector-icons"; import { useRouter, useLocalSearchParams } from "expo-router"; import { FlatList, StyleSheet, Text, TouchableOpacity, View } from "react-native"; -import { ExpenseItem, LoadingSymbol, TextInputBar } from "../../../components"; - +import { ExpenseItem, LoadingSymbol, TextInputBar, EmptyListCompenent, Plus } from "../../../components"; import useFetch from "../../../hooks/useFetch"; import { useTheme } from "../../contexts/ThemeContext"; import { SafeAreaView } from "react-native-safe-area-context"; @@ -37,6 +36,7 @@ export default function Page() { return ( + router.push(`/expense/new?category=${category_guid}`)}/> Back @@ -65,6 +65,7 @@ export default function Page() { ItemSeparatorComponent={() => { return (); }} + ListEmptyComponent={EmptyListCompenent} /> )} diff --git a/app/(tabs)/(budget)/editCategory.tsx b/app/(tabs)/(budget)/editCategory.tsx index 369667b..e0b2706 100644 --- a/app/(tabs)/(budget)/editCategory.tsx +++ b/app/(tabs)/(budget)/editCategory.tsx @@ -1,10 +1,11 @@ import { router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; -import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native"; -import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; +import { StyleSheet, Text, TextInput, View } from "react-native"; +import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch, Plus } from "../../../components"; import useFetch from "../../../hooks/useFetch"; import { deleteCategory, deleteExpense, updateCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; +import { SafeAreaView } from 'react-native-safe-area-context'; const addCategory = () => { const {colors} = useTheme(); @@ -20,7 +21,6 @@ const addCategory = () => { return ( Edit Category - { @@ -29,7 +29,7 @@ const addCategory = () => { - { + { setAmount(!Number.isNaN(Number.parseFloat(value)) ? Number.parseFloat(value) : 0); }}/> diff --git a/app/(tabs)/(budget)/index.tsx b/app/(tabs)/(budget)/index.tsx index e2da8ed..9b38322 100644 --- a/app/(tabs)/(budget)/index.tsx +++ b/app/(tabs)/(budget)/index.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { BudgetHeader, CategoryItem, LoadingSymbol, Plus, TextInputBar } from '../../../components'; +import { BudgetHeader, CategoryItem, EmptyListCompenent, LoadingSymbol, Plus, TextInputBar } from '../../../components'; import useFetch from '../../../hooks/useFetch'; import { useTheme } from '../../contexts/ThemeContext'; import { useFocusEffect } from 'expo-router/src/useFocusEffect'; @@ -13,41 +13,45 @@ export default function Page() { const {colors} = useTheme() const containerColor = colors.containerColor; const navigation = useNavigation(); - const [selectedPage, setSelectedPage] = useState("noPageLoaded"); + const [selectedPage, setSelectedPage] = useState<"expense"|"saving">("expense"); const [searchString, setSearchString] = useState(""); - useEffect(() => { - console.log("initial effect called") - AsyncStorage.getItem("currentBudgetPage").then((page) => { - if(page === "expenses" || page === "savings") { - setSelectedPage(page); - } - }).catch((error) => { - console.log("Error fetching previous page from Async Storage:", error); - }) - }, []); + // useEffect(() => { + // console.log("initial effect called") + // AsyncStorage.getItem("currentBudgetPage").then((page) => { + // if(page === "expenses" || page === "savings") { + // setSelectedPage(page); + // } + // }).catch((error) => { + // console.log("Error fetching previous page from Async Storage:", error); + // }) + // }, []); - const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM category c LEFT JOIN expense e ON e.category_guid = c.guid WHERE c.type = ? GROUP BY c.guid", args: selectedPage === "expenses" ? ["expense"] : selectedPage === "savings" ? ["saving"] : []}); + const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM category c LEFT JOIN expense e ON e.category_guid = c.guid GROUP BY c.guid", args: []}); useEffect(() => { reFetch() }, [selectedPage]); + useEffect(() => { const unsubscribe = navigation.addListener("focus", () => { + reFetch(); }) - return unsubscribe; + + const t = () => { + console.log("unsubscribed") + unsubscribe(); + } + return t; }, [navigation]) - const handlePageSelection = (page: string) => { - if(page !== selectedPage) { - setSelectedPage(page); - AsyncStorage.setItem("currentBudgetPage", page); - } + const handlePageSelection = (page: "expense" | "saving") => { + setSelectedPage(page); }; const handleCategoryPress = (item: {[column: string]: any;}) => { @@ -56,10 +60,14 @@ export default function Page() { const filteredData = useMemo(() => { return data.filter((item) => { + if(item.category_type !== selectedPage) { + return false + } return item.category_name.toLowerCase().includes(searchString.toLowerCase()); }) - }, [data, searchString]); + }, [data, searchString, selectedPage]); + console.log(selectedPage) return ( @@ -86,6 +94,7 @@ export default function Page() { ItemSeparatorComponent={() => { return (); }} + ListEmptyComponent={EmptyListCompenent} /> )} diff --git a/app/(tabs)/(home)/index.tsx b/app/(tabs)/(home)/index.tsx index 84f0c97..f76b36a 100644 --- a/app/(tabs)/(home)/index.tsx +++ b/app/(tabs)/(home)/index.tsx @@ -3,7 +3,7 @@ import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react import { Calendar } from 'react-native-calendars'; import { FlatList, RefreshControl } from 'react-native-gesture-handler'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { ExpenseItem, LoadingSymbol, Plus, TextInputBar, Welcome } from '../../../components'; +import { EmptyListCompenent, ExpenseItem, LoadingSymbol, Plus, TextInputBar, Welcome } from '../../../components'; import useFetch from '../../../hooks/useFetch'; import { useRouter } from "expo-router"; @@ -126,7 +126,10 @@ export default function Page() { }, [navigation]); const hanldeDaySelect = (date: string | undefined) => { - setFilter({...filter, day: date}); + if(filter.day === date) + setFilter({...filter, day: undefined}); + else + setFilter({...filter, day: date}); } const handleMonthSelect = (date: string | undefined) => { setFilter({...filter, month: date, day: undefined}); @@ -171,9 +174,6 @@ export default function Page() { onDayPress={(date) => { hanldeDaySelect(date.dateString) }} - onDayLongPress={(date) => { - hanldeDaySelect(undefined) - }} onMonthChange={(date) => { handleMonthSelect(date.dateString) }} @@ -201,6 +201,7 @@ export default function Page() { } onScroll={handleScroll} scrollEventThrottle={20} + ListEmptyComponent={EmptyListCompenent} /> ); diff --git a/app/expense/new.tsx b/app/expense/new.tsx index 007258e..d0456e7 100644 --- a/app/expense/new.tsx +++ b/app/expense/new.tsx @@ -1,18 +1,20 @@ import { View, Text, StyleSheet, Alert } from 'react-native' -import React, { useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { SIZES } from '../../constants/theme' import { useTheme } from '../contexts/ThemeContext' import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../components' import { Category } from '../../types/dbItems' import DateTimePicker from '@react-native-community/datetimepicker'; -import { addExpense } from '../../services/database' +import { addExpense, executeQuery } from '../../services/database' import { SimpleDate } from '../../util/SimpleDate' -import { useRouter } from 'expo-router' +import { useLocalSearchParams, useRouter } from 'expo-router' export default function AddItem() { const {colors} = useTheme(); const router = useRouter(); + const searchParams = useLocalSearchParams() + const [formatedValue, setFormatedValue] = useState(""); const [selectorModalVisible, setSelecorModalVisible] = useState(false); const [selectedCategory, setSelectedCategory] = useState() @@ -50,6 +52,20 @@ export default function AddItem() { } + useEffect(()=>{ + if(searchParams.category !== undefined){ + console.log(searchParams.category) + executeQuery({sql: "SELECT * FROM category WHERE guid = ?", args: [searchParams.category]}).then((result) =>{ + console.log("then") + if("rows" in result[0]){ + const category = result[0]["rows"][0]; + setSelectedCategory({name: category["name"], color: category["color"], guid: category["guid"]}) + } + //setSelectedCategory({name: category["name"], color: category["color"], guid: category["guid"]}) + }) + } + }, []) + return ( {setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}> diff --git a/components/budget/budgetHeader.tsx b/components/budget/budgetHeader.tsx index 6c8bc9d..5e65614 100644 --- a/components/budget/budgetHeader.tsx +++ b/components/budget/budgetHeader.tsx @@ -4,7 +4,7 @@ import TextInputBar from "../common/TextInputBar"; type BudgetHeaderProperties = { selectedPage: string, - handlePageSelection: (page: string) => void, + handlePageSelection: (page: "expense" | "saving") => void, } type PageSelectorButtonProperties = { @@ -21,16 +21,16 @@ const BudgetHeader = (properties: BudgetHeaderProperties) => { { - properties.handlePageSelection("expenses") + properties.handlePageSelection("expense") }} /> { - properties.handlePageSelection("savings"); + properties.handlePageSelection("saving"); }} /> diff --git a/components/common/EmptyListCompenent.tsx b/components/common/EmptyListCompenent.tsx new file mode 100644 index 0000000..c0a1746 --- /dev/null +++ b/components/common/EmptyListCompenent.tsx @@ -0,0 +1,24 @@ +import { View, Text, StyleSheet } from 'react-native' +import React from 'react' +import { useTheme } from '../../app/contexts/ThemeContext' + +const EmptyListCompenent:React.FC = () => { + const {colors} = useTheme(); + return ( + + No matching Data + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + height: 70, + borderRadius: 20, + } +}) + +export default EmptyListCompenent \ No newline at end of file diff --git a/components/common/TextInputBar.tsx b/components/common/TextInputBar.tsx index 65255fe..bfeaac8 100644 --- a/components/common/TextInputBar.tsx +++ b/components/common/TextInputBar.tsx @@ -42,7 +42,7 @@ export default function TextInputBar(props: SearchBarProps) { // changed styles.container to containerStyle return ( - setIsactive(false)}/> + (setIsactive(true))} onEndEditing={()=>setIsactive(false)}/> {isActive && { diff --git a/components/expense/CategorySelectorModal.tsx b/components/expense/CategorySelectorModal.tsx index 56ba20f..ef09fd1 100644 --- a/components/expense/CategorySelectorModal.tsx +++ b/components/expense/CategorySelectorModal.tsx @@ -6,6 +6,7 @@ import CategoryListItem from '../common/CategoryListItem'; import { SIZES } from '../../constants/theme'; import useFetch from '../../hooks/useFetch'; import TextInputBar from '../common/TextInputBar'; +import EmptyListCompenent from '../common/EmptyListCompenent'; interface CategorySelectorModalProps{ @@ -43,7 +44,7 @@ const CategorySelectorModal: React.FC = (props : Cat }, [visible]) return ( - + @@ -57,6 +58,7 @@ const CategorySelectorModal: React.FC = (props : Cat ItemSeparatorComponent={() => } ListFooterComponent={() => } keyboardShouldPersistTaps="always" + ListEmptyComponent={EmptyListCompenent} > diff --git a/components/index.tsx b/components/index.tsx index 29167eb..92ba31c 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -16,6 +16,7 @@ import Plus from "./common/plus" import TextInputBar from "./common/TextInputBar" import AutoDecimalInput from "./common/AutoDecimalInput" import RoundedButton from "./common/RoundedButton" +import EmptyListCompenent from "./common/EmptyListCompenent" //login import BudgetHeader from "./budget/budgetHeader" @@ -45,6 +46,7 @@ export { CategoryItem, TypeSelectorSwitch, NavigationButton, - CustomColorPicker + CustomColorPicker, + EmptyListCompenent, } From 114ad9281894ce91540219128522c5731d9a80df Mon Sep 17 00:00:00 2001 From: Tocuro Date: Mon, 8 Jan 2024 10:39:19 +0100 Subject: [PATCH 29/37] ?? --- package-lock.json | 563 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 463 insertions(+), 100 deletions(-) diff --git a/package-lock.json b/package-lock.json index b72a41b..3c97807 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,19 +78,19 @@ } }, "node_modules/@babel/core": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", - "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.6", + "@babel/helpers": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", + "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -158,9 +158,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.6.tgz", - "integrity": "sha512-cBXU1vZni/CpGF29iTu4YRbOZt3Wat6zCoMDxRF1MayiEc4URxOj31tT65HUM0CRpMowA3HCJaAOVOUnMf96cw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz", + "integrity": "sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", @@ -403,12 +403,12 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", - "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", + "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", + "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" }, "engines": { @@ -470,9 +470,9 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", - "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-plugin-utils": "^7.22.5" @@ -519,15 +519,12 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.6.tgz", - "integrity": "sha512-D7Ccq9LfkBFnow3azZGJvZYgcfeqAw3I1e5LoTpj6UKIFQilh8yqXsIGcRIqbBdsPWIz+Ze7ZZfggSj62Qp+Fg==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.7.tgz", + "integrity": "sha512-b1s5JyeMvqj7d9m9KhJNHKc18gEJiSyVzVX3bwbiPalQBQpuvfPh6lA9F7Kk/dWH0TIiXRpB9yicwijY6buPng==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.23.6", + "@babel/helper-create-class-features-plugin": "^7.23.7", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", "@babel/plugin-syntax-decorators": "^7.23.3" }, "engines": { @@ -977,9 +974,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", - "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", + "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-plugin-utils": "^7.22.5", @@ -1647,15 +1644,15 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.6.tgz", - "integrity": "sha512-kF1Zg62aPseQ11orDhFRw+aPG/eynNQtI+TyY+m33qJa2cJ5EEvza2P2BNTIA9E5MyqFABHEyY6CPHwgdy9aNg==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.7.tgz", + "integrity": "sha512-fa0hnfmiXc9fq/weK34MUV0drz2pOL/vfKWvN7Qw127hiUPabFCUMgAbYWcchRzMJit4o5ARsK/s+5h0249pLw==", "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "semver": "^6.3.1" }, "engines": { @@ -1813,9 +1810,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", - "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", + "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.23.6", @@ -1823,7 +1820,7 @@ "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -1844,7 +1841,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-generator-functions": "^7.23.7", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", "@babel/plugin-transform-block-scoping": "^7.23.4", @@ -1892,9 +1889,9 @@ "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -1953,14 +1950,14 @@ } }, "node_modules/@babel/register": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.22.15.tgz", - "integrity": "sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz", + "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==", "dependencies": { "clone-deep": "^4.0.1", "find-cache-dir": "^2.0.0", "make-dir": "^2.1.0", - "pirates": "^4.0.5", + "pirates": "^4.0.6", "source-map-support": "^0.5.16" }, "engines": { @@ -2005,9 +2002,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", - "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", + "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2029,9 +2026,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -3365,6 +3362,95 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -4277,6 +4363,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", @@ -6512,9 +6607,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", - "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -6572,9 +6667,9 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/node": { - "version": "20.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", - "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "version": "20.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.7.tgz", + "integrity": "sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==", "dependencies": { "undici-types": "~5.26.4" } @@ -6586,14 +6681,14 @@ "dev": true }, "node_modules/@types/qs": { - "version": "6.9.10", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", - "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" }, "node_modules/@types/react": { - "version": "18.2.45", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", - "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", + "version": "18.2.47", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.47.tgz", + "integrity": "sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -6700,9 +6795,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -7685,9 +7780,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001570", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", - "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", + "version": "1.0.30001576", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", + "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", "funding": [ { "type": "opencollective", @@ -8052,9 +8147,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/core-js-compat": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", - "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", "dependencies": { "browserslist": "^4.22.2" }, @@ -8654,15 +8749,20 @@ "node": ">=12" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.615", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.615.tgz", - "integrity": "sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==" + "version": "1.4.623", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.623.tgz", + "integrity": "sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==" }, "node_modules/emittery": { "version": "0.13.1", @@ -9552,6 +9652,86 @@ "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==" }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/foreground-child/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -10030,9 +10210,9 @@ } }, "node_modules/image-size": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", - "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", "dependencies": { "queue": "6.0.2" }, @@ -10040,7 +10220,7 @@ "image-size": "bin/image-size.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.x" } }, "node_modules/immediate": { @@ -10522,6 +10702,23 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -14506,9 +14703,9 @@ } }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "optional": true, "engines": { "node": "*" @@ -15160,6 +15357,37 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -15382,9 +15610,9 @@ "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "funding": [ { "type": "opencollective", @@ -15935,9 +16163,9 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.3.0.tgz", - "integrity": "sha512-LzfpPZ1qXBGy5BcUHqw3pBC0qSd22qXS3t8hWSbozXNrBkzMhhOrcILE/nEg/PHpNNp1xvGOW8NwpAMF006roQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.1.tgz", + "integrity": "sha512-F4vG9Yf9PKmE3GaWtVGUpzj3SM6YY2cx1yRHCwiMd1uY7W0gU017LfcVUorboJnj0y5QZqEriEK1Usq2Y8YZqg==", "dependencies": { "@babel/plugin-transform-object-assign": "^7.16.7", "@babel/preset-typescript": "^7.16.7", @@ -16006,9 +16234,9 @@ } }, "node_modules/react-native-web": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.9.tgz", - "integrity": "sha512-m69arZbS6FV+BNSKE6R/NQwUX+CzxCkYM7AJlSLlS8dz3BDzlaxG8Bzqtzv/r3r1YFowhnZLBXVKIwovKDw49g==", + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.10.tgz", + "integrity": "sha512-IQoHiTQq8egBCVVwmTrYcFLgEFyb4LMZYEktHn4k22JMk9+QTCEz5WTfvr+jdNoeqj/7rtE81xgowKbfGO74qg==", "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-color": "^2.1.0", @@ -16887,6 +17115,28 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -16906,6 +17156,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -16959,13 +17221,13 @@ "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==" }, "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -16976,7 +17238,15 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/sucrase/node_modules/commander": { @@ -16987,6 +17257,49 @@ "node": ">= 6" } }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/sudo-prompt": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-8.2.5.tgz", @@ -17349,9 +17662,12 @@ } }, "node_modules/traverse": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", - "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -17829,6 +18145,53 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -17875,9 +18238,9 @@ } }, "node_modules/ws": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz", - "integrity": "sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { "node": ">=10.0.0" }, From 6ca505d5050cec434e9f0022d5808bf6e2f63b5f Mon Sep 17 00:00:00 2001 From: Tocuro Date: Mon, 8 Jan 2024 10:42:31 +0100 Subject: [PATCH 30/37] updated Database.ts to use ENUM for Category Type --- services/database.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/services/database.ts b/services/database.ts index ec5f656..370108d 100644 --- a/services/database.ts +++ b/services/database.ts @@ -7,6 +7,11 @@ import { SimpleDate } from "../util/SimpleDate"; let db: SQLite.SQLiteDatabase; +export enum CategoryType { + SAVING = "saving", + EXPENSE = "expense", +} + export const initDatabase = async () => { db = SQLite.openDatabase("interactive_systeme.db"); try { @@ -24,11 +29,11 @@ export const initDatabase = async () => { } }; -export const updateCategory = async (guid: string, name: string, color: string, type: string, allocated_amount: number) => { +export const updateCategory = async (guid: string, name: string, color: string, CategoryType: CategoryType, allocated_amount: number) => { try { await db.transactionAsync(async (tx) => { - await tx.executeSqlAsync("UPDATE category SET name = ?, color = ?, type = ?, allocated_amount = ? WHERE guid = ?", [name, color, type, allocated_amount, guid]); + await tx.executeSqlAsync("UPDATE category SET name = ?, color = ?, type = ?, allocated_amount = ? WHERE guid = ?", [name, color, CategoryType, allocated_amount, guid]); }); } catch (error) { console.log("Error updating category: ", error); @@ -36,7 +41,7 @@ export const updateCategory = async (guid: string, name: string, color: string, } }; -export const addCategory = async (name: string, color: string, type: string, allocated_amount: number) => { +export const addCategory = async (name: string, color: string, CategoryType: CategoryType, allocated_amount: number) => { //needs user input validation for type and color (should be validated by this function) @@ -45,7 +50,7 @@ export const addCategory = async (name: string, color: string, type: string, all try { await db.transactionAsync(async (tx) => { await tx.executeSqlAsync("INSERT INTO category (guid, name, color, type, allocated_amount) VALUES (?, ?, ?, ?, ?);", - [UUID.toString(), name, color, type, allocated_amount] + [UUID.toString(), name, color, CategoryType, allocated_amount] ); }); } catch (error) { @@ -171,8 +176,9 @@ export const DEV_populateDatabase = async () => { for(let i=0; i < 5; i++){ let random = Math.floor(Math.random() * colors.length); - await addCategory(`Category ${i}`, colors[random], "budget", 50) - await addCategory(`Category ${i+6}`, colors[random], "budget", 50) + await addCategory(`Category ${i}`, colors[random], CategoryType.EXPENSE, 50) + random = Math.floor(Math.random() * colors.length); + await addCategory(`Category ${i+6}`, colors[random], CategoryType.SAVING, 50) } const result = await executeQuery({sql:"SELECT * from category", args:[]}) let categories: {[column: string]: any}[]; From bdc2ca22f677f1d85324c9bf19d4390705a89073 Mon Sep 17 00:00:00 2001 From: Walcher Date: Thu, 25 Jan 2024 17:23:17 +0100 Subject: [PATCH 31/37] Refactor category type to use enum --- app/(tabs)/(budget)/addCategory.tsx | 7 +++++-- components/budget/typeSelectorSwitch.tsx | 15 ++++++++------- components/stats/BudgetOverview.tsx | 5 +++-- components/stats/BudgetRemaining.tsx | 3 ++- components/stats/SavingsOverview.tsx | 5 +++-- services/database.ts | 2 +- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/(tabs)/(budget)/addCategory.tsx b/app/(tabs)/(budget)/addCategory.tsx index a6c53b6..eced44a 100644 --- a/app/(tabs)/(budget)/addCategory.tsx +++ b/app/(tabs)/(budget)/addCategory.tsx @@ -5,6 +5,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; import { addCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; +import { CategoryType } from "../../../services/database"; export default function Page() { const {colors} = useTheme(); @@ -13,7 +14,9 @@ export default function Page() { const [categoryName, setCategoryName] = useState(""); const [categoryColor, setCategoryColor] = useState('#' + Math.floor(Math.random()*16777215).toString(16)); - const [selectedType, setSelectedType] = useState("expense"); + + const [selectedType, setSelectedType] = useState(CategoryType.EXPENSE); + const [amount, setAmount] = useState(0); return ( @@ -35,7 +38,7 @@ export default function Page() { { + handleButtonPress={(type: CategoryType) => { setSelectedType(type); }} /> diff --git a/components/budget/typeSelectorSwitch.tsx b/components/budget/typeSelectorSwitch.tsx index dc6231b..a4edf58 100644 --- a/components/budget/typeSelectorSwitch.tsx +++ b/components/budget/typeSelectorSwitch.tsx @@ -1,10 +1,11 @@ import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { useTheme } from "../../app/contexts/ThemeContext"; +import { CategoryType } from "../../services/database"; export type TypeSelectorSwitchProperties = { - handleButtonPress: (type: string) => void, - currentSelected: string, + handleButtonPress: (type: CategoryType) => void, + currentSelected: CategoryType, } const TypeSelectorSwitch = (properties: TypeSelectorSwitchProperties) => { @@ -14,17 +15,17 @@ const TypeSelectorSwitch = (properties: TypeSelectorSwitchProperties) => { { - properties.handleButtonPress("expense"); + properties.handleButtonPress(CategoryType.EXPENSE); }} - style={[styles.touchableOpacityStyle, properties.currentSelected == "expense" ? {backgroundColor: colors.accentColor} : {backgroundColor: colors.elementDefaultColor}] + style={[styles.touchableOpacityStyle, properties.currentSelected == CategoryType.EXPENSE ? {backgroundColor: colors.accentColor} : {backgroundColor: colors.elementDefaultColor}] }> Expenses { - properties.handleButtonPress("saving"); + onPress={() => { + properties.handleButtonPress(CategoryType.SAVING); }} - style={[styles.touchableOpacityStyle, properties.currentSelected == "saving" ? {backgroundColor: colors.accentColor} : {backgroundColor: colors.elementDefaultColor}] + style={[styles.touchableOpacityStyle, properties.currentSelected == CategoryType.SAVING ? {backgroundColor: colors.accentColor} : {backgroundColor: colors.elementDefaultColor}] }> Savings diff --git a/components/stats/BudgetOverview.tsx b/components/stats/BudgetOverview.tsx index 05e89c4..8cc5c6c 100644 --- a/components/stats/BudgetOverview.tsx +++ b/components/stats/BudgetOverview.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; import useFetch from '../../hooks/useFetch'; +import { CategoryType } from '../../services/database'; const BudgetOverview = () => { const { colors } = useTheme(); @@ -9,12 +10,12 @@ const BudgetOverview = () => { const [budget, setBudget] = useState(0); const spentQuery = { - sql: "SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = 'budget'", + sql: `SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = '${CategoryType.EXPENSE.toString()}'`, args: [] }; const budgetQuery = { - sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'", + sql: `SELECT SUM(allocated_amount) as total FROM category WHERE type = '${CategoryType.EXPENSE.toString()}'`, args: [] }; diff --git a/components/stats/BudgetRemaining.tsx b/components/stats/BudgetRemaining.tsx index 37aa555..d5209fd 100644 --- a/components/stats/BudgetRemaining.tsx +++ b/components/stats/BudgetRemaining.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; import useFetch from '../../hooks/useFetch'; +import { CategoryType } from '../../services/database'; const styles = StyleSheet.create({ text: { @@ -23,7 +24,7 @@ const BudgetRemaining = () => { }; const budgetQuery = { - sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'", + sql: `SELECT SUM(allocated_amount) as total FROM category WHERE type = '${CategoryType.EXPENSE.toString()}'`, args: [] }; diff --git a/components/stats/SavingsOverview.tsx b/components/stats/SavingsOverview.tsx index a79234a..5162e75 100644 --- a/components/stats/SavingsOverview.tsx +++ b/components/stats/SavingsOverview.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; import useFetch from '../../hooks/useFetch'; +import { CategoryType } from '../../services/database'; const SavingsOverview = () => { const { colors } = useTheme(); @@ -9,12 +10,12 @@ const SavingsOverview = () => { const [goal, setGoal] = useState(0); const savedQuery = { - sql: "SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = 'savings'", + sql: `SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = '${CategoryType.SAVING.toString()}'`, args: [] }; const goalQuery = { - sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'", + sql: `SELECT SUM(allocated_amount) as total FROM category WHERE type = '${CategoryType.SAVING.toString()}'`, args: [] }; diff --git a/services/database.ts b/services/database.ts index 370108d..029131d 100644 --- a/services/database.ts +++ b/services/database.ts @@ -17,7 +17,7 @@ export const initDatabase = async () => { try { await db.transactionAsync(async (tx: SQLite.SQLTransactionAsync) => { await tx.executeSqlAsync( - "CREATE TABLE IF NOT EXISTS category (guid VARCHAR(36) PRIMARY KEY, name TEXT, color TEXT, type TEXT, allocated_amount DOUBLE);" + "CREATE TABLE IF NOT EXISTS category (guid VARCHAR(36) PRIMARY KEY, name TEXT, color TEXT, type string, allocated_amount DOUBLE);" ); await tx.executeSqlAsync( "CREATE TABLE IF NOT EXISTS expense (guid VARCHAR(36) PRIMARY KEY, name TEXT, category_guid VARCHAR(36), datetime DATETIME, amount DOUBLE, FOREIGN KEY (category_guid) REFERENCES category(guid));" From 85e92c85b4d1117bf31a65261b0e22568bbb9f03 Mon Sep 17 00:00:00 2001 From: Walcher Date: Thu, 25 Jan 2024 17:49:24 +0100 Subject: [PATCH 32/37] hold my beer des wird a katastrophe --- components/stats/BudgetRemaining.tsx | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/components/stats/BudgetRemaining.tsx b/components/stats/BudgetRemaining.tsx index d5209fd..98a5cf0 100644 --- a/components/stats/BudgetRemaining.tsx +++ b/components/stats/BudgetRemaining.tsx @@ -5,15 +5,33 @@ import useFetch from '../../hooks/useFetch'; import { CategoryType } from '../../services/database'; const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center' + }, text: { fontSize: 26, }, boldText: { - fontWeight: 'bold', + fontWeight: 'bold' + }, + negativeText: { + color: 'red', + }, + positiveText: { + color: 'green', }, }); -const BudgetRemaining = () => { + +interface BudgetRemainingProps { + goodColor?: string; + badColor?: string; +} + +const BudgetRemaining: React.FC = ({ goodColor = 'green', badColor = 'red' }) => { const { colors } = useTheme(); const [spent, setSpent] = useState(0); const [budget, setBudget] = useState(0); @@ -47,10 +65,18 @@ const BudgetRemaining = () => { } return ( - - You have {remaining.toFixed(2)}€ left. + + {remaining >= 0 ? ( + <> + Your remaining Budget is {remaining.toFixed(2)}€. + + ) : ( + <> + Your Budget is overspent by by -{Math.abs(remaining).toFixed(2)}€. + + )} - ); + ); }; export default BudgetRemaining; From 7a5ace403eab86d0a24871c0ec879911f7536639 Mon Sep 17 00:00:00 2001 From: Walcher Date: Thu, 25 Jan 2024 18:49:05 +0100 Subject: [PATCH 33/37] made hook to streamline getting totals and expense by type of category --- app/(tabs)/stats/index.tsx | 52 ++++++++++++++++ components/stats/BudgetOverview.tsx | 87 ++++++++++++-------------- components/stats/BudgetRemaining.tsx | 82 ------------------------ components/stats/BudgetTotal.tsx | 62 +++++++++++++++++++ components/stats/SavingsOverview.tsx | 93 ++++++++++++++-------------- hooks/useCategoryData.ts | 33 ++++++++++ 6 files changed, 233 insertions(+), 176 deletions(-) create mode 100644 app/(tabs)/stats/index.tsx delete mode 100644 components/stats/BudgetRemaining.tsx create mode 100644 components/stats/BudgetTotal.tsx create mode 100644 hooks/useCategoryData.ts diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx new file mode 100644 index 0000000..97a3b74 --- /dev/null +++ b/app/(tabs)/stats/index.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { StyleSheet, View, ScrollView } from 'react-native'; +import BudgetTotal from '../../../components/stats/BudgetTotal'; +import { useTheme } from '../../contexts/ThemeContext'; +import Widget from '../../../components/stats/Widget'; +import DebugMenu from '../../../services/DebugMenu'; +import SavingsOverview from '../../../components/stats/SavingsOverview'; +import FinancialAdvice from '../../../components/stats/FinancialAdvice'; +import BudgetOverview from '../../../components/stats/BudgetOverview'; + +export default function Page() { + const { colors } = useTheme(); + + // Mock data #TODO Database einbinden + // what to do when amount too small? + + const spent = 120.75; + const budget = 696.96; + + + + const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.backgroundColor, + marginTop: 50, + alignItems: 'center', + }, + }); + + return ( + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/components/stats/BudgetOverview.tsx b/components/stats/BudgetOverview.tsx index 8cc5c6c..de40065 100644 --- a/components/stats/BudgetOverview.tsx +++ b/components/stats/BudgetOverview.tsx @@ -1,63 +1,56 @@ import React, { useState, useEffect } from 'react'; -import { View, Text, StyleSheet } from 'react-native'; +import { Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; import useFetch from '../../hooks/useFetch'; import { CategoryType } from '../../services/database'; +import {useCategoryData} from '../../hooks/useCategoryData'; -const BudgetOverview = () => { +const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center' + }, + text: { + fontSize: 26, + color: `black` + }, + boldText: { + fontWeight: 'bold' + }, + negativeText: { + color: 'red', + }, + positiveText: { + color: 'green', + }, +}); + +interface BudgetTotalProps { + goodColor?: string; + badColor?: string; +} + +const BudgetTotal: React.FC = ({ goodColor = 'green', badColor = 'red' }) => { const { colors } = useTheme(); - const [spent, setSpent] = useState(0); - const [budget, setBudget] = useState(0); + const { data, isLoading } = useCategoryData(CategoryType.EXPENSE); - const spentQuery = { - sql: `SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = '${CategoryType.EXPENSE.toString()}'`, - args: [] - }; + const { total, expenseTotal } = data; - const budgetQuery = { - sql: `SELECT SUM(allocated_amount) as total FROM category WHERE type = '${CategoryType.EXPENSE.toString()}'`, - args: [] - }; + const remaining = total - expenseTotal; - const { data: spentData, isLoading: spentLoading } = useFetch(spentQuery); - const { data: budgetData, isLoading: budgetLoading } = useFetch(budgetQuery); - - useEffect(() => { - if (spentData) { - setSpent(spentData[0]?.total || 0); - } - if (budgetData) { - setBudget(budgetData[0]?.total || 0); - } - }, [spentData, budgetData]); - - const styles = StyleSheet.create({ - container: { - margin: 10, - borderRadius: 5, - alignItems: 'center', - justifyContent: 'center' - }, - text: { - fontSize: 26, - color: colors.primaryText - }, - boldText: { - fontWeight: 'bold' - } - }); - - if (spentLoading || budgetLoading) { + if (isLoading) { return Loading...; } return ( - - - You have spent {spent.toFixed(2)}€ out of your budget of {budget.toFixed(2)}€. - - + + <> + You have spent {expenseTotal.toFixed(2)}€ out of your Budget of {total.toFixed(2)}€ . + + ); }; -export default BudgetOverview; +export default BudgetTotal; \ No newline at end of file diff --git a/components/stats/BudgetRemaining.tsx b/components/stats/BudgetRemaining.tsx deleted file mode 100644 index 98a5cf0..0000000 --- a/components/stats/BudgetRemaining.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Text, StyleSheet } from 'react-native'; -import { useTheme } from '../../app/contexts/ThemeContext'; -import useFetch from '../../hooks/useFetch'; -import { CategoryType } from '../../services/database'; - -const styles = StyleSheet.create({ - container: { - margin: 10, - borderRadius: 5, - alignItems: 'center', - justifyContent: 'center' - }, - text: { - fontSize: 26, - }, - boldText: { - fontWeight: 'bold' - }, - negativeText: { - color: 'red', - }, - positiveText: { - color: 'green', - }, -}); - - -interface BudgetRemainingProps { - goodColor?: string; - badColor?: string; -} - -const BudgetRemaining: React.FC = ({ goodColor = 'green', badColor = 'red' }) => { - const { colors } = useTheme(); - const [spent, setSpent] = useState(0); - const [budget, setBudget] = useState(0); - - const spentQuery = { - sql: "SELECT SUM(amount) as total FROM expense", - args: [] - }; - - const budgetQuery = { - sql: `SELECT SUM(allocated_amount) as total FROM category WHERE type = '${CategoryType.EXPENSE.toString()}'`, - args: [] - }; - - const { data: spentData, isLoading: spentLoading } = useFetch(spentQuery); - const { data: budgetData, isLoading: budgetLoading } = useFetch(budgetQuery); - - useEffect(() => { - if (spentData) { - setSpent(spentData[0]?.total || 0); - } - if (budgetData) { - setBudget(budgetData[0]?.total || 0); - } - }, [spentData, budgetData]); - - const remaining = budget - spent; - - if (spentLoading || budgetLoading) { - return Loading...; - } - - return ( - - {remaining >= 0 ? ( - <> - Your remaining Budget is {remaining.toFixed(2)}€. - - ) : ( - <> - Your Budget is overspent by by -{Math.abs(remaining).toFixed(2)}€. - - )} - - ); -}; - -export default BudgetRemaining; diff --git a/components/stats/BudgetTotal.tsx b/components/stats/BudgetTotal.tsx new file mode 100644 index 0000000..6acfa6b --- /dev/null +++ b/components/stats/BudgetTotal.tsx @@ -0,0 +1,62 @@ +import React, { useState, useEffect } from 'react'; +import { Text, StyleSheet } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import useFetch from '../../hooks/useFetch'; +import { CategoryType } from '../../services/database'; +import {useCategoryData} from '../../hooks/useCategoryData'; + +const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center' + }, + text: { + fontSize: 26, + color: `black` + }, + boldText: { + fontWeight: 'bold' + }, + negativeText: { + color: 'red', + }, + positiveText: { + color: 'green', + }, +}); + +interface BudgetTotalProps { + goodColor?: string; + badColor?: string; +} + +const BudgetTotal: React.FC = ({ goodColor = 'green', badColor = 'red' }) => { + const { colors } = useTheme(); + const { data, isLoading } = useCategoryData(CategoryType.EXPENSE); + + const { total, expenseTotal } = data; + + const remaining = total - expenseTotal; + + if (isLoading) { + return Loading...; + } + + return ( + + {remaining >= 0 ? ( + <> + Your remaining overall Budget is {remaining.toFixed(2)}€. + + ) : ( + <> + Your Budget is overspent by -{Math.abs(remaining).toFixed(2)}€. + + )} + + ); +}; + +export default BudgetTotal; \ No newline at end of file diff --git a/components/stats/SavingsOverview.tsx b/components/stats/SavingsOverview.tsx index 5162e75..064cf5e 100644 --- a/components/stats/SavingsOverview.tsx +++ b/components/stats/SavingsOverview.tsx @@ -1,63 +1,62 @@ import React, { useState, useEffect } from 'react'; -import { View, Text, StyleSheet } from 'react-native'; +import { Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; import useFetch from '../../hooks/useFetch'; import { CategoryType } from '../../services/database'; +import {useCategoryData} from '../../hooks/useCategoryData'; -const SavingsOverview = () => { +const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center' + }, + text: { + fontSize: 26, + color: `black` + }, + boldText: { + fontWeight: 'bold' + }, + negativeText: { + color: 'red', + }, + positiveText: { + color: 'green', + }, +}); + +interface BudgetTotalProps { + goodColor?: string; + badColor?: string; +} + +const BudgetTotal: React.FC = ({ goodColor = 'green', badColor = 'red' }) => { const { colors } = useTheme(); - const [saved, setSaved] = useState(0); - const [goal, setGoal] = useState(0); + const { data, isLoading } = useCategoryData(CategoryType.SAVING); - const savedQuery = { - sql: `SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = '${CategoryType.SAVING.toString()}'`, - args: [] - }; + const { total, expenseTotal } = data; - const goalQuery = { - sql: `SELECT SUM(allocated_amount) as total FROM category WHERE type = '${CategoryType.SAVING.toString()}'`, - args: [] - }; + const remaining = total - expenseTotal; - const { data: savedData, isLoading: savedLoading } = useFetch(savedQuery); - const { data: goalData, isLoading: goalLoading } = useFetch(goalQuery); - - useEffect(() => { - if (savedData) { - setSaved(savedData[0]?.total || 0); - } - if (goalData) { - setGoal(goalData[0]?.total || 0); - } - }, [savedData, goalData]); - - const styles = StyleSheet.create({ - container: { - margin: 10, - borderRadius: 5, - alignItems: 'center', - justifyContent: 'center' - }, - text: { - fontSize: 26, - color: colors.primaryText - }, - boldText: { - fontWeight: 'bold' - } - }); - - if (savedLoading || goalLoading) { + if (isLoading) { return Loading...; } return ( - - - You have saved {saved.toFixed(2)}€ out of your goal of {goal.toFixed(2)}€. - - + + {remaining >= 0 ? ( + <> + You have saved {expenseTotal.toFixed(2)}€ out of your Goal of {total.toFixed(2)}€ . + + ) : ( + <> + You have surpassed your Savings Goal of {total.toFixed(2)}€ by {Math.abs(remaining).toFixed(2)}€. + + )} + ); }; -export default SavingsOverview; \ No newline at end of file +export default BudgetTotal; \ No newline at end of file diff --git a/hooks/useCategoryData.ts b/hooks/useCategoryData.ts new file mode 100644 index 0000000..3939a53 --- /dev/null +++ b/hooks/useCategoryData.ts @@ -0,0 +1,33 @@ +import { useState, useEffect } from 'react'; +import useFetch from './useFetch'; +import { CategoryType } from '../services/database'; + +export const useCategoryData = (CategoryType: string) => { + const [data, setData] = useState({ total: 0, expenseTotal: 0 }); + const [isLoading, setLoading] = useState(true); + + const categoryQuery = { + sql: `SELECT SUM(allocated_amount) as total FROM category WHERE type = '${CategoryType.toString()}'`, + args: [] + }; + + const expenseQuery = { + sql: `SELECT SUM(e.amount) as total FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.type = '${CategoryType.toString()}'`, + args: [] + }; + + const { data: categoryData, isLoading: categoryLoading } = useFetch(categoryQuery); + const { data: expenseData, isLoading: expenseLoading } = useFetch(expenseQuery); + + useEffect(() => { + if (categoryData && expenseData) { + setData({ + total: categoryData[0]?.total || 0, + expenseTotal: expenseData[0]?.total || 0 + }); + } + setLoading(categoryLoading || expenseLoading); + }, [categoryData, categoryLoading, expenseData, expenseLoading]); + + return { categoryData, expenseData, isLoading, data }; +}; \ No newline at end of file From 899b50ded400ad197b13a73332946650e1f1692e Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Thu, 25 Jan 2024 20:35:09 +0100 Subject: [PATCH 34/37] last changes --- app/(tabs)/(budget)/addCategory.tsx | 2 +- app/(tabs)/(budget)/editCategory.tsx | 3 +- app/(tabs)/(budget)/index.tsx | 1 - app/(tabs)/(stats)/index.tsx | 39 ++++++++++++------ app/(tabs)/stats/index.tsx | 52 ------------------------ app/expense/new.tsx | 2 - components/budget/typeSelectorSwitch.tsx | 2 +- components/stats/BudgetOverview.tsx | 4 +- components/stats/BudgetTotal.tsx | 6 +-- components/stats/SavingsOverview.tsx | 6 +-- hooks/useCategoryData.ts | 2 +- package-lock.json | 8 ++-- package.json | 2 +- services/DebugMenu.tsx | 3 +- services/database.ts | 6 +-- types/dbItems.ts | 4 ++ 16 files changed, 53 insertions(+), 89 deletions(-) delete mode 100644 app/(tabs)/stats/index.tsx diff --git a/app/(tabs)/(budget)/addCategory.tsx b/app/(tabs)/(budget)/addCategory.tsx index eced44a..695a708 100644 --- a/app/(tabs)/(budget)/addCategory.tsx +++ b/app/(tabs)/(budget)/addCategory.tsx @@ -5,7 +5,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; import { addCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; -import { CategoryType } from "../../../services/database"; +import { CategoryType } from "../../../types/dbItems"; export default function Page() { const {colors} = useTheme(); diff --git a/app/(tabs)/(budget)/editCategory.tsx b/app/(tabs)/(budget)/editCategory.tsx index e0b2706..19dc3e0 100644 --- a/app/(tabs)/(budget)/editCategory.tsx +++ b/app/(tabs)/(budget)/editCategory.tsx @@ -6,6 +6,7 @@ import useFetch from "../../../hooks/useFetch"; import { deleteCategory, deleteExpense, updateCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; import { SafeAreaView } from 'react-native-safe-area-context'; +import { CategoryType } from "../../../types/dbItems"; const addCategory = () => { const {colors} = useTheme(); @@ -13,7 +14,7 @@ const addCategory = () => { const [categoryName, setCategoryName] = useState(category_name.toString()); const [categoryColor, setCategoryColor] = useState(category_color.toString()); - const [selectedType, setSelectedType] = useState(category_type.toString()); + const [selectedType, setSelectedType] = useState(category_type === "expense" ? CategoryType.EXPENSE : CategoryType.SAVING); const [amount, setAmount] = useState(Number.parseFloat(category_amount.toString())); const {data} = useFetch({sql: "SELECT * FROM expense WHERE category_guid = ?", args: [category_guid.toString()]}); diff --git a/app/(tabs)/(budget)/index.tsx b/app/(tabs)/(budget)/index.tsx index 9b38322..fce6f05 100644 --- a/app/(tabs)/(budget)/index.tsx +++ b/app/(tabs)/(budget)/index.tsx @@ -67,7 +67,6 @@ export default function Page() { }) }, [data, searchString, selectedPage]); - console.log(selectedPage) return ( diff --git a/app/(tabs)/(stats)/index.tsx b/app/(tabs)/(stats)/index.tsx index 6a6c0c9..5113d08 100644 --- a/app/(tabs)/(stats)/index.tsx +++ b/app/(tabs)/(stats)/index.tsx @@ -1,24 +1,39 @@ import React from 'react'; -import { SafeAreaView } from 'react-native'; + import { Graph } from '../../../components'; import BudgetOverview from '../../../components/stats/BudgetOverview'; -import BudgetRemaining from '../../../components/stats/BudgetRemaining'; +import BudgetRemaining from '../../../components/stats/SavingsOverview'; import SavingsOverview from '../../../components/stats/SavingsOverview'; import Widget from '../../../components/stats/Widget'; +import FinancialAdvice from '../../../components/stats/FinancialAdvice'; +import BudgetTotal from '../../../components/stats/BudgetTotal'; +import { ScrollView } from 'react-native-gesture-handler'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import DebugMenu from '../../../services/DebugMenu'; export default function Page() { return ( - - - - - - - - - - + + + {/* */} + + + + + + + + + + + + {/* + + */} + + + ); } diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx deleted file mode 100644 index 97a3b74..0000000 --- a/app/(tabs)/stats/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { StyleSheet, View, ScrollView } from 'react-native'; -import BudgetTotal from '../../../components/stats/BudgetTotal'; -import { useTheme } from '../../contexts/ThemeContext'; -import Widget from '../../../components/stats/Widget'; -import DebugMenu from '../../../services/DebugMenu'; -import SavingsOverview from '../../../components/stats/SavingsOverview'; -import FinancialAdvice from '../../../components/stats/FinancialAdvice'; -import BudgetOverview from '../../../components/stats/BudgetOverview'; - -export default function Page() { - const { colors } = useTheme(); - - // Mock data #TODO Database einbinden - // what to do when amount too small? - - const spent = 120.75; - const budget = 696.96; - - - - const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.backgroundColor, - marginTop: 50, - alignItems: 'center', - }, - }); - - return ( - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/app/expense/new.tsx b/app/expense/new.tsx index d0456e7..bfbd7bd 100644 --- a/app/expense/new.tsx +++ b/app/expense/new.tsx @@ -54,9 +54,7 @@ export default function AddItem() { useEffect(()=>{ if(searchParams.category !== undefined){ - console.log(searchParams.category) executeQuery({sql: "SELECT * FROM category WHERE guid = ?", args: [searchParams.category]}).then((result) =>{ - console.log("then") if("rows" in result[0]){ const category = result[0]["rows"][0]; setSelectedCategory({name: category["name"], color: category["color"], guid: category["guid"]}) diff --git a/components/budget/typeSelectorSwitch.tsx b/components/budget/typeSelectorSwitch.tsx index a4edf58..f3e8f25 100644 --- a/components/budget/typeSelectorSwitch.tsx +++ b/components/budget/typeSelectorSwitch.tsx @@ -1,7 +1,7 @@ import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { useTheme } from "../../app/contexts/ThemeContext"; -import { CategoryType } from "../../services/database"; +import { CategoryType } from "../../types/dbItems"; export type TypeSelectorSwitchProperties = { handleButtonPress: (type: CategoryType) => void, diff --git a/components/stats/BudgetOverview.tsx b/components/stats/BudgetOverview.tsx index de40065..4148e6a 100644 --- a/components/stats/BudgetOverview.tsx +++ b/components/stats/BudgetOverview.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; import useFetch from '../../hooks/useFetch'; -import { CategoryType } from '../../services/database'; +import { CategoryType } from '../../types/dbItems'; import {useCategoryData} from '../../hooks/useCategoryData'; const styles = StyleSheet.create({ @@ -47,7 +47,7 @@ const BudgetTotal: React.FC = ({ goodColor = 'green', badColor return ( <> - You have spent {expenseTotal.toFixed(2)}€ out of your Budget of {total.toFixed(2)}€ . + You have spent {expenseTotal.toFixed(2)}€ out of your Budget of {total.toFixed(2)}€ ); diff --git a/components/stats/BudgetTotal.tsx b/components/stats/BudgetTotal.tsx index 6acfa6b..f4b0aa8 100644 --- a/components/stats/BudgetTotal.tsx +++ b/components/stats/BudgetTotal.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; import useFetch from '../../hooks/useFetch'; -import { CategoryType } from '../../services/database'; +import { CategoryType } from '../../types/dbItems'; import {useCategoryData} from '../../hooks/useCategoryData'; const styles = StyleSheet.create({ @@ -48,11 +48,11 @@ const BudgetTotal: React.FC = ({ goodColor = 'green', badColor {remaining >= 0 ? ( <> - Your remaining overall Budget is {remaining.toFixed(2)}€. + Your remaining overall Budget is {remaining.toFixed(2)}€ ) : ( <> - Your Budget is overspent by -{Math.abs(remaining).toFixed(2)}€. + Your Budget is overspent by -{Math.abs(remaining).toFixed(2)}€ )} diff --git a/components/stats/SavingsOverview.tsx b/components/stats/SavingsOverview.tsx index 064cf5e..ea906a1 100644 --- a/components/stats/SavingsOverview.tsx +++ b/components/stats/SavingsOverview.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Text, StyleSheet } from 'react-native'; import { useTheme } from '../../app/contexts/ThemeContext'; import useFetch from '../../hooks/useFetch'; -import { CategoryType } from '../../services/database'; +import { CategoryType } from '../../types/dbItems'; import {useCategoryData} from '../../hooks/useCategoryData'; const styles = StyleSheet.create({ @@ -48,11 +48,11 @@ const BudgetTotal: React.FC = ({ goodColor = 'green', badColor {remaining >= 0 ? ( <> - You have saved {expenseTotal.toFixed(2)}€ out of your Goal of {total.toFixed(2)}€ . + You have saved {expenseTotal.toFixed(2)}€ out of your Goal of {total.toFixed(2)}€ ) : ( <> - You have surpassed your Savings Goal of {total.toFixed(2)}€ by {Math.abs(remaining).toFixed(2)}€. + You have surpassed your Savings Goal of {total.toFixed(2)}€ by {Math.abs(remaining).toFixed(2)}€ )} diff --git a/hooks/useCategoryData.ts b/hooks/useCategoryData.ts index 3939a53..72cbe8b 100644 --- a/hooks/useCategoryData.ts +++ b/hooks/useCategoryData.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import useFetch from './useFetch'; -import { CategoryType } from '../services/database'; +import { CategoryType } from '../types/dbItems'; export const useCategoryData = (CategoryType: string) => { const [data, setData] = useState({ total: 0, expenseTotal: 0 }); diff --git a/package-lock.json b/package-lock.json index 3c97807..8eacd8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "react-native-calendars": "^1.1303.0", "react-native-chart-kit": "^6.12.0", "react-native-gesture-handler": "~2.12.0", - "react-native-reanimated": "^3.3.0", + "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", "react-native-uuid": "^2.0.1", @@ -16163,9 +16163,9 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.1.tgz", - "integrity": "sha512-F4vG9Yf9PKmE3GaWtVGUpzj3SM6YY2cx1yRHCwiMd1uY7W0gU017LfcVUorboJnj0y5QZqEriEK1Usq2Y8YZqg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.3.0.tgz", + "integrity": "sha512-LzfpPZ1qXBGy5BcUHqw3pBC0qSd22qXS3t8hWSbozXNrBkzMhhOrcILE/nEg/PHpNNp1xvGOW8NwpAMF006roQ==", "dependencies": { "@babel/plugin-transform-object-assign": "^7.16.7", "@babel/preset-typescript": "^7.16.7", diff --git a/package.json b/package.json index 6203ad9..f8cf56a 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "react-native-calendars": "^1.1303.0", "react-native-chart-kit": "^6.12.0", "react-native-gesture-handler": "~2.12.0", - "react-native-reanimated": "^3.3.0", + "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", "react-native-uuid": "^2.0.1", diff --git a/services/DebugMenu.tsx b/services/DebugMenu.tsx index 29ce4e8..ff728ff 100644 --- a/services/DebugMenu.tsx +++ b/services/DebugMenu.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { View, Button, Alert } from 'react-native'; import { addCategory, addExpense, deleteExpenses, deleteCategories, DEV_populateDatabase, deleteDatabase } from './database'; import uuid from 'react-native-uuid'; +import { CategoryType } from '../types/dbItems'; const randomColors = ["red", "blue", "green", "purple", "yellow"]; @@ -46,7 +47,7 @@ const DebugMenu = () => { const allocated_amount = getRandomNumber(); const type = "expense"; - addCategory(name, color, type, allocated_amount) + addCategory(name, color, CategoryType.EXPENSE, allocated_amount) .then(() => Alert.alert("Category Added", `Name: ${name}, Color: ${color}`)) .catch((error: any) => console.error("Error adding category: ", error)); }; diff --git a/services/database.ts b/services/database.ts index 029131d..e4c25bf 100644 --- a/services/database.ts +++ b/services/database.ts @@ -4,13 +4,11 @@ import uuid from "react-native-uuid"; import { Query } from "expo-sqlite"; import { SimpleDate } from "../util/SimpleDate"; +import { CategoryType } from "../types/dbItems"; let db: SQLite.SQLiteDatabase; -export enum CategoryType { - SAVING = "saving", - EXPENSE = "expense", -} + export const initDatabase = async () => { db = SQLite.openDatabase("interactive_systeme.db"); diff --git a/types/dbItems.ts b/types/dbItems.ts index c2df2dc..2d9906d 100644 --- a/types/dbItems.ts +++ b/types/dbItems.ts @@ -1,3 +1,7 @@ +export enum CategoryType { + SAVING = "saving", + EXPENSE = "expense", +} export interface Category { guid? : string; name? : string; From 2e0b92d5b85392c8e70419ca0c137ef92dd0ab25 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Thu, 25 Jan 2024 20:41:19 +0100 Subject: [PATCH 35/37] deleteEverything kept in userSettings --- app/(tabs)/(home)/userSettings.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/app/(tabs)/(home)/userSettings.tsx b/app/(tabs)/(home)/userSettings.tsx index c865110..fb163ae 100644 --- a/app/(tabs)/(home)/userSettings.tsx +++ b/app/(tabs)/(home)/userSettings.tsx @@ -4,7 +4,7 @@ import { SIZES } from '../../../constants/theme' import { SafeAreaView } from 'react-native-safe-area-context' import { ButtonSetting, ToggleSetting } from '../../../components' import { useTheme } from '../../contexts/ThemeContext' -import { deleteExpenses, DEV_populateDatabase } from '../../../services/database' +import { deleteCategories, deleteExpenses, DEV_populateDatabase } from '../../../services/database' import { useAuth } from '../../contexts/AuthContext' import { TouchableOpacity } from 'react-native-gesture-handler' @@ -50,17 +50,14 @@ export default function userSettings() { - { - deleteExpenses().then(() => { - console.log("Expenses Deleted!"); - })}} - /> - { - const del = async () => { - await DEV_populateDatabase() + { + const deleteAll = async () => { + await deleteExpenses(); + await deleteCategories(); } - del() - }}/> + deleteAll(); + }} + /> From f44a42779f87867e4a9034a3f4ea7ddde7e9952d Mon Sep 17 00:00:00 2001 From: Ghost_Element Date: Fri, 26 Jan 2024 23:53:59 +0100 Subject: [PATCH 36/37] documentation --- DOCUMENTATION.md | 4 ++-- README.md | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 971359c..e561888 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -24,12 +24,12 @@ Our design philosophy was rooted in simplicity and functionality. We aimed to cr - **Component Development**: Built reusable components for uniformity and efficiency. - **State Management and Hooks**: Implemented custom hooks for state management, ensuring smooth data flow and component reusability. -- [ ] **Stage 4: Implementation-Phase 2** +- [x] **Stage 4: Implementation-Phase 2** - **Refining Components**: Enhancing component functionality and aesthetics. - **Optimization and Debugging**: Identifying and fixing bugs, optimizing code for better performance. - **Expanding Features**: Implementing additional pages and components to enrich the application. -- [ ] **Stage 5: Final Implementation-Phase** +- [x] **Stage 5: Final Implementation-Phase** - **UI Touch-Ups**: Final tweaks to the user interface, ensuring a polished look and feel. - **Finalization**: Wrapping up the project, finalizing drafts, and completing outstanding tasks. diff --git a/README.md b/README.md index b5967dd..378d956 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,16 @@ A more convenient way to run Finanzfuchs is to download the prebuilt apk which c ## Usage -- **Adding Expenses**: Press the plus button to add expenses. -- **Removing Expenses**: Go to the budget tab and press the red button in the middle to reset expenses. -- **Login Screen**: Go to the login screen by pressing the profile picture in the top left of the home screen. +- **Adding Expenses(/Savings)**: Press the plus button to add expenses. +- **Removing Expenses(/Savings)**: Go to the certain expense and press it. You will then be directed to the edit expense tab, where you can change the expense as you wish. +- **Adding Categories**: Go to the budget tab and press the plus button to add a new category with custom color. +- **Removing Categories**: Go to a certain category by pressing on the budget tab and choosing one. At the top is a big button with a pencil on it, which when you press it takes you to the edit category tab, where you can customize the category or remove it completely with all expenses it contains. +- **Searching for Expenses, Savings and Categories**: At the Home Screen, the Budget tab or in a certain category you can search for certain expenses, categories or savings by writing its name in the Search bar. +- **Home Screen**: Go to the login screen by pressing the profile picture in the top left of the home screen. Furthermore, at the bottom there are the last expenses, which were added. + - **Calendar**: The calendar has little colored dots under certain days indicating, that at that day an expense has been added. By pressing at a day the list of expenses below it will be sorted out and only showing the expenses which were added at that day. + - **List of Expenses**: The list shows the most recently added expenses which can be searched through and filtered by using the calendar. + - **Profile**: By pressing at the profile picture on the home screen you get to the My Profile tab where you can activate the dark mode, reset the database and sign out of the app. +- **Stats**: Here is a Graph showing an Overview of your Budget and the sums of all expenses and savings. ## Team From 675ffe97bca8cae606e098d2face53a3d0f48673 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Sat, 27 Jan 2024 00:06:35 +0100 Subject: [PATCH 37/37] missing deps --- package-lock.json | 24 +++++++----------------- package.json | 1 + 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8eacd8d..20b4a11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", + "react-native-svg": "13.9.0", "react-native-uuid": "^2.0.1", "react-native-web": "~0.19.6", "reanimated-color-picker": "^2.4.2" @@ -7509,8 +7510,7 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "peer": true + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/bplist-creator": { "version": "0.1.0", @@ -8339,7 +8339,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -8355,7 +8354,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "peer": true, "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -8368,7 +8366,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "peer": true, "engines": { "node": ">= 6" }, @@ -8669,7 +8666,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -8688,8 +8684,7 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ], - "peer": true + ] }, "node_modules/domexception": { "version": "4.0.0", @@ -8708,7 +8703,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -8723,7 +8717,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -13786,8 +13779,7 @@ "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "peer": true + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" }, "node_modules/media-typer": { "version": "0.3.0", @@ -14996,7 +14988,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "peer": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -16206,10 +16197,9 @@ } }, "node_modules/react-native-svg": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-14.1.0.tgz", - "integrity": "sha512-HeseElmEk+AXGwFZl3h56s0LtYD9HyGdrpg8yd9QM26X+d7kjETrRQ9vCjtxuT5dCZEIQ5uggU1dQhzasnsCWA==", - "peer": true, + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.9.0.tgz", + "integrity": "sha512-Ey18POH0dA0ob/QiwCBVrxIiwflhYuw0P0hBlOHeY4J5cdbs8ngdKHeWC/Kt9+ryP6fNoEQ1PUgPYw2Bs/rp5Q==", "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3" diff --git a/package.json b/package.json index f8cf56a..b86787c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", + "react-native-svg": "13.9.0", "react-native-uuid": "^2.0.1", "react-native-web": "~0.19.6", "reanimated-color-picker": "^2.4.2"