Compare commits
27 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f1753df3f | ||
|
|
3ff8127347 | ||
|
|
675ffe97bc | ||
|
|
f44a42779f | ||
|
|
93757d52d7 | ||
|
|
2e0b92d5b8 | ||
|
|
899b50ded4 | ||
|
|
7a5ace403e | ||
|
|
85e92c85b4 | ||
|
|
bdc2ca22f6 | ||
|
|
6ca505d505 | ||
|
|
114ad92818 | ||
|
|
305e110da5 | ||
|
|
4ec0f214f7 | ||
|
|
8149ec234f | ||
|
|
0edc962bc9 | ||
|
|
0e22e63ce9 | ||
|
|
4b06a217c5 | ||
|
|
84dbae49c1 | ||
|
|
ab2ef25615 | ||
|
|
e0f3cf947c | ||
|
|
716a41cffd | ||
|
|
79405f318d | ||
|
|
62a1267f1e | ||
|
|
c46eec06a5 | ||
|
|
7fecf8c5d9 | ||
|
|
4ddc76faa4 |
31 changed files with 1067 additions and 390 deletions
|
|
@ -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.
|
- **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.
|
- **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.
|
- **Refining Components**: Enhancing component functionality and aesthetics.
|
||||||
- **Optimization and Debugging**: Identifying and fixing bugs, optimizing code for better performance.
|
- **Optimization and Debugging**: Identifying and fixing bugs, optimizing code for better performance.
|
||||||
- **Expanding Features**: Implementing additional pages and components to enrich the application.
|
- **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.
|
- **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.
|
- **Finalization**: Wrapping up the project, finalizing drafts, and completing outstanding tasks.
|
||||||
|
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -25,9 +25,16 @@ A more convenient way to run Finanzfuchs is to download the prebuilt apk which c
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- **Adding Expenses**: Press the plus button to add expenses.
|
- **Adding Expenses(/Savings)**: 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.
|
- **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.
|
||||||
- **Login Screen**: Go to the login screen by pressing the profile picture in the top left of the home screen.
|
- **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
|
## Team
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components";
|
import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components";
|
||||||
import { addCategory } from "../../../services/database";
|
import { addCategory } from "../../../services/database";
|
||||||
import { useTheme } from "../../contexts/ThemeContext";
|
import { useTheme } from "../../contexts/ThemeContext";
|
||||||
|
import { CategoryType } from "../../../types/dbItems";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const {colors} = useTheme();
|
const {colors} = useTheme();
|
||||||
|
|
@ -13,7 +14,9 @@ export default function Page() {
|
||||||
|
|
||||||
const [categoryName, setCategoryName] = useState<string>("");
|
const [categoryName, setCategoryName] = useState<string>("");
|
||||||
const [categoryColor, setCategoryColor] = useState<string>('#' + Math.floor(Math.random()*16777215).toString(16));
|
const [categoryColor, setCategoryColor] = useState<string>('#' + Math.floor(Math.random()*16777215).toString(16));
|
||||||
const [selectedType, setSelectedType] = useState<string>("expense");
|
|
||||||
|
const [selectedType, setSelectedType] = useState<CategoryType>(CategoryType.EXPENSE);
|
||||||
|
|
||||||
const [amount, setAmount] = useState(0);
|
const [amount, setAmount] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -35,7 +38,7 @@ export default function Page() {
|
||||||
|
|
||||||
<TypeSelectorSwitch
|
<TypeSelectorSwitch
|
||||||
currentSelected={selectedType}
|
currentSelected={selectedType}
|
||||||
handleButtonPress={(type) => {
|
handleButtonPress={(type: CategoryType) => {
|
||||||
setSelectedType(type);
|
setSelectedType(type);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -51,8 +54,7 @@ export default function Page() {
|
||||||
router.back();
|
router.back();
|
||||||
}}/>
|
}}/>
|
||||||
<NavigationButton text="Save" onPress={() => {
|
<NavigationButton text="Save" onPress={() => {
|
||||||
addCategory(categoryName, categoryColor, selectedType, amount);
|
addCategory(categoryName, categoryColor, selectedType, amount).then(() => router.back());
|
||||||
router.back();
|
|
||||||
}}/>
|
}}/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { FontAwesome } from "@expo/vector-icons";
|
import { FontAwesome } from "@expo/vector-icons";
|
||||||
import { useRouter, useLocalSearchParams } from "expo-router";
|
import { useRouter, useLocalSearchParams } from "expo-router";
|
||||||
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
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 useFetch from "../../../hooks/useFetch";
|
||||||
import { useTheme } from "../../contexts/ThemeContext";
|
import { useTheme } from "../../contexts/ThemeContext";
|
||||||
import { SafeAreaView } from "react-native-safe-area-context";
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
|
|
@ -37,6 +36,7 @@ export default function Page() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={[styles.safeAreaView, {backgroundColor: colors.containerColor}]}>
|
<SafeAreaView style={[styles.safeAreaView, {backgroundColor: colors.containerColor}]}>
|
||||||
|
<Plus onPress={()=> router.push(`/expense/new?category=${category_guid}`)}/>
|
||||||
<TouchableOpacity style={styles.backContainer} onPress={handleBackButton}>
|
<TouchableOpacity style={styles.backContainer} onPress={handleBackButton}>
|
||||||
<FontAwesome style={styles.iconBack} name="arrow-left" size={35} color={colors.primaryText}/>
|
<FontAwesome style={styles.iconBack} name="arrow-left" size={35} color={colors.primaryText}/>
|
||||||
<Text style={[styles.backText, {color: colors.secondaryText}]}>Back</Text>
|
<Text style={[styles.backText, {color: colors.secondaryText}]}>Back</Text>
|
||||||
|
|
@ -65,6 +65,7 @@ export default function Page() {
|
||||||
ItemSeparatorComponent={() => {
|
ItemSeparatorComponent={() => {
|
||||||
return (<View style={styles.itemSeperator}/>);
|
return (<View style={styles.itemSeperator}/>);
|
||||||
}}
|
}}
|
||||||
|
ListEmptyComponent={EmptyListCompenent}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native";
|
import { StyleSheet, Text, TextInput, View } from "react-native";
|
||||||
import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components";
|
import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch, Plus } from "../../../components";
|
||||||
import { updateCategory } from "../../../services/database";
|
import useFetch from "../../../hooks/useFetch";
|
||||||
|
import { deleteCategory, deleteExpense, updateCategory } from "../../../services/database";
|
||||||
import { useTheme } from "../../contexts/ThemeContext";
|
import { useTheme } from "../../contexts/ThemeContext";
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
import { CategoryType } from "../../../types/dbItems";
|
||||||
|
|
||||||
const addCategory = () => {
|
const addCategory = () => {
|
||||||
const {colors} = useTheme();
|
const {colors} = useTheme();
|
||||||
|
|
@ -11,13 +14,14 @@ const addCategory = () => {
|
||||||
|
|
||||||
const [categoryName, setCategoryName] = useState(category_name.toString());
|
const [categoryName, setCategoryName] = useState(category_name.toString());
|
||||||
const [categoryColor, setCategoryColor] = useState(category_color.toString());
|
const [categoryColor, setCategoryColor] = useState(category_color.toString());
|
||||||
const [selectedType, setSelectedType] = useState(category_type.toString());
|
const [selectedType, setSelectedType] = useState<CategoryType>(category_type === "expense" ? CategoryType.EXPENSE : CategoryType.SAVING);
|
||||||
const [amount, setAmount] = useState(Number.parseFloat(category_amount.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 (
|
return (
|
||||||
<SafeAreaView style={[styles.safeAreaViewStyle, {backgroundColor: colors.backgroundColor}]}>
|
<SafeAreaView style={[styles.safeAreaViewStyle, {backgroundColor: colors.backgroundColor}]}>
|
||||||
<Text style={[styles.headingTextStyle, {color: colors.primaryText}]}>Edit Category</Text>
|
<Text style={[styles.headingTextStyle, {color: colors.primaryText}]}>Edit Category</Text>
|
||||||
|
|
||||||
<View style={[styles.containerStyle, {backgroundColor: colors.containerColor}]}>
|
<View style={[styles.containerStyle, {backgroundColor: colors.containerColor}]}>
|
||||||
<View style={[styles.textInputViewStyle, {backgroundColor: colors.elementDefaultColor}]}>
|
<View style={[styles.textInputViewStyle, {backgroundColor: colors.elementDefaultColor}]}>
|
||||||
<TextInput placeholder={"Enter Category Name..."} value={categoryName} placeholderTextColor={colors.secondaryText} style={[styles.textInputStyle, {color: colors.primaryText}]} onChangeText={(newName: string) => {
|
<TextInput placeholder={"Enter Category Name..."} value={categoryName} placeholderTextColor={colors.secondaryText} style={[styles.textInputStyle, {color: colors.primaryText}]} onChangeText={(newName: string) => {
|
||||||
|
|
@ -26,7 +30,7 @@ const addCategory = () => {
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.budgetInput}>
|
<View style={styles.budgetInput}>
|
||||||
<AutoDecimalInput label={"Allocated:"} onValueChange={(value) => {
|
<AutoDecimalInput label={"Allocated:"} initialValue={amount} onValueChange={(value) => {
|
||||||
setAmount(!Number.isNaN(Number.parseFloat(value)) ? Number.parseFloat(value) : 0);
|
setAmount(!Number.isNaN(Number.parseFloat(value)) ? Number.parseFloat(value) : 0);
|
||||||
}}/>
|
}}/>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -44,13 +48,27 @@ const addCategory = () => {
|
||||||
}}/>
|
}}/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.deleteButtonView}>
|
||||||
|
<NavigationButton text={"Delete Cateogry"} onPress={() => {
|
||||||
|
for (let i = 0; i < data.length;) {
|
||||||
|
deleteExpense(data[i].guid).then(() => {
|
||||||
|
i++
|
||||||
|
});
|
||||||
|
}
|
||||||
|
deleteCategory(category_guid.toString()).then(() => {
|
||||||
|
router.push("/(tabs)/(budget)");
|
||||||
|
});
|
||||||
|
}}/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.navigationButtonViewStyle}>
|
<View style={styles.navigationButtonViewStyle}>
|
||||||
<NavigationButton text="Back" onPress={() => {
|
<NavigationButton text="Back" onPress={() => {
|
||||||
router.back();
|
router.back();
|
||||||
}}/>
|
}}/>
|
||||||
<NavigationButton text="Save" onPress={() => {
|
<NavigationButton text="Save" onPress={() => {
|
||||||
updateCategory(category_guid.toString(), categoryName, categoryColor, selectedType, amount);
|
updateCategory(category_guid.toString(), categoryName, categoryColor, selectedType, amount).then(() => {
|
||||||
router.back();
|
router.back();
|
||||||
|
});
|
||||||
}}/>
|
}}/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -61,6 +79,9 @@ const addCategory = () => {
|
||||||
export default addCategory;
|
export default addCategory;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
deleteButtonView: {
|
||||||
|
|
||||||
|
},
|
||||||
containerStyle: {
|
containerStyle: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: 10,
|
margin: 10,
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,77 @@
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { router, useNavigation } from 'expo-router';
|
import { router, useNavigation } from 'expo-router';
|
||||||
import { useEffect, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import { FlatList } from 'react-native-gesture-handler';
|
import { FlatList } from 'react-native-gesture-handler';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { BudgetHeader, CategoryItem, LoadingSymbol, Plus } from '../../../components';
|
import { BudgetHeader, CategoryItem, EmptyListCompenent, LoadingSymbol, Plus, TextInputBar } from '../../../components';
|
||||||
import useFetch from '../../../hooks/useFetch';
|
import useFetch from '../../../hooks/useFetch';
|
||||||
import { useTheme } from '../../contexts/ThemeContext';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
|
import { useFocusEffect } from 'expo-router/src/useFocusEffect';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const {colors} = useTheme()
|
const {colors} = useTheme()
|
||||||
const containerColor = colors.containerColor;
|
const containerColor = colors.containerColor;
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const [selectedPage, setSelectedPage] = useState("noPageLoaded");
|
const [selectedPage, setSelectedPage] = useState<"expense"|"saving">("expense");
|
||||||
|
const [searchString, setSearchString] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
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"] : []});
|
|
||||||
|
// 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 GROUP BY c.guid", args: []});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reFetch()
|
reFetch()
|
||||||
}, [selectedPage]);
|
}, [selectedPage]);
|
||||||
|
|
||||||
|
|
||||||
const handlePageSelection = (page: string) => {
|
useEffect(() => {
|
||||||
if(page !== selectedPage) {
|
const unsubscribe = navigation.addListener("focus", () => {
|
||||||
setSelectedPage(page);
|
|
||||||
AsyncStorage.setItem("currentBudgetPage", page);
|
reFetch();
|
||||||
|
})
|
||||||
|
|
||||||
|
const t = () => {
|
||||||
|
console.log("unsubscribed")
|
||||||
|
unsubscribe();
|
||||||
}
|
}
|
||||||
|
return t;
|
||||||
|
}, [navigation])
|
||||||
|
|
||||||
|
|
||||||
|
const handlePageSelection = (page: "expense" | "saving") => {
|
||||||
|
setSelectedPage(page);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCategoryPress = (item: {[column: string]: any;}) => {
|
const handleCategoryPress = (item: {[column: string]: any;}) => {
|
||||||
router.push({pathname: "/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}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
return data.filter((item) => {
|
||||||
|
if(item.category_type !== selectedPage) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return item.category_name.toLowerCase().includes(searchString.toLowerCase());
|
||||||
|
})
|
||||||
|
}, [data, searchString, selectedPage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={[styles.safeAreaViewStyle, {backgroundColor: containerColor}]}>
|
<SafeAreaView style={[styles.safeAreaViewStyle, {backgroundColor: containerColor}]}>
|
||||||
<View style={{flex: 1, marginHorizontal: 10}}>
|
<View style={{flex: 1, marginHorizontal: 10}}>
|
||||||
<BudgetHeader selectedPage={selectedPage} handlePageSelection={handlePageSelection}/>
|
<BudgetHeader selectedPage={selectedPage} handlePageSelection={handlePageSelection}/>
|
||||||
|
<TextInputBar style={{marginBottom: 20}} value={searchString} onChangeText={setSearchString} placeholder='Search...'></TextInputBar>
|
||||||
|
|
||||||
<Plus onPress={() => {
|
<Plus onPress={() => {
|
||||||
router.push({pathname: '/addCategory'});
|
router.push({pathname: '/addCategory'});
|
||||||
|
|
@ -53,7 +79,7 @@ export default function Page() {
|
||||||
|
|
||||||
{isLoading ? (<LoadingSymbol/>) : (
|
{isLoading ? (<LoadingSymbol/>) : (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={data}
|
data={filteredData}
|
||||||
renderItem = {({item}) => <CategoryItem
|
renderItem = {({item}) => <CategoryItem
|
||||||
category={item.category_name}
|
category={item.category_name}
|
||||||
allocated_amount={item.allocated_amount ?? 0}
|
allocated_amount={item.allocated_amount ?? 0}
|
||||||
|
|
@ -67,6 +93,7 @@ export default function Page() {
|
||||||
ItemSeparatorComponent={() => {
|
ItemSeparatorComponent={() => {
|
||||||
return (<View style={styles.itemSeperatorStyle}/>);
|
return (<View style={styles.itemSeperatorStyle}/>);
|
||||||
}}
|
}}
|
||||||
|
ListEmptyComponent={EmptyListCompenent}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react
|
||||||
import { Calendar } from 'react-native-calendars';
|
import { Calendar } from 'react-native-calendars';
|
||||||
import { FlatList, RefreshControl } from 'react-native-gesture-handler';
|
import { FlatList, RefreshControl } from 'react-native-gesture-handler';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
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 useFetch from '../../../hooks/useFetch';
|
||||||
|
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
|
|
@ -126,7 +126,10 @@ export default function Page() {
|
||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|
||||||
const hanldeDaySelect = (date: string | undefined) => {
|
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) => {
|
const handleMonthSelect = (date: string | undefined) => {
|
||||||
setFilter({...filter, month: date, day: undefined});
|
setFilter({...filter, month: date, day: undefined});
|
||||||
|
|
@ -171,9 +174,6 @@ export default function Page() {
|
||||||
onDayPress={(date) => {
|
onDayPress={(date) => {
|
||||||
hanldeDaySelect(date.dateString)
|
hanldeDaySelect(date.dateString)
|
||||||
}}
|
}}
|
||||||
onDayLongPress={(date) => {
|
|
||||||
hanldeDaySelect(undefined)
|
|
||||||
}}
|
|
||||||
onMonthChange={(date) => {
|
onMonthChange={(date) => {
|
||||||
handleMonthSelect(date.dateString)
|
handleMonthSelect(date.dateString)
|
||||||
}}
|
}}
|
||||||
|
|
@ -201,6 +201,7 @@ export default function Page() {
|
||||||
}
|
}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
scrollEventThrottle={20}
|
scrollEventThrottle={20}
|
||||||
|
ListEmptyComponent={EmptyListCompenent}
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { SIZES } from '../../../constants/theme'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
import { ButtonSetting, ToggleSetting } from '../../../components'
|
import { ButtonSetting, ToggleSetting } from '../../../components'
|
||||||
import { useTheme } from '../../contexts/ThemeContext'
|
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 { useAuth } from '../../contexts/AuthContext'
|
||||||
import { TouchableOpacity } from 'react-native-gesture-handler'
|
import { TouchableOpacity } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
|
|
@ -50,17 +50,14 @@ export default function userSettings() {
|
||||||
<View style={styles.settingsContainer}>
|
<View style={styles.settingsContainer}>
|
||||||
<ToggleSetting settingsTitle='Use System Theme' value={systemTheme} onChange={handleSystemTheme}/>
|
<ToggleSetting settingsTitle='Use System Theme' value={systemTheme} onChange={handleSystemTheme}/>
|
||||||
<ToggleSetting settingsTitle='Dark Mode' disabled={systemTheme} onChange={handleDarkMode} value={darkMode}/>
|
<ToggleSetting settingsTitle='Dark Mode' disabled={systemTheme} onChange={handleDarkMode} value={darkMode}/>
|
||||||
<ButtonSetting settingsTitle='Reset Expenses' onPress={() => {
|
<ButtonSetting settingsTitle='Reset Database' onPress={()=> {
|
||||||
deleteExpenses().then(() => {
|
const deleteAll = async () => {
|
||||||
console.log("Expenses Deleted!");
|
await deleteExpenses();
|
||||||
})}}
|
await deleteCategories();
|
||||||
/>
|
|
||||||
<ButtonSetting settingsTitle='Populate Database' onPress={() => {
|
|
||||||
const del = async () => {
|
|
||||||
await DEV_populateDatabase()
|
|
||||||
}
|
}
|
||||||
del()
|
deleteAll();
|
||||||
}}/>
|
}}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,39 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View, ScrollView } from 'react-native';
|
|
||||||
|
import { Graph } from '../../../components';
|
||||||
import BudgetOverview from '../../../components/stats/BudgetOverview';
|
import BudgetOverview from '../../../components/stats/BudgetOverview';
|
||||||
import { useTheme } from '../../contexts/ThemeContext';
|
import BudgetRemaining from '../../../components/stats/SavingsOverview';
|
||||||
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 SavingsOverview from '../../../components/stats/SavingsOverview';
|
||||||
|
import Widget from '../../../components/stats/Widget';
|
||||||
import FinancialAdvice from '../../../components/stats/FinancialAdvice';
|
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() {
|
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 (
|
return (
|
||||||
<View style={styles.container}>
|
<SafeAreaView style={{flex:1}}>
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<DebugMenu />
|
{/* <DebugMenu/> */}
|
||||||
<Widget title='"Financial" Tips' backgroundColor='orange'>
|
<Widget title="Budget Overview"/>
|
||||||
<FinancialAdvice/>
|
<Graph/>
|
||||||
</Widget>
|
|
||||||
<Widget title="Budget Overview" />
|
|
||||||
<Widget>
|
|
||||||
<BudgetRemaining/>
|
|
||||||
</Widget>
|
|
||||||
<Widget title='Your Expenses so far' image={require('../../../assets/images/8b14el.jpg')}/>
|
|
||||||
<Widget>
|
<Widget>
|
||||||
<BudgetOverview/>
|
<BudgetOverview/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget>
|
<Widget>
|
||||||
<SavingsOverview/>
|
<SavingsOverview/>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
<Widget>
|
||||||
|
<BudgetTotal/>
|
||||||
|
</Widget>
|
||||||
|
{/* <Widget title='"Financial" Tips' backgroundColor='orange'>
|
||||||
|
<FinancialAdvice/>
|
||||||
|
</Widget> */}
|
||||||
|
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import { StyleSheet } from "react-native";
|
||||||
|
|
||||||
import { FontAwesome } from "@expo/vector-icons";
|
import { FontAwesome } from "@expo/vector-icons";
|
||||||
import { Redirect } from "expo-router";
|
import { Redirect } from "expo-router";
|
||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
import { useTheme } from "../contexts/ThemeContext";
|
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
|
import { useTheme } from "../contexts/ThemeContext";
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const {authState} = useAuth()
|
const {authState} = useAuth()
|
||||||
|
|
@ -34,7 +34,7 @@ export default function Layout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs sceneContainerStyle={styles.sceneContainer} screenOptions={screenOptions}>
|
<Tabs sceneContainerStyle={styles.sceneContainer} backBehavior="initialRoute" initialRouteName="(home)" screenOptions={screenOptions}>
|
||||||
<Tabs.Screen name="(budget)" options={
|
<Tabs.Screen name="(budget)" options={
|
||||||
{
|
{
|
||||||
tabBarLabel: "Budget",
|
tabBarLabel: "Budget",
|
||||||
|
|
@ -52,6 +52,7 @@ export default function Layout() {
|
||||||
<Tabs.Screen name="(stats)/index" options={
|
<Tabs.Screen name="(stats)/index" options={
|
||||||
{
|
{
|
||||||
tabBarLabel: "Stats",
|
tabBarLabel: "Stats",
|
||||||
|
unmountOnBlur: true,
|
||||||
tabBarIcon: ({size, color}) => (
|
tabBarIcon: ({size, color}) => (
|
||||||
<FontAwesome name="bar-chart" size={size} color={color}/>),
|
<FontAwesome name="bar-chart" size={size} color={color}/>),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
import { View, Text, StyleSheet, Alert } from 'react-native'
|
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 { SIZES } from '../../constants/theme'
|
||||||
import { useTheme } from '../contexts/ThemeContext'
|
import { useTheme } from '../contexts/ThemeContext'
|
||||||
import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../components'
|
import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../components'
|
||||||
import { Category } from '../../types/dbItems'
|
import { Category } from '../../types/dbItems'
|
||||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||||
import { addExpense } from '../../services/database'
|
import { addExpense, executeQuery } from '../../services/database'
|
||||||
import { SimpleDate } from '../../util/SimpleDate'
|
import { SimpleDate } from '../../util/SimpleDate'
|
||||||
import { useRouter } from 'expo-router'
|
import { useLocalSearchParams, useRouter } from 'expo-router'
|
||||||
|
|
||||||
export default function AddItem() {
|
export default function AddItem() {
|
||||||
const {colors} = useTheme();
|
const {colors} = useTheme();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const searchParams = useLocalSearchParams()
|
||||||
|
|
||||||
const [formatedValue, setFormatedValue] = useState<string>("");
|
const [formatedValue, setFormatedValue] = useState<string>("");
|
||||||
const [selectorModalVisible, setSelecorModalVisible] = useState<boolean>(false);
|
const [selectorModalVisible, setSelecorModalVisible] = useState<boolean>(false);
|
||||||
const [selectedCategory, setSelectedCategory] = useState<Category|undefined>()
|
const [selectedCategory, setSelectedCategory] = useState<Category|undefined>()
|
||||||
|
|
@ -50,6 +52,18 @@ export default function AddItem() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if(searchParams.category !== undefined){
|
||||||
|
executeQuery({sql: "SELECT * FROM category WHERE guid = ?", args: [searchParams.category]}).then((result) =>{
|
||||||
|
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 (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<CategorySelectorModal visible={selectorModalVisible} onRequestClose={()=>{setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}></CategorySelectorModal>
|
<CategorySelectorModal visible={selectorModalVisible} onRequestClose={()=>{setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}></CategorySelectorModal>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import TextInputBar from "../common/TextInputBar";
|
||||||
|
|
||||||
type BudgetHeaderProperties = {
|
type BudgetHeaderProperties = {
|
||||||
selectedPage: string,
|
selectedPage: string,
|
||||||
handlePageSelection: (page: string) => void,
|
handlePageSelection: (page: "expense" | "saving") => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageSelectorButtonProperties = {
|
type PageSelectorButtonProperties = {
|
||||||
|
|
@ -21,20 +21,19 @@ const BudgetHeader = (properties: BudgetHeaderProperties) => {
|
||||||
<View style={styles.containerStyle}>
|
<View style={styles.containerStyle}>
|
||||||
<PageSelectorButton
|
<PageSelectorButton
|
||||||
label="Expenses"
|
label="Expenses"
|
||||||
isSelected={properties.selectedPage === "expenses"}
|
isSelected={properties.selectedPage === "expense"}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
properties.handlePageSelection("expenses")
|
properties.handlePageSelection("expense")
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<PageSelectorButton
|
<PageSelectorButton
|
||||||
label="Savings"
|
label="Savings"
|
||||||
isSelected={properties.selectedPage === "savings"}
|
isSelected={properties.selectedPage === "saving"}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
properties.handlePageSelection("savings");
|
properties.handlePageSelection("saving");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<TextInputBar style={styles.searchBarStyle} placeholder='Search...'></TextInputBar>
|
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
|
||||||
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||||
import { useTheme } from "../../app/contexts/ThemeContext";
|
import { useTheme } from "../../app/contexts/ThemeContext";
|
||||||
|
import { CategoryType } from "../../types/dbItems";
|
||||||
|
|
||||||
export type TypeSelectorSwitchProperties = {
|
export type TypeSelectorSwitchProperties = {
|
||||||
handleButtonPress: (type: string) => void,
|
handleButtonPress: (type: CategoryType) => void,
|
||||||
currentSelected: string,
|
currentSelected: CategoryType,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TypeSelectorSwitch = (properties: TypeSelectorSwitchProperties) => {
|
const TypeSelectorSwitch = (properties: TypeSelectorSwitchProperties) => {
|
||||||
|
|
@ -14,17 +15,17 @@ const TypeSelectorSwitch = (properties: TypeSelectorSwitchProperties) => {
|
||||||
<View style={styles.containerStyle}>
|
<View style={styles.containerStyle}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
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}]
|
||||||
}>
|
}>
|
||||||
<Text style={[styles.textStyle, {color: colors.primaryText}]}>Expenses</Text>
|
<Text style={[styles.textStyle, {color: colors.primaryText}]}>Expenses</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
properties.handleButtonPress("saving");
|
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}]
|
||||||
}>
|
}>
|
||||||
<Text style={[styles.textStyle, {color: colors.primaryText}]}>Savings</Text>
|
<Text style={[styles.textStyle, {color: colors.primaryText}]}>Savings</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
|
||||||
24
components/common/EmptyListCompenent.tsx
Normal file
24
components/common/EmptyListCompenent.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<View style={[styles.container, {backgroundColor: colors.backgroundColor}]}>
|
||||||
|
<Text style={[{fontSize: 20}, {color: colors.primaryText}]}>No matching Data</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: 70,
|
||||||
|
borderRadius: 20,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default EmptyListCompenent
|
||||||
|
|
@ -42,7 +42,7 @@ export default function TextInputBar(props: SearchBarProps) {
|
||||||
// changed styles.container to containerStyle
|
// changed styles.container to containerStyle
|
||||||
return (
|
return (
|
||||||
<View style={[containerStyle, props.style]}>
|
<View style={[containerStyle, props.style]}>
|
||||||
<TextInput placeholderTextColor={colors.secondaryText} onChangeText = {handleChange} style={[{fontSize: SIZES.normal, height: "100%", color:colors.primaryText}, styles.TextInput]} autoCorrect={false} keyboardType='default' placeholder={props.placeholder} value={props.value} onEndEditing={()=>setIsactive(false)}/>
|
<TextInput placeholderTextColor={colors.secondaryText} onChangeText = {handleChange} style={[{fontSize: SIZES.normal, height: "100%", color:colors.primaryText}, styles.TextInput]} autoCorrect={false} keyboardType='default' placeholder={props.placeholder} value={props.value} onPressIn={()=>(setIsactive(true))} onEndEditing={()=>setIsactive(false)}/>
|
||||||
|
|
||||||
{isActive &&
|
{isActive &&
|
||||||
<TouchableOpacity style={styles.cancel} onPress={()=>{
|
<TouchableOpacity style={styles.cancel} onPress={()=>{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import CategoryListItem from '../common/CategoryListItem';
|
||||||
import { SIZES } from '../../constants/theme';
|
import { SIZES } from '../../constants/theme';
|
||||||
import useFetch from '../../hooks/useFetch';
|
import useFetch from '../../hooks/useFetch';
|
||||||
import TextInputBar from '../common/TextInputBar';
|
import TextInputBar from '../common/TextInputBar';
|
||||||
|
import EmptyListCompenent from '../common/EmptyListCompenent';
|
||||||
|
|
||||||
|
|
||||||
interface CategorySelectorModalProps{
|
interface CategorySelectorModalProps{
|
||||||
|
|
@ -43,7 +44,7 @@ const CategorySelectorModal: React.FC<CategorySelectorModalProps> = (props : Cat
|
||||||
}, [visible])
|
}, [visible])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal visible={visible} transparent={true} onRequestClose={props.onRequestClose}>
|
<Modal visible={visible} transparent={true} onRequestClose={props.onRequestClose} animationType='slide'>
|
||||||
<View style={styles.main}>
|
<View style={styles.main}>
|
||||||
<View style={[styles.modal, {backgroundColor: colors.containerColor}]}>
|
<View style={[styles.modal, {backgroundColor: colors.containerColor}]}>
|
||||||
<View>
|
<View>
|
||||||
|
|
@ -57,6 +58,7 @@ const CategorySelectorModal: React.FC<CategorySelectorModalProps> = (props : Cat
|
||||||
ItemSeparatorComponent={() => <View style={styles.itemSeperatorStyle}/>}
|
ItemSeparatorComponent={() => <View style={styles.itemSeperatorStyle}/>}
|
||||||
ListFooterComponent={() => <View style={styles.itemSeperatorStyle}/>}
|
ListFooterComponent={() => <View style={styles.itemSeperatorStyle}/>}
|
||||||
keyboardShouldPersistTaps="always"
|
keyboardShouldPersistTaps="always"
|
||||||
|
ListEmptyComponent={EmptyListCompenent}
|
||||||
>
|
>
|
||||||
</FlatList>
|
</FlatList>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ColorValue, StyleSheet, Text, View } from 'react-native';
|
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 { SIZES } from '../../constants/theme';
|
||||||
import CustomCard from "../common/CustomCard";
|
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}
|
//export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string}
|
||||||
interface ExpenseItemProps {
|
interface ExpenseItemProps {
|
||||||
|
|
@ -44,9 +44,10 @@ export default function ExpenseItem(itemProps : ExpenseItemProps) {
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.valueSection, {backgroundColor: colors.backgroundColor}]}>
|
<View style={[styles.valueSection, {backgroundColor: colors.backgroundColor}]}>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
|
paddingRight: 10,
|
||||||
fontSize: SIZES.xxLarge,
|
fontSize: SIZES.xxLarge,
|
||||||
color: colors.primaryText
|
color: colors.primaryText
|
||||||
}} numberOfLines={1}>{itemProps.value}</Text>
|
}} numberOfLines={1}>{itemProps.value + " €"}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,14 @@ import CategorySelectorModal from "./expense/CategorySelectorModal"
|
||||||
import DateSelectorButton from "./expense/DateSelectorButton"
|
import DateSelectorButton from "./expense/DateSelectorButton"
|
||||||
|
|
||||||
//common
|
//common
|
||||||
|
import AutoDecimalInput from "./common/AutoDecimalInput"
|
||||||
import CustomCard from "./common/CustomCard"
|
import CustomCard from "./common/CustomCard"
|
||||||
|
import RoundedButton from "./common/RoundedButton"
|
||||||
|
import TextInputBar from "./common/TextInputBar"
|
||||||
import NavigationButton from "./common/button"
|
import NavigationButton from "./common/button"
|
||||||
import LoadingSymbol from "./common/loadingSymbol"
|
import LoadingSymbol from "./common/loadingSymbol"
|
||||||
import Plus from "./common/plus"
|
import Plus from "./common/plus"
|
||||||
import TextInputBar from "./common/TextInputBar"
|
import EmptyListCompenent from "./common/EmptyListCompenent"
|
||||||
import AutoDecimalInput from "./common/AutoDecimalInput"
|
|
||||||
import RoundedButton from "./common/RoundedButton"
|
|
||||||
|
|
||||||
//login
|
//login
|
||||||
import BudgetHeader from "./budget/budgetHeader"
|
import BudgetHeader from "./budget/budgetHeader"
|
||||||
|
|
@ -26,25 +27,30 @@ import TypeSelectorSwitch from "./budget/typeSelectorSwitch"
|
||||||
//login
|
//login
|
||||||
import Input from "./login/input"
|
import Input from "./login/input"
|
||||||
|
|
||||||
|
//stats
|
||||||
|
import Graph from "./stats/Graph"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
AutoDecimalInput,
|
||||||
BudgetHeader,
|
BudgetHeader,
|
||||||
ButtonSetting,
|
ButtonSetting,
|
||||||
CustomCard,
|
CategoryItem,
|
||||||
ExpenseItem,
|
|
||||||
Input,
|
|
||||||
LoadingSymbol,
|
|
||||||
Plus,
|
|
||||||
TextInputBar,
|
|
||||||
ToggleSetting,
|
|
||||||
Welcome,
|
|
||||||
AutoDecimalInput,
|
|
||||||
CategorySelector,
|
CategorySelector,
|
||||||
CategorySelectorModal,
|
CategorySelectorModal,
|
||||||
|
CustomCard,
|
||||||
|
CustomColorPicker,
|
||||||
DateSelectorButton,
|
DateSelectorButton,
|
||||||
RoundedButton,
|
EmptyListCompenent,
|
||||||
CategoryItem,
|
ExpenseItem,
|
||||||
TypeSelectorSwitch,
|
Graph,
|
||||||
|
Input,
|
||||||
|
LoadingSymbol,
|
||||||
NavigationButton,
|
NavigationButton,
|
||||||
CustomColorPicker
|
Plus,
|
||||||
|
RoundedButton,
|
||||||
|
TextInputBar,
|
||||||
|
ToggleSetting,
|
||||||
|
TypeSelectorSwitch,
|
||||||
|
Welcome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,56 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
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 { useTheme } from '../../app/contexts/ThemeContext';
|
||||||
import useFetch from '../../hooks/useFetch';
|
import useFetch from '../../hooks/useFetch';
|
||||||
|
import { CategoryType } from '../../types/dbItems';
|
||||||
|
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<BudgetTotalProps> = ({ goodColor = 'green', badColor = 'red' }) => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const [spent, setSpent] = useState(0);
|
const { data, isLoading } = useCategoryData(CategoryType.EXPENSE);
|
||||||
const [budget, setBudget] = useState(0);
|
|
||||||
|
|
||||||
const spentQuery = {
|
const { total, expenseTotal } = data;
|
||||||
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 = {
|
const remaining = total - expenseTotal;
|
||||||
sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'",
|
|
||||||
args: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: spentData, isLoading: spentLoading } = useFetch(spentQuery);
|
if (isLoading) {
|
||||||
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) {
|
|
||||||
return <Text>Loading...</Text>;
|
return <Text>Loading...</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<Text style={[styles.text, { color: colors.primaryText }]}>
|
||||||
<Text style={styles.text}>
|
<>
|
||||||
You have spent <Text style={styles.boldText}>{spent.toFixed(2)}€</Text> out of your budget of <Text style={styles.boldText}>{budget.toFixed(2)}€</Text>.
|
You have spent <Text style={[styles.boldText, { color: goodColor }]}>{expenseTotal.toFixed(2)}€</Text> out of your Budget of <Text style={[styles.boldText]}>{total.toFixed(2)}€ </Text>
|
||||||
</Text>
|
</>
|
||||||
</View>
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BudgetOverview;
|
export default BudgetTotal;
|
||||||
|
|
@ -1,55 +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';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
text: {
|
|
||||||
fontSize: 26,
|
|
||||||
},
|
|
||||||
boldText: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (spentLoading || budgetLoading) {
|
|
||||||
return <Text>Loading...</Text>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text style={[styles.text, {color: colors.primaryText}]}>
|
|
||||||
You have <Text style={styles.boldText}>{remaining.toFixed(2)}€</Text> left.
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BudgetRemaining;
|
|
||||||
62
components/stats/BudgetTotal.tsx
Normal file
62
components/stats/BudgetTotal.tsx
Normal file
|
|
@ -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 '../../types/dbItems';
|
||||||
|
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<BudgetTotalProps> = ({ goodColor = 'green', badColor = 'red' }) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const { data, isLoading } = useCategoryData(CategoryType.EXPENSE);
|
||||||
|
|
||||||
|
const { total, expenseTotal } = data;
|
||||||
|
|
||||||
|
const remaining = total - expenseTotal;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Text>Loading...</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text style={[styles.text, { color: colors.primaryText }]}>
|
||||||
|
{remaining >= 0 ? (
|
||||||
|
<>
|
||||||
|
Your remaining overall Budget is <Text style={[styles.boldText, { color: goodColor }]}>{remaining.toFixed(2)}€</Text>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Your Budget is overspent by <Text style={[styles.boldText, { color: badColor }]}>-{Math.abs(remaining).toFixed(2)}€</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BudgetTotal;
|
||||||
30
components/stats/Graph.tsx
Normal file
30
components/stats/Graph.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
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} = 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 (
|
||||||
|
<View style={{backgroundColor: colors.backgroundColor, borderRadius: 10, margin: 10}}>
|
||||||
|
<PieChart
|
||||||
|
data={acctual_data}
|
||||||
|
width={Dimensions.get("window").width}
|
||||||
|
height={240}
|
||||||
|
chartConfig={{
|
||||||
|
color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
|
||||||
|
}}
|
||||||
|
backgroundColor="transparent"
|
||||||
|
accessor="total"
|
||||||
|
paddingLeft="15"
|
||||||
|
avoidFalseZero={true}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Graph;
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
//honestly just fuck graphs
|
|
||||||
|
|
@ -1,62 +1,62 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
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 { useTheme } from '../../app/contexts/ThemeContext';
|
||||||
import useFetch from '../../hooks/useFetch';
|
import useFetch from '../../hooks/useFetch';
|
||||||
|
import { CategoryType } from '../../types/dbItems';
|
||||||
|
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<BudgetTotalProps> = ({ goodColor = 'green', badColor = 'red' }) => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const [saved, setSaved] = useState(0);
|
const { data, isLoading } = useCategoryData(CategoryType.SAVING);
|
||||||
const [goal, setGoal] = useState(0);
|
|
||||||
|
|
||||||
const savedQuery = {
|
const { total, expenseTotal } = data;
|
||||||
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 = {
|
const remaining = total - expenseTotal;
|
||||||
sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'",
|
|
||||||
args: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: savedData, isLoading: savedLoading } = useFetch(savedQuery);
|
if (isLoading) {
|
||||||
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 <Text>Loading...</Text>;
|
return <Text>Loading...</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<Text style={[styles.text, { color: colors.primaryText }]}>
|
||||||
<Text style={styles.text}>
|
{remaining >= 0 ? (
|
||||||
You have saved <Text style={styles.boldText}>{saved.toFixed(2)}€</Text> out of your goal of <Text style={styles.boldText}>{goal.toFixed(2)}€</Text>.
|
<>
|
||||||
</Text>
|
You have saved <Text style={[styles.boldText, { color: badColor }]}>{expenseTotal.toFixed(2)}€</Text> out of your Goal of <Text style={[styles.boldText]}>{total.toFixed(2)}€</Text>
|
||||||
</View>
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
You have surpassed your Savings Goal of <Text style={[styles.boldText]}>{total.toFixed(2)}€</Text> by <Text style={[styles.boldText, { color: goodColor }]}>{Math.abs(remaining).toFixed(2)}€</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SavingsOverview;
|
export default BudgetTotal;
|
||||||
33
hooks/useCategoryData.ts
Normal file
33
hooks/useCategoryData.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import useFetch from './useFetch';
|
||||||
|
import { CategoryType } from '../types/dbItems';
|
||||||
|
|
||||||
|
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 };
|
||||||
|
};
|
||||||
|
|
@ -28,7 +28,6 @@ const useFetch = (query: Query) => {
|
||||||
|
|
||||||
|
|
||||||
const reFetch = () => {
|
const reFetch = () => {
|
||||||
console.log("refetch called")
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
executeQuery(query).then((result) => {
|
executeQuery(query).then((result) => {
|
||||||
if("rows" in result[0]) {
|
if("rows" in result[0]) {
|
||||||
|
|
@ -36,7 +35,6 @@ const useFetch = (query: Query) => {
|
||||||
if(result[0]["rows"].length == 0){
|
if(result[0]["rows"].length == 0){
|
||||||
setIsEmptyResult(true);
|
setIsEmptyResult(true);
|
||||||
}
|
}
|
||||||
console.log("len", result[0]["rows"].length)
|
|
||||||
}
|
}
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
console.error("Fetching data from database has failed: ", error);
|
console.error("Fetching data from database has failed: ", error);
|
||||||
|
|
|
||||||
713
package-lock.json
generated
713
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^13.0.0",
|
"@expo/vector-icons": "^13.0.0",
|
||||||
"@react-native-async-storage/async-storage": "1.18.2",
|
"@react-native-async-storage/async-storage": "1.18.2",
|
||||||
|
"@react-native-community/datetimepicker": "7.2.0",
|
||||||
"@react-navigation/native": "^6.0.2",
|
"@react-navigation/native": "^6.0.2",
|
||||||
"expo": "~49.0.15",
|
"expo": "~49.0.15",
|
||||||
"expo-font": "~11.4.0",
|
"expo-font": "~11.4.0",
|
||||||
|
|
@ -35,14 +36,15 @@
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-native": "0.72.6",
|
"react-native": "0.72.6",
|
||||||
"react-native-calendars": "^1.1303.0",
|
"react-native-calendars": "^1.1303.0",
|
||||||
|
"react-native-chart-kit": "^6.12.0",
|
||||||
"react-native-gesture-handler": "~2.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-safe-area-context": "4.6.3",
|
||||||
"react-native-screens": "~3.22.0",
|
"react-native-screens": "~3.22.0",
|
||||||
|
"react-native-svg": "13.9.0",
|
||||||
"react-native-uuid": "^2.0.1",
|
"react-native-uuid": "^2.0.1",
|
||||||
"react-native-web": "~0.19.6",
|
"react-native-web": "~0.19.6",
|
||||||
"reanimated-color-picker": "^2.4.2",
|
"reanimated-color-picker": "^2.4.2"
|
||||||
"@react-native-community/datetimepicker": "7.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { View, Button, Alert } from 'react-native';
|
import { View, Button, Alert } from 'react-native';
|
||||||
import { addCategory, addExpense, deleteExpenses, deleteCategories, DEV_populateDatabase, deleteDatabase } from './database';
|
import { addCategory, addExpense, deleteExpenses, deleteCategories, DEV_populateDatabase, deleteDatabase } from './database';
|
||||||
import uuid from 'react-native-uuid';
|
import uuid from 'react-native-uuid';
|
||||||
|
import { CategoryType } from '../types/dbItems';
|
||||||
|
|
||||||
const randomColors = ["red", "blue", "green", "purple", "yellow"];
|
const randomColors = ["red", "blue", "green", "purple", "yellow"];
|
||||||
|
|
||||||
|
|
@ -46,7 +47,7 @@ const DebugMenu = () => {
|
||||||
const allocated_amount = getRandomNumber();
|
const allocated_amount = getRandomNumber();
|
||||||
const type = "expense";
|
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}`))
|
.then(() => Alert.alert("Category Added", `Name: ${name}, Color: ${color}`))
|
||||||
.catch((error: any) => console.error("Error adding category: ", error));
|
.catch((error: any) => console.error("Error adding category: ", error));
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,18 @@ import uuid from "react-native-uuid";
|
||||||
|
|
||||||
import { Query } from "expo-sqlite";
|
import { Query } from "expo-sqlite";
|
||||||
import { SimpleDate } from "../util/SimpleDate";
|
import { SimpleDate } from "../util/SimpleDate";
|
||||||
|
import { CategoryType } from "../types/dbItems";
|
||||||
|
|
||||||
let db: SQLite.SQLiteDatabase;
|
let db: SQLite.SQLiteDatabase;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const initDatabase = async () => {
|
export const initDatabase = async () => {
|
||||||
db = SQLite.openDatabase("interactive_systeme.db");
|
db = SQLite.openDatabase("interactive_systeme.db");
|
||||||
try {
|
try {
|
||||||
await db.transactionAsync(async (tx: SQLite.SQLTransactionAsync) => {
|
await db.transactionAsync(async (tx: SQLite.SQLTransactionAsync) => {
|
||||||
await tx.executeSqlAsync(
|
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(
|
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));"
|
"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));"
|
||||||
|
|
@ -24,11 +27,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 {
|
try {
|
||||||
await db.transactionAsync(async (tx) => {
|
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) {
|
} catch (error) {
|
||||||
console.log("Error updating category: ", error);
|
console.log("Error updating category: ", error);
|
||||||
|
|
@ -36,7 +39,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)
|
//needs user input validation for type and color (should be validated by this function)
|
||||||
|
|
||||||
|
|
@ -45,7 +48,7 @@ export const addCategory = async (name: string, color: string, type: string, all
|
||||||
try {
|
try {
|
||||||
await db.transactionAsync(async (tx) => {
|
await db.transactionAsync(async (tx) => {
|
||||||
await tx.executeSqlAsync("INSERT INTO category (guid, name, color, type, allocated_amount) VALUES (?, ?, ?, ?, ?);",
|
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) {
|
} catch (error) {
|
||||||
|
|
@ -171,8 +174,9 @@ export const DEV_populateDatabase = async () => {
|
||||||
|
|
||||||
for(let i=0; i < 5; i++){
|
for(let i=0; i < 5; i++){
|
||||||
let random = Math.floor(Math.random() * colors.length);
|
let random = Math.floor(Math.random() * colors.length);
|
||||||
await addCategory(`Category ${i}`, colors[random], "budget", 50)
|
await addCategory(`Category ${i}`, colors[random], CategoryType.EXPENSE, 50)
|
||||||
await addCategory(`Category ${i+6}`, colors[random], "budget", 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:[]})
|
const result = await executeQuery({sql:"SELECT * from category", args:[]})
|
||||||
let categories: {[column: string]: any}[];
|
let categories: {[column: string]: any}[];
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
export enum CategoryType {
|
||||||
|
SAVING = "saving",
|
||||||
|
EXPENSE = "expense",
|
||||||
|
}
|
||||||
export interface Category {
|
export interface Category {
|
||||||
guid? : string;
|
guid? : string;
|
||||||
name? : string;
|
name? : string;
|
||||||
|
|
|
||||||
Reference in a new issue