diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index b7e477d..cac1e04 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -6,6 +6,8 @@ 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(); @@ -16,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: { @@ -49,13 +33,19 @@ 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. ); 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; 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 diff --git a/components/stats/Widget.tsx b/components/stats/Widget.tsx index f4e3e0b..3162d62 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,18 @@ 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, 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)