Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/expense-tracker-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions en/beginner-projects/expense-tracker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 💰 Expense Tracker

![Expense Tracker Preview](../../assets/expense-tracker-preview.png)

## Description

A simple **Expense Tracker** built using **HTML, CSS, and JavaScript (ES Modules)** that allows users to manage their daily income and expenses. Users can **add, edit, and delete** transactions, view their **balance summary**, and store data locally using **localStorage** — all without a backend.

## Features

- Add new transactions (income or expense)
- Edit existing transactions
- Delete unwanted transactions
- View total balance, income, and expense summaries
- Persistent data storage using `localStorage`
- Clean and responsive UI design

## Concepts Practiced

- DOM manipulation
- Working with arrays and objects
- Event handling and form submission
- Data persistence using `localStorage`
- Conditional rendering and dynamic UI updates
- Modular JavaScript (`index.mjs`)

## Bonus Challenge

- Add a **dark mode toggle** in the navbar 🌙
- Show a **monthly expense chart** using a JS chart library
- Add a **filter/search** option for transactions
- Allow users to **export data** as CSV

## Live Demo

<div align="center">
<iframe src="https://codesandbox.io/p/sandbox/f9n98v"
style="width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;"
title="expense-tracker"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
</div>
40 changes: 40 additions & 0 deletions examples/Expense-Tracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 💰 Expense Tracker

A simple and interactive **Expense Tracker** built using **HTML, CSS, and modern JavaScript (ES Modules)**.
It helps users manage their daily income and expenses, view summaries, and store data locally — all without a backend.

## Features

- ➕ **Add new transactions** — income or expense.
- ✏️ **Edit transactions** — update existing entries easily.
- ❌ **Delete transactions** you no longer need.
- 💵 **Live balance summary** showing total, income, and expenses.
- 💾 **Data persistence** using `localStorage` (no backend required).
- 📱 **Clean and responsive UI** — works on both desktop and mobile.

## Files

- `index.html` — main structure and layout of the app.
- `styles.css` — styling, layout, and responsive design.
- `index.mjs` — handles core functionality like adding, editing, deleting, and storing transactions.

## How to Use

1. Open `index.html` in your browser (Chrome, Edge, or Firefox recommended).
2. Enter a **description** and **amount**.
- Use positive numbers for income (e.g., `+5000`).
- Use negative numbers for expenses (e.g., `-1200`).
3. Click **Add Transaction** to save it.
4. View your total **balance**, **income**, and **expense** at a glance.
5. Edit or delete transactions anytime — all changes are saved automatically.

## Notes

- Data is stored locally in the browser using **localStorage**.
- The app automatically loads your saved data on refresh.
- Built entirely with **vanilla JS, HTML, and CSS** — no external libraries used.
- Great for beginners learning **DOM manipulation, events, and localStorage**.

---

✨ **Easily track where your money goes with the Expense Tracker!**
47 changes: 47 additions & 0 deletions examples/Expense-Tracker/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Expense Tracker</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="container">
<h1>Expense Tracker</h1>

<div class="balance">
<h2>Your Balance</h2>
<h3 id="balance">₹0</h3>
</div>

<div class="summary">
<div class="income">
<h4>Income</h4>
<p id="income">₹0</p>
</div>
<div class="expense">
<h4>Expense</h4>
<p id="expense">₹0</p>
</div>
</div>

<h3>Add New Transaction</h3>
<form id="transaction-form">
<input type="text" id="text" placeholder="Enter description" required />
<input
type="number"
id="amount"
placeholder="Enter amount (+ for income, - for expense)"
required
/>
<button type="submit">Add Transaction</button>
</form>

<h3>Transaction History</h3>
<ul id="transaction-list" class="list"></ul>
</div>

<script type="module" src="index.mjs"></script>
</body>
</html>
112 changes: 112 additions & 0 deletions examples/Expense-Tracker/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Select elements
const balanceEl = document.getElementById("balance");
const incomeEl = document.getElementById("income");
const expenseEl = document.getElementById("expense");
const listEl = document.getElementById("transaction-list");
const form = document.getElementById("transaction-form");
const textInput = document.getElementById("text");
const amountInput = document.getElementById("amount");

