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
107 changes: 82 additions & 25 deletions projects/expense-tracker/index.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,87 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Expense Tracker</title>
<link rel="stylesheet" href="./styles.css">
<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">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>

<body>
<main>
<h1>Expense Tracker</h1>

<form id="form">
<input id="desc" placeholder="Description" required />
<input id="amount" type="number" step="0.01" placeholder="Amount" required />
<button type="submit">Add</button>
</form>

<ul id="list"></ul>
<canvas id="chart" width="300" height="150"></canvas>

<button id="exportBtn">Export as CSV</button>
<p class="notes">Add categories, charts, and filters.</p>
</main>
<script type="module" src="./main.js"></script>
</body>
<header>
<h1>Expense Tracker</h1>
</header>

<div class="graphs-section">
<div class="chart-box">
<h2>Monthly Expenses</h2>
<canvas id="monthlyChart"></canvas>
</div>
<div class="chart-box">
<h2>Category Distribution</h2>
<canvas id="categoryChart"></canvas>
</div>
</div>

<div class="container">
<!-- LEFT: Add Expense Form -->
<div class="left-panel">
<h2>Add Expense</h2>
<form id="expenseForm">
<label>Description:</label>
<input type="text" id="title" required>

<label>Amount (₹):</label>
<input type="number" id="amount" required>

<label>Category:</label>
<select id="category" required>
<option value="Food">Food</option>
<option value="Transport">Transport</option>
<option value="Shopping">Shopping</option>
<option value="Bills">Bills</option>
<option value="Entertainment">Entertainment</option>
<option value="Other">Other</option>
</select>

</html>
<label>Date:</label>
<input type="date" id="date" required>

<button type="submit">Save Expense</button>
</form>
</div>

<!-- RIGHT: Expense Table -->
<div class="right-panel">
<div class="table-header">
<h2>Expenses</h2>
<div class="filter">
<label for="categoryFilter">Sort by Category:</label>
<select id="categoryFilter">
<option value="all">All</option>
</select>
</div>
</div>

<button id="exportBtn">Export as CSV</button>

<table id="expenseTable">
<thead>
<tr>
<th>Date</th>
<th>Title</th>
<th>Category</th>
<th>Amount (₹)</th>
<th>Action</th>
</tr>
</thead>
<tbody id="tableBody">
<tr><td colspan="5" style="text-align:center;">No data entered</td></tr>
</tbody>
</table>
</div>
</div>

<script src="main.js"></script>
</body>
</html>
242 changes: 173 additions & 69 deletions projects/expense-tracker/main.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,175 @@
const form = document.getElementById('form');
const desc = document.getElementById('desc');
const amount = document.getElementById('amount');
const list = document.getElementById('list');
const chart = document.getElementById('chart').getContext('2d');
const exportBtn = document.getElementById('exportBtn');

let items = [];

