diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index 9dd775a..9a2033a 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -1,61 +1,69 @@ -import { Query } from 'expo-sqlite'; -import { StyleSheet, Text, View } from 'react-native'; -import { addCategory, deleteDatabase, deleteExpenses, executeQuery, initDatabase } from '../../../services/database'; +import React from 'react'; +import { StyleSheet, View, ScrollView } from 'react-native'; +import BudgetOverview from '../../../components/stats/BudgetOverview'; +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'; 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 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: { flex: 1, - justifyContent: 'space-evenly', + backgroundColor: colors.backgroundColor, + marginTop: 50, alignItems: 'center', }, - text: { - fontSize: 40, - color: "yellow", - } }); return ( - - { - deleteExpenses().then(() => { - console.log("Expenses Deleted!"); - }) - }}>Reset Expenses - - { - deleteDatabase(); - console.log("Database Deleted!"); - }}>Reset Database - - { - initDatabase().then(() => { - console.log("Database Initialized!"); - }); - }}>Init Database - - { - addCategory("Category", "green", "expense", 500).then(() => { - const getCategoryQuery: Query = {sql: "SELECT guid FROM category", args: []}; - executeQuery(getCategoryQuery).then((result) => { - if("rows" in result[0]) { - console.log(result[0]["rows"]); - } - }) - console.log("Category added with success!"); - }) - }}>Add new Category Expense - - { - addCategory("Category", "yellow", "saving", 420).then(() => { - const getCategoryQuery: Query = {sql: "SELECT guid FROM category", args: []}; - executeQuery(getCategoryQuery).then((result) => { - if("rows" in result[0]) { - console.log(result[0]["rows"]); - } - }) - console.log("Category added with success!"); - }) - }}>Add new Category Savings - ); + + + + + + + + + + + + + + + + + + + + ); } diff --git a/assets/images/8b14el.jpg b/assets/images/8b14el.jpg new file mode 100644 index 0000000..3e1e0e6 Binary files /dev/null and b/assets/images/8b14el.jpg differ diff --git a/components/stats/BudgetOverview.tsx b/components/stats/BudgetOverview.tsx new file mode 100644 index 0000000..5339fb3 --- /dev/null +++ b/components/stats/BudgetOverview.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; + +interface StatsBudgetProps { + spent: number; + budget: number; +} + +const BudgetOverview: React.FC = ({ spent, budget }) => { + const { colors } = useTheme(); + + const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center', + }, + text: { + fontSize: 26, + color: colors.primaryText, + }, + boldText: { + fontWeight: 'bold', + }, + }); + + return ( + + + You have spent {spent.toFixed(2)}€ out of your budget of {budget.toFixed(2)}€. + + + ); +}; + +export default BudgetOverview diff --git a/components/stats/BudgetRemaining.tsx b/components/stats/BudgetRemaining.tsx new file mode 100644 index 0000000..1eb038e --- /dev/null +++ b/components/stats/BudgetRemaining.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Text, StyleSheet } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; + +interface BudgetRemainingProps { + budget: number; + spent: number; +} + +const BudgetRemaining: React.FC = ({ budget, spent }) => { + const { colors, theme } = useTheme(); + + 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', + }, + }); + + return ( + + You have {remaining.toFixed(2)}€ left. + + ); +}; + +export default BudgetRemaining; diff --git a/components/stats/CategoryProgressBar.tsx b/components/stats/CategoryProgressBar.tsx new file mode 100644 index 0000000..4e49975 --- /dev/null +++ b/components/stats/CategoryProgressBar.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; + +interface CategoryProgressBarProps { + categoryName: string; + color?: string; + maxValue: number; + currentValue: number; +} + +const CategoryProgressBar: React.FC = ({ + categoryName, + color, + maxValue, + currentValue, +}) => { + const { colors } = useTheme(); + + const progress = (currentValue / maxValue) * 100; + const progressText = `${currentValue}€ / ${maxValue}€`; + + const dynamicStyles = StyleSheet.create({ + progressBarFill: { + height: '100%', + width: `${progress}%`, + backgroundColor: color || colors.accentColor, + alignItems: 'center', + justifyContent: 'center', + }, + progressText: { + color: colors.primaryText, + fontSize: 20, + fontWeight: 'bold', + }, + }); + + const styles = StyleSheet.create({ + container: { + padding: 10, + }, + progressBarContainer: { + flexDirection: 'row', + height: 50, + backgroundColor: colors.elementSelectedColor, + borderRadius: 15, + overflow: 'hidden', + marginTop: 4, + }, + categoryName: { + color: colors.primaryText, + fontSize: 20, + fontWeight: 'bold', + }, + }); + + return ( + + {categoryName} + + + {progressText} + + + + ); +}; + +export default CategoryProgressBar; diff --git a/components/stats/CategoryProgressBarList.tsx b/components/stats/CategoryProgressBarList.tsx new file mode 100644 index 0000000..566f352 --- /dev/null +++ b/components/stats/CategoryProgressBarList.tsx @@ -0,0 +1,61 @@ +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; + color: string; + maxValue: number; + currentValue: number; +} + +interface CategoryProgressBarListProps { + categories: CategoryItem[]; +} + +const MAX_VISIBLE_BARS = 4; + +const CategoryProgressBarList: React.FC = ({ categories }) => { + const [visibleBars, setVisibleBars] = useState(MAX_VISIBLE_BARS); + + const showMore = () => { + setVisibleBars(prevVisibleBars => prevVisibleBars + MAX_VISIBLE_BARS); + }; + + return ( + + {categories.slice(0, visibleBars).map((category, index) => ( + + ))} + {visibleBars < categories.length && ( + + Show More + + )} + + ); +}; + +const styles = StyleSheet.create({ + showMoreButton: { + backgroundColor: '#EF6C00', + padding: 10, + borderRadius: 5, + margin: 10, + alignItems: 'center', + }, + buttonText: { + color: 'white', + fontSize: 16, + fontWeight: 'bold', + }, +}); + +export default CategoryProgressBarList; \ No newline at end of file diff --git a/components/stats/PieChart.tsx b/components/stats/PieChart.tsx new file mode 100644 index 0000000..f3fe67e --- /dev/null +++ b/components/stats/PieChart.tsx @@ -0,0 +1 @@ +//honestly just fuck graphs \ No newline at end of file diff --git a/components/stats/Widget.tsx b/components/stats/Widget.tsx new file mode 100644 index 0000000..153ed65 --- /dev/null +++ b/components/stats/Widget.tsx @@ -0,0 +1,63 @@ +import React, { ReactNode } from 'react'; +import { View, StyleSheet, Text, Image } from 'react-native'; // Add the missing import statement for Image +import { useTheme } from '../../app/contexts/ThemeContext'; + +interface WidgetProps { + title?: string; + text?: string; + children?: ReactNode; + image?: any; +} + +const Widget: React.FC = ({ title, text, children, image }) => { // Add the 'image' prop to the destructuring + const { colors } = useTheme(); + + const styles = StyleSheet.create({ + widgetContainer: { + backgroundColor: colors.widgetBackgroundColor, + borderColor: colors.widgetBorderColor, + borderRadius: 5, + padding: 16, + marginVertical: 8, + marginHorizontal: 10, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 1, + }, + shadowOpacity: 0.22, + shadowRadius: 2.22, + elevation: 3, + }, + widgetTitle: { + color: colors.primaryText, + fontSize: 26, + fontWeight: 'bold', + marginBottom: 8, + textAlign: 'center', + }, + widgetText: { + color: colors.primaryText, + fontSize: 16, + marginBottom: 8, + textAlign: 'center', + }, + imageStyle: { + width: '100%', + height: 200, + borderRadius: 5, + marginBottom: 8, + }, + }); + + return ( + + {!!title && {title}} + {!!text && {text}} + {!!image && } + {children} + + ); +}; + +export default Widget; \ No newline at end of file diff --git a/constants/colors.ts b/constants/colors.ts index 6b8937d..a707f73 100644 --- a/constants/colors.ts +++ b/constants/colors.ts @@ -12,6 +12,9 @@ export default { elementDefaultColor: "#E0E0E0", elementSelectedColor: "#9E9E9E", accentColor: "#EF6C00", + + widgetBackgroundColor: "#F7F7F7", + widgetBorderColor: "#E0E0E0", }, dark: { primaryText: "#FFFFFF", @@ -26,5 +29,8 @@ export default { elementDefaultColor: "#535353", elementSelectedColor: "#B3B3B3", accentColor: "#EF6C00", + + widgetBackgroundColor: "#252525", + widgetBorderColor: "#535353", } } \ No newline at end of file diff --git a/services/DebugMenu.tsx b/services/DebugMenu.tsx new file mode 100644 index 0000000..896a667 --- /dev/null +++ b/services/DebugMenu.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { View, Button, Alert } from 'react-native'; +import { addCategory, addExpense, deleteExpenses, deleteCategories, DEV_populateDatabase } from './database'; +import uuid from 'react-native-uuid'; + +const randomColors = ["red", "blue", "green", "purple", "yellow"]; + +const getRandomColor = () => { + return randomColors[Math.floor(Math.random() * randomColors.length)]; +}; + +const getRandomName = () => { + return `RandomName-${Math.floor(Math.random() * 1000)}`; +}; + +const getRandomNumber = () => { + return Math.floor(Math.random() * 1000); +}; + +const DebugMenu = () => { + + const handleNukeDatabase = () => { + return deleteExpenses(), deleteCategories() + }; + + const handlePopulateDatabase = () => { + return DEV_populateDatabase() + }; + + const handleDeleteExpenses = () => { + return deleteExpenses(); + } + const handleDeleteCategories = () => { + return deleteCategories(); + } + +//for some reason this function does not work + const handleAddCategory = () => { + const name = getRandomName(); + const color = getRandomColor(); + const allocated_amount = getRandomNumber(); + const type = "expense"; + + addCategory(name, color, type, allocated_amount) + .then(() => Alert.alert("Category Added", `Name: ${name}, Color: ${color}`)) + .catch((error: any) => console.error("Error adding category: ", error)); + }; + +//for some reason this function does not work + const handleAddExpense = () => { + const name = getRandomName(); + const categoryGuid = uuid.v4().toString(); + const datetime = new Date().toISOString(); + const amount = Math.floor(Math.random() * 1000); + + addExpense(name, categoryGuid, datetime, amount) + .then(() => Alert.alert("Expense Added", `Name: ${name}, Amount: ${amount}`)) + .catch((error: any) => console.error("Error adding expense: ", error)); + }; + + return ( + +