Auto fill amount category edit Empty list component calendar filtering in category screen, expenses can be added directly
218 lines
No EOL
8.2 KiB
TypeScript
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,
|
|
}
|
|
}); |