From b895bf576719982eb79dff6aacfedcf84ab6121e Mon Sep 17 00:00:00 2001 From: basundhara-070 <2022ugec070@nitjsr.ac.in> Date: Wed, 22 Oct 2025 14:37:58 +0530 Subject: [PATCH] Improved expense tracker --- projects/expense-tracker/index.html | 107 +++++++++--- projects/expense-tracker/main.js | 242 ++++++++++++++++++-------- projects/expense-tracker/styles.css | 260 +++++++++++++++++++++++++++- 3 files changed, 514 insertions(+), 95 deletions(-) diff --git a/projects/expense-tracker/index.html b/projects/expense-tracker/index.html index 949a10b..27cf177 100644 --- a/projects/expense-tracker/index.html +++ b/projects/expense-tracker/index.html @@ -1,30 +1,87 @@ - + - - - - Expense Tracker - + + + Expense Tracker + + - -
-

Expense Tracker

- -
- - - -
- - - - - -

Add categories, charts, and filters.

-
- - +
+

Expense Tracker

+
+ +
+
+

Monthly Expenses

+ +
+
+

Category Distribution

+ +
+
+ +
+ +
+

Add Expense

+
+ + + + + + + + - \ No newline at end of file + + + + +
+
+ + +
+
+

Expenses

+
+ + +
+
+ + + + + + + + + + + + + + + + +
DateTitleCategoryAmount (₹)Action
No data entered
+
+
+ + + + diff --git a/projects/expense-tracker/main.js b/projects/expense-tracker/main.js index 619ce8e..05b3121 100644 --- a/projects/expense-tracker/main.js +++ b/projects/expense-tracker/main.js @@ -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 = `No data entered`; + return; + } + + tableBody.innerHTML = ""; + expenses.forEach((exp, index) => { + const row = document.createElement("tr"); + row.innerHTML = ` + ${exp.date} + ${exp.title} + ${exp.category} + ₹${exp.amount} + + `; + 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) => ``) + .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 = ` + ${exp.date} + ${exp.title} + ${exp.category} + ₹${exp.amount} + + `; + 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(); \ No newline at end of file +window.addEventListener("DOMContentLoaded", updateAll); diff --git a/projects/expense-tracker/styles.css b/projects/expense-tracker/styles.css index 0ac6953..67cef2e 100644 --- a/projects/expense-tracker/styles.css +++ b/projects/expense-tracker/styles.css @@ -1,2 +1,260 @@ -body{font-family:system-ui;background:#0f0f12;color:#eef1f8;margin:0;padding:2rem;display:grid;place-items:center}main{max-width:760px;width:100%}form{display:flex;gap:.5rem;flex-wrap:wrap}input,button{font:inherit}input{padding:.5rem .75rem;border-radius:.5rem;border:1px solid #262631;background:#17171c;color:#eef1f8}button{background:#6ee7b7;color:#0b1020;border:none;padding:.5rem .75rem;border-radius:.5rem;font-weight:600;cursor:pointer}ul{list-style:none;padding:0;margin:1rem 0}.notes{color:#a6adbb;font-size:.9rem} +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +body { + font-family: 'Poppins', sans-serif; + background-color: #121212; + color: #fff; + margin: 0; + min-height: 100vh; +} + +header { + display: flex; + justify-content: center; + align-items: center; + background-color: #1f1f1f; + padding: 20px 30px; + border-bottom: 2px solid #333; +} + +header h1 { + font-size: 1.8rem; + color: #ffffff; +} + +.graphs-section { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + background-color: #181818; + padding: 20px; + border-bottom: 2px solid #333; +} + +.chart-box { + background-color: #1f1f1f; + border-radius: 10px; + padding: 20px; + margin: 10px; + flex: 1 1 45%; + min-width: 350px; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.3); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.chart-box:hover { + transform: translateY(-3px); + box-shadow: 0 0 14px rgba(98, 0, 234, 0.3); +} + +.chart-box h2 { + margin-bottom: 10px; + text-align: center; + color: #ccc; + font-weight: 600; +} + +canvas { + width: 100%; + max-height: 350px; +} + +.container { + display: flex; + flex-wrap: wrap; + padding: 20px; + justify-content: space-between; +} + +.left-panel { + flex: 1 1 40%; + min-width: 300px; + padding: 20px; + background-color: #181818; + border-radius: 10px; + border-right: 2px solid #333; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.3); +} + +.left-panel h2 { + margin-bottom: 15px; +} + +form { + display: flex; + flex-direction: column; + gap: 15px; + margin-bottom: 20px; +} + +label { + font-weight: 500; + margin-bottom: 5px; +} + +input, +select { + padding: 10px; + border: 1px solid #333; + border-radius: 6px; + background-color: #222; + color: #fff; + font-size: 1rem; +} + +input:focus, +select:focus { + outline: none; + border-color: #6200ea; +} + +form button { + background-color: #6200ea; + color: white; + border: none; + padding: 10px; + border-radius: 6px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s ease; +} + +form button:hover { + background-color: #3700b3; +} + +.right-panel { + flex: 1 1 55%; + min-width: 350px; + background-color: #181818; + border-radius: 10px; + padding: 20px; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.3); +} + +.table-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.filter { + display: flex; + align-items: center; +} + +.filter label { + margin-right: 10px; + font-weight: 500; +} + +.filter select { + background-color: #222; + color: #fff; + border: 1px solid #444; + padding: 6px 10px; + border-radius: 4px; +} + +#exportBtn { + background-color: #03dac6; + color: #000; + margin-bottom: 15px; + padding: 10px; + border-radius: 20px; + border: none; + cursor: pointer; + font-weight: 600; + transition: background-color 0.2s ease; +} + +#exportBtn:hover { + background-color: #00bfa5; +} + +table { + width: 100%; + border-collapse: collapse; + background-color: #1f1f1f; + border-radius: 8px; + overflow: hidden; +} + +th, +td { + border: 1px solid #333; + padding: 10px; + text-align: center; + font-size: 0.95rem; +} + +th { + background-color: #222; + font-weight: 600; + text-transform: uppercase; +} + +tr:nth-child(even) { + background-color: #181818; +} + +tr:hover { + background-color: #252525; +} + +.no-data { + text-align: center; + color: #aaa; + font-style: italic; + padding: 15px 0; +} + +.deleteBtn { + background: transparent; + border: none; + color: #ff5555; + cursor: pointer; + font-size: 1.1rem; + transition: color 0.2s ease, transform 0.2s ease; +} + +.deleteBtn:hover { + color: #ff8888; + transform: scale(1.1); +} + +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-thumb { + background-color: #333; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #555; +} + +@media (max-width: 900px) { + .graphs-section { + flex-direction: column; + align-items: center; + } + + .container { + flex-direction: column; + } + + .left-panel, + .right-panel { + min-width: 100%; + margin-bottom: 20px; + border-right: none; + } +}