diff --git a/app/(tabs)/home/_layout.tsx b/app/(tabs)/home/_layout.tsx index abe83bf..ba7964c 100644 --- a/app/(tabs)/home/_layout.tsx +++ b/app/(tabs)/home/_layout.tsx @@ -24,8 +24,8 @@ export default function _Layout() { title: "test", headerShown: false, }}/> - (); + const [selectedCategory, setSelectedCategory] = useState(); + const [formatedValue, setFormatedValue] = useState(""); + const [initialValue, setInitialValue] = useState(); + const [selectorModalVisible, setSelecorModalVisible] = useState(false); + const [expenseName, setExpenseName] = useState(""); + const [datePickerShown, setDatePickerShown] = useState(false); + const [selectedDate, setSelectedDate] = useState(new Date()); + + useEffect(()=>{ + const entry = data[0]; + console.log(entry) + if(entry){ + console.log(entry) + const extractedExpense: Expense = {name: entry["e_name"], amount: entry["e_amount"], dateTime: entry["e_datetime"], guid: entry["e_guid"]} + const extractedCategory: Category = {name: entry["c_name"], color: entry["c_color"], guid: entry["c_guid"]} + + console.log(extractedCategory.color) + + setSelectedExpense(extractedExpense); + setSelectedCategory(extractedCategory); + setInitialValue(extractedExpense.amount) + setExpenseName(extractedExpense.name ?? "") + setSelectedDate(extractedExpense.dateTime? new Date(extractedExpense.dateTime) : new Date()); + } + + + }, [data]) + + const handleValueChange = (formatedValue: string) => { + setFormatedValue(formatedValue); + } + + const handleCategorySelect = (category : Category) => { + setSelecorModalVisible(false); + setSelectedCategory(category); + } + + const validateInput = ():boolean => { + if(formatedValue == "" || expenseName == "" || selectedCategory === undefined || selectedDate === null){ + return false; + } + return true; + } + + const submit = () => { + console.log(selectedExpense?.guid) + const insert = async () => { + await updateExpense(selectedExpense!.guid!, expenseName, selectedCategory!.guid!, new SimpleDate(selectedDate).format("YYYY-MM-DD"), Number(formatedValue)) + } + if(validateInput()){ + insert(); + router.back(); + }else { + Alert.alert("Invalid input", "One of the Props is not properly defined") + } + + } + + const handleDelete = () => { + const del = async () => { + await deleteExpense(selectedExpense!.guid!) + router.back(); + } + del(); + } + + return ( + + {setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}> + + {setSelecorModalVisible(true)}} selectedCategory={selectedCategory}/> + setExpenseName(text)} value={expenseName}/> + {setDatePickerShown(true)}}/> + {datePickerShown && + { + setDatePickerShown(false); + if(date){ + setSelectedDate(date); + } + }} + />} + + Delete Expense + + + Save + + + ) +} + +const styles = StyleSheet.create({ + notFound: { + alignItems: "center", + justifyContent: "center" + }, + + notFoundMessageContainer: { + backgroundColor: "#e37b7b", + padding: 50, + }, + save: { + padding: 10, + }, + container: { + margin: SIZES.normal, + display: "flex", + gap: 10 + }, + + submitText: { + fontSize: SIZES.large + } + +}) \ No newline at end of file diff --git a/app/(tabs)/home/expense/_layout.tsx b/app/(tabs)/home/expense/_layout.tsx new file mode 100644 index 0000000..211509f --- /dev/null +++ b/app/(tabs)/home/expense/_layout.tsx @@ -0,0 +1,39 @@ +import { StyleSheet, Text, View } from 'react-native' +import { Stack } from 'expo-router' +import React from 'react' +import { useTheme } from '../../../contexts/ThemeContext' + +const _layout = () => { + const {colors} = useTheme(); + return ( + + + String(Date.now())} + /> + + ) +} + +export default _layout + +const styles = StyleSheet.create({}) \ No newline at end of file diff --git a/app/(tabs)/home/addItem.tsx b/app/(tabs)/home/expense/new.tsx similarity index 90% rename from app/(tabs)/home/addItem.tsx rename to app/(tabs)/home/expense/new.tsx index d7d24f2..f2525c2 100644 --- a/app/(tabs)/home/addItem.tsx +++ b/app/(tabs)/home/expense/new.tsx @@ -1,12 +1,12 @@ 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 { 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 { addExpense } from '../../../../services/database' +import { SimpleDate } from '../../../../util/SimpleDate' import { useRouter } from 'expo-router' export default function AddItem() { @@ -52,7 +52,7 @@ export default function AddItem() { return ( {setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}> - + {setSelecorModalVisible(true)}} selectedCategory={selectedCategory}/> setExpenseName(text)}/> {setDatePickerShown(true)}}/> diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index 5a0a5a8..d57614f 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -74,7 +74,7 @@ export default function Page() { return ( {plusShow && { - router.push("/(tabs)/home/addItem"); + router.push("/(tabs)/home/expense/new"); // executeQuery({sql: "SELECT guid FROM category", args: []}).then((result) => { // if("rows" in result[0]) { @@ -109,7 +109,7 @@ export default function Page() { } - renderItem = {({item}) => } + renderItem = {({item}) => {router.push(`/(tabs)/home/expense/${guid}`)}}/>} keyExtractor={item => item.expense_guid} ItemSeparatorComponent={() => { return (); diff --git a/components/common/AutoDecimalInput.tsx b/components/common/AutoDecimalInput.tsx index 52821fc..260b1ba 100644 --- a/components/common/AutoDecimalInput.tsx +++ b/components/common/AutoDecimalInput.tsx @@ -1,5 +1,5 @@ import { View, Text, TouchableOpacity, TextInput, StyleSheet, NativeSyntheticEvent, TextInputKeyPressEventData } from 'react-native' -import React, {LegacyRef, MutableRefObject, useRef, useState} from 'react' +import React, {LegacyRef, MutableRefObject, useEffect, useRef, useState} from 'react' import { SIZES } from '../../constants/theme'; import { useTheme } from '../../app/contexts/ThemeContext'; @@ -19,13 +19,21 @@ const formatDecimal = (value: string)=>{ interface AutoDecimalInputProps{ onValueChange?: (formattedValue: string) => void | undefined label: string, + initialValue? : number } -const AutoDecimalInput: React.FC = ({onValueChange, label}) => { +const AutoDecimalInput: React.FC = ({onValueChange, label, initialValue}) => { const { colors } = useTheme(); const inputRef = useRef(null); const [pressedNumbers, setPressedNumbers] = useState(""); + const init = () => { + if(initialValue){ + const pressedNumber = initialValue.toFixed(2).replace(".", "") + update(pressedNumber); + } + } + const update = (newValues : string) => { if(onValueChange){ onValueChange(formatDecimal(newValues)) @@ -33,6 +41,10 @@ const AutoDecimalInput: React.FC = ({onValueChange, label setPressedNumbers(newValues); } + useEffect(() => { + init() + }, [initialValue]) + const handleInput = (e: NativeSyntheticEvent)=>{ const pressedKey:string = e.nativeEvent.key if(Number.isInteger(Number.parseInt(pressedKey))){ diff --git a/components/common/TextInputBar.tsx b/components/common/TextInputBar.tsx index a2ffa84..7b0242b 100644 --- a/components/common/TextInputBar.tsx +++ b/components/common/TextInputBar.tsx @@ -7,14 +7,13 @@ import { useTheme } from '../../app/contexts/ThemeContext'; interface SearchBarProps extends ViewProps { placeholder? : string; onChangeText? : (text: string) => void | undefined + value?: string } export default function TextInputBar(props: SearchBarProps) { const [isActive, setIsactive] = React.useState(false); const { colors } = useTheme(); - const [text, setText] = useState(""); - const textColor = colors const backgroundColor = colors.elementDefaultColor; const handleChange = (text:string) : void => { @@ -30,7 +29,7 @@ export default function TextInputBar(props: SearchBarProps) { if(props.onChangeText){ props.onChangeText(text) } - setText(text) + } // cant apply the background color otherwise @@ -43,7 +42,7 @@ export default function TextInputBar(props: SearchBarProps) { // changed styles.container to containerStyle return ( - handleChange(text)} onEndEditing={()=>setIsactive(false)}/> + setIsactive(false)}/> {isActive && {handleChange("")}}> diff --git a/components/home/expenseItem.tsx b/components/home/expenseItem.tsx index ade5ccd..ff6eb2a 100644 --- a/components/home/expenseItem.tsx +++ b/components/home/expenseItem.tsx @@ -3,35 +3,53 @@ import { ColorValue, StyleSheet, Text, View } from 'react-native'; 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 { + color: ColorValue; + category: string; + title: string; + date: string; + value : string; + guid? : string; + onPress? : (guid?: string) => void +} -export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} export default function ExpenseItem(itemProps : ExpenseItemProps) { const { colors } = useTheme(); + const handlePress = ()=>{ + if(itemProps.onPress){ + itemProps.onPress(itemProps.guid); + } + } return ( - - - - {itemProps.category} - {itemProps.title} - {itemProps.date} + + + + + {itemProps.category} + {itemProps.title} + {itemProps.date} + + + {itemProps.value} + - - {itemProps.value} - - + ) diff --git a/hooks/useFetch.ts b/hooks/useFetch.ts index 496beba..cec238d 100644 --- a/hooks/useFetch.ts +++ b/hooks/useFetch.ts @@ -6,12 +6,16 @@ const useFetch = (query: Query) => { const [isLoading, setIsLoading] = useState(false); const [data, setData] = useState<{[column: string]: any;}[]>([]); + const [isEmptyResult, setIsEmptyResult] = useState(undefined); const reFetch = () => { setIsLoading(true); executeQuery(query).then((result) => { if("rows" in result[0]) { setData(result[0]["rows"]); + if(result[0]["rows"].length == 0){ + setIsEmptyResult(true); + } } }).catch((error: any) => { console.error("Fetching data from database has failed: ", error); @@ -24,7 +28,7 @@ const useFetch = (query: Query) => { reFetch(); }, []) - return {data, isLoading, reFetch}; + return {data, isLoading, reFetch, isEmptyResult}; } export default useFetch; \ No newline at end of file diff --git a/services/database.ts b/services/database.ts index fbb9b6c..016e635 100644 --- a/services/database.ts +++ b/services/database.ts @@ -42,6 +42,21 @@ 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) + + 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] + ); + }); + } catch (error) { + console.log("Error updating expense: ", error); + throw error; + } +}; + export const addExpense = async (name: string, category_guid: string, datetime: string, amount: number) => { //needs user input validation for type and color (should be validated by this function) diff --git a/types/dbItems.ts b/types/dbItems.ts index c425095..c2df2dc 100644 --- a/types/dbItems.ts +++ b/types/dbItems.ts @@ -4,4 +4,12 @@ export interface Category { color? :string; type? : "expense" | "saving" allocatedAmount? : number +} + +export interface Expense { + guid? : string; + name? : string; + dateTime? : string; + amount?: number; + category_guid?: string; } \ No newline at end of file