Merge branch '51-skill-issue' into 'main'
Resolve "skill issue" Closes #51 See merge request thschleicher/interaktive-systeme!36
This commit is contained in:
commit
d2837c12b3
10 changed files with 407 additions and 50 deletions
|
|
@ -1,61 +1,69 @@
|
|||
import { Query } from 'expo-sqlite';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { addCategory, deleteDatabase, deleteExpenses, executeQuery, initDatabase } from '../../../services/database';
|
||||
import React from 'react';
|
||||
import { StyleSheet, View, ScrollView } from 'react-native';
|
||||
import BudgetOverview from '../../../components/stats/BudgetOverview';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import Widget from '../../../components/stats/Widget';
|
||||
import CategoryProgressBarList from '../../../components/stats/CategoryProgressBarList';
|
||||
import BudgetRemaining from '../../../components/stats/BudgetRemaining';
|
||||
import DebugMenu from '../../../services/DebugMenu';
|
||||
|
||||
export default function Page() {
|
||||
const { colors } = useTheme();
|
||||
|
||||
// Mock data #TODO Database einbinden
|
||||
// what to do when amount too small?
|
||||
|
||||
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: {
|
||||
flex: 1,
|
||||
justifyContent: 'space-evenly',
|
||||
backgroundColor: colors.backgroundColor,
|
||||
marginTop: 50,
|
||||
alignItems: 'center',
|
||||
},
|
||||
text: {
|
||||
fontSize: 40,
|
||||
color: "yellow",
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text} onPress={() => {
|
||||
deleteExpenses().then(() => {
|
||||
console.log("Expenses Deleted!");
|
||||
})
|
||||
}}>Reset Expenses</Text>
|
||||
|
||||
<Text style={styles.text} onPress={() => {
|
||||
deleteDatabase();
|
||||
console.log("Database Deleted!");
|
||||
}}>Reset Database</Text>
|
||||
|
||||
<Text style={styles.text} onPress={() => {
|
||||
initDatabase().then(() => {
|
||||
console.log("Database Initialized!");
|
||||
});
|
||||
}}>Init Database</Text>
|
||||
|
||||
<Text style={styles.text} onPress={() => {
|
||||
addCategory("Category", "green", "expense", 500).then(() => {
|
||||
const getCategoryQuery: Query = {sql: "SELECT guid FROM category", args: []};
|
||||
executeQuery(getCategoryQuery).then((result) => {
|
||||
if("rows" in result[0]) {
|
||||
console.log(result[0]["rows"]);
|
||||
}
|
||||
})
|
||||
console.log("Category added with success!");
|
||||
})
|
||||
}}>Add new Category Expense</Text>
|
||||
|
||||
<Text style={styles.text} onPress={() => {
|
||||
addCategory("Category", "yellow", "saving", 420).then(() => {
|
||||
const getCategoryQuery: Query = {sql: "SELECT guid FROM category", args: []};
|
||||
executeQuery(getCategoryQuery).then((result) => {
|
||||
if("rows" in result[0]) {
|
||||
console.log(result[0]["rows"]);
|
||||
}
|
||||
})
|
||||
console.log("Category added with success!");
|
||||
})
|
||||
}}>Add new Category Savings</Text>
|
||||
</View>);
|
||||
<View style={styles.container}>
|
||||
<ScrollView>
|
||||
<DebugMenu />
|
||||
<Widget title="Budget Overview" />
|
||||
<Widget>
|
||||
<BudgetRemaining budget={budget} spent={spent} />
|
||||
</Widget>
|
||||
<Widget text='#TODO Insert Pie-Chart' image={require('../../../assets/images/8b14el.jpg')}/>
|
||||
<Widget>
|
||||
<BudgetOverview budget={budget} spent={spent} />
|
||||
</Widget>
|
||||
<Widget title="Budget Progress">
|
||||
<CategoryProgressBarList categories={BudgetData} />
|
||||
</Widget>
|
||||
<Widget title="Savings Progress">
|
||||
<CategoryProgressBarList categories={SavingsData} />
|
||||
</Widget>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
BIN
assets/images/8b14el.jpg
Normal file
BIN
assets/images/8b14el.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
38
components/stats/BudgetOverview.tsx
Normal file
38
components/stats/BudgetOverview.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { useTheme } from '../../app/contexts/ThemeContext';
|
||||
|
||||
interface StatsBudgetProps {
|
||||
spent: number;
|
||||
budget: number;
|
||||
}
|
||||
|
||||
const BudgetOverview: React.FC<StatsBudgetProps> = ({ spent, budget }) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
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}>
|
||||
You have spent <Text style={styles.boldText}>{spent.toFixed(2)}€</Text> out of your budget of <Text style={styles.boldText}>{budget.toFixed(2)}€</Text>.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default BudgetOverview
|
||||
38
components/stats/BudgetRemaining.tsx
Normal file
38
components/stats/BudgetRemaining.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Text, StyleSheet } from 'react-native';
|
||||
import { useTheme } from '../../app/contexts/ThemeContext';
|
||||
|
||||
interface BudgetRemainingProps {
|
||||
budget: number;
|
||||
spent: number;
|
||||
}
|
||||
|
||||
const BudgetRemaining: React.FC<BudgetRemainingProps> = ({ budget, spent }) => {
|
||||
const { colors, theme } = useTheme();
|
||||
|
||||
const remaining = budget - spent;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
margin: 10,
|
||||
borderRadius: 5,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
text: {
|
||||
fontSize: 26,
|
||||
color: colors.primaryText,
|
||||
},
|
||||
boldText: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Text style={styles.text}>
|
||||
You have <Text style={styles.boldText}>{remaining.toFixed(2)}€</Text> left.
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default BudgetRemaining;
|
||||
69
components/stats/CategoryProgressBar.tsx
Normal file
69
components/stats/CategoryProgressBar.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { useTheme } from '../../app/contexts/ThemeContext';
|
||||
|
||||
interface CategoryProgressBarProps {
|
||||
categoryName: string;
|
||||
color?: string;
|
||||
maxValue: number;
|
||||
currentValue: number;
|
||||
}
|
||||
|
||||
const CategoryProgressBar: React.FC<CategoryProgressBarProps> = ({
|
||||
categoryName,
|
||||
color,
|
||||
maxValue,
|
||||
currentValue,
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
const progress = (currentValue / maxValue) * 100;
|
||||
const progressText = `${currentValue}€ / ${maxValue}€`;
|
||||
|
||||
const dynamicStyles = StyleSheet.create({
|
||||
progressBarFill: {
|
||||
height: '100%',
|
||||
width: `${progress}%`,
|
||||
backgroundColor: color || colors.accentColor,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
progressText: {
|
||||
color: colors.primaryText,
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
padding: 10,
|
||||
},
|
||||
progressBarContainer: {
|
||||
flexDirection: 'row',
|
||||
height: 50,
|
||||
backgroundColor: colors.elementSelectedColor,
|
||||
borderRadius: 15,
|
||||
overflow: 'hidden',
|
||||
marginTop: 4,
|
||||
},
|
||||
categoryName: {
|
||||
color: colors.primaryText,
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.categoryName}>{categoryName}</Text>
|
||||
<View style={styles.progressBarContainer}>
|
||||
<View style={dynamicStyles.progressBarFill}>
|
||||
<Text style={dynamicStyles.progressText}>{progressText}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryProgressBar;
|
||||
61
components/stats/CategoryProgressBarList.tsx
Normal file
61
components/stats/CategoryProgressBarList.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useState } from 'react';
|
||||
import { View, Text, Button, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import CategoryProgressBar from './CategoryProgressBar';
|
||||
import { useTheme } from '../../app/contexts/ThemeContext';
|
||||
|
||||
interface CategoryItem {
|
||||
name: string;
|
||||
color: string;
|
||||
maxValue: number;
|
||||
currentValue: number;
|
||||
}
|
||||
|
||||
interface CategoryProgressBarListProps {
|
||||
categories: CategoryItem[];
|
||||
}
|
||||
|
||||
const MAX_VISIBLE_BARS = 4;
|
||||
|
||||
const CategoryProgressBarList: React.FC<CategoryProgressBarListProps> = ({ categories }) => {
|
||||
const [visibleBars, setVisibleBars] = useState(MAX_VISIBLE_BARS);
|
||||
|
||||
const showMore = () => {
|
||||
setVisibleBars(prevVisibleBars => prevVisibleBars + MAX_VISIBLE_BARS);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
{categories.slice(0, visibleBars).map((category, index) => (
|
||||
<CategoryProgressBar
|
||||
key={index}
|
||||
categoryName={category.name}
|
||||
color={category.color}
|
||||
maxValue={category.maxValue}
|
||||
currentValue={category.currentValue}
|
||||
/>
|
||||
))}
|
||||
{visibleBars < categories.length && (
|
||||
<TouchableOpacity style={styles.showMoreButton} onPress={showMore}>
|
||||
<Text style={styles.buttonText}>Show More</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
showMoreButton: {
|
||||
backgroundColor: '#EF6C00',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
margin: 10,
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
export default CategoryProgressBarList;
|
||||
1
components/stats/PieChart.tsx
Normal file
1
components/stats/PieChart.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
//honestly just fuck graphs
|
||||
63
components/stats/Widget.tsx
Normal file
63
components/stats/Widget.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { View, StyleSheet, Text, Image } from 'react-native'; // Add the missing import statement for Image
|
||||
import { useTheme } from '../../app/contexts/ThemeContext';
|
||||
|
||||
interface WidgetProps {
|
||||
title?: string;
|
||||
text?: string;
|
||||
children?: ReactNode;
|
||||
image?: any;
|
||||
}
|
||||
|
||||
const Widget: React.FC<WidgetProps> = ({ title, text, children, image }) => { // Add the 'image' prop to the destructuring
|
||||
const { colors } = useTheme();
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
widgetContainer: {
|
||||
backgroundColor: colors.widgetBackgroundColor,
|
||||
borderColor: colors.widgetBorderColor,
|
||||
borderRadius: 5,
|
||||
padding: 16,
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 10,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 1,
|
||||
},
|
||||
shadowOpacity: 0.22,
|
||||
shadowRadius: 2.22,
|
||||
elevation: 3,
|
||||
},
|
||||
widgetTitle: {
|
||||
color: colors.primaryText,
|
||||
fontSize: 26,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
widgetText: {
|
||||
color: colors.primaryText,
|
||||
fontSize: 16,
|
||||
marginBottom: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
imageStyle: {
|
||||
width: '100%',
|
||||
height: 200,
|
||||
borderRadius: 5,
|
||||
marginBottom: 8,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.widgetContainer}>
|
||||
{!!title && <Text style={styles.widgetTitle}>{title}</Text>}
|
||||
{!!text && <Text style={styles.widgetText}>{text}</Text>}
|
||||
{!!image && <Image source={image} style={styles.imageStyle} />}
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Widget;
|
||||
|
|
@ -12,6 +12,9 @@ export default {
|
|||
elementDefaultColor: "#E0E0E0",
|
||||
elementSelectedColor: "#9E9E9E",
|
||||
accentColor: "#EF6C00",
|
||||
|
||||
widgetBackgroundColor: "#F7F7F7",
|
||||
widgetBorderColor: "#E0E0E0",
|
||||
},
|
||||
dark: {
|
||||
primaryText: "#FFFFFF",
|
||||
|
|
@ -26,5 +29,8 @@ export default {
|
|||
elementDefaultColor: "#535353",
|
||||
elementSelectedColor: "#B3B3B3",
|
||||
accentColor: "#EF6C00",
|
||||
|
||||
widgetBackgroundColor: "#252525",
|
||||
widgetBorderColor: "#535353",
|
||||
}
|
||||
}
|
||||
73
services/DebugMenu.tsx
Normal file
73
services/DebugMenu.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react';
|
||||
import { View, Button, Alert } from 'react-native';
|
||||
import { addCategory, addExpense, deleteExpenses, deleteCategories, DEV_populateDatabase } from './database';
|
||||
import uuid from 'react-native-uuid';
|
||||
|
||||
const randomColors = ["red", "blue", "green", "purple", "yellow"];
|
||||
|
||||
const getRandomColor = () => {
|
||||
return randomColors[Math.floor(Math.random() * randomColors.length)];
|
||||
};
|
||||
|
||||
const getRandomName = () => {
|
||||
return `RandomName-${Math.floor(Math.random() * 1000)}`;
|
||||
};
|
||||
|
||||
const getRandomNumber = () => {
|
||||
return Math.floor(Math.random() * 1000);
|
||||
};
|
||||
|
||||
const DebugMenu = () => {
|
||||
|
||||
const handleNukeDatabase = () => {
|
||||
return deleteExpenses(), deleteCategories()
|
||||
};
|
||||
|
||||
const handlePopulateDatabase = () => {
|
||||
return DEV_populateDatabase()
|
||||
};
|
||||
|
||||
const handleDeleteExpenses = () => {
|
||||
return deleteExpenses();
|
||||
}
|
||||
const handleDeleteCategories = () => {
|
||||
return deleteCategories();
|
||||
}
|
||||
|
||||
//for some reason this function does not work
|
||||
const handleAddCategory = () => {
|
||||
const name = getRandomName();
|
||||
const color = getRandomColor();
|
||||
const allocated_amount = getRandomNumber();
|
||||
const type = "expense";
|
||||
|
||||
addCategory(name, color, type, allocated_amount)
|
||||
.then(() => Alert.alert("Category Added", `Name: ${name}, Color: ${color}`))
|
||||
.catch((error: any) => console.error("Error adding category: ", error));
|
||||
};
|
||||
|
||||
//for some reason this function does not work
|
||||
const handleAddExpense = () => {
|
||||
const name = getRandomName();
|
||||
const categoryGuid = uuid.v4().toString();
|
||||
const datetime = new Date().toISOString();
|
||||
const amount = Math.floor(Math.random() * 1000);
|
||||
|
||||
addExpense(name, categoryGuid, datetime, amount)
|
||||
.then(() => Alert.alert("Expense Added", `Name: ${name}, Amount: ${amount}`))
|
||||
.catch((error: any) => console.error("Error adding expense: ", error));
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Button title="Nuke Database" onPress={handleNukeDatabase} />
|
||||
<Button title="DEV_populateDatabase" onPress={handlePopulateDatabase} />
|
||||
<Button title="Delete All Expenses" onPress={handleDeleteExpenses} />
|
||||
<Button title="Delete All Categories" onPress={handleDeleteCategories} />
|
||||
<Button title="Add Random Category" onPress={handleAddCategory} />
|
||||
<Button title="Add Random Expense" onPress={handleAddExpense} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugMenu;
|
||||
Reference in a new issue