188 lines
3.6 KiB
JavaScript
188 lines
3.6 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
|
import axios from "axios";
|
|
import "./App.css";
|
|
|
|
function App() {
|
|
|
|
const [transactions, setTransactions] = useState([]);
|
|
const [title, setTitle] = useState("");
|
|
const [amount, setAmount] = useState("");
|
|
const [type, setType] = useState("expense");
|
|
|
|
const fetchTransactions = async () => {
|
|
|
|
const res = await axios.get("/api/transactions");
|
|
|
|
setTransactions(res.data);
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchTransactions();
|
|
}, []);
|
|
|
|
const addTransaction = async (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
await axios.post("/api/transactions", {
|
|
title,
|
|
amount,
|
|
type
|
|
});
|
|
|
|
setTitle("");
|
|
setAmount("");
|
|
|
|
fetchTransactions();
|
|
};
|
|
|
|
const deleteTransaction = async (id) => {
|
|
|
|
await axios.delete(`/api/transactions/${id}`);
|
|
|
|
fetchTransactions();
|
|
};
|
|
|
|
const income = transactions
|
|
.filter(t => t.type === "income")
|
|
.reduce((acc, t) => acc + Number(t.amount), 0);
|
|
|
|
const expenses = transactions
|
|
.filter(t => t.type === "expense")
|
|
.reduce((acc, t) => acc + Number(t.amount), 0);
|
|
|
|
const balance = (income - expenses).toFixed(2);
|
|
|
|
return (
|
|
|
|
<div className="page">
|
|
|
|
<div className="container">
|
|
|
|
<h1 className="title">
|
|
Budget Tracker
|
|
</h1>
|
|
|
|
<div className="balance-card">
|
|
|
|
<h2>Balance</h2>
|
|
|
|
<h1>{balance} €</h1>
|
|
|
|
</div>
|
|
|
|
<div className="stats-container">
|
|
|
|
<div className="income-card">
|
|
|
|
<h3>Income</h3>
|
|
|
|
<p>{income.toFixed(2)} €</p>
|
|
|
|
</div>
|
|
|
|
<div className="expense-card">
|
|
|
|
<h3>Expenses</h3>
|
|
|
|
<p>{expenses.toFixed(2)} €</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<form
|
|
onSubmit={addTransaction}
|
|
className="form"
|
|
>
|
|
|
|
<input
|
|
type="text"
|
|
placeholder="transaction title"
|
|
value={title}
|
|
onChange={(e) => setTitle(e.target.value)}
|
|
required
|
|
className="input"
|
|
/>
|
|
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
placeholder="amount"
|
|
value={amount}
|
|
onChange={(e) => setAmount(e.target.value)}
|
|
required
|
|
className="input"
|
|
/>
|
|
|
|
<select
|
|
value={type}
|
|
onChange={(e) => setType(e.target.value)}
|
|
className="select"
|
|
>
|
|
|
|
<option value="expense">
|
|
expense
|
|
</option>
|
|
|
|
<option value="income">
|
|
income
|
|
</option>
|
|
|
|
</select>
|
|
|
|
<button
|
|
type="submit"
|
|
className="add-button"
|
|
>
|
|
Add Transaction
|
|
</button>
|
|
|
|
</form>
|
|
|
|
<div className="transactions-container">
|
|
|
|
{transactions.map(transaction => (
|
|
|
|
<div
|
|
key={transaction.id}
|
|
className={`transaction-card ${
|
|
transaction.type === "income"
|
|
? "income-border"
|
|
: "expense-border"
|
|
}`}
|
|
>
|
|
|
|
<div>
|
|
|
|
<h3>{transaction.title}</h3>
|
|
|
|
<p>{Number(transaction.amount).toFixed(2)} €</p>
|
|
|
|
<small>
|
|
{new Date(transaction.created_at).toLocaleString()}
|
|
</small>
|
|
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => deleteTransaction(transaction.id)}
|
|
className="delete-button"
|
|
>
|
|
Delete
|
|
</button>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|