// Load transactions from localStorage
let transactions = JSON.parse(localStorage.getItem("transactions")) || [];

// Add transaction
function addTransaction(e) {
e.preventDefault();

const text = textInput.value.trim();
const amount = +amountInput.value;

if (text === "" || amount === 0 || isNaN(amount)) {
alert("Please enter valid description and amount.");
return;
}

const transaction = {
id: Date.now(),
text,
amount,
};

transactions.push(transaction);
updateLocalStorage();
renderTransactions();

textInput.value = "";
amountInput.value = "";
}

// Delete transaction
function deleteTransaction(id) {
transactions = transactions.filter((t) => t.id !== id);
updateLocalStorage();
renderTransactions();
}

// Edit transaction
function editTransaction(id) {
const t = transactions.find((t) => t.id === id);
if (!t) return;

const newText = prompt("Edit description:", t.text);
const newAmount = parseFloat(prompt("Edit amount:", t.amount));

if (newText && !isNaN(newAmount)) {
t.text = newText;
t.amount = newAmount;
updateLocalStorage();
renderTransactions();
}
}

// Update totals
function updateSummary() {
const amounts = transactions.map((t) => t.amount);
const total = amounts.reduce((a, b) => a + b, 0).toFixed(2);
const income = amounts
.filter((a) => a > 0)
.reduce((a, b) => a + b, 0)
.toFixed(2);
const expense = (
amounts.filter((a) => a < 0).reduce((a, b) => a + b, 0) * -1
).toFixed(2);

balanceEl.textContent = `₹${total}`;
incomeEl.textContent = `₹${income}`;
expenseEl.textContent = `₹${expense}`;
}

// Render transactions
function renderTransactions() {
listEl.innerHTML = "";

transactions.forEach((t) => {
const li = document.createElement("li");
li.classList.add(t.amount < 0 ? "expense" : "income");

li.innerHTML = `
${t.text} <span>₹${t.amount}</span>
<div>
<button onclick="editTransaction(${t.id})">✏️</button>
<button onclick="deleteTransaction(${t.id})">❌</button>
</div>
`;

listEl.appendChild(li);
});

updateSummary();
}

// Save to localStorage
function updateLocalStorage() {
localStorage.setItem("transactions", JSON.stringify(transactions));
}

// Initialize
renderTransactions();
form.addEventListener("submit", addTransaction);

// Expose functions globally (for inline onclick)
window.editTransaction = editTransaction;
window.deleteTransaction = deleteTransaction;
125 changes: 125 additions & 0 deletions examples/Expense-Tracker/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: "Poppins", sans-serif;
}

body {
background: #f4f4f9;
color: #333;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
padding: 20px;
}

.container {
background: #fff;
border-radius: 16px;
padding: 30px;
width: 100%;
max-width: 400px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}

h1 {
text-align: center;
margin-bottom: 20px;
color: #2d3436;
}

.balance {
text-align: center;
margin-bottom: 20px;
}

#balance {
font-size: 2rem;
color: #2d3436;
margin-top: 10px;
}

.summary {
display: flex;
justify-content: space-between;
background: #fafafa;
border-radius: 8px;
padding: 10px 20px;
margin-bottom: 30px;
border: 1px solid #ddd;
}

.summary div {
text-align: center;
}

.income p {
color: #27ae60;
font-weight: bold;
}

.expense p {
color: #c0392b;
font-weight: bold;
}

form {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 30px;
}

form input {
padding: 10px;
border-radius: 8px;
border: 1px solid #ccc;
}

form button {
padding: 10px;
border: none;
border-radius: 8px;
background-color: #0984e3;
color: #fff;
cursor: pointer;
transition: background 0.3s ease;
}

form button:hover {
background-color: #74b9ff;
}

.list {
list-style-type: none;
margin-top: 10px;
}

.list li {
background: #f9f9f9;
border-left: 5px solid #2ecc71;
margin-bottom: 10px;
padding: 10px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}

.list li.expense {
border-left-color: #e74c3c;
}

.list button {
border: none;
background: transparent;
color: #e74c3c;
font-weight: bold;
cursor: pointer;
}

.list button:hover {
color: #c0392b;
}