diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 971359c..e561888 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -24,12 +24,12 @@ Our design philosophy was rooted in simplicity and functionality. We aimed to cr - **Component Development**: Built reusable components for uniformity and efficiency. - **State Management and Hooks**: Implemented custom hooks for state management, ensuring smooth data flow and component reusability. -- [ ] **Stage 4: Implementation-Phase 2** +- [x] **Stage 4: Implementation-Phase 2** - **Refining Components**: Enhancing component functionality and aesthetics. - **Optimization and Debugging**: Identifying and fixing bugs, optimizing code for better performance. - **Expanding Features**: Implementing additional pages and components to enrich the application. -- [ ] **Stage 5: Final Implementation-Phase** +- [x] **Stage 5: Final Implementation-Phase** - **UI Touch-Ups**: Final tweaks to the user interface, ensuring a polished look and feel. - **Finalization**: Wrapping up the project, finalizing drafts, and completing outstanding tasks. diff --git a/README.md b/README.md index b5967dd..378d956 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,16 @@ A more convenient way to run Finanzfuchs is to download the prebuilt apk which c ## Usage -- **Adding Expenses**: Press the plus button to add expenses. -- **Removing Expenses**: Go to the budget tab and press the red button in the middle to reset expenses. -- **Login Screen**: Go to the login screen by pressing the profile picture in the top left of the home screen. +- **Adding Expenses(/Savings)**: Press the plus button to add expenses. +- **Removing Expenses(/Savings)**: Go to the certain expense and press it. You will then be directed to the edit expense tab, where you can change the expense as you wish. +- **Adding Categories**: Go to the budget tab and press the plus button to add a new category with custom color. +- **Removing Categories**: Go to a certain category by pressing on the budget tab and choosing one. At the top is a big button with a pencil on it, which when you press it takes you to the edit category tab, where you can customize the category or remove it completely with all expenses it contains. +- **Searching for Expenses, Savings and Categories**: At the Home Screen, the Budget tab or in a certain category you can search for certain expenses, categories or savings by writing its name in the Search bar. +- **Home Screen**: Go to the login screen by pressing the profile picture in the top left of the home screen. Furthermore, at the bottom there are the last expenses, which were added. + - **Calendar**: The calendar has little colored dots under certain days indicating, that at that day an expense has been added. By pressing at a day the list of expenses below it will be sorted out and only showing the expenses which were added at that day. + - **List of Expenses**: The list shows the most recently added expenses which can be searched through and filtered by using the calendar. + - **Profile**: By pressing at the profile picture on the home screen you get to the My Profile tab where you can activate the dark mode, reset the database and sign out of the app. +- **Stats**: Here is a Graph showing an Overview of your Budget and the sums of all expenses and savings. ## Team diff --git a/app/(tabs)/budget/_layout.tsx b/app/(tabs)/(budget)/_layout.tsx similarity index 100% rename from app/(tabs)/budget/_layout.tsx rename to app/(tabs)/(budget)/_layout.tsx diff --git a/app/(tabs)/budget/addCategory.tsx b/app/(tabs)/(budget)/addCategory.tsx similarity index 92% rename from app/(tabs)/budget/addCategory.tsx rename to app/(tabs)/(budget)/addCategory.tsx index bdc1a86..695a708 100644 --- a/app/(tabs)/budget/addCategory.tsx +++ b/app/(tabs)/(budget)/addCategory.tsx @@ -5,6 +5,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; import { addCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; +import { CategoryType } from "../../../types/dbItems"; export default function Page() { const {colors} = useTheme(); @@ -13,7 +14,9 @@ export default function Page() { const [categoryName, setCategoryName] = useState(""); const [categoryColor, setCategoryColor] = useState('#' + Math.floor(Math.random()*16777215).toString(16)); - const [selectedType, setSelectedType] = useState("expense"); + + const [selectedType, setSelectedType] = useState(CategoryType.EXPENSE); + const [amount, setAmount] = useState(0); return ( @@ -35,7 +38,7 @@ export default function Page() { { + handleButtonPress={(type: CategoryType) => { setSelectedType(type); }} /> @@ -51,8 +54,7 @@ export default function Page() { router.back(); }}/> { - addCategory(categoryName, categoryColor, selectedType, amount); - router.back(); + addCategory(categoryName, categoryColor, selectedType, amount).then(() => router.back()); }}/> diff --git a/app/(tabs)/budget/category.tsx b/app/(tabs)/(budget)/category.tsx similarity index 80% rename from app/(tabs)/budget/category.tsx rename to app/(tabs)/(budget)/category.tsx index 3a88f32..93d0374 100644 --- a/app/(tabs)/budget/category.tsx +++ b/app/(tabs)/(budget)/category.tsx @@ -1,14 +1,16 @@ import { FontAwesome } from "@expo/vector-icons"; import { useRouter, useLocalSearchParams } from "expo-router"; import { FlatList, StyleSheet, Text, TouchableOpacity, View } from "react-native"; -import { ExpenseItem, LoadingSymbol, TextInputBar } from "../../../components"; - +import { ExpenseItem, LoadingSymbol, TextInputBar, EmptyListCompenent, Plus } from "../../../components"; import useFetch from "../../../hooks/useFetch"; import { useTheme } from "../../contexts/ThemeContext"; import { SafeAreaView } from "react-native-safe-area-context"; +import { useNavigation } from "expo-router/src/useNavigation"; +import { useEffect } from "react"; export default function Page() { const router = useRouter(); + const navigation = useNavigation(); const {colors} = useTheme(); const {category_guid} = useLocalSearchParams(); @@ -17,15 +19,24 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]}); const handleEditCategory = () => { - router.push({pathname: "/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); + router.push({pathname: "/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); } const handleBackButton = () => { router.back(); } + useEffect(() => { + console.log("useEffect called") + const unsubscribe = navigation.addListener("focus", () => { + reFetch(); + }) + return unsubscribe; + }, [navigation]) + return ( + router.push(`/expense/new?category=${category_guid}`)}/> Back @@ -40,7 +51,7 @@ export default function Page() { {isLoading ? () : ( - router.push(`/home/expense/${item.guid}`)} + onPress={()=>router.push(`/expense/${item.expense_guid}`)} />} - keyExtractor={item => item.guid} + keyExtractor={item => item.expense_guid} ItemSeparatorComponent={() => { return (); }} + ListEmptyComponent={EmptyListCompenent} /> )} diff --git a/app/(tabs)/budget/editCategory.tsx b/app/(tabs)/(budget)/editCategory.tsx similarity index 68% rename from app/(tabs)/budget/editCategory.tsx rename to app/(tabs)/(budget)/editCategory.tsx index 60705fa..19dc3e0 100644 --- a/app/(tabs)/budget/editCategory.tsx +++ b/app/(tabs)/(budget)/editCategory.tsx @@ -1,9 +1,12 @@ import { router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; -import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native"; -import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components"; -import { updateCategory } from "../../../services/database"; +import { StyleSheet, Text, TextInput, View } from "react-native"; +import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch, Plus } from "../../../components"; +import useFetch from "../../../hooks/useFetch"; +import { deleteCategory, deleteExpense, updateCategory } from "../../../services/database"; import { useTheme } from "../../contexts/ThemeContext"; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { CategoryType } from "../../../types/dbItems"; const addCategory = () => { const {colors} = useTheme(); @@ -11,13 +14,14 @@ const addCategory = () => { const [categoryName, setCategoryName] = useState(category_name.toString()); const [categoryColor, setCategoryColor] = useState(category_color.toString()); - const [selectedType, setSelectedType] = useState(category_type.toString()); + const [selectedType, setSelectedType] = useState(category_type === "expense" ? CategoryType.EXPENSE : CategoryType.SAVING); const [amount, setAmount] = useState(Number.parseFloat(category_amount.toString())); + const {data} = useFetch({sql: "SELECT * FROM expense WHERE category_guid = ?", args: [category_guid.toString()]}); + return ( Edit Category - { @@ -26,7 +30,7 @@ const addCategory = () => { - { + { setAmount(!Number.isNaN(Number.parseFloat(value)) ? Number.parseFloat(value) : 0); }}/> @@ -44,13 +48,27 @@ const addCategory = () => { }}/> + + { + for (let i = 0; i < data.length;) { + deleteExpense(data[i].guid).then(() => { + i++ + }); + } + deleteCategory(category_guid.toString()).then(() => { + router.push("/(tabs)/(budget)"); + }); + }}/> + + { router.back(); }}/> { - updateCategory(category_guid.toString(), categoryName, categoryColor, selectedType, amount); - router.back(); + updateCategory(category_guid.toString(), categoryName, categoryColor, selectedType, amount).then(() => { + router.back(); + }); }}/> @@ -61,6 +79,9 @@ const addCategory = () => { export default addCategory; const styles = StyleSheet.create({ + deleteButtonView: { + + }, containerStyle: { flex: 1, margin: 10, diff --git a/app/(tabs)/(budget)/index.tsx b/app/(tabs)/(budget)/index.tsx new file mode 100644 index 0000000..fce6f05 --- /dev/null +++ b/app/(tabs)/(budget)/index.tsx @@ -0,0 +1,111 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { router, useNavigation } from 'expo-router'; +import React, { useEffect, useMemo, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { FlatList } from 'react-native-gesture-handler'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { BudgetHeader, CategoryItem, EmptyListCompenent, LoadingSymbol, Plus, TextInputBar } from '../../../components'; +import useFetch from '../../../hooks/useFetch'; +import { useTheme } from '../../contexts/ThemeContext'; +import { useFocusEffect } from 'expo-router/src/useFocusEffect'; + +export default function Page() { + const {colors} = useTheme() + const containerColor = colors.containerColor; + const navigation = useNavigation(); + const [selectedPage, setSelectedPage] = useState<"expense"|"saving">("expense"); + const [searchString, setSearchString] = useState(""); + + + + // useEffect(() => { + // console.log("initial effect called") + // AsyncStorage.getItem("currentBudgetPage").then((page) => { + // if(page === "expenses" || page === "savings") { + // setSelectedPage(page); + // } + // }).catch((error) => { + // console.log("Error fetching previous page from Async Storage:", error); + // }) + // }, []); + + const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM category c LEFT JOIN expense e ON e.category_guid = c.guid GROUP BY c.guid", args: []}); + + useEffect(() => { + reFetch() + }, [selectedPage]); + + + useEffect(() => { + const unsubscribe = navigation.addListener("focus", () => { + + reFetch(); + }) + + const t = () => { + console.log("unsubscribed") + unsubscribe(); + } + return t; + }, [navigation]) + + + const handlePageSelection = (page: "expense" | "saving") => { + setSelectedPage(page); + }; + + const handleCategoryPress = (item: {[column: string]: any;}) => { + 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 ( + + + + + + { + router.push({pathname: '/addCategory'}); + }}/> + + {isLoading ? () : ( + { + handleCategoryPress(item); + }}/>} + keyExtractor={item => item.category_guid} + ItemSeparatorComponent={() => { + return (); + }} + ListEmptyComponent={EmptyListCompenent} + /> + )} + + + ); +} + +const styles = StyleSheet.create({ + safeAreaViewStyle: { + flex: 1, + }, + itemSeperatorStyle: { + marginVertical: 5, + }, +}); \ No newline at end of file diff --git a/app/(tabs)/home/_layout.tsx b/app/(tabs)/(home)/_layout.tsx similarity index 90% rename from app/(tabs)/home/_layout.tsx rename to app/(tabs)/(home)/_layout.tsx index ba7964c..8e16222 100644 --- a/app/(tabs)/home/_layout.tsx +++ b/app/(tabs)/(home)/_layout.tsx @@ -24,9 +24,6 @@ export default function _Layout() { title: "test", headerShown: false, }}/> - { + let markedDates: MarkedDates = {}; + data.forEach((value: any) => { + const dateKey: string = String(value["expense_datetime"]).split(" ")[0] + + if(markedDates[dateKey] === undefined){ + markedDates[dateKey] = {dots: []} + } + markedDates[dateKey].dots?.push({color: value["category_color"]}) + }) + return markedDates; +} + +export default function Page() { + const { colors, theme } = useTheme() + + const navigation = useNavigation(); + const router = useRouter(); + const [plusShow, setPlusShow] = useState(true); + const prevOffset = useRef(0); + + const [filter, setFilter] = useState({}) + + const profile = require("../../../assets/images/profile.jpg") + + const handleScroll = (event: NativeSyntheticEvent)=>{ + const currentOffset = event.nativeEvent.contentOffset.y >= 0 ? event.nativeEvent.contentOffset.y : 0 + const isScrollingUp : boolean = currentOffset <= prevOffset.current; + const isTop : boolean = currentOffset === 0 + prevOffset.current = currentOffset + setPlusShow(isScrollingUp || isTop) + } + + const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, c.guid AS category_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color, c.type AS category_type FROM expense e JOIN category c ON e.category_guid = c.guid ORDER BY expense_datetime desc;", args: []}); + + const expenses = useMemo( + () => { + console.log("expenses updated") + return data.map((elem) => { + return { + guid: elem["expense_guid"], + name: elem["expense_name"], + amount: elem["expense_amount"], + dateTime: elem["expense_datetime"], + category_name: elem["category_name"], + color: elem["category_color"]} + }) + }, [data]) + + const filteredExpenses = useMemo( + () => { + console.log("filter called") + return expenses.filter((elem) => { + if(filter.month && filter.month.substring(5, 7) !== elem.dateTime?.substring(5, 7)){ + return false; + } + if(filter.day && filter.day.substring(8, 10) !== elem.dateTime?.substring(8, 10)){ + return false; + } + if(filter.search && !elem.name?.toLowerCase().includes(filter.search.toLowerCase())){ + return false; + } + return true; + }) + }, [expenses, filter] + ) + + const expenseDates = useMemo( + () => { + let markedDates = constructMarkedDates(data) + if(filter.day) { + const dateKey: string = String(filter.day).split(" ")[0] + if(markedDates[dateKey] === undefined){ + markedDates[dateKey] = {} + } + markedDates[dateKey].selected = true; + markedDates[dateKey].selectedColor = colors.accentColor; + } + return markedDates; + } + , [data, filter.day]) + + useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + console.log("focus event triggered") + reFetch(); + }); + return unsubscribe; + }, [navigation]); + + const hanldeDaySelect = (date: string | undefined) => { + if(filter.day === date) + setFilter({...filter, day: undefined}); + else + setFilter({...filter, day: date}); + } + const handleMonthSelect = (date: string | undefined) => { + setFilter({...filter, month: date, day: undefined}); + } + const handleSearch = (text: string) => { + setFilter({...filter, search: text}); + } + + console.log("rendered") + return ( + + {plusShow && { + router.push("/expense/new"); + + // executeQuery({sql: "SELECT guid FROM category", args: []}).then((result) => { + // if("rows" in result[0]) { + // newExpense("Test Title", result[0]["rows"][0]["guid"], "69.69.1234", 100).then(() => { + // reFetch(); + // }); + // } + // }) + }}/>} + + {/* {isLoading && } */} + + + {router.push("/userSettings")}}/> + { + hanldeDaySelect(date.dateString) + }} + onMonthChange={(date) => { + handleMonthSelect(date.dateString) + }} + /> + + handleSearch(text)} style={{marginBottom: 20}}> + + } + renderItem = {({item}) => + {router.push(`/expense/${guid}`)}} + />} + keyExtractor={item => item.guid!} + ItemSeparatorComponent={() => { + return (); + }} + refreshControl={ + + } + onScroll={handleScroll} + scrollEventThrottle={20} + ListEmptyComponent={EmptyListCompenent} + /> + + ); +} + +const styles = StyleSheet.create({ + safeAreaViewStyle: { + flex: 1, + paddingHorizontal: 10 + }, + itemSeperatorStyle: { + marginVertical: 5, + } +}); \ No newline at end of file diff --git a/app/(tabs)/home/userSettings.tsx b/app/(tabs)/(home)/userSettings.tsx similarity index 86% rename from app/(tabs)/home/userSettings.tsx rename to app/(tabs)/(home)/userSettings.tsx index c865110..fb163ae 100644 --- a/app/(tabs)/home/userSettings.tsx +++ b/app/(tabs)/(home)/userSettings.tsx @@ -4,7 +4,7 @@ import { SIZES } from '../../../constants/theme' import { SafeAreaView } from 'react-native-safe-area-context' import { ButtonSetting, ToggleSetting } from '../../../components' import { useTheme } from '../../contexts/ThemeContext' -import { deleteExpenses, DEV_populateDatabase } from '../../../services/database' +import { deleteCategories, deleteExpenses, DEV_populateDatabase } from '../../../services/database' import { useAuth } from '../../contexts/AuthContext' import { TouchableOpacity } from 'react-native-gesture-handler' @@ -50,17 +50,14 @@ export default function userSettings() { - { - deleteExpenses().then(() => { - console.log("Expenses Deleted!"); - })}} - /> - { - const del = async () => { - await DEV_populateDatabase() + { + const deleteAll = async () => { + await deleteExpenses(); + await deleteCategories(); } - del() - }}/> + deleteAll(); + }} + /> diff --git a/app/(tabs)/(stats)/index.tsx b/app/(tabs)/(stats)/index.tsx new file mode 100644 index 0000000..5113d08 --- /dev/null +++ b/app/(tabs)/(stats)/index.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +import { Graph } from '../../../components'; +import BudgetOverview from '../../../components/stats/BudgetOverview'; +import BudgetRemaining from '../../../components/stats/SavingsOverview'; +import SavingsOverview from '../../../components/stats/SavingsOverview'; +import Widget from '../../../components/stats/Widget'; +import FinancialAdvice from '../../../components/stats/FinancialAdvice'; +import BudgetTotal from '../../../components/stats/BudgetTotal'; +import { ScrollView } from 'react-native-gesture-handler'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import DebugMenu from '../../../services/DebugMenu'; + +export default function Page() { + + return ( + + + {/* */} + + + + + + + + + + + + {/* + + */} + + + + + ); +} diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 79ff582..62d5907 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -3,9 +3,9 @@ import { StyleSheet } from "react-native"; import { FontAwesome } from "@expo/vector-icons"; import { Redirect } from "expo-router"; -import React, { useEffect } from "react"; -import { useTheme } from "../contexts/ThemeContext"; +import React from "react"; import { useAuth } from "../contexts/AuthContext"; +import { useTheme } from "../contexts/ThemeContext"; export default function Layout() { const {authState} = useAuth() @@ -34,31 +34,27 @@ export default function Layout() { } return ( - - + ( ), - unmountOnBlur: true, - href: "(tabs)/budget" } }/> - ( ), - unmountOnBlur: true, - href: "(tabs)/home/" } }/> - ( ), - unmountOnBlur: true, } }/> diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx deleted file mode 100644 index 6615367..0000000 --- a/app/(tabs)/budget/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { router } from 'expo-router'; -import { useEffect, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { FlatList } from 'react-native-gesture-handler'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import { BudgetHeader, CategoryItem, LoadingSymbol, Plus } from '../../../components'; -import useFetch from '../../../hooks/useFetch'; -import { useTheme } from '../../contexts/ThemeContext'; - -export default function Page() { - const {colors} = useTheme() - const containerColor = colors.containerColor; - - const [selectedPage, setSelectedPage] = useState("noPageLoaded"); - - 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(() => { - reFetch(); - }, [selectedPage]); - - const handlePageSelection = (page: string) => { - if(page !== selectedPage) { - setSelectedPage(page); - AsyncStorage.setItem("currentBudgetPage", page); - } - }; - - const handleCategoryPress = (item: {[column: string]: any;}) => { - router.push({pathname: "/(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) - } - - return ( - - - - { - router.push({pathname: '/(tabs)/budget/addCategory'}); - }}/> - - {isLoading ? () : ( - { - handleCategoryPress(item); - }}/>} - keyExtractor={item => item.category_guid} - ItemSeparatorComponent={() => { - return (); - }} - /> - )} - - ); -} - -const styles = StyleSheet.create({ - safeAreaViewStyle: { - flex: 1, - }, - itemSeperatorStyle: { - marginVertical: 5, - }, -}); \ No newline at end of file diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx deleted file mode 100644 index 8f78e74..0000000 --- a/app/(tabs)/home/index.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React, { useMemo, useRef, useState } from 'react'; -import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native'; -import { Calendar } from 'react-native-calendars'; -import { FlatList, RefreshControl } from 'react-native-gesture-handler'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import { ExpenseItem, LoadingSymbol, Plus, TextInputBar, Welcome } from '../../../components'; -import useFetch from '../../../hooks/useFetch'; - -import { useRouter } from "expo-router"; -import { addExpense } from "../../../services/database"; -import { SimpleDate } from '../../../util/SimpleDate'; -import { useTheme } from '../../contexts/ThemeContext'; - - -interface MarkingProps { - dots?:{color:string, selectedColor?:string, key?:string}[]; -} - -type MarkedDates = { - [key: string]: MarkingProps; -} - -const constructMarkedDates = (data : {[column: string]: any}) => { - let markedDates: MarkedDates = {}; - data.forEach((value: any) => { - const dateKey: string = String(value["expense_datetime"]).split(" ")[0] - - if(markedDates[dateKey] === undefined){ - markedDates[dateKey] = {dots: []} - } - markedDates[dateKey].dots?.push({color: value["category_color"]}) - }) - return markedDates; -} - -export default function Page() { - const { colors, theme } = useTheme() - - - - const router = useRouter(); - const [plusShow, setPlusShow] = useState(true); - const prevOffset = useRef(0); - - - const profile = require("../../../assets/images/profile.jpg") - - const handleScroll = (event: NativeSyntheticEvent)=>{ - const currentOffset = event.nativeEvent.contentOffset.y >= 0 ? event.nativeEvent.contentOffset.y : 0 - const isScrollingUp : boolean = currentOffset <= prevOffset.current; - const isTop : boolean = currentOffset === 0 - prevOffset.current = currentOffset - setPlusShow(isScrollingUp || isTop) - } - - const newExpense = async (title: string, category_guid: string, date: string, amount: number) => { - try { - await addExpense(title, category_guid, date, amount); - } catch (error: any) { - console.error("Adding new expense has failed: ", error); - } - } - - const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, c.guid AS category_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color, c.type AS category_type FROM expense e JOIN category c ON e.category_guid = c.guid ORDER BY expense_datetime desc;", args: []}); - - const expenseDates = useMemo(()=> - constructMarkedDates(data) - , [data]) - - - - return ( - - {plusShow && { - router.push("/(tabs)/home/expense/new"); - - // executeQuery({sql: "SELECT guid FROM category", args: []}).then((result) => { - // if("rows" in result[0]) { - // newExpense("Test Title", result[0]["rows"][0]["guid"], "69.69.1234", 100).then(() => { - // reFetch(); - // }); - // } - // }) - }}/>} - - {isLoading && } - - - {router.push("/home/userSettings")}}/> - - - - - - } - renderItem = {({item}) => {router.push(`/(tabs)/home/expense/${guid}`)}}/>} - keyExtractor={item => item.expense_guid} - ItemSeparatorComponent={() => { - return (); - }} - refreshControl={ - - } - onScroll={handleScroll} - scrollEventThrottle={20} - /> - - ); -} - -const styles = StyleSheet.create({ - safeAreaViewStyle: { - flex: 1, - paddingHorizontal: 10 - }, - itemSeperatorStyle: { - marginVertical: 5, - } -}); \ No newline at end of file diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx deleted file mode 100644 index cac1e04..0000000 --- a/app/(tabs)/stats/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -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'; -import SavingsOverview from '../../../components/stats/SavingsOverview'; -import FinancialAdvice from '../../../components/stats/FinancialAdvice'; - -export default function Page() { - const { colors } = useTheme(); - - // Mock data #TODO Database einbinden - // what to do when amount too small? - - const spent = 120.75; - const budget = 696.96; - - - - const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.backgroundColor, - marginTop: 50, - alignItems: 'center', - }, - }); - - return ( - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/app/_layout.tsx b/app/_layout.tsx index f34a4ea..e99a541 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,20 +1,37 @@ -import { Slot } from 'expo-router'; +import { SplashScreen, Stack } from 'expo-router'; import React, { useEffect } from 'react'; import { addCategory, initDatabase } from '../services/database'; import { AuthProvider } from './contexts/AuthContext'; import { ThemeProvider } from './contexts/ThemeContext'; +import { useTheme } from './contexts/ThemeContext'; + export default function _layout() { - + const {colors} = useTheme(); useEffect(() => { initDatabase(); + }, []); + console.log(colors.backgroundColor) return ( - - - - - + + + + + + + + ) } \ No newline at end of file diff --git a/app/(tabs)/home/expense/[expense].tsx b/app/expense/[expense].tsx similarity index 91% rename from app/(tabs)/home/expense/[expense].tsx rename to app/expense/[expense].tsx index 53e38ae..1eb15ff 100644 --- a/app/(tabs)/home/expense/[expense].tsx +++ b/app/expense/[expense].tsx @@ -1,15 +1,15 @@ import { View, Text, Alert, StyleSheet } from 'react-native' import React, { useEffect, useState } from 'react' import { router, useLocalSearchParams, useRouter } from 'expo-router' -import useFetch from '../../../../hooks/useFetch'; -import { Category, Expense } from '../../../../types/dbItems'; -import { CategorySelectorModal, AutoDecimalInput, CategorySelector, TextInputBar, DateSelectorButton, RoundedButton } from '../../../../components'; -import colors from '../../../../constants/colors'; -import { addExpense, deleteExpense, executeQuery, updateExpense } from '../../../../services/database'; -import { SimpleDate } from '../../../../util/SimpleDate'; +import useFetch from '../../hooks/useFetch'; +import { Category, Expense } from '../../types/dbItems'; +import { CategorySelectorModal, AutoDecimalInput, CategorySelector, TextInputBar, DateSelectorButton, RoundedButton } from '../../components'; +import colors from '../../constants/colors'; +import { addExpense, deleteExpense, executeQuery, updateExpense } from '../../services/database'; +import { SimpleDate } from '../../util/SimpleDate'; import DateTimePicker from '@react-native-community/datetimepicker'; -import { SIZES } from '../../../../constants/theme'; -import { useTheme } from '../../../contexts/ThemeContext'; +import { SIZES } from '../../constants/theme'; +import { useTheme } from '../contexts/ThemeContext'; export default function Page() { const router = useRouter(); @@ -25,8 +25,6 @@ export default function Page() { const [datePickerShown, setDatePickerShown] = useState(false); const [selectedDate, setSelectedDate] = useState(new Date()); - - console.log(router.canGoBack()) useEffect(()=>{ const entry = data[0]; console.log(entry) @@ -69,8 +67,7 @@ export default function Page() { await updateExpense(selectedExpense!.guid!, expenseName, selectedCategory!.guid!, new SimpleDate(selectedDate).format("YYYY-MM-DD"), Number(formatedValue)) } if(validateInput()){ - insert(); - router.back(); + insert().then( () => router.back()) }else { Alert.alert("Invalid input", "One of the Props is not properly defined") } diff --git a/app/(tabs)/home/expense/_layout.tsx b/app/expense/_layout.tsx similarity index 93% rename from app/(tabs)/home/expense/_layout.tsx rename to app/expense/_layout.tsx index ef1ee81..f867c8a 100644 --- a/app/(tabs)/home/expense/_layout.tsx +++ b/app/expense/_layout.tsx @@ -1,7 +1,7 @@ import { StyleSheet, Text, View } from 'react-native' import { Stack } from 'expo-router' import React from 'react' -import { useTheme } from '../../../contexts/ThemeContext' +import { useTheme } from '../contexts/ThemeContext' const _layout = () => { const {colors} = useTheme(); diff --git a/app/(tabs)/home/expense/new.tsx b/app/expense/new.tsx similarity index 71% rename from app/(tabs)/home/expense/new.tsx rename to app/expense/new.tsx index f2525c2..bfbd7bd 100644 --- a/app/(tabs)/home/expense/new.tsx +++ b/app/expense/new.tsx @@ -1,18 +1,20 @@ import { View, Text, StyleSheet, Alert } from 'react-native' -import React, { useRef, useState } from 'react' -import { SIZES } from '../../../../constants/theme' -import { useTheme } from '../../../contexts/ThemeContext' -import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../../../components' -import { Category } from '../../../../types/dbItems' +import React, { useEffect, useRef, useState } from 'react' +import { SIZES } from '../../constants/theme' +import { useTheme } from '../contexts/ThemeContext' +import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../components' +import { Category } from '../../types/dbItems' import DateTimePicker from '@react-native-community/datetimepicker'; -import { addExpense } from '../../../../services/database' -import { SimpleDate } from '../../../../util/SimpleDate' -import { useRouter } from 'expo-router' +import { addExpense, executeQuery } from '../../services/database' +import { SimpleDate } from '../../util/SimpleDate' +import { useLocalSearchParams, useRouter } from 'expo-router' export default function AddItem() { const {colors} = useTheme(); const router = useRouter(); + const searchParams = useLocalSearchParams() + const [formatedValue, setFormatedValue] = useState(""); const [selectorModalVisible, setSelecorModalVisible] = useState(false); const [selectedCategory, setSelectedCategory] = useState() @@ -41,20 +43,33 @@ export default function AddItem() { await addExpense(expenseName, selectedCategory?.guid!, new SimpleDate(selectedDate).format("YYYY-MM-DD"), Number(formatedValue)) } if(validateInput()){ - insert(); - router.back(); + insert().then(() => { + router.back(); + }) }else { Alert.alert("Invalid input", "One of the Props is not properly defined") } } + 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 ( {setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}> {setSelecorModalVisible(true)}} selectedCategory={selectedCategory}/> - setExpenseName(text)}/> + setExpenseName(text)}/> {setDatePickerShown(true)}}/> {datePickerShown && + ) } \ No newline at end of file diff --git a/components/budget/budgetHeader.tsx b/components/budget/budgetHeader.tsx index bca2e9b..5e65614 100644 --- a/components/budget/budgetHeader.tsx +++ b/components/budget/budgetHeader.tsx @@ -4,7 +4,7 @@ import TextInputBar from "../common/TextInputBar"; type BudgetHeaderProperties = { selectedPage: string, - handlePageSelection: (page: string) => void, + handlePageSelection: (page: "expense" | "saving") => void, } type PageSelectorButtonProperties = { @@ -21,20 +21,19 @@ const BudgetHeader = (properties: BudgetHeaderProperties) => { { - properties.handlePageSelection("expenses") + properties.handlePageSelection("expense") }} /> { - properties.handlePageSelection("savings"); + properties.handlePageSelection("saving"); }} /> - ); } @@ -77,12 +76,11 @@ const styles = StyleSheet.create({ containerStyle: { flexDirection: "row", justifyContent: "space-evenly", - marginHorizontal: 20, marginBottom: 20, + marginHorizontal: 10, marginTop: 10, }, searchBarStyle: { marginBottom: 20, - marginHorizontal: 10, } }); \ No newline at end of file diff --git a/components/budget/typeSelectorSwitch.tsx b/components/budget/typeSelectorSwitch.tsx index dc6231b..f3e8f25 100644 --- a/components/budget/typeSelectorSwitch.tsx +++ b/components/budget/typeSelectorSwitch.tsx @@ -1,10 +1,11 @@ import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { useTheme } from "../../app/contexts/ThemeContext"; +import { CategoryType } from "../../types/dbItems"; export type TypeSelectorSwitchProperties = { - handleButtonPress: (type: string) => void, - currentSelected: string, + handleButtonPress: (type: CategoryType) => void, + currentSelected: CategoryType, } const TypeSelectorSwitch = (properties: TypeSelectorSwitchProperties) => { @@ -14,17 +15,17 @@ const TypeSelectorSwitch = (properties: TypeSelectorSwitchProperties) => { { - properties.handleButtonPress("expense"); + properties.handleButtonPress(CategoryType.EXPENSE); }} - style={[styles.touchableOpacityStyle, properties.currentSelected == "expense" ? {backgroundColor: colors.accentColor} : {backgroundColor: colors.elementDefaultColor}] + style={[styles.touchableOpacityStyle, properties.currentSelected == CategoryType.EXPENSE ? {backgroundColor: colors.accentColor} : {backgroundColor: colors.elementDefaultColor}] }> Expenses { - properties.handleButtonPress("saving"); + onPress={() => { + properties.handleButtonPress(CategoryType.SAVING); }} - style={[styles.touchableOpacityStyle, properties.currentSelected == "saving" ? {backgroundColor: colors.accentColor} : {backgroundColor: colors.elementDefaultColor}] + style={[styles.touchableOpacityStyle, properties.currentSelected == CategoryType.SAVING ? {backgroundColor: colors.accentColor} : {backgroundColor: colors.elementDefaultColor}] }> Savings diff --git a/components/common/CustomCard.tsx b/components/common/CustomCard.tsx index a866d8c..87aae81 100644 --- a/components/common/CustomCard.tsx +++ b/components/common/CustomCard.tsx @@ -40,7 +40,6 @@ export default function CustomCard(props : ViewProps) { const styles = StyleSheet.create({ container:{ borderRadius: 20, - marginHorizontal: 10, }, boxShadow: {}, }) \ No newline at end of file diff --git a/components/common/EmptyListCompenent.tsx b/components/common/EmptyListCompenent.tsx new file mode 100644 index 0000000..c0a1746 --- /dev/null +++ b/components/common/EmptyListCompenent.tsx @@ -0,0 +1,24 @@ +import { View, Text, StyleSheet } from 'react-native' +import React from 'react' +import { useTheme } from '../../app/contexts/ThemeContext' + +const EmptyListCompenent:React.FC = () => { + const {colors} = useTheme(); + return ( + + No matching Data + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + height: 70, + borderRadius: 20, + } +}) + +export default EmptyListCompenent \ No newline at end of file diff --git a/components/common/TextInputBar.tsx b/components/common/TextInputBar.tsx index 7b0242b..bfeaac8 100644 --- a/components/common/TextInputBar.tsx +++ b/components/common/TextInputBar.tsx @@ -42,10 +42,12 @@ export default function TextInputBar(props: SearchBarProps) { // changed styles.container to containerStyle return ( - setIsactive(false)}/> + (setIsactive(true))} onEndEditing={()=>setIsactive(false)}/> {isActive && - {handleChange("")}}> + { + console.log("cancel") + handleChange("")}}> } diff --git a/components/home/addItem/CategorySelector.tsx b/components/expense/CategorySelector.tsx similarity index 89% rename from components/home/addItem/CategorySelector.tsx rename to components/expense/CategorySelector.tsx index 27d5cfe..59e5edf 100644 --- a/components/home/addItem/CategorySelector.tsx +++ b/components/expense/CategorySelector.tsx @@ -1,8 +1,8 @@ import { StyleSheet, Text, View, TouchableOpacity } from 'react-native' import React, { useState } from 'react' -import { useTheme } from '../../../app/contexts/ThemeContext' -import { SIZES } from '../../../constants/theme'; -import { Category } from '../../../types/dbItems'; +import { useTheme } from '../../app/contexts/ThemeContext' +import { SIZES } from '../../constants/theme'; +import { Category } from '../../types/dbItems'; import CategorySelectorModal from './CategorySelectorModal'; interface CategorySelectorProps { diff --git a/components/home/addItem/CategorySelectorModal.tsx b/components/expense/CategorySelectorModal.tsx similarity index 80% rename from components/home/addItem/CategorySelectorModal.tsx rename to components/expense/CategorySelectorModal.tsx index d758669..ef09fd1 100644 --- a/components/home/addItem/CategorySelectorModal.tsx +++ b/components/expense/CategorySelectorModal.tsx @@ -1,11 +1,12 @@ import { Modal, NativeSyntheticEvent, StyleSheet, Text, View, FlatList } from 'react-native' import React, { useEffect, useMemo, useState } from 'react' -import { Category } from '../../../types/dbItems'; -import { useTheme } from '../../../app/contexts/ThemeContext'; -import CategoryListItem from '../../common/CategoryListItem'; -import { SIZES } from '../../../constants/theme'; -import useFetch from '../../../hooks/useFetch'; -import TextInputBar from '../../common/TextInputBar'; +import { Category } from '../../types/dbItems'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import CategoryListItem from '../common/CategoryListItem'; +import { SIZES } from '../../constants/theme'; +import useFetch from '../../hooks/useFetch'; +import TextInputBar from '../common/TextInputBar'; +import EmptyListCompenent from '../common/EmptyListCompenent'; interface CategorySelectorModalProps{ @@ -43,13 +44,13 @@ const CategorySelectorModal: React.FC = (props : Cat }, [visible]) return ( - + {selectMulitple ? "Categories" : "Category"} - + item.guid!} @@ -57,6 +58,7 @@ const CategorySelectorModal: React.FC = (props : Cat ItemSeparatorComponent={() => } ListFooterComponent={() => } keyboardShouldPersistTaps="always" + ListEmptyComponent={EmptyListCompenent} > diff --git a/components/home/addItem/DateSelectorButton.tsx b/components/expense/DateSelectorButton.tsx similarity index 87% rename from components/home/addItem/DateSelectorButton.tsx rename to components/expense/DateSelectorButton.tsx index 4f07297..ec6916d 100644 --- a/components/home/addItem/DateSelectorButton.tsx +++ b/components/expense/DateSelectorButton.tsx @@ -1,8 +1,8 @@ import { View, Text, StyleSheet, TouchableOpacity, TouchableOpacityProps } from 'react-native' import React, { useState } from 'react' -import { useTheme } from '../../../app/contexts/ThemeContext'; -import { SIZES } from '../../../constants/theme'; -import { SimpleDate } from '../../../util/SimpleDate'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import { SIZES } from '../../constants/theme'; +import { SimpleDate } from '../../util/SimpleDate'; import { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes'; interface DateSelectorProps extends ViewProps { diff --git a/components/home/expenseItem.tsx b/components/home/expenseItem.tsx index ff6eb2a..fd00c7f 100644 --- a/components/home/expenseItem.tsx +++ b/components/home/expenseItem.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { ColorValue, StyleSheet, Text, View } from 'react-native'; +import { TouchableOpacity } from 'react-native-gesture-handler'; +import { useTheme } from '../../app/contexts/ThemeContext'; import { SIZES } from '../../constants/theme'; import CustomCard from "../common/CustomCard"; -import { useTheme } from '../../app/contexts/ThemeContext'; -import { TouchableOpacity } from 'react-native-gesture-handler'; //export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} interface ExpenseItemProps { @@ -44,9 +44,10 @@ export default function ExpenseItem(itemProps : ExpenseItemProps) { {itemProps.value} + }} numberOfLines={1}>{itemProps.value + " €"} diff --git a/components/index.tsx b/components/index.tsx index b20c5fd..0c51fed 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -4,18 +4,19 @@ import Welcome from "./home/Welcome" import ExpenseItem from "./home/expenseItem" //home/addItem -import CategorySelector from "./home/addItem/CategorySelector" -import CategorySelectorModal from "./home/addItem/CategorySelectorModal" -import DateSelectorButton from "./home/addItem/DateSelectorButton" +import CategorySelector from "./expense/CategorySelector" +import CategorySelectorModal from "./expense/CategorySelectorModal" +import DateSelectorButton from "./expense/DateSelectorButton" //common +import AutoDecimalInput from "./common/AutoDecimalInput" import CustomCard from "./common/CustomCard" +import RoundedButton from "./common/RoundedButton" +import TextInputBar from "./common/TextInputBar" import NavigationButton from "./common/button" import LoadingSymbol from "./common/loadingSymbol" import Plus from "./common/plus" -import TextInputBar from "./common/TextInputBar" -import AutoDecimalInput from "./common/AutoDecimalInput" -import RoundedButton from "./common/RoundedButton" +import EmptyListCompenent from "./common/EmptyListCompenent" //login import BudgetHeader from "./budget/budgetHeader" @@ -26,25 +27,30 @@ import TypeSelectorSwitch from "./budget/typeSelectorSwitch" //login import Input from "./login/input" +//stats +import Graph from "./stats/Graph" + export { + AutoDecimalInput, BudgetHeader, ButtonSetting, - CustomCard, - ExpenseItem, - Input, - LoadingSymbol, - Plus, - TextInputBar, - ToggleSetting, - Welcome, - AutoDecimalInput, + CategoryItem, CategorySelector, CategorySelectorModal, + CustomCard, + CustomColorPicker, DateSelectorButton, - RoundedButton, - CategoryItem, - TypeSelectorSwitch, + EmptyListCompenent, + ExpenseItem, + Graph, + Input, + LoadingSymbol, NavigationButton, - CustomColorPicker + Plus, + RoundedButton, + TextInputBar, + ToggleSetting, + TypeSelectorSwitch, + Welcome } diff --git a/components/stats/BudgetOverview.tsx b/components/stats/BudgetOverview.tsx index 05e89c4..4148e6a 100644 --- a/components/stats/BudgetOverview.tsx +++ b/components/stats/BudgetOverview.tsx @@ -1,62 +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 '../../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 = ({ 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 = 'budget'", - args: [] - }; + const { total, expenseTotal } = data; - const budgetQuery = { - sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'", - 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 37aa555..0000000 --- a/components/stats/BudgetRemaining.tsx +++ /dev/null @@ -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 Loading...; - } - - return ( - - You have {remaining.toFixed(2)}€ left. - - ); -}; - -export default BudgetRemaining; diff --git a/components/stats/BudgetTotal.tsx b/components/stats/BudgetTotal.tsx new file mode 100644 index 0000000..f4b0aa8 --- /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 '../../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 = ({ 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/Graph.tsx b/components/stats/Graph.tsx new file mode 100644 index 0000000..6d737d8 --- /dev/null +++ b/components/stats/Graph.tsx @@ -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 ( + + `rgba(255, 255, 255, ${opacity})`, + }} + backgroundColor="transparent" + accessor="total" + paddingLeft="15" + avoidFalseZero={true} + /> + + ); +} + +export default Graph; \ No newline at end of file diff --git a/components/stats/PieChart.tsx b/components/stats/PieChart.tsx deleted file mode 100644 index f3fe67e..0000000 --- a/components/stats/PieChart.tsx +++ /dev/null @@ -1 +0,0 @@ -//honestly just fuck graphs \ No newline at end of file diff --git a/components/stats/SavingsOverview.tsx b/components/stats/SavingsOverview.tsx index a79234a..ea906a1 100644 --- a/components/stats/SavingsOverview.tsx +++ b/components/stats/SavingsOverview.tsx @@ -1,62 +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 '../../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 = ({ 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 = 'savings'", - args: [] - }; + const { total, expenseTotal } = data; - const goalQuery = { - sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'", - 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..72cbe8b --- /dev/null +++ b/hooks/useCategoryData.ts @@ -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 }; +}; \ No newline at end of file diff --git a/hooks/useFetch.ts b/hooks/useFetch.ts index cec238d..a828d31 100644 --- a/hooks/useFetch.ts +++ b/hooks/useFetch.ts @@ -4,9 +4,28 @@ import { executeQuery } from "../services/database"; const useFetch = (query: Query) => { - const [isLoading, setIsLoading] = useState(false); - const [data, setData] = useState<{[column: string]: any;}[]>([]); - const [isEmptyResult, setIsEmptyResult] = useState(undefined); + const [fetchState, setFetchState] = useState<{ + data: {[column: string]: any;}[]; + isLoading: boolean; + isEmptyResult: boolean | undefined; + }>({ + data: [], + isLoading: false, + isEmptyResult: undefined + }); + + const setIsLoading = (isLoading: boolean) => { + setFetchState((prevState) => ( {...prevState, isLoading} )); + } + + const setData = (data: {[column: string]: any;}[]) => { + setFetchState((prevState) => ( {...prevState, data} )); + } + + const setIsEmptyResult = (isEmptyResult: boolean) => { + setFetchState((prevState) => ( {...prevState, isEmptyResult} )); + } + const reFetch = () => { setIsLoading(true); @@ -28,7 +47,7 @@ const useFetch = (query: Query) => { reFetch(); }, []) - return {data, isLoading, reFetch, isEmptyResult}; + return {...fetchState, reFetch}; } export default useFetch; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aab706f..20b4a11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,10 +26,12 @@ "react-dom": "18.2.0", "react-native": "0.72.6", "react-native-calendars": "^1.1303.0", + "react-native-chart-kit": "^6.12.0", "react-native-gesture-handler": "~2.12.0", - "react-native-reanimated": "^3.3.0", + "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", + "react-native-svg": "13.9.0", "react-native-uuid": "^2.0.1", "react-native-web": "~0.19.6", "reanimated-color-picker": "^2.4.2" @@ -77,19 +79,19 @@ } }, "node_modules/@babel/core": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", - "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.6", + "@babel/helpers": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", + "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -157,9 +159,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.6.tgz", - "integrity": "sha512-cBXU1vZni/CpGF29iTu4YRbOZt3Wat6zCoMDxRF1MayiEc4URxOj31tT65HUM0CRpMowA3HCJaAOVOUnMf96cw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz", + "integrity": "sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", @@ -402,12 +404,12 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", - "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", + "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", + "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" }, "engines": { @@ -469,9 +471,9 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", - "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-plugin-utils": "^7.22.5" @@ -518,15 +520,12 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.6.tgz", - "integrity": "sha512-D7Ccq9LfkBFnow3azZGJvZYgcfeqAw3I1e5LoTpj6UKIFQilh8yqXsIGcRIqbBdsPWIz+Ze7ZZfggSj62Qp+Fg==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.7.tgz", + "integrity": "sha512-b1s5JyeMvqj7d9m9KhJNHKc18gEJiSyVzVX3bwbiPalQBQpuvfPh6lA9F7Kk/dWH0TIiXRpB9yicwijY6buPng==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.23.6", + "@babel/helper-create-class-features-plugin": "^7.23.7", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", "@babel/plugin-syntax-decorators": "^7.23.3" }, "engines": { @@ -976,9 +975,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", - "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", + "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-plugin-utils": "^7.22.5", @@ -1646,15 +1645,15 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.6.tgz", - "integrity": "sha512-kF1Zg62aPseQ11orDhFRw+aPG/eynNQtI+TyY+m33qJa2cJ5EEvza2P2BNTIA9E5MyqFABHEyY6CPHwgdy9aNg==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.7.tgz", + "integrity": "sha512-fa0hnfmiXc9fq/weK34MUV0drz2pOL/vfKWvN7Qw127hiUPabFCUMgAbYWcchRzMJit4o5ARsK/s+5h0249pLw==", "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "semver": "^6.3.1" }, "engines": { @@ -1812,9 +1811,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", - "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", + "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.23.6", @@ -1822,7 +1821,7 @@ "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -1843,7 +1842,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-generator-functions": "^7.23.7", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", "@babel/plugin-transform-block-scoping": "^7.23.4", @@ -1891,9 +1890,9 @@ "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -1952,14 +1951,14 @@ } }, "node_modules/@babel/register": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.22.15.tgz", - "integrity": "sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz", + "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==", "dependencies": { "clone-deep": "^4.0.1", "find-cache-dir": "^2.0.0", "make-dir": "^2.1.0", - "pirates": "^4.0.5", + "pirates": "^4.0.6", "source-map-support": "^0.5.16" }, "engines": { @@ -2004,9 +2003,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", - "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", + "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2028,9 +2027,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -3364,6 +3363,95 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -4276,6 +4364,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", @@ -6511,9 +6608,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", - "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -6571,9 +6668,9 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/node": { - "version": "20.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", - "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "version": "20.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.7.tgz", + "integrity": "sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==", "dependencies": { "undici-types": "~5.26.4" } @@ -6585,14 +6682,14 @@ "dev": true }, "node_modules/@types/qs": { - "version": "6.9.10", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", - "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" }, "node_modules/@types/react": { - "version": "18.2.45", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", - "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", + "version": "18.2.47", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.47.tgz", + "integrity": "sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -6699,9 +6796,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -7410,6 +7507,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -7678,9 +7780,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001570", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", - "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", + "version": "1.0.30001576", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", + "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", "funding": [ { "type": "opencollective", @@ -8045,9 +8147,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/core-js-compat": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", - "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", "dependencies": { "browserslist": "^4.22.2" }, @@ -8233,6 +8335,44 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -8522,6 +8662,30 @@ "node": ">=8" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -8535,6 +8699,33 @@ "node": ">=12" } }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -8551,15 +8742,20 @@ "node": ">=12" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.615", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.615.tgz", - "integrity": "sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==" + "version": "1.4.623", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.623.tgz", + "integrity": "sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==" }, "node_modules/emittery": { "version": "0.13.1", @@ -8598,7 +8794,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "engines": { "node": ">=0.12" }, @@ -9450,6 +9645,86 @@ "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==" }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/foreground-child/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -9928,9 +10203,9 @@ } }, "node_modules/image-size": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", - "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", "dependencies": { "queue": "6.0.2" }, @@ -9938,7 +10213,7 @@ "image-size": "bin/image-size.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.x" } }, "node_modules/immediate": { @@ -10420,6 +10695,23 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -13484,6 +13776,11 @@ "resolved": "https://registry.npmjs.org/md5hex/-/md5hex-1.0.0.tgz", "integrity": "sha512-c2YOUbp33+6thdCUi34xIyOU/a7bvGKj/3DB1iaPMTuPHf/Q2d5s4sn1FaCOO43XkXggnb08y5W2PU8UNYNLKQ==" }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -14398,9 +14695,9 @@ } }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "optional": true, "engines": { "node": "*" @@ -14687,6 +14984,17 @@ "node": ">=4" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -15040,6 +15348,37 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -15048,6 +15387,14 @@ "node": ">=8" } }, + "node_modules/paths-js": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/paths-js/-/paths-js-0.4.11.tgz", + "integrity": "sha512-3mqcLomDBXOo7Fo+UlaenG6f71bk1ZezPQy2JCmYHy2W2k5VKpP+Jbin9H0bjXynelTbglCqdFhSEkeIkKTYUA==", + "engines": { + "node": ">=0.11.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -15248,10 +15595,15 @@ "node": ">=4.0.0" } }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" + }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "funding": [ { "type": "opencollective", @@ -15770,6 +16122,21 @@ "moment": "^2.29.4" } }, + "node_modules/react-native-chart-kit": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/react-native-chart-kit/-/react-native-chart-kit-6.12.0.tgz", + "integrity": "sha512-nZLGyCFzZ7zmX0KjYeeSV1HKuPhl1wOMlTAqa0JhlyW62qV/1ZPXHgT8o9s8mkFaGxdqbspOeuaa6I9jUQDgnA==", + "dependencies": { + "lodash": "^4.17.13", + "paths-js": "^0.4.10", + "point-in-polygon": "^1.0.1" + }, + "peerDependencies": { + "react": "> 16.7.0", + "react-native": ">= 0.50.0", + "react-native-svg": "> 6.4.1" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.1.tgz", @@ -15829,6 +16196,19 @@ "react-native": "*" } }, + "node_modules/react-native-svg": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.9.0.tgz", + "integrity": "sha512-Ey18POH0dA0ob/QiwCBVrxIiwflhYuw0P0hBlOHeY4J5cdbs8ngdKHeWC/Kt9+ryP6fNoEQ1PUgPYw2Bs/rp5Q==", + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-swipe-gestures": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz", @@ -15844,9 +16224,9 @@ } }, "node_modules/react-native-web": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.9.tgz", - "integrity": "sha512-m69arZbS6FV+BNSKE6R/NQwUX+CzxCkYM7AJlSLlS8dz3BDzlaxG8Bzqtzv/r3r1YFowhnZLBXVKIwovKDw49g==", + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.10.tgz", + "integrity": "sha512-IQoHiTQq8egBCVVwmTrYcFLgEFyb4LMZYEktHn4k22JMk9+QTCEz5WTfvr+jdNoeqj/7rtE81xgowKbfGO74qg==", "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-color": "^2.1.0", @@ -16725,6 +17105,28 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -16744,6 +17146,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -16797,13 +17211,13 @@ "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==" }, "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -16814,7 +17228,15 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/sucrase/node_modules/commander": { @@ -16825,6 +17247,49 @@ "node": ">= 6" } }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/sudo-prompt": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-8.2.5.tgz", @@ -17187,9 +17652,12 @@ } }, "node_modules/traverse": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", - "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -17667,6 +18135,53 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -17713,9 +18228,9 @@ } }, "node_modules/ws": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz", - "integrity": "sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 2e627b2..b86787c 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@expo/vector-icons": "^13.0.0", "@react-native-async-storage/async-storage": "1.18.2", + "@react-native-community/datetimepicker": "7.2.0", "@react-navigation/native": "^6.0.2", "expo": "~49.0.15", "expo-font": "~11.4.0", @@ -35,14 +36,15 @@ "react-dom": "18.2.0", "react-native": "0.72.6", "react-native-calendars": "^1.1303.0", + "react-native-chart-kit": "^6.12.0", "react-native-gesture-handler": "~2.12.0", - "react-native-reanimated": "^3.3.0", + "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", + "react-native-svg": "13.9.0", "react-native-uuid": "^2.0.1", "react-native-web": "~0.19.6", - "reanimated-color-picker": "^2.4.2", - "@react-native-community/datetimepicker": "7.2.0" + "reanimated-color-picker": "^2.4.2" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/services/DebugMenu.tsx b/services/DebugMenu.tsx index 29ce4e8..ff728ff 100644 --- a/services/DebugMenu.tsx +++ b/services/DebugMenu.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { View, Button, Alert } from 'react-native'; import { addCategory, addExpense, deleteExpenses, deleteCategories, DEV_populateDatabase, deleteDatabase } from './database'; import uuid from 'react-native-uuid'; +import { CategoryType } from '../types/dbItems'; const randomColors = ["red", "blue", "green", "purple", "yellow"]; @@ -46,7 +47,7 @@ const DebugMenu = () => { const allocated_amount = getRandomNumber(); const type = "expense"; - addCategory(name, color, type, allocated_amount) + addCategory(name, color, CategoryType.EXPENSE, allocated_amount) .then(() => Alert.alert("Category Added", `Name: ${name}, Color: ${color}`)) .catch((error: any) => console.error("Error adding category: ", error)); }; diff --git a/services/database.ts b/services/database.ts index 7fe3921..e4c25bf 100644 --- a/services/database.ts +++ b/services/database.ts @@ -4,15 +4,18 @@ import uuid from "react-native-uuid"; import { Query } from "expo-sqlite"; import { SimpleDate } from "../util/SimpleDate"; +import { CategoryType } from "../types/dbItems"; let db: SQLite.SQLiteDatabase; + + export const initDatabase = async () => { db = SQLite.openDatabase("interactive_systeme.db"); try { await db.transactionAsync(async (tx: SQLite.SQLTransactionAsync) => { await tx.executeSqlAsync( - "CREATE TABLE IF NOT EXISTS category (guid VARCHAR(36) PRIMARY KEY, name TEXT, color TEXT, type TEXT, allocated_amount DOUBLE);" + "CREATE TABLE IF NOT EXISTS category (guid VARCHAR(36) PRIMARY KEY, name TEXT, color TEXT, type string, allocated_amount DOUBLE);" ); await tx.executeSqlAsync( "CREATE TABLE IF NOT EXISTS expense (guid VARCHAR(36) PRIMARY KEY, name TEXT, category_guid VARCHAR(36), datetime DATETIME, amount DOUBLE, FOREIGN KEY (category_guid) REFERENCES category(guid));" @@ -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 { await db.transactionAsync(async (tx) => { - await tx.executeSqlAsync("UPDATE category SET name = ?, color = ?, type = ?, allocated_amount = ? WHERE guid = ?", [name, color, type, allocated_amount, guid]); + await tx.executeSqlAsync("UPDATE category SET name = ?, color = ?, type = ?, allocated_amount = ? WHERE guid = ?", [name, color, CategoryType, allocated_amount, guid]); }); } catch (error) { console.log("Error updating category: ", error); @@ -36,7 +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) @@ -45,7 +48,7 @@ export const addCategory = async (name: string, color: string, type: string, all try { await db.transactionAsync(async (tx) => { await tx.executeSqlAsync("INSERT INTO category (guid, name, color, type, allocated_amount) VALUES (?, ?, ?, ?, ?);", - [UUID.toString(), name, color, type, allocated_amount] + [UUID.toString(), name, color, CategoryType, allocated_amount] ); }); } catch (error) { @@ -57,7 +60,7 @@ export const addCategory = async (name: string, color: string, type: string, all export const updateExpense = async (guid: string, name: string, category_guid: string, datetime: string, amount: number) => { //needs user input validation for type and color (should be validated by this function) - + console.log("update expense called") try { await db.transactionAsync(async (tx) => { await tx.executeSqlAsync("UPDATE expense SET name = ?, category_guid = ?, datetime = ?, amount = ? WHERE guid = ?", [name, category_guid, datetime, amount, guid] @@ -67,6 +70,7 @@ export const updateExpense = async (guid: string, name: string, category_guid: s console.log("Error updating expense: ", error); throw error; } + console.log("update expense finished") }; export const addExpense = async (name: string, category_guid: string, datetime: string, amount: number) => { @@ -170,8 +174,9 @@ export const DEV_populateDatabase = async () => { for(let i=0; i < 5; i++){ let random = Math.floor(Math.random() * colors.length); - await addCategory(`Category ${i}`, colors[random], "budget", 50) - await addCategory(`Category ${i+6}`, colors[random], "budget", 50) + await addCategory(`Category ${i}`, colors[random], CategoryType.EXPENSE, 50) + random = Math.floor(Math.random() * colors.length); + await addCategory(`Category ${i+6}`, colors[random], CategoryType.SAVING, 50) } const result = await executeQuery({sql:"SELECT * from category", args:[]}) let categories: {[column: string]: any}[]; diff --git a/types/dbItems.ts b/types/dbItems.ts index c2df2dc..2d9906d 100644 --- a/types/dbItems.ts +++ b/types/dbItems.ts @@ -1,3 +1,7 @@ +export enum CategoryType { + SAVING = "saving", + EXPENSE = "expense", +} export interface Category { guid? : string; name? : string;