Skip to content

Commit

Permalink
feat: Add transaction creation with account linking
Browse files Browse the repository at this point in the history
- Add account selection to TransactionForm
- Fix currency formatting and amount validation
- Update account balances on transaction creation
- Add proper error handling with user feedback
  • Loading branch information
EXTREMOPHILARUM committed Nov 25, 2024
1 parent e11f813 commit 9b69c1e
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 6 deletions.
28 changes: 27 additions & 1 deletion src/components/forms/TransactionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,38 @@ import { Input } from '../common/Input';
import { Button } from '../common/Button';
import { CurrencyInput } from '../form/CurrencyInput';
import { DateInput } from '../form/DateInput';
import { Select } from '../form/Select';
import { Account } from '../../types/account';

interface TransactionFormData {
amount: string;
description: string;
category: string;
date: Date;
type: 'income' | 'expense';
accountId: string;
}

interface TransactionFormProps {
onSubmit: (data: TransactionFormData) => void;
initialData?: Partial<TransactionFormData>;
isLoading?: boolean;
accounts: Account[];
}

export const TransactionForm: React.FC<TransactionFormProps> = ({
onSubmit,
initialData,
isLoading = false,
accounts,
}) => {
const [formData, setFormData] = useState<TransactionFormData>({
amount: initialData?.amount || '',
description: initialData?.description || '',
category: initialData?.category || '',
date: initialData?.date || new Date(),
type: initialData?.type || 'expense',
accountId: initialData?.accountId || '',
});

const [errors, setErrors] = useState<Partial<Record<keyof TransactionFormData, string>>>({});
Expand All @@ -46,19 +52,39 @@ export const TransactionForm: React.FC<TransactionFormProps> = ({
if (!formData.category) {
newErrors.category = 'Category is required';
}
if (!formData.accountId) {
newErrors.accountId = 'Account is required';
}

setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};

const handleSubmit = () => {
if (validate()) {
onSubmit(formData);
// Clean the amount value before submitting
const cleanAmount = formData.amount.replace(/[^0-9.]/g, '');
onSubmit({
...formData,
amount: cleanAmount
});
}
};

return (
<View style={styles.container}>
<Select
label="Account"
value={formData.accountId}
onValueChange={(value) => setFormData({ ...formData, accountId: value })}
items={accounts.map(account => ({
label: account.name,
value: account.id
}))}
error={errors.accountId}
placeholder="Select an account"
/>

<CurrencyInput
label="Amount"
value={formData.amount}
Expand Down
104 changes: 99 additions & 5 deletions src/screens/DashboardScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, TouchableOpacity, RefreshControl, Platform } from 'react-native';
import { View, Text, ScrollView, TouchableOpacity, RefreshControl, Platform, Alert } from 'react-native';
import { useApp } from '../context/AppContext';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { useNavigation } from '@react-navigation/native';
Expand All @@ -14,7 +14,10 @@ import { GoalCard } from '../components/list/GoalCard';
import { MonthlyComparison } from '../components/charts/MonthlyComparison';
import { CategoryBreakdown } from '../components/charts/CategoryBreakdown';
import { formatCurrency } from '../utils/currency';
import { generateUUID } from '../utils/uuid';
import { Account } from '../types/account';
import { TransactionForm } from '../components/forms/TransactionForm';
import { FormModal } from '../components/common/FormModal';

interface Summary {
totalBalance: number;
Expand Down Expand Up @@ -47,6 +50,7 @@ type RootStackParamList = {
Budget: undefined;
Investments: undefined;
Goals: undefined;
AddTransaction: undefined;
};

const DashboardScreen = () => {
Expand All @@ -65,6 +69,9 @@ const DashboardScreen = () => {
monthlyData: [],
categoryData: [],
});
const [isTransactionFormVisible, setIsTransactionFormVisible] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [accounts, setAccounts] = useState<Account[]>([]);
const insets = useSafeAreaInsets();

const fetchSummary = async () => {
Expand Down Expand Up @@ -160,16 +167,81 @@ const DashboardScreen = () => {
});
};

const fetchAccounts = async () => {
if (!database) return;
const allAccounts = await database.accounts.find().exec();
setAccounts(allAccounts);
};

useEffect(() => {
fetchSummary();
fetchAccounts();
}, [database]);

const onRefresh = async () => {
setRefreshing(true);
await fetchSummary();
await fetchAccounts();
setRefreshing(false);
};

const handleCreateTransaction = async (transactionData: any) => {
if (!database) return;

try {
setIsSubmitting(true);
const id = generateUUID('tx');
const now = Date.now();

// Get the account to use its currency
const account = await database.accounts.findOne(transactionData.accountId).exec();
if (!account) {
throw new Error('Account not found');
}

// Parse the amount as a float, ensuring it's a valid number
const amount = parseFloat(transactionData.amount);
if (isNaN(amount)) {
throw new Error('Invalid amount value');
}

const newTransaction = {
id,
accountId: transactionData.accountId,
amount,
description: transactionData.description.trim(),
category: transactionData.category.trim(),
date: transactionData.date.getTime(),
type: transactionData.type,
currency: account.currency,
createdAt: now,
updatedAt: now,
};

// Update account balance
const balanceChange = transactionData.type === 'income' ? amount : -amount;
await account.patch({
balance: account.balance + balanceChange,
updatedAt: now,
});

await database.transactions.insert(newTransaction);
await fetchSummary();
await fetchAccounts();
setIsTransactionFormVisible(false);
} catch (error) {
console.error('Error creating transaction:', error);
Alert.alert('Error', error.message || 'Failed to create transaction');
} finally {
setIsSubmitting(false);
}
};

const handleOpenTransactionForm = async () => {
await fetchAccounts();
setIsTransactionFormVisible(true);
};

const DashboardHeader: React.FC<{ title: string; subtitle: string }> = ({ title, subtitle }) => (
<View className="px-4 pt-6 pb-4">
<Text className="text-2xl font-bold text-neutral-900 mb-1">{title}</Text>
Expand Down Expand Up @@ -258,6 +330,11 @@ const DashboardScreen = () => {
icon: 'trending-up-outline',
label: 'Add Investment',
onPress: () => navigation.navigate('Investments')
},
{
icon: 'add-circle-outline',
label: 'Add Transaction',
onPress: handleOpenTransactionForm
}
];

Expand Down Expand Up @@ -341,9 +418,11 @@ const DashboardScreen = () => {
<Card className="mx-4 mb-6">
<View className="flex-row justify-between items-center mb-4">
<Text className="text-xl font-bold text-neutral-900">Recent Activity</Text>
<TouchableOpacity onPress={() => navigation.navigate('Transactions')}>
<Text className="text-primary-600">See All</Text>
</TouchableOpacity>
<View className="flex-row space-x-4">
<TouchableOpacity onPress={() => navigation.navigate('Transactions')}>
<Text className="text-primary-600">See All</Text>
</TouchableOpacity>
</View>
</View>
{summary.recentTransactions.length > 0 ? (
summary.recentTransactions.map((transaction: any) => (
Expand All @@ -365,7 +444,22 @@ const DashboardScreen = () => {
);
};

return renderContent();
return (
<View className="flex-1">
{renderContent()}
<FormModal
visible={isTransactionFormVisible}
onClose={() => setIsTransactionFormVisible(false)}
title="Add Transaction"
>
<TransactionForm
onSubmit={handleCreateTransaction}
isLoading={isSubmitting}
accounts={accounts}
/>
</FormModal>
</View>
);
};

export default DashboardScreen;

0 comments on commit 9b69c1e

Please sign in to comment.