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