diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..4e5a775 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,8 +13,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Ausgaben Tracker", + description: "Eine einfache Anwendung zum Verfolgen von Ausgaben und Einnahmen", }; export default function RootLayout({ diff --git a/src/app/page.tsx b/src/app/page.tsx index e68abe6..036afac 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,103 +1,51 @@ -import Image from "next/image"; +"use client"; + +import { useState, useEffect } from 'react'; +import { Transaction } from '@/types'; +import TransactionForm from '@/components/TransactionForm'; +import TransactionList from '@/components/TransactionList'; +import Summary from '@/components/Summary'; export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - src/app/page.tsx - - . -
  2. -
  3. - Save and see your changes instantly. -
  4. -
+ const [transactions, setTransactions] = useState([]); -
- - Vercel logomark - Deploy now - - - Read our docs - + // Load transactions from localStorage on component mount + useEffect(() => { + const savedTransactions = localStorage.getItem('transactions'); + if (savedTransactions) { + setTransactions(JSON.parse(savedTransactions)); + } + }, []); + + // Save transactions to localStorage whenever they change + useEffect(() => { + localStorage.setItem('transactions', JSON.stringify(transactions)); + }, [transactions]); + + const handleAddTransaction = (transaction: Transaction) => { + setTransactions([...transactions, transaction]); + }; + + const handleDeleteTransaction = (id: string) => { + setTransactions(transactions.filter(transaction => transaction.id !== id)); + }; + + return ( +
+
+

Ausgaben Tracker

+ +
+ + + + +
-
- +
); } diff --git a/src/components/Summary.tsx b/src/components/Summary.tsx new file mode 100644 index 0000000..fa2ea60 --- /dev/null +++ b/src/components/Summary.tsx @@ -0,0 +1,42 @@ +import { Transaction } from '@/types'; + +interface SummaryProps { + transactions: Transaction[]; +} + +export default function Summary({ transactions }: SummaryProps) { + const totalIncome = transactions + .filter(t => t.type === 'income') + .reduce((sum, transaction) => sum + transaction.amount, 0); + + const totalExpenses = transactions + .filter(t => t.type === 'expense') + .reduce((sum, transaction) => sum + transaction.amount, 0); + + const balance = totalIncome - totalExpenses; + + return ( +
+

Zusammenfassung

+ +
+
+

Einnahmen

+

+{totalIncome.toFixed(2)} €

+
+ +
+

Ausgaben

+

-{totalExpenses.toFixed(2)} €

+
+ +
+

Bilanz

+

= 0 ? 'text-green-600' : 'text-red-600'}`}> + {balance >= 0 ? '+' : ''}{balance.toFixed(2)} € +

+
+
+
+ ); +} diff --git a/src/components/TransactionForm.tsx b/src/components/TransactionForm.tsx new file mode 100644 index 0000000..ba5270b --- /dev/null +++ b/src/components/TransactionForm.tsx @@ -0,0 +1,104 @@ +import { useState } from 'react'; +import { Transaction } from '@/types'; + +interface TransactionFormProps { + onAddTransaction: (transaction: Transaction) => void; +} + +export default function TransactionForm({ onAddTransaction }: TransactionFormProps) { + const [description, setDescription] = useState(''); + const [amount, setAmount] = useState(''); + const [type, setType] = useState<'expense' | 'income'>('expense'); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!description || !amount) return; + + const newTransaction: Transaction = { + id: Date.now().toString(), + description, + amount: parseFloat(amount), + type, + date: new Date().toISOString().split('T')[0] + }; + + onAddTransaction(newTransaction); + + // Reset form + setDescription(''); + setAmount(''); + setType('expense'); + }; + + return ( +
+

Neue Transaktion

+ +
+ + setDescription(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + required + /> +
+ +
+ + setAmount(e.target.value)} + step="0.01" + min="0.01" + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + required + /> +
+ +
+ +
+ + +
+
+ + +
+ ); +} diff --git a/src/components/TransactionList.tsx b/src/components/TransactionList.tsx new file mode 100644 index 0000000..6190dd6 --- /dev/null +++ b/src/components/TransactionList.tsx @@ -0,0 +1,47 @@ +import { Transaction } from '@/types'; + +interface TransactionListProps { + transactions: Transaction[]; + onDeleteTransaction: (id: string) => void; +} + +export default function TransactionList({ transactions, onDeleteTransaction }: TransactionListProps) { + if (transactions.length === 0) { + return ( +
+

Keine Transaktionen vorhanden

+
+ ); + } + + return ( +
+

Transaktionen

+ +
+ ); +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..36f3438 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,7 @@ +export interface Transaction { + id: string; + description: string; + amount: number; + type: 'expense' | 'income'; + date: string; +}