feat: filter functionality on Home screen

Long press on any day to remove filter
This commit is contained in:
Jakob Stornig 2024-01-20 17:01:08 +01:00
parent 1beee68bff
commit bc3d243ffc
3 changed files with 107 additions and 37 deletions

View file

@ -11,16 +11,30 @@ import { addExpense } from "../../../services/database";
import { SimpleDate } from '../../../util/SimpleDate'; import { SimpleDate } from '../../../util/SimpleDate';
import { useTheme } from '../../contexts/ThemeContext'; import { useTheme } from '../../contexts/ThemeContext';
import { useNavigation } from 'expo-router'; import { useNavigation } from 'expo-router';
import { Category, Expense } from '../../../types/dbItems';
interface MarkingProps { interface MarkingProps {
dots?:{color:string, selectedColor?:string, key?:string}[]; dots?:{color:string, selectedColor?:string, key?:string}[];
selected?: boolean;
selectedColor?: string;
} }
type MarkedDates = { type MarkedDates = {
[key: string]: MarkingProps; [key: string]: MarkingProps;
} }
interface ExpenseEntry extends Expense {
category_name?: string;
color?: string;
}
type Filters = {
search?: string;
month?: string;
day?: string;
}
const constructMarkedDates = (data : {[column: string]: any}) => { const constructMarkedDates = (data : {[column: string]: any}) => {
let markedDates: MarkedDates = {}; let markedDates: MarkedDates = {};
data.forEach((value: any) => { data.forEach((value: any) => {
@ -42,6 +56,7 @@ export default function Page() {
const [plusShow, setPlusShow] = useState(true); const [plusShow, setPlusShow] = useState(true);
const prevOffset = useRef(0); const prevOffset = useRef(0);
const [filter, setFilter] = useState<Filters>({})
const profile = require("../../../assets/images/profile.jpg") const profile = require("../../../assets/images/profile.jpg")
@ -53,19 +68,54 @@ export default function Page() {
setPlusShow(isScrollingUp || isTop) 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 {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(()=> const expenses = useMemo<ExpenseEntry[]>(
constructMarkedDates(data) () => {
, [data]) 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<ExpenseEntry[]>(
() => {
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(() => { useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => { const unsubscribe = navigation.addListener('focus', () => {
@ -75,7 +125,17 @@ export default function Page() {
return unsubscribe; return unsubscribe;
}, [navigation]); }, [navigation]);
const hanldeDaySelect = (date: string | undefined) => {
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 ( return (
<SafeAreaView edges={["left", "right", "top"]} style={[styles.safeAreaViewStyle, {backgroundColor: colors.containerColor}]}> <SafeAreaView edges={["left", "right", "top"]} style={[styles.safeAreaViewStyle, {backgroundColor: colors.containerColor}]}>
{plusShow && <Plus onPress={()=>{ {plusShow && <Plus onPress={()=>{
@ -90,10 +150,10 @@ export default function Page() {
// }) // })
}}/>} }}/>}
{isLoading && <LoadingSymbol/>} {/* {isLoading && <LoadingSymbol/>} */}
<FlatList <FlatList
data={data} data={filteredExpenses}
ListHeaderComponent={ ListHeaderComponent={
<> <>
<Welcome name="My Dude" image={profile} onPress={() => {router.push("/userSettings")}}/> <Welcome name="My Dude" image={profile} onPress={() => {router.push("/userSettings")}}/>
@ -108,23 +168,31 @@ export default function Page() {
}} }}
markingType='multi-dot' markingType='multi-dot'
markedDates={expenseDates} markedDates={expenseDates}
> onDayPress={(date) => {
hanldeDaySelect(date.dateString)
}}
onDayLongPress={(date) => {
hanldeDaySelect(undefined)
}}
onMonthChange={(date) => {
handleMonthSelect(date.dateString)
}}
/>
</Calendar> <TextInputBar placeholder='Type to Search...' value={filter.search} onChangeText={(text) => handleSearch(text)} style={{marginBottom: 20}}></TextInputBar>
<TextInputBar placeholder='Type to Search...' style={{marginBottom: 20}}></TextInputBar>
</> </>
} }
renderItem = {({item}) => renderItem = {({item}) =>
<ExpenseItem <ExpenseItem
category={item.category_name} category={item.category_name!}
color={item.category_color} color={item.color!}
date={item.expense_datetime} date={item.dateTime!}
title={item.expense_name} title={item.name!}
value={item.expense_amount} value={String(item.amount!)}
guid={item.expense_guid} guid={item.guid!}
onPress={(guid) => {router.push(`/expense/${guid}`)}} onPress={(guid) => {router.push(`/expense/${guid}`)}}
/>} />}
keyExtractor={item => item.expense_guid } keyExtractor={item => item.guid!}
ItemSeparatorComponent={() => { ItemSeparatorComponent={() => {
return (<View style={styles.itemSeperatorStyle}/>); return (<View style={styles.itemSeperatorStyle}/>);
}} }}

View file

@ -55,7 +55,7 @@ export default function AddItem() {
<CategorySelectorModal visible={selectorModalVisible} onRequestClose={()=>{setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}></CategorySelectorModal> <CategorySelectorModal visible={selectorModalVisible} onRequestClose={()=>{setSelecorModalVisible(false)}} onCategoryTap={handleCategorySelect}></CategorySelectorModal>
<AutoDecimalInput onValueChange={handleValueChange} label='Amount'/> <AutoDecimalInput onValueChange={handleValueChange} label='Amount'/>
<CategorySelector onPress={()=>{setSelecorModalVisible(true)}} selectedCategory={selectedCategory}/> <CategorySelector onPress={()=>{setSelecorModalVisible(true)}} selectedCategory={selectedCategory}/>
<TextInputBar placeholder='Name' onChangeText={(text)=>setExpenseName(text)}/> <TextInputBar placeholder='Name' value={expenseName} onChangeText={(text)=>setExpenseName(text)}/>
<DateSelectorButton selectedDate={selectedDate} onPress={()=>{setDatePickerShown(true)}}/> <DateSelectorButton selectedDate={selectedDate} onPress={()=>{setDatePickerShown(true)}}/>
{datePickerShown && {datePickerShown &&
<DateTimePicker <DateTimePicker

View file

@ -45,7 +45,9 @@ export default function TextInputBar(props: SearchBarProps) {
<TextInput placeholderTextColor={colors.secondaryText} onChangeText = {handleChange} style={[{fontSize: SIZES.normal, height: "100%", color:colors.primaryText}, styles.TextInput]} autoCorrect={false} keyboardType='default' placeholder={props.placeholder} value={props.value} onEndEditing={()=>setIsactive(false)}/> <TextInput placeholderTextColor={colors.secondaryText} onChangeText = {handleChange} style={[{fontSize: SIZES.normal, height: "100%", color:colors.primaryText}, styles.TextInput]} autoCorrect={false} keyboardType='default' placeholder={props.placeholder} value={props.value} onEndEditing={()=>setIsactive(false)}/>
{isActive && {isActive &&
<TouchableOpacity style={styles.cancel} onPress={()=>{handleChange("")}}> <TouchableOpacity style={styles.cancel} onPress={()=>{
console.log("cancel")
handleChange("")}}>
<AntDesign size={15} name='closecircle' color={colors.primaryText}></AntDesign> <AntDesign size={15} name='closecircle' color={colors.primaryText}></AntDesign>
</TouchableOpacity> </TouchableOpacity>
} }