form.addEventListener('submit', e => {
e.preventDefault();
items.push({
id: crypto.randomUUID(),
desc: desc.value,
amount: parseFloat(amount.value) || 0,
date: new Date().toLocaleDateString()
});
desc.value = '';
amount.value = '';
render();
const tableBody = document.getElementById("tableBody");
const exportBtn = document.getElementById("exportBtn");
const categoryFilter = document.getElementById("categoryFilter");
const form = document.getElementById("expenseForm");

const monthlyCtx = document.getElementById("monthlyChart").getContext("2d");
const categoryCtx = document.getElementById("categoryChart").getContext("2d");

let expenses = JSON.parse(localStorage.getItem("expenses")) || [];
let monthlyChart, categoryChart;

function saveExpenses() {
localStorage.setItem("expenses", JSON.stringify(expenses));
}

function updateTable() {
if (expenses.length === 0) {
tableBody.innerHTML = `<tr><td colspan="5" style="text-align:center;">No data entered</td></tr>`;
return;
}

tableBody.innerHTML = "";
expenses.forEach((exp, index) => {
const row = document.createElement("tr");
row.innerHTML = `
<td>${exp.date}</td>
<td>${exp.title}</td>
<td>${exp.category}</td>
<td>₹${exp.amount}</td>
<td><button class="deleteBtn" data-index="${index}">🗑️</button></td>
`;
tableBody.appendChild(row);
});


document.querySelectorAll(".deleteBtn").forEach((btn) =>
btn.addEventListener("click", (e) => {
const i = e.target.getAttribute("data-index");
expenses.splice(i, 1);
saveExpenses();
updateAll();
})
);
}

function updateCategoryFilter() {
const categories = ["all", ...new Set(expenses.map((e) => e.category))];
categoryFilter.innerHTML = categories
.map((cat) => `<option value="${cat}">${cat}</option>`)
.join("");
}

function renderMonthlyChart() {
if (monthlyChart) monthlyChart.destroy();

const monthMap = {};
expenses.forEach((e) => {
const month = e.date ? e.date.slice(0, 7) : "Unknown"; // YYYY-MM
monthMap[month] = (monthMap[month] || 0) + parseFloat(e.amount);
});

const labels = Object.keys(monthMap);
const data = Object.values(monthMap);

monthlyChart = new Chart(monthlyCtx, {
type: "bar",
data: {
labels,
datasets: [
{
label: "Total Monthly Expense (₹)",
data,
backgroundColor: "#6200ea",
},
],
},
options: {
responsive: true,
scales: { y: { beginAtZero: true } },
},
});
}

function renderCategoryChart() {
if (categoryChart) categoryChart.destroy();

const categoryTotals = {};
expenses.forEach((e) => {
categoryTotals[e.category] =
(categoryTotals[e.category] || 0) + parseFloat(e.amount);
});

const labels = Object.keys(categoryTotals);
const data = Object.values(categoryTotals);

categoryChart = new Chart(categoryCtx, {
type: "pie",
data: {
labels,
datasets: [
{
data,
backgroundColor: [
"#ff6384",
"#36a2eb",
"#ffce56",
"#4caf50",
"#ff9800",
"#9c27b0",
],
},
],
},
options: { responsive: true },
});
}

exportBtn.addEventListener("click", () => {
if (expenses.length === 0) return alert("No data to export!");
let csvContent = "data:text/csv;charset=utf-8,Date,Title,Category,Amount\n";
expenses.forEach((e) => {
csvContent += `${e.date},${e.title},${e.category},${e.amount}\n`;
});
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "expenses.csv");
document.body.appendChild(link);
link.click();
});

form.addEventListener("submit", (e) => {
e.preventDefault();
const title = document.getElementById("title").value.trim();
const amount = parseFloat(document.getElementById("amount").value);
const category = document.getElementById("category").value;
const date = document.getElementById("date").value;

if (!title || !amount || !category || !date)
return alert("Please fill all fields!");

expenses.push({ title, amount, category, date });
saveExpenses();
form.reset();
updateAll();
});

function updateAll() {
updateTable();
updateCategoryFilter();
renderMonthlyChart();
renderCategoryChart();
}

categoryFilter.addEventListener("change", (e) => {
const filter = e.target.value;
if (filter === "all") updateAll();
else {
const filtered = expenses.filter((ex) => ex.category === filter);
tableBody.innerHTML = "";
filtered.forEach((exp) => {
const row = document.createElement("tr");
row.innerHTML = `
<td>${exp.date}</td>
<td>${exp.title}</td>
<td>${exp.category}</td>
<td>₹${exp.amount}</td>
<td></td>
`;
tableBody.appendChild(row);
});
}
});

function render() {
list.innerHTML = '';
let total = 0;
for (const it of items) {
total += it.amount;
const li = document.createElement('li');
li.textContent = `${it.date} — ${it.desc} — ₹${it.amount.toFixed(2)}`;
list.appendChild(li);
}
drawChart(total);
}

function drawChart(total) {
const w = 300, h = 150;
chart.clearRect(0, 0, w, h);
chart.fillStyle = '#17171c';
chart.fillRect(0, 0, w, h);
chart.fillStyle = '#6ee7b7';
chart.fillRect(0, h - (total % h), w, (total % h));
}

// --- CSV Export Feature ---
function exportCSV() {
if (items.length === 0) {
alert('No expenses to export!');
return;
}

// Convert items to CSV format
const headers = ['Date', 'Description', 'Amount (INR)'];
const rows = items.map(it => [it.date, it.desc, it.amount]);
const csvContent = [headers, ...rows]
.map(row => row.map(val => `"${val}"`).join(','))
.join('\\n');

// Create and trigger download
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `expenses-${new Date().toISOString().slice(0, 10)}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}

exportBtn.addEventListener('click', exportCSV);
render();
window.addEventListener("DOMContentLoaded", updateAll);
Loading
Loading