Skip to content

Commit 97e14f1

Browse files
committed
Refactor AddExpenseScreen to improve payer selection logic and enhance expense calculation; ensure proper handling of rounding errors
1 parent 3110bef commit 97e14f1

File tree

1 file changed

+49
-27
lines changed

1 file changed

+49
-27
lines changed

frontend/screens/AddExpenseScreen.js

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useContext, useEffect, useState } from 'react';
22
import { Alert, KeyboardAvoidingView, Platform, StyleSheet, View } from 'react-native';
3-
import { ActivityIndicator, Appbar, Button, Checkbox, Menu, Paragraph, SegmentedButtons, Text, TextInput, Title } from 'react-native-paper';
3+
import { ActivityIndicator, Button, Checkbox, Menu, Paragraph, SegmentedButtons, Text, TextInput, Title } from 'react-native-paper';
44
import { createExpense, getGroupMembers } from '../api/groups';
55
import { AuthContext } from '../context/AuthContext';
66

@@ -13,7 +13,7 @@ const AddExpenseScreen = ({ route, navigation }) => {
1313
const [isLoading, setIsLoading] = useState(true);
1414
const [isSubmitting, setIsSubmitting] = useState(false);
1515
const [splitMethod, setSplitMethod] = useState('equal');
16-
const [payerId, setPayerId] = useState(user._id);
16+
const [payerId, setPayerId] = useState(null); // Initialize as null until members are loaded
1717
const [menuVisible, setMenuVisible] = useState(false);
1818

1919
// State for different split methods
@@ -51,6 +51,14 @@ const AddExpenseScreen = ({ route, navigation }) => {
5151
setPercentages(initialPercentages);
5252
setExactAmounts(initialExactAmounts);
5353
setSelectedMembers(initialSelectedMembers);
54+
55+
// Set default payer to current user if they're a member
56+
const currentUserMember = response.data.find(member => member.userId === user._id);
57+
if (currentUserMember) {
58+
setPayerId(user._id);
59+
} else if (response.data.length > 0) {
60+
setPayerId(response.data[0].userId);
61+
}
5462
} catch (error) {
5563
console.error('Failed to fetch members:', error);
5664
Alert.alert('Error', 'Failed to fetch group members.');
@@ -68,6 +76,10 @@ const AddExpenseScreen = ({ route, navigation }) => {
6876
Alert.alert('Error', 'Please fill in all fields.');
6977
return;
7078
}
79+
if (!payerId) {
80+
Alert.alert('Error', 'Please select who paid for this expense.');
81+
return;
82+
}
7183
const numericAmount = parseFloat(amount);
7284
if (isNaN(numericAmount) || numericAmount <= 0) {
7385
Alert.alert('Error', 'Please enter a valid amount.');
@@ -87,9 +99,13 @@ const AddExpenseScreen = ({ route, navigation }) => {
8799
throw new Error('You must select at least one member for the split.');
88100
}
89101
const splitAmount = Math.round((numericAmount / includedMembers.length) * 100) / 100;
90-
splits = includedMembers.map(userId => ({
102+
// Calculate remainder to handle rounding
103+
const totalSplitAmount = splitAmount * includedMembers.length;
104+
const remainder = Math.round((numericAmount - totalSplitAmount) * 100) / 100;
105+
106+
splits = includedMembers.map((userId, index) => ({
91107
userId,
92-
amount: splitAmount,
108+
amount: index === 0 ? splitAmount + remainder : splitAmount, // Add remainder to first member
93109
type: 'equal'
94110
}));
95111
splitType = 'equal';
@@ -120,17 +136,32 @@ const AddExpenseScreen = ({ route, navigation }) => {
120136
}));
121137
splitType = 'percentage';
122138
} else if (splitMethod === 'shares') {
123-
const totalShares = Object.values(shares).reduce((sum, val) => sum + parseInt(val || '0', 10), 0);
139+
const nonZeroShares = Object.entries(shares).filter(([userId, value]) => parseInt(value || '0', 10) > 0);
140+
const totalShares = nonZeroShares.reduce((sum, [, value]) => sum + parseInt(value || '0', 10), 0);
141+
124142
if (totalShares === 0) {
125143
throw new Error('Total shares cannot be zero.');
126144
}
127-
splits = Object.entries(shares)
128-
.filter(([userId, value]) => parseInt(value || '0', 10) > 0)
129-
.map(([userId, value]) => ({
145+
146+
// Calculate amounts with proper rounding
147+
const amounts = nonZeroShares.map(([userId, value]) => {
148+
const shareRatio = parseInt(value, 10) / totalShares;
149+
return {
130150
userId,
131-
amount: Math.round((numericAmount * (parseInt(value, 10) / totalShares)) * 100) / 100,
151+
amount: Math.round((numericAmount * shareRatio) * 100) / 100,
132152
type: 'unequal'
133-
}));
153+
};
154+
});
155+
156+
// Adjust for rounding errors
157+
const totalCalculated = amounts.reduce((sum, item) => sum + item.amount, 0);
158+
const difference = Math.round((numericAmount - totalCalculated) * 100) / 100;
159+
160+
if (Math.abs(difference) > 0) {
161+
amounts[0].amount = Math.round((amounts[0].amount + difference) * 100) / 100;
162+
}
163+
164+
splits = amounts;
134165
splitType = 'unequal'; // Backend uses 'unequal' for shares
135166
}
136167

@@ -143,6 +174,7 @@ const AddExpenseScreen = ({ route, navigation }) => {
143174
tags: []
144175
};
145176

177+
console.log('Expense data being sent:', JSON.stringify(expenseData, null, 2));
146178
await createExpense(token, groupId, expenseData);
147179
Alert.alert('Success', 'Expense added successfully.');
148180
navigation.goBack();
@@ -244,20 +276,14 @@ const AddExpenseScreen = ({ route, navigation }) => {
244276
);
245277
}
246278

247-
const selectedPayerName = members.find(m => m.userId === payerId)?.user.name || 'Select Payer';
279+
const selectedPayerName = payerId ? (members.find(m => m.userId === payerId)?.user.name || 'Select Payer') : 'Select Payer';
248280

249281
return (
250-
<View style={styles.container}>
251-
<Appbar.Header>
252-
<Appbar.BackAction onPress={() => navigation.goBack()} />
253-
<Appbar.Content title="Add Expense" />
254-
</Appbar.Header>
255-
<KeyboardAvoidingView
256-
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
257-
style={styles.keyboardAvoidingView}
258-
>
259-
<View style={styles.content}>
260-
<Title>New Expense Details</Title>
282+
<KeyboardAvoidingView
283+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
284+
style={styles.container}
285+
>
286+
<View style={styles.content}>
261287
<TextInput
262288
label="Description"
263289
value={description}
@@ -341,18 +367,14 @@ const AddExpenseScreen = ({ route, navigation }) => {
341367
Add Expense
342368
</Button>
343369
</View>
344-
</KeyboardAvoidingView>
345-
</View>
370+
</KeyboardAvoidingView>
346371
);
347372
};
348373

349374
const styles = StyleSheet.create({
350375
container: {
351376
flex: 1,
352377
},
353-
keyboardAvoidingView: {
354-
flex: 1,
355-
},
356378
content: {
357379
flex: 1,
358380
padding: 16,

0 commit comments

Comments
 (0)