This repository has been archived on 2026-04-20. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
interaktive-systeme/app/(tabs)/(home)/index.tsx
Jakob Stornig 8149ec234f A few changes:
Auto fill amount category edit
Empty list component
calendar filtering
in category screen, expenses can be added directly
2024-01-25 19:18:15 +01:00

218 lines
No EOL
8.2 KiB
TypeScript

import React, { useEffect, 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 { EmptyListCompenent, 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';
import { useNavigation } from 'expo-router';
import { Category, Expense } from '../../../types/dbItems';
interface MarkingProps {
dots?:{color:string, selectedColor?:string, key?:string}[];
selected?: boolean;
selectedColor?: string;
}
type MarkedDates = {
[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}) => {
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<Filters>({})
const profile = require("../../../assets/images/profile.jpg")
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>)=>{
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<ExpenseEntry[]>(
() => {
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(() => {
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 (
<SafeAreaView edges={["left", "right", "top"]} style={[styles.safeAreaViewStyle, {backgroundColor: colors.containerColor}]}>
{plusShow && <Plus onPress={()=>{
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 && <LoadingSymbol/>} */}
<FlatList
data={filteredExpenses}
ListHeaderComponent={
<>
<Welcome name="My Dude" image={profile} onPress={() => {router.push("/userSettings")}}/>
<Calendar key={theme} maxDate={SimpleDate.now().format("YYYY-MM-DD")} style={{borderRadius: 20, padding:10}} theme={{
dayTextColor: colors.primaryText,
textDisabledColor: colors.secondaryText,
todayTextColor: colors.accentColor,
calendarBackground: colors.containerColor,
arrowColor: colors.accentColor,
monthTextColor: colors.accentColor
}}
markingType='multi-dot'
markedDates={expenseDates}
onDayPress={(date) => {
hanldeDaySelect(date.dateString)
}}
onMonthChange={(date) => {
handleMonthSelect(date.dateString)
}}
/>
<TextInputBar placeholder='Type to Search...' value={filter.search} onChangeText={(text) => handleSearch(text)} style={{marginBottom: 20}}></TextInputBar>
</>
}
renderItem = {({item}) =>
<ExpenseItem
category={item.category_name!}
color={item.color!}
date={item.dateTime!}
title={item.name!}
value={String(item.amount!)}
guid={item.guid!}
onPress={(guid) => {router.push(`/expense/${guid}`)}}
/>}
keyExtractor={item => item.guid!}
ItemSeparatorComponent={() => {
return (<View style={styles.itemSeperatorStyle}/>);
}}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={reFetch}/>
}
onScroll={handleScroll}
scrollEventThrottle={20}
ListEmptyComponent={EmptyListCompenent}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safeAreaViewStyle: {
flex: 1,
paddingHorizontal: 10
},
itemSeperatorStyle: {
marginVertical: 5,
}
});