Merge branch 'filter-on-home-screen' into 'main'
feat: filter functionality on Home screen See merge request thschleicher/interaktive-systeme!53
This commit is contained in:
commit
7a77b9ca79
3 changed files with 107 additions and 37 deletions
|
|
@ -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}/>);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue