217 lines
No EOL
8.1 KiB
TypeScript
217 lines
No EOL
8.1 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 { 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) => {
|
|
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)
|
|
}}
|
|
onDayLongPress={(date) => {
|
|
hanldeDaySelect(undefined)
|
|
}}
|
|
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}
|
|
/>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
safeAreaViewStyle: {
|
|
flex: 1,
|
|
paddingHorizontal: 10
|
|
},
|
|
itemSeperatorStyle: {
|
|
marginVertical: 5,
|
|
}
|
|
}); |