Merge branch '57-implement-and-split-edit-category-from-add-category' into 'main'
Resolve "Implement and Split edit Category from add Category" Closes #57 See merge request thschleicher/interaktive-systeme!46
This commit is contained in:
commit
123e358f9e
8 changed files with 146 additions and 21 deletions
|
|
@ -25,13 +25,14 @@ stages:
|
||||||
|
|
||||||
lint_job:
|
lint_job:
|
||||||
stage: lint
|
stage: lint
|
||||||
image: node
|
image: "reactnativecommunity/react-native-android:latest"
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
when: manual
|
when: manual
|
||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
script:
|
script:
|
||||||
- npm install
|
- yarn install
|
||||||
|
- npx expo install
|
||||||
- npx tsc
|
- npx tsc
|
||||||
cache:
|
cache:
|
||||||
policy: pull-push
|
policy: pull-push
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export default function _Layout() {
|
||||||
<Stack initialRouteName="index" screenOptions={{headerShown: false}}>
|
<Stack initialRouteName="index" screenOptions={{headerShown: false}}>
|
||||||
<Stack.Screen name="index"/>
|
<Stack.Screen name="index"/>
|
||||||
<Stack.Screen name="addCategory" options={{presentation: "modal"}}/>
|
<Stack.Screen name="addCategory" options={{presentation: "modal"}}/>
|
||||||
|
<Stack.Screen name="editCategory" options={{presentation: "modal"}}/>
|
||||||
<Stack.Screen name="category" options={{headerShown: false, animation: "slide_from_right"}}/>
|
<Stack.Screen name="category" options={{headerShown: false, animation: "slide_from_right"}}/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,23 @@
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native";
|
import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native";
|
||||||
import { CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components";
|
import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components";
|
||||||
|
import { addCategory } from "../../../services/database";
|
||||||
import { useTheme } from "../../contexts/ThemeContext";
|
import { useTheme } from "../../contexts/ThemeContext";
|
||||||
|
|
||||||
const addCategory = () => {
|
export default function Page() {
|
||||||
const {colors} = useTheme();
|
const {colors} = useTheme();
|
||||||
|
|
||||||
const parameters = useLocalSearchParams();
|
const parameters = useLocalSearchParams();
|
||||||
|
|
||||||
const [categoryName, setCategoryName] = useState<string>("Enter Category Name...");
|
const [categoryName, setCategoryName] = useState<string>("Enter Category Name...");
|
||||||
const [categoryColor, setCategoryColor] = useState<null|string>(null);
|
const [categoryColor, setCategoryColor] = useState<string>('#' + Math.floor(Math.random()*16777215).toString(16));
|
||||||
const [selectedType, setSelectedType] = useState<string>("expense");
|
const [selectedType, setSelectedType] = useState<string>("expense");
|
||||||
|
const [amount, setAmount] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={[styles.safeAreaViewStyle, {backgroundColor: colors.backgroundColor}]}>
|
<SafeAreaView style={[styles.safeAreaViewStyle, {backgroundColor: colors.backgroundColor}]}>
|
||||||
<Text style={[styles.headingTextStyle, {color: colors.primaryText}]}>Category Editor</Text>
|
<Text style={[styles.headingTextStyle, {color: colors.primaryText}]}>Add Category</Text>
|
||||||
|
|
||||||
<View style={[styles.containerStyle, {backgroundColor: colors.containerColor}]}>
|
<View style={[styles.containerStyle, {backgroundColor: colors.containerColor}]}>
|
||||||
<View style={[styles.textInputViewStyle, {backgroundColor: colors.elementDefaultColor}]}>
|
<View style={[styles.textInputViewStyle, {backgroundColor: colors.elementDefaultColor}]}>
|
||||||
|
|
@ -24,6 +26,12 @@ const addCategory = () => {
|
||||||
}}/>
|
}}/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.budgetInput}>
|
||||||
|
<AutoDecimalInput label={"Allocated:"} onValueChange={(value) => {
|
||||||
|
setAmount(!Number.isNaN(Number.parseFloat(value)) ? Number.parseFloat(value) : 0);
|
||||||
|
}}/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<TypeSelectorSwitch
|
<TypeSelectorSwitch
|
||||||
currentSelected={selectedType}
|
currentSelected={selectedType}
|
||||||
handleButtonPress={(type) => {
|
handleButtonPress={(type) => {
|
||||||
|
|
@ -32,7 +40,7 @@ const addCategory = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View>
|
<View>
|
||||||
<CustomColorPicker currentColor={categoryColor} handleColorChange={(color) => {
|
<CustomColorPicker color={categoryColor} handleColorChange={(color) => {
|
||||||
setCategoryColor(color);
|
setCategoryColor(color);
|
||||||
}}/>
|
}}/>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -42,7 +50,7 @@ const addCategory = () => {
|
||||||
router.back();
|
router.back();
|
||||||
}}/>
|
}}/>
|
||||||
<NavigationButton text="Save" onPress={() => {
|
<NavigationButton text="Save" onPress={() => {
|
||||||
console.log("Implement Saving here!");
|
addCategory(categoryName, categoryColor, selectedType, amount);
|
||||||
router.back();
|
router.back();
|
||||||
}}/>
|
}}/>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -51,8 +59,6 @@ const addCategory = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default addCategory;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
containerStyle: {
|
containerStyle: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -82,5 +88,9 @@ const styles = StyleSheet.create({
|
||||||
textInputStyle: {
|
textInputStyle: {
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
fontSize: 25,
|
fontSize: 25,
|
||||||
|
},
|
||||||
|
budgetInput: {
|
||||||
|
marginBottom: 10,
|
||||||
|
marginHorizontal: 10,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -7,12 +7,14 @@ import { useTheme } from "../../contexts/ThemeContext";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const {colors} = useTheme();
|
const {colors} = useTheme();
|
||||||
const {category_guid, category_name} = useLocalSearchParams();
|
const {category_guid} = useLocalSearchParams();
|
||||||
|
|
||||||
|
const {category_amount, category_color, category_name, category_type} = fetchCategoryInformation(category_guid.toString());
|
||||||
|
|
||||||
const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_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 FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]});
|
const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_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 FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]});
|
||||||
|
|
||||||
const handleEditCategory = () => {
|
const handleEditCategory = () => {
|
||||||
console.log("edit category");
|
router.push({pathname: "./(tabs)/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}});
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBackButton = () => {
|
const handleBackButton = () => {
|
||||||
|
|
@ -54,6 +56,25 @@ export default function Page() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchCategoryInformation = (guid: string) => {
|
||||||
|
|
||||||
|
const {data} = useFetch({sql: "SELECT * FROM category WHERE guid = ?", args: [guid]});
|
||||||
|
|
||||||
|
let category_name = "";
|
||||||
|
let category_color = "";
|
||||||
|
let category_amount = 0;
|
||||||
|
let category_type = "";
|
||||||
|
|
||||||
|
if (data && data[0]) {
|
||||||
|
if ("name" in data[0]) category_name = data[0].name as string;
|
||||||
|
if ("color" in data[0]) category_color = data[0].color as string;
|
||||||
|
if ("allocated_amount" in data[0]) category_amount = data[0].allocated_amount as number;
|
||||||
|
if ("type" in data[0]) category_type = data[0].type as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {category_name, category_color, category_amount, category_type};
|
||||||
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
safeAreaView: {
|
safeAreaView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
||||||
96
app/(tabs)/budget/editCategory.tsx
Normal file
96
app/(tabs)/budget/editCategory.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { SafeAreaView, StyleSheet, Text, TextInput, View } from "react-native";
|
||||||
|
import { AutoDecimalInput, CustomColorPicker, NavigationButton, TypeSelectorSwitch } from "../../../components";
|
||||||
|
import { useTheme } from "../../contexts/ThemeContext";
|
||||||
|
|
||||||
|
const addCategory = () => {
|
||||||
|
const {colors} = useTheme();
|
||||||
|
const {category_guid, category_amount, category_color, category_name, category_type} = useLocalSearchParams();
|
||||||
|
|
||||||
|
const [categoryName, setCategoryName] = useState(category_name.toString());
|
||||||
|
const [categoryColor, setCategoryColor] = useState(category_color.toString());
|
||||||
|
const [selectedType, setSelectedType] = useState(category_type.toString());
|
||||||
|
const [amount, setAmount] = useState(Number.parseFloat(category_amount.toString()));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={[styles.safeAreaViewStyle, {backgroundColor: colors.backgroundColor}]}>
|
||||||
|
<Text style={[styles.headingTextStyle, {color: colors.primaryText}]}>Edit Category</Text>
|
||||||
|
|
||||||
|
<View style={[styles.containerStyle, {backgroundColor: colors.containerColor}]}>
|
||||||
|
<View style={[styles.textInputViewStyle, {backgroundColor: colors.elementDefaultColor}]}>
|
||||||
|
<TextInput placeholder={categoryName} placeholderTextColor={colors.secondaryText} style={[styles.textInputStyle, {color: colors.primaryText}]} onChangeText={(newName: string) => {
|
||||||
|
setCategoryName(newName);
|
||||||
|
}}/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.budgetInput}>
|
||||||
|
<AutoDecimalInput label={"Allocated:"} onValueChange={(value) => {
|
||||||
|
setAmount(!Number.isNaN(Number.parseFloat(value)) ? Number.parseFloat(value) : 0);
|
||||||
|
}}/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TypeSelectorSwitch
|
||||||
|
currentSelected={selectedType}
|
||||||
|
handleButtonPress={(type) => {
|
||||||
|
setSelectedType(type);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<CustomColorPicker color={categoryColor} handleColorChange={(color) => {
|
||||||
|
setCategoryColor(color);
|
||||||
|
}}/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.navigationButtonViewStyle}>
|
||||||
|
<NavigationButton text="Back" onPress={() => {
|
||||||
|
router.back();
|
||||||
|
}}/>
|
||||||
|
<NavigationButton text="Save" onPress={() => {
|
||||||
|
console.log("Implement Saving here!");
|
||||||
|
router.back();
|
||||||
|
}}/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addCategory;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
containerStyle: {
|
||||||
|
flex: 1,
|
||||||
|
margin: 10,
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
|
safeAreaViewStyle: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: "column"
|
||||||
|
},
|
||||||
|
headingTextStyle: {
|
||||||
|
fontSize: 40,
|
||||||
|
fontWeight: "bold",
|
||||||
|
alignSelf: "center",
|
||||||
|
marginVertical: 10,
|
||||||
|
},
|
||||||
|
navigationButtonViewStyle: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "center",
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
textInputViewStyle: {
|
||||||
|
borderRadius: 10,
|
||||||
|
paddingVertical: 10,
|
||||||
|
margin: 10,
|
||||||
|
},
|
||||||
|
textInputStyle: {
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
fontSize: 25,
|
||||||
|
},
|
||||||
|
budgetInput: {
|
||||||
|
marginBottom: 10,
|
||||||
|
marginHorizontal: 10,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -38,7 +38,7 @@ export default function Page() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCategoryPress = (item: {[column: string]: any;}) => {
|
const handleCategoryPress = (item: {[column: string]: any;}) => {
|
||||||
router.push({pathname: "/(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}})
|
router.push({pathname: "./(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@ const CategoryItem = (properties: CategoryItemProps) => {
|
||||||
|
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
const subText = `${properties.total_expenses} / ${properties.allocated_amount} €`;
|
const subText = `${properties.total_expenses.toFixed(2)} / ${properties.allocated_amount} €`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={properties.onPress}>
|
<TouchableOpacity onPress={properties.onPress}>
|
||||||
<CustomCard style={styles.customCardStyle}>
|
<CustomCard style={styles.customCardStyle}>
|
||||||
<View style={[styles.colorTipStyle, {backgroundColor: "blue"}]}/>
|
<View style={[styles.colorTipStyle, {backgroundColor: properties.color}]}/>
|
||||||
<View style={[styles.textViewStyle]}>
|
<View style={[styles.textViewStyle]}>
|
||||||
<Text style={[styles.categoryNameStyle, {color: colors.primaryText}]}>
|
<Text style={[styles.categoryNameStyle, {color: colors.primaryText}]}>
|
||||||
{properties.category}
|
{properties.category}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ import { StyleSheet } from "react-native";
|
||||||
import ColorPicker, { BrightnessSlider, HueSlider, Preview, SaturationSlider, } from "reanimated-color-picker";
|
import ColorPicker, { BrightnessSlider, HueSlider, Preview, SaturationSlider, } from "reanimated-color-picker";
|
||||||
|
|
||||||
export type CustomColorPickerProperties = {
|
export type CustomColorPickerProperties = {
|
||||||
currentColor?: string | null,
|
color: string,
|
||||||
handleColorChange: (color: string) => void | undefined,
|
handleColorChange: (color: string) => void | undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomColorPicker = (properties: CustomColorPickerProperties) => {
|
const CustomColorPicker = (properties: CustomColorPickerProperties) => {
|
||||||
return (
|
return (
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
value={properties.currentColor ?? generateRandomColor()}
|
value={properties.color}
|
||||||
onChange={(color) => {
|
onChange={(color) => {
|
||||||
properties.handleColorChange(color["hex"])
|
properties.handleColorChange(color["hex"])
|
||||||
}}
|
}}
|
||||||
|
|
@ -30,10 +30,6 @@ const CustomColorPicker = (properties: CustomColorPickerProperties) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateRandomColor = (): string => {
|
|
||||||
return '#' + Math.floor(Math.random()*16777215).toString(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CustomColorPicker;
|
export default CustomColorPicker;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
|
||||||
Reference in a new issue