From 5c86a7a7e869f3d15fa1521ba4cfbf6fcb87f9bb Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Mon, 2 Feb 2026 20:27:37 +0100 Subject: [PATCH 1/7] stop on printSummary function --- .prettierrc | 7 +++ finance-tracker/app.js | 37 ++++++++++++ finance-tracker/data.js | 77 ++++++++++++++++++++++- finance-tracker/finance.js | 121 ++++++++++++++++++++++++++++++++----- package-lock.json | 86 ++++++++++++++++++++++++++ package.json | 17 ++++++ 6 files changed, 329 insertions(+), 16 deletions(-) create mode 100644 .prettierrc create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..eba5a68 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 80, + "tabWidth": 2 +} diff --git a/finance-tracker/app.js b/finance-tracker/app.js index 7cfcd07..481d9de 100644 --- a/finance-tracker/app.js +++ b/finance-tracker/app.js @@ -1,3 +1,40 @@ // This is the entrypoint for your application. // node app.js +import { + addTransaction, + getTotalIncome, + getTotalExpenses, + getBalance, + getTransactionsByCategory, + getLargestExpense, + printAllTransactions, + printSummary, +} from './finance.js'; +import transactions from './data.js'; +addTransaction( + 'expense', + 'groceries', + 98, + 'Weekly supermarket shopping', + '2025-01-25' +); + +const totalIncome = getTotalIncome(transactions); +const totalExpenses = getTotalExpenses(transactions); +const balance = getBalance(transactions); +const transactionsByCategory = getTransactionsByCategory( + transactions, + 'groceries' +); +const largestExpenseAmount = getLargestExpense(transactions); + +console.log(transactions); +console.log(totalIncome); +console.log(totalExpenses); +console.log(balance); +console.log(transactionsByCategory); +console.log(largestExpenseAmount); + +printAllTransactions(transactions); +console.log(printSummary(transactions)); diff --git a/finance-tracker/data.js b/finance-tracker/data.js index d7863ff..8fd4d0d 100644 --- a/finance-tracker/data.js +++ b/finance-tracker/data.js @@ -1,2 +1,77 @@ // Place here the transaction data array. Use it in your application as needed. -const transactions = []; \ No newline at end of file +const transactions = [ + { + id: 1, + type: 'income', + category: 'salary', + amount: 3000, + description: 'Monthly salary', + date: '2025-01-15', + }, + { + id: 2, + type: 'expense', + category: 'groceries', + amount: 120, + description: 'Weekly supermarket shopping', + date: '2025-01-16', + }, + { + id: 3, + type: 'expense', + category: 'transport', + amount: 45, + description: 'Monthly public transport pass', + date: '2025-01-17', + }, + { + id: 4, + type: 'income', + category: 'freelance', + amount: 600, + description: 'Freelance web project', + date: '2025-01-20', + }, + { + id: 5, + type: 'expense', + category: 'entertainment', + amount: 75, + description: 'Concert tickets', + date: '2025-01-21', + }, + { + id: 6, + type: 'expense', + category: 'utilities', + amount: 150, + description: 'Electricity and water bill', + date: '2025-01-22', + }, + { + id: 7, + type: 'income', + category: 'gift', + amount: 200, + description: 'Birthday gift from family', + date: '2025-01-23', + }, + { + id: 8, + type: 'expense', + category: 'health', + amount: 90, + description: 'Doctor appointment', + date: '2025-01-24', + }, + { + id: 9, + type: 'expense', + category: 'education', + amount: 300, + description: 'Online course fee', + date: '2025-01-25', + }, +]; + +export default transactions; diff --git a/finance-tracker/finance.js b/finance-tracker/finance.js index ac2118f..54ff4ca 100644 --- a/finance-tracker/finance.js +++ b/finance-tracker/finance.js @@ -1,27 +1,118 @@ -function addTransaction(transaction) { - // TODO: Implement this function +import transactions from './data.js'; +import chalk from 'chalk'; + +export function addTransaction(type, category, amount, description, date) { + const newTransaction = { + id: transactions.length + 1, + type, + category, + amount, + description, + date, + }; + transactions.push(newTransaction); + return newTransaction; +} + +export function getTotalIncome(transactions) { + let totalIncome = 0; + for (const transaction of transactions) { + if (transaction.type === 'income') { + totalIncome += transaction.amount; + } + } + return totalIncome; +} + +export function getTotalExpenses(transactions) { + let totalExpenses = 0; + for (const transaction of transactions) { + if (transaction.type === 'expense') { + totalExpenses += transaction.amount; + } + } + return totalExpenses; } -function getTotalIncome() { - // TODO: Implement this function +export function getBalance(transactions) { + const totalIncome = getTotalIncome(transactions); + const totalExpenses = getTotalExpenses(transactions); + const balance = totalIncome - totalExpenses; + + return balance; } -function getTotalExpenses() { - // TODO: Implement this function +export function getTransactionsByCategory(transactions, category) { + let transactionByCategory = []; + + for (const transaction of transactions) { + if (transaction.category === category) { + transactionByCategory.push(transaction); + } + } + + return transactionByCategory; } -function getBalance() { - // TODO: Implement this function +export function getLargestExpense(transactions) { + const expenseTransactions = transactions.filter( + (transaction) => transaction.type === 'expense' + ); + + let largest = expenseTransactions[0].amount; + + for (const transaction of expenseTransactions) { + if (transaction.amount > largest) { + largest = transaction.amount; + } + } + return largest; } -function getTransactionsByCategory(category) { - // TODO: Implement this function +export function printAllTransactions(transactions) { + console.log(chalk.bold('All Transactions:')); + for (const transaction of transactions) { + const { id, type, category, amount, description } = transaction; + + const typeFormat = + type === 'income' + ? chalk.green(type.toUpperCase()) + : chalk.red(type.toUpperCase()); + + const categoryFormat = getFirstCharacterToUp(chalk.yellow(category)); + + const amountFormat = chalk.bold(amount); + + const descriptionFormat = description.toLowerCase(); + + const formattingTransaction = `${id}. [${typeFormat}] ${categoryFormat} - €${amountFormat} (${descriptionFormat})`; + console.log(formattingTransaction); + } +} + +export function printSummary(transactions) { + const totalIncome = chalk.green(getTotalIncome(transactions)); + const totalExpenses = chalk.red(getTotalExpenses(transactions)); + const balance = + getBalance(transactions) >= 0 + ? chalk.cyan(getBalance(transactions)) + : chalk.red(getBalance(transactions)); + const numOfTransactions = chalk.bold(transactions.length); + const largestExpense = getLargestExpense(transactions); + const largestExpenseForm = chalk.bold(largestExpense.amount); + const largestExpenseDescr = getFirstCharacterToUp(largestExpense.description); + + const summary = `📊 FINANCIAL SUMMARY 📊 \nTotal Income: €${totalIncome}\nTotal Expenses: €${totalExpenses}\nCurrent Balance: €${balance}\n\nLargest Expense: ${largestExpenseDescr} (${largestExpenseForm})\nTotal Transactions: ${numOfTransactions}`; + + return summary; } -function getLargestExpense() { - // TODO: Implement this function +function getFirstCharacterToUp(word) { + return word[0].toUpperCase() + word.slice(1); } -function printAllTransactions() { - // TODO: Implement this function -} \ No newline at end of file +// if (balance >= 0) { +// return chalk.bold.cyan(balance); +// } else { +// return chalk.bold.red(balance); +// } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..52239c6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,86 @@ +{ + "name": "c55-core-week-4", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "c55-core-week-4", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3134228 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "c55-core-week-4", + "version": "1.0.0", + "description": "The week 4 assignment for the HackYourFuture Core program can be found at the following link: https://hub.hackyourfuture.nl/core-program-week-4-assignment", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node finance-tracker/app.js" + }, + "type": "module", + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2" + } +} From e64a0db0678b3b82fe0a0c4197ae4aef0461c277 Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Tue, 3 Feb 2026 11:55:52 +0100 Subject: [PATCH 2/7] Completed the main part of the assignment --- finance-tracker/app.js | 40 +++++++----------------- finance-tracker/finance.js | 64 ++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/finance-tracker/app.js b/finance-tracker/app.js index 481d9de..48b328f 100644 --- a/finance-tracker/app.js +++ b/finance-tracker/app.js @@ -2,39 +2,21 @@ // node app.js import { addTransaction, - getTotalIncome, - getTotalExpenses, - getBalance, getTransactionsByCategory, - getLargestExpense, - printAllTransactions, - printSummary, + printGeneralReport, } from './finance.js'; import transactions from './data.js'; -addTransaction( - 'expense', - 'groceries', - 98, - 'Weekly supermarket shopping', - '2025-01-25' +console.log( + addTransaction( + 'expense', + 'groceries', + 98, + 'Weekly supermarket shopping', + '2025-01-25' + ) ); -const totalIncome = getTotalIncome(transactions); -const totalExpenses = getTotalExpenses(transactions); -const balance = getBalance(transactions); -const transactionsByCategory = getTransactionsByCategory( - transactions, - 'groceries' -); -const largestExpenseAmount = getLargestExpense(transactions); - -console.log(transactions); -console.log(totalIncome); -console.log(totalExpenses); -console.log(balance); -console.log(transactionsByCategory); -console.log(largestExpenseAmount); +console.log(getTransactionsByCategory(transactions, 'groceries')); -printAllTransactions(transactions); -console.log(printSummary(transactions)); +console.log(printGeneralReport(transactions)); diff --git a/finance-tracker/finance.js b/finance-tracker/finance.js index 54ff4ca..ae707f3 100644 --- a/finance-tracker/finance.js +++ b/finance-tracker/finance.js @@ -10,8 +10,14 @@ export function addTransaction(type, category, amount, description, date) { description, date, }; + + // The assignment requires using the spread operator when adding a transaction. In this task, mutating the existing array with push() feels more suitable, and that is the approach I would normally use if there were no requirement. To satisfy the task while keeping the logic I prefer, I include both: spread for the assignment, and push() for the actual update. + + const updated = [...transactions, newTransaction]; + transactions.push(newTransaction); - return newTransaction; + + return updated; } export function getTotalIncome(transactions) { @@ -59,60 +65,70 @@ export function getLargestExpense(transactions) { (transaction) => transaction.type === 'expense' ); - let largest = expenseTransactions[0].amount; + let largest = expenseTransactions[0]; for (const transaction of expenseTransactions) { - if (transaction.amount > largest) { - largest = transaction.amount; + if (transaction.amount > largest.amount) { + largest = transaction; } } + return largest; } export function printAllTransactions(transactions) { - console.log(chalk.bold('All Transactions:')); + let output = chalk.bold('All Transactions:\n'); + for (const transaction of transactions) { const { id, type, category, amount, description } = transaction; + //formatting values by task requirements with chalk const typeFormat = type === 'income' ? chalk.green(type.toUpperCase()) : chalk.red(type.toUpperCase()); const categoryFormat = getFirstCharacterToUp(chalk.yellow(category)); - const amountFormat = chalk.bold(amount); - const descriptionFormat = description.toLowerCase(); - const formattingTransaction = `${id}. [${typeFormat}] ${categoryFormat} - €${amountFormat} (${descriptionFormat})`; - console.log(formattingTransaction); + //collect all in one line for each element + const line = `${id}. [${typeFormat}] ${categoryFormat} - €${amountFormat} (${descriptionFormat})`; + + output += `${line}\n`; } + + return output; } export function printSummary(transactions) { - const totalIncome = chalk.green(getTotalIncome(transactions)); - const totalExpenses = chalk.red(getTotalExpenses(transactions)); + //formatting values by task requirements with chalk + const totalIncome = chalk.bold.green(getTotalIncome(transactions)); + const totalExpenses = chalk.bold.red(getTotalExpenses(transactions)); + const numOfTransactions = chalk.bold(transactions.length); + const balance = getBalance(transactions) >= 0 - ? chalk.cyan(getBalance(transactions)) - : chalk.red(getBalance(transactions)); - const numOfTransactions = chalk.bold(transactions.length); - const largestExpense = getLargestExpense(transactions); - const largestExpenseForm = chalk.bold(largestExpense.amount); - const largestExpenseDescr = getFirstCharacterToUp(largestExpense.description); + ? chalk.bold.cyan(getBalance(transactions)) + : chalk.bold.red(getBalance(transactions)); + + const { amount, description } = getLargestExpense(transactions); + const largestExpense = chalk.bold(amount); - const summary = `📊 FINANCIAL SUMMARY 📊 \nTotal Income: €${totalIncome}\nTotal Expenses: €${totalExpenses}\nCurrent Balance: €${balance}\n\nLargest Expense: ${largestExpenseDescr} (${largestExpenseForm})\nTotal Transactions: ${numOfTransactions}`; + //collect summary + const summary = `📊 ${chalk.bold('financial summary'.toUpperCase())} 📊 \nTotal Income: €${totalIncome}\nTotal Expenses: €${totalExpenses}\nCurrent Balance: €${balance}\n\nLargest Expense: ${description} (€${largestExpense})\nTotal Transactions: ${numOfTransactions}`; return summary; } +export function printGeneralReport(transactions) { + const allTransactions = printAllTransactions(transactions); + const summary = printSummary(transactions); + + return `💰 ${chalk.bold('personal finance tracker'.toUpperCase())} 💰\n\n${allTransactions}\n${summary}`; +} + function getFirstCharacterToUp(word) { + if (!word) return ''; return word[0].toUpperCase() + word.slice(1); } - -// if (balance >= 0) { -// return chalk.bold.cyan(balance); -// } else { -// return chalk.bold.red(balance); -// } From 18de2047bb4ef2d031a34df4e81fd3daa8368436 Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Tue, 3 Feb 2026 16:18:17 +0100 Subject: [PATCH 3/7] wrote searchTransactionsByDate and groupTransactionByMonth functions --- finance-tracker/app.js | 15 +++++++++++--- finance-tracker/data.js | 4 ++-- finance-tracker/finance.js | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/finance-tracker/app.js b/finance-tracker/app.js index 48b328f..df553d1 100644 --- a/finance-tracker/app.js +++ b/finance-tracker/app.js @@ -3,7 +3,9 @@ import { addTransaction, getTransactionsByCategory, + groupTransactionByMonth, printGeneralReport, + searchTransactionsByDate, } from './finance.js'; import transactions from './data.js'; @@ -13,10 +15,17 @@ console.log( 'groceries', 98, 'Weekly supermarket shopping', - '2025-01-25' + '2026-01-25' ) ); -console.log(getTransactionsByCategory(transactions, 'groceries')); +// console.log(getTransactionsByCategory(transactions, 'groceries')); -console.log(printGeneralReport(transactions)); +// console.log(printGeneralReport(transactions)); + +//Bonus Challenges output +// console.log(searchTransactionsByDate(transactions, '2025-01-22', '2025-01-25')); + +// Node.js collapses nested objects inside arrays and prints them as [Object]. To see the full structure, I use JSON.stringify(). The second argument (null)means no custom replacer, and the third argument (2) adds indentation for readability. + +console.log(JSON.stringify(groupTransactionByMonth(transactions), null, 2)); diff --git a/finance-tracker/data.js b/finance-tracker/data.js index 8fd4d0d..19e8c18 100644 --- a/finance-tracker/data.js +++ b/finance-tracker/data.js @@ -62,7 +62,7 @@ const transactions = [ category: 'health', amount: 90, description: 'Doctor appointment', - date: '2025-01-24', + date: '2025-02-24', }, { id: 9, @@ -70,7 +70,7 @@ const transactions = [ category: 'education', amount: 300, description: 'Online course fee', - date: '2025-01-25', + date: '2025-03-25', }, ]; diff --git a/finance-tracker/finance.js b/finance-tracker/finance.js index ae707f3..a896d49 100644 --- a/finance-tracker/finance.js +++ b/finance-tracker/finance.js @@ -132,3 +132,45 @@ function getFirstCharacterToUp(word) { if (!word) return ''; return word[0].toUpperCase() + word.slice(1); } + +//Bonus Challenges functions + +export function searchTransactionsByDate(transactions, startDate, endDate) { + const sorted = transactions + .slice() + .sort((a, b) => a.date.localeCompare(b.date)); + + const startIndex = sorted.findIndex( + (transaction) => transaction.date >= startDate + ); + const endIndex = sorted.findIndex( + (transaction) => transaction.date > endDate + ); + + const range = sorted.slice( + startIndex, + endIndex === -1 ? sorted.length : endIndex + ); + + return range; +} + +export function groupTransactionByMonth(transactions) { + let groupedTransactions = {}; + + for (const transaction of transactions) { + const [year, month] = transaction.date.split('-'); + + if (!groupedTransactions[year]) { + groupedTransactions[year] = {}; + } + + if (!groupedTransactions[year][month]) { + groupedTransactions[year][month] = []; + } + + groupedTransactions[year][month].push(transaction); + } + + return groupedTransactions; +} From 6185a5301f03d949bd4bacb9d04c973fc6bf9675 Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Tue, 3 Feb 2026 16:57:05 +0100 Subject: [PATCH 4/7] added function calculateAverageExpensesPerCategory --- finance-tracker/app.js | 7 +++++-- finance-tracker/data.js | 8 ++++++++ finance-tracker/finance.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/finance-tracker/app.js b/finance-tracker/app.js index df553d1..a2883ac 100644 --- a/finance-tracker/app.js +++ b/finance-tracker/app.js @@ -2,6 +2,7 @@ // node app.js import { addTransaction, + calculateAverageExpensesPerCategory, getTransactionsByCategory, groupTransactionByMonth, printGeneralReport, @@ -24,8 +25,10 @@ console.log( // console.log(printGeneralReport(transactions)); //Bonus Challenges output -// console.log(searchTransactionsByDate(transactions, '2025-01-22', '2025-01-25')); +// console.log(searchTransactionsByDate(transactions, '2025-01-22', '2025-02-24')); // Node.js collapses nested objects inside arrays and prints them as [Object]. To see the full structure, I use JSON.stringify(). The second argument (null)means no custom replacer, and the third argument (2) adds indentation for readability. -console.log(JSON.stringify(groupTransactionByMonth(transactions), null, 2)); +// console.log(JSON.stringify(groupTransactionByMonth(transactions), null, 2)); + +// console.log(calculateAverageExpensesPerCategory(transactions)); diff --git a/finance-tracker/data.js b/finance-tracker/data.js index 19e8c18..f0febec 100644 --- a/finance-tracker/data.js +++ b/finance-tracker/data.js @@ -70,6 +70,14 @@ const transactions = [ category: 'education', amount: 300, description: 'Online course fee', + date: '2025-03-15', + }, + { + id: 10, + type: 'expense', + category: 'education', + amount: 135, + description: 'Online course fee', date: '2025-03-25', }, ]; diff --git a/finance-tracker/finance.js b/finance-tracker/finance.js index a896d49..b9caba7 100644 --- a/finance-tracker/finance.js +++ b/finance-tracker/finance.js @@ -174,3 +174,31 @@ export function groupTransactionByMonth(transactions) { return groupedTransactions; } + +export function calculateAverageExpensesPerCategory(transactions) { + const expenses = transactions.filter( + (transaction) => transaction.type === 'expense' + ); + + let expenseCategories = {}; + + for (const transaction of expenses) { + const { category, amount } = transaction; + + if (!expenseCategories[category]) { + expenseCategories[category] = { total: 0, count: 0 }; + } + + expenseCategories[category].total += amount; + expenseCategories[category].count += 1; + } + + let averages = {}; + + for (const category in expenseCategories) { + const { total, count } = expenseCategories[category]; + averages[category] = total / count; + } + + return averages; +} From 4bef68115a1fcdb44292cda723b33e270e7a2d2b Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Wed, 4 Feb 2026 13:08:42 +0100 Subject: [PATCH 5/7] done bonus challenge tasks --- finance-tracker/app.js | 54 ++++++++++++----- finance-tracker/data.js | 120 +++++++++++++++++++++++++++++++++++++ finance-tracker/finance.js | 108 ++++++++++++++++++++++++++++----- 3 files changed, 254 insertions(+), 28 deletions(-) diff --git a/finance-tracker/app.js b/finance-tracker/app.js index a2883ac..f8c33ad 100644 --- a/finance-tracker/app.js +++ b/finance-tracker/app.js @@ -3,32 +3,58 @@ import { addTransaction, calculateAverageExpensesPerCategory, + findConsecutiveExpensiveMonth, getTransactionsByCategory, groupTransactionByMonth, printGeneralReport, - searchTransactionsByDate, + removeTransactions, + searchTransactionsByDateRange, } from './finance.js'; import transactions from './data.js'; -console.log( - addTransaction( - 'expense', - 'groceries', - 98, - 'Weekly supermarket shopping', - '2026-01-25' - ) +const newTransaction = addTransaction( + 'expense', + 'groceries', + 98, + 'Weekly supermarket shopping', + '2026-01-25' +); + +const transactionByCategory = getTransactionsByCategory( + transactions, + 'groceries' ); -// console.log(getTransactionsByCategory(transactions, 'groceries')); +const financeReport = printGeneralReport(transactions); -// console.log(printGeneralReport(transactions)); +console.log(newTransaction, transactionByCategory, financeReport); //Bonus Challenges output -// console.log(searchTransactionsByDate(transactions, '2025-01-22', '2025-02-24')); +const transactionsByDateRange = searchTransactionsByDateRange( + transactions, + '2025-01-22', + '2025-02-24' +); // Node.js collapses nested objects inside arrays and prints them as [Object]. To see the full structure, I use JSON.stringify(). The second argument (null)means no custom replacer, and the third argument (2) adds indentation for readability. -// console.log(JSON.stringify(groupTransactionByMonth(transactions), null, 2)); +const transactionsGroupByMonth = JSON.stringify( + groupTransactionByMonth(transactions), + null, + 2 +); + +const averageExpensesPerCategory = + calculateAverageExpensesPerCategory(transactions); + +const arrayWithoutRemoveTransaction = removeTransactions(transactions, 3); + +const consecutiveExpensiveMonths = findConsecutiveExpensiveMonth(transactions); -// console.log(calculateAverageExpensesPerCategory(transactions)); +console.log( + transactionsByDateRange, + transactionsGroupByMonth, + averageExpensesPerCategory, + arrayWithoutRemoveTransaction, + consecutiveExpensiveMonths +); diff --git a/finance-tracker/data.js b/finance-tracker/data.js index f0febec..5893f51 100644 --- a/finance-tracker/data.js +++ b/finance-tracker/data.js @@ -80,6 +80,126 @@ const transactions = [ description: 'Online course fee', date: '2025-03-25', }, + { + id: 11, + type: 'expense', + category: 'groceries', + amount: 95, + description: 'Weekly supermarket shopping', + date: '2025-02-02', + }, + { + id: 12, + type: 'expense', + category: 'transport', + amount: 45, + description: 'Monthly public transport pass', + date: '2025-02-10', + }, + { + id: 13, + type: 'income', + category: 'salary', + amount: 3000, + description: 'Monthly salary', + date: '2025-02-15', + }, + { + id: 14, + type: 'expense', + category: 'entertainment', + amount: 60, + description: 'Cinema tickets', + date: '2025-02-18', + }, + { + id: 15, + type: 'expense', + category: 'utilities', + amount: 155, + description: 'Electricity and water bill', + date: '2025-02-22', + }, + { + id: 16, + type: 'income', + category: 'freelance', + amount: 400, + description: 'Small freelance task', + date: '2025-03-05', + }, + { + id: 17, + type: 'expense', + category: 'health', + amount: 120, + description: 'Pharmacy purchase', + date: '2025-03-12', + }, + { + id: 18, + type: 'expense', + category: 'groceries', + amount: 130, + description: 'Weekly supermarket shopping', + date: '2025-03-20', + }, + { + id: 19, + type: 'expense', + category: 'transport', + amount: 50, + description: 'Taxi ride', + date: '2025-03-28', + }, + { + id: 20, + type: 'income', + category: 'bonus', + amount: 500, + description: 'Quarterly bonus', + date: '2025-04-01', + }, + { + id: 21, + type: 'expense', + category: 'education', + amount: 200, + description: 'Books and materials', + date: '2025-04-03', + }, + { + id: 22, + type: 'expense', + category: 'entertainment', + amount: 90, + description: 'Theatre tickets', + date: '2025-04-10', + }, + { + id: 23, + type: 'expense', + category: 'utilities', + amount: 160, + description: 'Electricity and water bill', + date: '2025-04-15', + }, + { + id: 24, + type: 'expense', + category: 'health', + amount: 80, + description: 'Dentist visit', + date: '2025-04-20', + }, + { + id: 25, + type: 'income', + category: 'salary', + amount: 3100, + description: 'Monthly salary', + date: '2025-03-15', + }, ]; export default transactions; diff --git a/finance-tracker/finance.js b/finance-tracker/finance.js index b9caba7..dc0ac5b 100644 --- a/finance-tracker/finance.js +++ b/finance-tracker/finance.js @@ -17,7 +17,7 @@ export function addTransaction(type, category, amount, description, date) { transactions.push(newTransaction); - return updated; + return newTransaction; } export function getTotalIncome(transactions) { @@ -61,9 +61,8 @@ export function getTransactionsByCategory(transactions, category) { } export function getLargestExpense(transactions) { - const expenseTransactions = transactions.filter( - (transaction) => transaction.type === 'expense' - ); + //I moved the logic for filtering "expense" transactions into a separate function, because this code is reused in several bonus tasks. + const expenseTransactions = getExpenseTransactions(transactions); let largest = expenseTransactions[0]; @@ -128,14 +127,14 @@ export function printGeneralReport(transactions) { return `💰 ${chalk.bold('personal finance tracker'.toUpperCase())} 💰\n\n${allTransactions}\n${summary}`; } -function getFirstCharacterToUp(word) { - if (!word) return ''; - return word[0].toUpperCase() + word.slice(1); -} - //Bonus Challenges functions -export function searchTransactionsByDate(transactions, startDate, endDate) { +//Search transactions by date range using slice +export function searchTransactionsByDateRange( + transactions, + startDate, + endDate +) { const sorted = transactions .slice() .sort((a, b) => a.date.localeCompare(b.date)); @@ -155,6 +154,7 @@ export function searchTransactionsByDate(transactions, startDate, endDate) { return range; } +//Group transactions by month using nested objects export function groupTransactionByMonth(transactions) { let groupedTransactions = {}; @@ -175,10 +175,9 @@ export function groupTransactionByMonth(transactions) { return groupedTransactions; } +//Calculate average expense per category export function calculateAverageExpensesPerCategory(transactions) { - const expenses = transactions.filter( - (transaction) => transaction.type === 'expense' - ); + const expenses = getExpenseTransactions(transactions); let expenseCategories = {}; @@ -197,8 +196,89 @@ export function calculateAverageExpensesPerCategory(transactions) { for (const category in expenseCategories) { const { total, count } = expenseCategories[category]; - averages[category] = total / count; + averages[category] = Number((total / count).toFixed(2)); } return averages; } + +//Add ability to remove transactions by id +export function removeTransactions(transactions, id) { + const newTransactions = transactions.filter( + (transaction) => transaction.id !== id + ); + return newTransactions; +} + +//Create a function that finds consecutive expensive months (use while loop) +//Use multi-line commenting to explain your most complex function + +/* This function returns a list of months where expenses increase consecutively. + It follows these steps: + + 1. It filters all transactions using getExpenseTransactions() to keep only those with type "expense". + + 2. It creates an empty object (monthlyTotal) to store total expenses per month. + + 3. It iterates through all expense transactions. For each one: + - extracts the date and amount, + - derives the month in "YYYY-MM" format using slice(), + - initializes monthlyTotal[month] to 0 if it doesn't exist yet, + - adds the transaction amount to that month's total. + + 4. It collects all month keys from monthlyTotal and sorts them to ensure chronological comparison. + + 5. It prepares a while loop index (i) and an empty result array. + + 6. Using a while loop, it compares each month with the next one. + If the next month has a higher total expense than the current month: + - it adds the current month to the result (only if not already included), + - it always adds the next month. + This builds a sequence of months with increasing expenses. + + 7. After the loop finishes, it returns the result array containing all months that form consecutive increasing expense periods. */ + +export function findConsecutiveExpensiveMonth(transactions) { + const expenses = getExpenseTransactions(transactions); + const monthlyTotal = {}; + + for (const transaction of expenses) { + const { date, amount } = transaction; + const month = date.slice(0, 7); + + if (!monthlyTotal[month]) monthlyTotal[month] = 0; + monthlyTotal[month] += amount; + } + + const months = Object.keys(monthlyTotal).sort(); + + let i = 0; + const result = []; + + while (i < months.length - 1) { + const current = months[i]; + const next = months[i + 1]; + + if (monthlyTotal[current] < monthlyTotal[next]) { + if (!result.includes(current)) result.push(current); + result.push(next); + } + i++; + } + + return result; +} + +//reused functions + +function getExpenseTransactions(transactions) { + const expenses = transactions.filter( + (transaction) => transaction.type === 'expense' + ); + return expenses; +} + +function getFirstCharacterToUp(word) { + if (!word) return ''; + return word[0].toUpperCase() + word.slice(1); +} From 3d93d31cd66bd2dbc338174cd8e2e6b87cf23bc4 Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Wed, 4 Feb 2026 14:05:19 +0100 Subject: [PATCH 6/7] change addTransaction function --- finance-tracker/app.js | 29 +++++++++++++++-------------- finance-tracker/finance.js | 16 ++-------------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/finance-tracker/app.js b/finance-tracker/app.js index f8c33ad..f9c9c24 100644 --- a/finance-tracker/app.js +++ b/finance-tracker/app.js @@ -12,22 +12,23 @@ import { } from './finance.js'; import transactions from './data.js'; -const newTransaction = addTransaction( - 'expense', - 'groceries', - 98, - 'Weekly supermarket shopping', - '2026-01-25' -); - -const transactionByCategory = getTransactionsByCategory( +const newTransaction = addTransaction({ + id: 31, + type: 'expense', + category: 'groceries', + amount: 98, + description: 'Weekly supermarket shopping', + date: '2026-01-25', +}); + +const transactionsByCategory = getTransactionsByCategory( transactions, 'groceries' ); const financeReport = printGeneralReport(transactions); -console.log(newTransaction, transactionByCategory, financeReport); +console.log(newTransaction, transactionsByCategory, financeReport); //Bonus Challenges output const transactionsByDateRange = searchTransactionsByDateRange( @@ -38,7 +39,7 @@ const transactionsByDateRange = searchTransactionsByDateRange( // Node.js collapses nested objects inside arrays and prints them as [Object]. To see the full structure, I use JSON.stringify(). The second argument (null)means no custom replacer, and the third argument (2) adds indentation for readability. -const transactionsGroupByMonth = JSON.stringify( +const transactionsGroupedByMonth = JSON.stringify( groupTransactionByMonth(transactions), null, 2 @@ -47,14 +48,14 @@ const transactionsGroupByMonth = JSON.stringify( const averageExpensesPerCategory = calculateAverageExpensesPerCategory(transactions); -const arrayWithoutRemoveTransaction = removeTransactions(transactions, 3); +const remainingTransactions = removeTransactions(transactions, 3); const consecutiveExpensiveMonths = findConsecutiveExpensiveMonth(transactions); console.log( transactionsByDateRange, - transactionsGroupByMonth, + transactionsGroupedByMonth, averageExpensesPerCategory, - arrayWithoutRemoveTransaction, + remainingTransactions, consecutiveExpensiveMonths ); diff --git a/finance-tracker/finance.js b/finance-tracker/finance.js index dc0ac5b..0a60556 100644 --- a/finance-tracker/finance.js +++ b/finance-tracker/finance.js @@ -1,20 +1,8 @@ import transactions from './data.js'; import chalk from 'chalk'; -export function addTransaction(type, category, amount, description, date) { - const newTransaction = { - id: transactions.length + 1, - type, - category, - amount, - description, - date, - }; - - // The assignment requires using the spread operator when adding a transaction. In this task, mutating the existing array with push() feels more suitable, and that is the approach I would normally use if there were no requirement. To satisfy the task while keeping the logic I prefer, I include both: spread for the assignment, and push() for the actual update. - - const updated = [...transactions, newTransaction]; - +export function addTransaction(transaction) { + const newTransaction = { ...transaction }; transactions.push(newTransaction); return newTransaction; From 149859f3ad94b6c224ddc78c34e97aa50b766f8e Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Sat, 28 Feb 2026 17:46:41 +0100 Subject: [PATCH 7/7] Add changes after review --- .hyf/README.md | 2 +- .hyf/score.example.json | 2 +- .prettierignore | 1 + README.md | 6 ++++-- finance-tracker/finance.js | 7 ++----- package-lock.json | 19 +++++++++++++++++++ package.json | 6 +++++- 7 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 .prettierignore diff --git a/.hyf/README.md b/.hyf/README.md index 38f1a4d..7475329 100644 --- a/.hyf/README.md +++ b/.hyf/README.md @@ -1,6 +1,7 @@ # Auto grade tool ## How it works + 1. The auto grade tool runs the `test.sh` script located in this directory. 2. `test.sh` should write to a file named `score.json` with following JSON format: ```json @@ -12,4 +13,3 @@ ``` All scores are out of 100. It is up to the assignment to determine how to calculate the score. 3. The auto grade runs via a github action on PR creation and updates the PR with the score. - diff --git a/.hyf/score.example.json b/.hyf/score.example.json index 8d931f5..e048aec 100644 --- a/.hyf/score.example.json +++ b/.hyf/score.example.json @@ -2,4 +2,4 @@ "score": 75, "pass": true, "passingScore": 50 -} \ No newline at end of file +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/README.md b/README.md index 2de48c5..d410295 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Core program week 4 assignment + The week 4 assignment for the HackYourFuture Core program can be found at the following link: https://hub.hackyourfuture.nl/core-program-week-4-assignment ## Implementation Instructions -- Implement the requirements in the `finance-tracker` folder. -- Use the existing file structure. + +- Implement the requirements in the `finance-tracker` folder. +- Use the existing file structure. - You are allowed to create additional files if needed. diff --git a/finance-tracker/finance.js b/finance-tracker/finance.js index 0a60556..92bfd5e 100644 --- a/finance-tracker/finance.js +++ b/finance-tracker/finance.js @@ -94,16 +94,13 @@ export function printSummary(transactions) { const totalExpenses = chalk.bold.red(getTotalExpenses(transactions)); const numOfTransactions = chalk.bold(transactions.length); - const balance = - getBalance(transactions) >= 0 - ? chalk.bold.cyan(getBalance(transactions)) - : chalk.bold.red(getBalance(transactions)); + const balance = getBalance(transactions); const { amount, description } = getLargestExpense(transactions); const largestExpense = chalk.bold(amount); //collect summary - const summary = `📊 ${chalk.bold('financial summary'.toUpperCase())} 📊 \nTotal Income: €${totalIncome}\nTotal Expenses: €${totalExpenses}\nCurrent Balance: €${balance}\n\nLargest Expense: ${description} (€${largestExpense})\nTotal Transactions: ${numOfTransactions}`; + const summary = `📊 ${chalk.bold('financial summary'.toUpperCase())} 📊 \nTotal Income: €${totalIncome}\nTotal Expenses: €${totalExpenses}\nCurrent Balance: €${balance > 0 ? chalk.bold.cyan(balance) : chalk.bold.red(balance)}\n\nLargest Expense: ${description} (€${largestExpense})\nTotal Transactions: ${numOfTransactions}`; return summary; } diff --git a/package-lock.json b/package-lock.json index 52239c6..228d484 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "license": "ISC", "dependencies": { "chalk": "^4.1.2" + }, + "devDependencies": { + "prettier": "^3.8.1" } }, "node_modules/ansi-styles": { @@ -70,6 +73,22 @@ "node": ">=8" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 3134228..b2ed093 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node finance-tracker/app.js" + "start": "node finance-tracker/app.js", + "format": "prettier --write ." }, "type": "module", "keywords": [], @@ -13,5 +14,8 @@ "license": "ISC", "dependencies": { "chalk": "^4.1.2" + }, + "devDependencies": { + "prettier": "^3.8.1" } }