Merge branch '58-stats-page-database-connection' into 'main'

Resolve "stats page, database connection"

Closes #58

See merge request thschleicher/interaktive-systeme!47
This commit is contained in:
jastornig 2024-01-05 13:56:58 +00:00
commit 21ea0bfe07
7 changed files with 216 additions and 59 deletions

View file

@ -6,6 +6,8 @@ import Widget from '../../../components/stats/Widget';
import CategoryProgressBarList from '../../../components/stats/CategoryProgressBarList';
import BudgetRemaining from '../../../components/stats/BudgetRemaining';
import DebugMenu from '../../../services/DebugMenu';
import SavingsOverview from '../../../components/stats/SavingsOverview';
import FinancialAdvice from '../../../components/stats/FinancialAdvice';
export default function Page() {
const { colors } = useTheme();
@ -16,25 +18,7 @@ export default function Page() {
const spent = 120.75;
const budget = 696.96;
const BudgetData = [
{ name: 'Utilities', color: '#20B2AA', maxValue: 80, currentValue: 46 },
{ name: 'Food', color: '#FF6347', maxValue: 88, currentValue: 31 },
];
const SavingsData = [
{ name: 'Education', color: '#FF6347', maxValue: 135, currentValue: 0 },
{ name: 'Rent', color: '#DA70D6', maxValue: 140, currentValue: 96 },
{ name: 'Food', color: '#F08080', maxValue: 84, currentValue: 78 },
{ name: 'Healthcare', color: '#20B2AA', maxValue: 134, currentValue: 48 },
{ name: 'Healthcare', color: '#32CD32', maxValue: 119, currentValue: 69 },
{ name: 'Clothing', color: '#32CD32', maxValue: 115, currentValue: 99 },
];
const categoryData = [
{ category: 'Food', value: 50 },
{ category: 'Rent', value: 300 },
{ category: 'Utilities', value: 100 },
];
const styles = StyleSheet.create({
container: {
@ -49,13 +33,19 @@ export default function Page() {
<View style={styles.container}>
<ScrollView>
<DebugMenu />
<Widget title='"Financial" Tips' backgroundColor='orange'>
<FinancialAdvice/>
</Widget>
<Widget title="Budget Overview" />
<Widget>
<BudgetRemaining budget={budget} spent={spent} />
<BudgetRemaining/>
</Widget>
<Widget title='Your Expenses so far' image={require('../../../assets/images/8b14el.jpg')}/>
<Widget>
<BudgetOverview budget={budget} spent={spent} />
<BudgetOverview/>
</Widget>
<Widget>
<SavingsOverview/>
</Widget>
</ScrollView>
</View>

View file

@ -1,31 +1,55 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useTheme } from '../../app/contexts/ThemeContext';
import useFetch from '../../hooks/useFetch';
interface StatsBudgetProps {
spent: number;
budget: number;
}
const BudgetOverview: React.FC<StatsBudgetProps> = ({ spent, budget }) => {
const BudgetOverview = () => {
const { colors } = useTheme();
const [spent, setSpent] = useState(0);
const [budget, setBudget] = useState(0);
const spentQuery = {
sql: "SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = 'budget'",
args: []
};
const budgetQuery = {
sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'",
args: []
};
const { data: spentData, isLoading: spentLoading } = useFetch(spentQuery);
const { data: budgetData, isLoading: budgetLoading } = useFetch(budgetQuery);
useEffect(() => {
if (spentData) {
setSpent(spentData[0]?.total || 0);
}
if (budgetData) {
setBudget(budgetData[0]?.total || 0);
}
}, [spentData, budgetData]);
const styles = StyleSheet.create({
container: {
margin: 10,
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
text: {
fontSize: 26,
color: colors.primaryText,
color: colors.primaryText
},
boldText: {
fontWeight: 'bold',
},
fontWeight: 'bold'
}
});
if (spentLoading || budgetLoading) {
return <Text>Loading...</Text>;
}
return (
<View style={styles.container}>
<Text style={styles.text}>
@ -35,4 +59,4 @@ const BudgetOverview: React.FC<StatsBudgetProps> = ({ spent, budget }) => {
);
};
export default BudgetOverview
export default BudgetOverview;

View file

@ -1,35 +1,52 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { Text, StyleSheet } from 'react-native';
import { useTheme } from '../../app/contexts/ThemeContext';
import useFetch from '../../hooks/useFetch';
interface BudgetRemainingProps {
budget: number;
spent: number;
}
const BudgetRemaining: React.FC<BudgetRemainingProps> = ({ budget, spent }) => {
const { colors } = useTheme();
const remaining = budget - spent;
const styles = StyleSheet.create({
container: {
margin: 10,
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center',
},
const styles = StyleSheet.create({
text: {
fontSize: 26,
color: colors.primaryText,
},
boldText: {
fontWeight: 'bold',
},
});
});
const BudgetRemaining = () => {
const { colors } = useTheme();
const [spent, setSpent] = useState(0);
const [budget, setBudget] = useState(0);
const spentQuery = {
sql: "SELECT SUM(amount) as total FROM expense",
args: []
};
const budgetQuery = {
sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'",
args: []
};
const { data: spentData, isLoading: spentLoading } = useFetch(spentQuery);
const { data: budgetData, isLoading: budgetLoading } = useFetch(budgetQuery);
useEffect(() => {
if (spentData) {
setSpent(spentData[0]?.total || 0);
}
if (budgetData) {
setBudget(budgetData[0]?.total || 0);
}
}, [spentData, budgetData]);
const remaining = budget - spent;
if (spentLoading || budgetLoading) {
return <Text>Loading...</Text>;
}
return (
<Text style={styles.text}>
<Text style={[styles.text, {color: colors.primaryText}]}>
You have <Text style={styles.boldText}>{remaining.toFixed(2)}</Text> left.
</Text>
);

View file

@ -0,0 +1,59 @@
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { useTheme } from '../../app/contexts/ThemeContext';
const FinancialAdvice = () => {
const tips = [
"Maybe you shouldn't have bought that full-price video game. But who needs savings when you have high scores, right?",
"That daily gourmet coffee is essential, isn't it? Who needs a retirement fund when you've got caffeine!",
"Oh, another pair of designer shoes? Because the other twenty pairs just aren't enough.",
"A luxury car to drive two blocks? Obviously, walking is for peasants.",
"Sure, subscribe to all streaming services. Who needs to socialize outside when you can binge-watch shows alone forever?",
"A gym membership you never use? At least your wallet's getting a workout.",
"Booking another expensive vacation? It's not like you need to save for a rainy day or anything.",
"Another impulse purchase online? Because 'limited time offer' is definitely not a marketing tactic.",
"Eating out for every meal? Clearly, cooking at home is way too mainstream.",
"Upgrading to the latest phone model again? It must be tough having a phone that's 6 months old."
];
const { colors } = useTheme();
const [tip, setTip] = useState('');
useEffect(() => {
// Change the tip every 10 seconds
const intervalId = setInterval(() => {
const randomTip = tips[Math.floor(Math.random() * tips.length)];
setTip(randomTip);
}, 10000);
// Clear the interval on component unmount
return () => clearInterval(intervalId);
}, [tips]);
const styles = StyleSheet.create({
container: {
margin: 10,
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center'
},
text: {
fontSize: 26,
color: colors.primaryText
},
boldText: {
fontWeight: 'bold'
}
});
return (
<View style={styles.container}>
<Text style={styles.text}>
{tip}
</Text>
</View>
);
};
export default FinancialAdvice;

View file

@ -0,0 +1,62 @@
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useTheme } from '../../app/contexts/ThemeContext';
import useFetch from '../../hooks/useFetch';
const SavingsOverview = () => {
const { colors } = useTheme();
const [saved, setSaved] = useState(0);
const [goal, setGoal] = useState(0);
const savedQuery = {
sql: "SELECT SUM(e.amount) as total FROM expense e LEFT JOIN category c ON e.category_guid = c.guid WHERE c.type = 'savings'",
args: []
};
const goalQuery = {
sql: "SELECT SUM(allocated_amount) as total FROM category WHERE type = 'budget'",
args: []
};
const { data: savedData, isLoading: savedLoading } = useFetch(savedQuery);
const { data: goalData, isLoading: goalLoading } = useFetch(goalQuery);
useEffect(() => {
if (savedData) {
setSaved(savedData[0]?.total || 0);
}
if (goalData) {
setGoal(goalData[0]?.total || 0);
}
}, [savedData, goalData]);
const styles = StyleSheet.create({
container: {
margin: 10,
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center'
},
text: {
fontSize: 26,
color: colors.primaryText
},
boldText: {
fontWeight: 'bold'
}
});
if (savedLoading || goalLoading) {
return <Text>Loading...</Text>;
}
return (
<View style={styles.container}>
<Text style={styles.text}>
You have saved <Text style={styles.boldText}>{saved.toFixed(2)}</Text> out of your goal of <Text style={styles.boldText}>{goal.toFixed(2)}</Text>.
</Text>
</View>
);
};
export default SavingsOverview;

View file

@ -1,5 +1,5 @@
import React, { ReactNode } from 'react';
import { View, StyleSheet, Text, Image } from 'react-native'; // Add the missing import statement for Image
import { View, StyleSheet, Text, Image } from 'react-native';
import { useTheme } from '../../app/contexts/ThemeContext';
interface WidgetProps {
@ -7,14 +7,18 @@ interface WidgetProps {
text?: string;
children?: ReactNode;
image?: any;
backgroundColor?: string;
}
const Widget: React.FC<WidgetProps> = ({ title, text, children, image }) => { // Add the 'image' prop to the destructuring
const Widget: React.FC<WidgetProps> = ({ title, text, children, image, backgroundColor }) => {
const { colors } = useTheme();
const actualBackgroundColor = backgroundColor ? backgroundColor : colors.widgetBackgroundColor;
const styles = StyleSheet.create({
widgetContainer: {
backgroundColor: colors.widgetBackgroundColor,
backgroundColor: actualBackgroundColor,
borderColor: colors.widgetBorderColor,
borderRadius: 5,
padding: 16,

View file

@ -144,6 +144,7 @@ export const DEV_populateDatabase = async () => {
for(let i=0; i < 5; i++){
let random = Math.floor(Math.random() * colors.length);
await addCategory(`Category ${i}`, colors[random], "budget", 50)
await addCategory(`Category ${i+6}`, colors[random], "budget", 50)
}
const result = await executeQuery({sql:"SELECT * from category", args:[]})
let categories: {[column: string]: any}[];
@ -160,7 +161,7 @@ export const DEV_populateDatabase = async () => {
let randomDay = Math.floor(Math.random() * 20)
date.setDate(randomDay)
let string = new SimpleDate(date).toISOString()
await addExpense(`Expense ${i}`, categories[random].guid, string, 15)
await addExpense(`Expense ${i}`, categories[random].guid, string, 30)
}
} catch(e){
console.error(e)