feat: Add expense screen
This commit is contained in:
parent
e1efed5b21
commit
36679279c1
18 changed files with 459 additions and 57 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { StyleSheet, Text, TouchableHighlight, View } from "react-native";
|
||||
import SearchBar from "../common/SearchBar";
|
||||
import TextInputBar from "../common/TextInputBar";
|
||||
import { useTheme } from "../../app/contexts/ThemeContext";
|
||||
|
||||
type BudgetHeaderProperties = {
|
||||
|
|
@ -34,7 +34,7 @@ const BudgetHeader = (properties: BudgetHeaderProperties) => {
|
|||
}}
|
||||
/>
|
||||
</View>
|
||||
<SearchBar placeholder='Search...'></SearchBar>
|
||||
<TextInputBar placeholder='Search...'></TextInputBar>
|
||||
</>);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { View, Text, TouchableOpacity, TextInput, StyleSheet, NativeSyntheticEvent, TextInputKeyPressEventData } from 'react-native'
|
||||
import React, {LegacyRef, MutableRefObject, useRef, useState} from 'react'
|
||||
import colors from '../../constants/colors';
|
||||
import { SIZES } from '../../constants/theme';
|
||||
import { useTheme } from '../../app/contexts/ThemeContext';
|
||||
|
||||
|
|
@ -69,17 +68,15 @@ const AutoDecimalInput: React.FC<AutoDecimalInputProps> = ({onValueChange, label
|
|||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
margin: SIZES.normal,
|
||||
},
|
||||
inputContainer: {
|
||||
minHeight: 50,
|
||||
borderRadius: 20,
|
||||
flexDirection: "row",
|
||||
justifyContent: 'space-between'
|
||||
justifyContent: 'space-between',
|
||||
alignItems: "center"
|
||||
},
|
||||
text:{
|
||||
fontSize: SIZES.large,
|
||||
marginVertical: 12,
|
||||
marginHorizontal: 15,
|
||||
},
|
||||
currency: {
|
||||
|
|
|
|||
66
components/common/CategoryListItem.tsx
Normal file
66
components/common/CategoryListItem.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { StyleSheet, Text, View, Pressable } from 'react-native'
|
||||
import React from 'react'
|
||||
import { Category } from '../../types/dbItems'
|
||||
import CustomCard from './CustomCard';
|
||||
import { SIZES } from '../../constants/theme';
|
||||
import { useTheme } from '../../app/contexts/ThemeContext';
|
||||
|
||||
interface CategoryListItemProps{
|
||||
category: Category;
|
||||
onPress?: (category: Category) =>void | undefined
|
||||
}
|
||||
|
||||
|
||||
|
||||
const CategoryListItem: React.FC<CategoryListItemProps> = (props: CategoryListItemProps) => {
|
||||
const {category, onPress} = props;
|
||||
const {colors} = useTheme();
|
||||
|
||||
const handlePress = ()=>{
|
||||
if(onPress){
|
||||
onPress(category);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable onPress={handlePress}>
|
||||
<View style={[styles.tile, {backgroundColor: colors.backgroundColor}]}>
|
||||
<View style={[styles.colorTip, {backgroundColor: category.color}]}/>
|
||||
<View style={[styles.textWrapper]}>
|
||||
<Text style={[styles.text, {color: colors.primaryText}]}>{category.name ?? "#noData#"}</Text>
|
||||
</View>
|
||||
<View style={styles.tileTail}/>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
export default CategoryListItem
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tile: {
|
||||
height: 60,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
borderRadius: 20
|
||||
},
|
||||
colorTip:{
|
||||
height: "100%",
|
||||
width: 30,
|
||||
borderTopLeftRadius:20,
|
||||
borderBottomLeftRadius: 20
|
||||
},
|
||||
textWrapper: {
|
||||
|
||||
},
|
||||
tileTail:{
|
||||
height: "100%",
|
||||
width: 30,
|
||||
borderTopRightRadius:20,
|
||||
borderBottomRightRadius: 20
|
||||
},
|
||||
text: {
|
||||
fontSize: SIZES.large,
|
||||
}
|
||||
})
|
||||
|
|
@ -39,10 +39,7 @@ export default function CustomCard(props : ViewProps) {
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container:{
|
||||
flexDirection: "row",
|
||||
alignItems: "stretch",
|
||||
alignContent: "space-between",
|
||||
borderRadius: 10,
|
||||
borderRadius: 20,
|
||||
marginHorizontal: 10,
|
||||
},
|
||||
boxShadow: {},
|
||||
|
|
|
|||
32
components/common/RoundedButton.tsx
Normal file
32
components/common/RoundedButton.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { StyleSheet, Text, View, ViewProps, TouchableOpacity } from 'react-native'
|
||||
import React from 'react'
|
||||
import { SIZES } from '../../constants/theme';
|
||||
|
||||
interface RoundedButtonProps extends ViewProps{
|
||||
onPress?: ()=> void | undefined;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const RoundedButton: React.FC<RoundedButtonProps> = (props: RoundedButtonProps) => {
|
||||
const {onPress, color, style, ...restProps} = props;
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress}>
|
||||
<View style={[{backgroundColor: color}, styles.btn, style]}{...restProps}>
|
||||
{restProps.children}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
export default RoundedButton
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
btn:{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 80
|
||||
},
|
||||
text: {
|
||||
fontSize: SIZES.normal
|
||||
}
|
||||
})
|
||||
|
|
@ -42,12 +42,12 @@ export default function TextInputBar(props: SearchBarProps) {
|
|||
//TODO: Handle textCancel
|
||||
// changed styles.container to containerStyle
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<TextInput onChangeText = {handleChange} style={[{fontSize: SIZES.normal, height: "100%"}, styles.TextInput]} autoCorrect={false} keyboardType='default' placeholder={props.placeholder} value={text}/>
|
||||
<View style={[containerStyle, props.style]}>
|
||||
<TextInput placeholderTextColor={colors.secondaryText} onChangeText = {handleChange} style={[{fontSize: SIZES.normal, height: "100%", color:colors.primaryText}, styles.TextInput]} autoCorrect={false} keyboardType='default' placeholder={props.placeholder} value={text} onFocus={()=>handleChange(text)} onEndEditing={()=>setIsactive(false)}/>
|
||||
|
||||
{isActive &&
|
||||
<TouchableOpacity style={styles.cancel} onPress={()=>{handleChange("")}}>
|
||||
<AntDesign size={15} name='closecircle'></AntDesign>
|
||||
<AntDesign size={15} name='closecircle' color={colors.primaryText}></AntDesign>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
</View>
|
||||
|
|
@ -56,8 +56,6 @@ export default function TextInputBar(props: SearchBarProps) {
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginHorizontal: 10,
|
||||
marginBottom: 20,
|
||||
flexDirection: 'row',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
|
|
|
|||
60
components/home/addItem/CategorySelector.tsx
Normal file
60
components/home/addItem/CategorySelector.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
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 CategorySelectorModal from './CategorySelectorModal';
|
||||
|
||||
interface CategorySelectorProps {
|
||||
onPress? : () => void | undefined;
|
||||
selectedCategory? : Category;
|
||||
}
|
||||
|
||||
const CategorySelector: React.FC<CategorySelectorProps> = (props : CategorySelectorProps) => {
|
||||
const {colors} = useTheme();
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableOpacity style={[styles.tile, {backgroundColor: colors.backgroundColor}]} onPress={props.onPress}>
|
||||
<View style={[styles.colorTip, {backgroundColor: props.selectedCategory?.color}]}>
|
||||
|
||||
</View>
|
||||
<View style={[styles.textWrapper]}>
|
||||
<Text style={[styles.text, {color: colors.primaryText}]}>{props.selectedCategory?.name ?? "Tap to select Categroy"}</Text>
|
||||
</View>
|
||||
<View style={styles.tileTail}></View>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CategorySelector
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tile: {
|
||||
height: 60,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
borderRadius: 20
|
||||
},
|
||||
colorTip:{
|
||||
height: "100%",
|
||||
width: 30,
|
||||
borderTopLeftRadius:20,
|
||||
borderBottomLeftRadius: 20
|
||||
},
|
||||
textWrapper: {
|
||||
|
||||
},
|
||||
tileTail:{
|
||||
height: "100%",
|
||||
width: 30,
|
||||
borderTopRightRadius:20,
|
||||
borderBottomRightRadius: 20
|
||||
},
|
||||
text: {
|
||||
fontSize: SIZES.large,
|
||||
}
|
||||
})
|
||||
93
components/home/addItem/CategorySelectorModal.tsx
Normal file
93
components/home/addItem/CategorySelectorModal.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
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';
|
||||
|
||||
|
||||
interface CategorySelectorModalProps{
|
||||
visible: boolean;
|
||||
onCategoryTap?: (category : Category) => void | undefined;
|
||||
selectMulitple?: boolean;
|
||||
onRequestClose?: ((event: NativeSyntheticEvent<any>) => void) | undefined;
|
||||
}
|
||||
|
||||
//TODO: select Multiple
|
||||
|
||||
const CategorySelectorModal: React.FC<CategorySelectorModalProps> = (props : CategorySelectorModalProps) => {
|
||||
const {visible, onCategoryTap, selectMulitple} = props;
|
||||
const {data, reFetch} = useFetch({sql: "SELECT * FROM category;", args:[]});
|
||||
const {colors} = useTheme();
|
||||
const [searchtext, setSearchtext] = useState<string>("");
|
||||
|
||||
const handleSearchText = (text : string) => {
|
||||
setSearchtext(text);
|
||||
}
|
||||
|
||||
const categories = useMemo<Category[]>(()=>{
|
||||
return data.map((elem) => {
|
||||
return {name: elem["name"], color: elem["color"], guid: elem["guid"]}
|
||||
})
|
||||
}, [data])
|
||||
|
||||
const filteredCategories = categories.filter((category) => category.name?.toLowerCase().includes(searchtext.toLowerCase()))
|
||||
|
||||
useEffect(()=>{
|
||||
if(visible){
|
||||
//reFetch(); Uncomment if newly added categories do not appear
|
||||
handleSearchText("");
|
||||
}
|
||||
}, [visible])
|
||||
|
||||
return (
|
||||
<Modal visible={visible} transparent={true} onRequestClose={props.onRequestClose}>
|
||||
<View style={styles.main}>
|
||||
<View style={[styles.modal, {backgroundColor: colors.containerColor}]}>
|
||||
<View>
|
||||
<Text style={[styles.heading, {color: colors.primaryText}]}>{selectMulitple ? "Categories" : "Category"}</Text>
|
||||
</View>
|
||||
<TextInputBar placeholder='TypeToSearch' onChangeText={handleSearchText}></TextInputBar>
|
||||
<FlatList
|
||||
data={filteredCategories}
|
||||
keyExtractor={(item) => item.guid!}
|
||||
renderItem={({item})=> <CategoryListItem category={item} onPress={onCategoryTap}/>}
|
||||
ItemSeparatorComponent={() => <View style={styles.itemSeperatorStyle}/>}
|
||||
ListFooterComponent={() => <View style={styles.itemSeperatorStyle}/>}
|
||||
keyboardShouldPersistTaps="always"
|
||||
>
|
||||
</FlatList>
|
||||
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CategorySelectorModal
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
},
|
||||
modal: {
|
||||
height: "70%",
|
||||
width: "90%",
|
||||
top: "10%",
|
||||
borderRadius: 30,
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 20
|
||||
},
|
||||
heading: {
|
||||
fontSize: SIZES.xlarge,
|
||||
fontWeight: "bold"
|
||||
},
|
||||
itemSeperatorStyle: {
|
||||
paddingBottom: 10
|
||||
}
|
||||
})
|
||||
38
components/home/addItem/DateSelectorButton.tsx
Normal file
38
components/home/addItem/DateSelectorButton.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
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 { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes';
|
||||
|
||||
interface DateSelectorProps extends ViewProps {
|
||||
onPress?: ()=>void | undefined;
|
||||
selectedDate: Date
|
||||
}
|
||||
|
||||
const DateSelectorButton:React.FC<DateSelectorProps> = (props: DateSelectorProps) => {
|
||||
const {onPress, selectedDate, ...restProps} = props;
|
||||
const {colors} = useTheme();
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} {...restProps} style={[styles.inputContainer, {backgroundColor: colors.elementDefaultColor}]}>
|
||||
<Text style={[styles.text, {color: colors.primaryText}]}>Date:</Text>
|
||||
<Text style={[styles.text, {color: colors.primaryText}]}>{new SimpleDate(selectedDate).format("DD.MM.YYYY")}</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
inputContainer: {
|
||||
minHeight: 50,
|
||||
borderRadius: 20,
|
||||
flexDirection: "row",
|
||||
justifyContent: 'space-between',
|
||||
alignItems: "center"
|
||||
},
|
||||
text:{
|
||||
fontSize: SIZES.large,
|
||||
marginHorizontal: 15,
|
||||
},
|
||||
})
|
||||
|
||||
export default DateSelectorButton
|
||||
|
|
@ -9,28 +9,29 @@ export default function ExpenseItem(itemProps : ExpenseItemProps) {
|
|||
const { colors } = useTheme();
|
||||
return (
|
||||
<CustomCard>
|
||||
<View style={[styles.colorTip, {backgroundColor: itemProps.color}]}></View>
|
||||
<View style={[styles.textSection, {backgroundColor: colors.backgroundColor}]}>
|
||||
<Text style={{
|
||||
fontSize: SIZES.normal,
|
||||
color: colors.primaryText
|
||||
}} numberOfLines={1}>{itemProps.category}</Text>
|
||||
<Text style={{
|
||||
fontSize: SIZES.large,
|
||||
color: colors.primaryText
|
||||
}} numberOfLines={1}>{itemProps.title}</Text>
|
||||
<Text style={{
|
||||
fontSize: SIZES.small,
|
||||
color: colors.primaryText
|
||||
}} numberOfLines={1}>{itemProps.date}</Text>
|
||||
</View>
|
||||
<View style={[styles.valueSection, {backgroundColor: colors.backgroundColor}]}>
|
||||
<Text style={{
|
||||
fontSize: SIZES.xxLarge,
|
||||
color: colors.primaryText
|
||||
}} numberOfLines={1}>{itemProps.value}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.tile}>
|
||||
<View style={[styles.colorTip, {backgroundColor: itemProps.color}]}></View>
|
||||
<View style={[styles.textSection, {backgroundColor: colors.backgroundColor}]}>
|
||||
<Text style={{
|
||||
fontSize: SIZES.normal,
|
||||
color: colors.primaryText
|
||||
}} numberOfLines={1}>{itemProps.category}</Text>
|
||||
<Text style={{
|
||||
fontSize: SIZES.large,
|
||||
color: colors.primaryText
|
||||
}} numberOfLines={1}>{itemProps.title}</Text>
|
||||
<Text style={{
|
||||
fontSize: SIZES.small,
|
||||
color: colors.primaryText
|
||||
}} numberOfLines={1}>{itemProps.date}</Text>
|
||||
</View>
|
||||
<View style={[styles.valueSection, {backgroundColor: colors.backgroundColor}]}>
|
||||
<Text style={{
|
||||
fontSize: SIZES.xxLarge,
|
||||
color: colors.primaryText
|
||||
}} numberOfLines={1}>{itemProps.value}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</CustomCard>
|
||||
|
||||
)
|
||||
|
|
@ -42,6 +43,12 @@ const styles = StyleSheet.create({
|
|||
borderTopLeftRadius: 20,
|
||||
borderBottomLeftRadius: 20,
|
||||
},
|
||||
|
||||
tile: {
|
||||
flexDirection: "row",
|
||||
alignItems: "stretch",
|
||||
alignContent: "space-between",
|
||||
},
|
||||
|
||||
textSection: {
|
||||
flexDirection: "column",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@ import { ButtonSetting, ToggleSetting } from "./home/Setting"
|
|||
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"
|
||||
|
||||
//common
|
||||
import CustomCard from "./common/CustomCard"
|
||||
import NavigationButton from "./common/button"
|
||||
|
|
@ -10,8 +15,7 @@ import LoadingSymbol from "./common/loadingSymbol"
|
|||
import Plus from "./common/plus"
|
||||
import TextInputBar from "./common/TextInputBar"
|
||||
import AutoDecimalInput from "./common/AutoDecimalInput"
|
||||
import CategorySelector from "./common/CategorySelector"
|
||||
import CategorySelectorModal from "./common/CategorySelectorModal"
|
||||
import RoundedButton from "./common/RoundedButton"
|
||||
|
||||
//login
|
||||
import BudgetHeader from "./budget/budgetHeader"
|
||||
|
|
@ -23,8 +27,24 @@ import TypeSelectorSwitch from "./budget/typeSelectorSwitch"
|
|||
import Input from "./login/input"
|
||||
|
||||
export {
|
||||
BudgetHeader, ButtonSetting, CustomCard, ExpenseItem, Input,
|
||||
LoadingSymbol, Plus,
|
||||
TextInputBar, ToggleSetting, Welcome, AutoDecimalInput
|
||||
BudgetHeader,
|
||||
ButtonSetting,
|
||||
CustomCard,
|
||||
ExpenseItem,
|
||||
Input,
|
||||
LoadingSymbol,
|
||||
Plus,
|
||||
TextInputBar,
|
||||
ToggleSetting,
|
||||
Welcome,
|
||||
AutoDecimalInput,
|
||||
CategorySelector,
|
||||
CategorySelectorModal,
|
||||
DateSelectorButton,
|
||||
RoundedButton,
|
||||
CategoryItem,
|
||||
TypeSelectorSwitch,
|
||||
NavigationButton,
|
||||
CustomColorPicker
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in a new issue