Budget Tracker
Coding Challenge :: Budget Tracker
Problem Statement: Create a Budget Tracker App
Wire up the budgeting tool so it tells the user whether or not they can afford an item based on their available funds. Style it as you wish. 💅
Github Repo for the App & Live Demo
Here's the final version of my app (not really final as I still have multiple things to improve/upgrade)
Action Plan:
- Determine input & output fields
- Define folder struture & create necessary files
- Add content to the html page
- Add styling using CSS
- Add interaction using JavaScript
- Publish the app.
Plan Execution
- Determine input & output fields
Let's determine input and output values for our currency converter app
- Input Fields
- User Income
- Expense Category
- Output Fileds
- Availabe budget for each category
- Does a new expense for each category is allowed (depending on availabe funds)? If allowed, show a success message. If user has already consumed the budget, show a failure message.
- Show a pie graph to display available budget for each category
- Define folder struture Use a code editor (recommended: VS Code) to create a basic folder structure and files.
Folder: Budget Calculator App:
|__ index.html|__ index.js|__ css -> style.css
- Add content to html page
Now we'll add elements to the html page to display input & output value.
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="./css/style.css"><title>Budget Tracker</title></head><body><header><h1>Budget 💸 Smart</h1></header><main><div id="success"><div id="success-modal"><h2>Congratulations 🎉</h2><p id="message">You have a monthly budget available for this expense.</p><button id="update-expense">Update Expense Table</button><button id="close-modal">Close</button></div></div><div id="failure"><div id="failure-modal"><h2>Sorry...</h2><p id="message">You don't have a monthly budget available for this expense.</p><button id="close-sorry-modal">Close</button></div></div><div id="container"><div id="summary"><p class="sign-up-text">Please enter your income amount to start managing your budget</p><label class="label">Total Income: </label><input id="total-income" type="number" min="1"></input><button id="continue">Continue</button><p id="error-text-income"></p><p class="input-text">Input your expense details to see if you have a budget available. You can also buy later by adding to favourite.</p></div><div id="expense-eligibility"><input type="text" id="prod-desc" name="desc" placeholder="description"></input><input type="number" id="prod-value" name="value" placeholder="value" min="1"></input><select id="expense-type"><!-- <option>Expense type</option> --><option>Grocery</option><option>Housing</option><option>Transportation</option><option>Educational</option><option>Entertainment</option><option>Medical</option><option>Personal</option><option>Utility</option><option>Other</option></select><button id="btn-budget-check">Budget Check</button><p id="error-text"></p></div><div id="budget-allocation" class="columns"><div id="expense-chart" class="chart"><p class="chart-heading">Budget breakdown</p><canvas id="myCanvas"></canvas><div id="myLegend"></div></div><div id="expense-detail" class="detail"><table><tr class="expense-type-header"><th>Expense Type</th><th>Budget %</th><th>Monthly Budget</th><th>Your Expense</th><th>Available</th></tr><tr class="grocery"><td><span class="legend legend--medium bg-green"></span>Grocery</td><td>20%</td><td id="exp-grocery-limit" class="budget-limit">0</td><td id="exp-grocery" class="exp-amount">0</td><td id="exp-grocery-available" class="exp-amount-available">0</td></tr><tr class="housing"><td><span class="legend legend--medium bg-dodger-blue"></span>Housing</td><td>20%</td><td id="exp-housing-limit" class="budget-limit">0</td><td id="exp-housing" class="exp-amount">0</td><td id="exp-housing-available" class="exp-amount-available">0</td></tr><tr class="transport"><td><span class="legend legend--medium bg-yellow-green"></span>Transportation</td><td>5%</td><td id="exp-transportation-limit" class="budget-limit">0</td><td id="exp-transportation" class="exp-amount">0</td><td id="exp-transportation-available" class="exp-amount-available">0</td></tr><tr class="education"><td><span class="legend legend--medium bg-orange"></span>Educational</td><td>15%</td><td id="exp-educational-limit" class="budget-limit">0</td><td id="exp-educational" class="exp-amount">0</td><td id="exp-educational-available" class="exp-amount-available">0</td></tr><tr class="entertainment"><td><span class="legend legend--medium bg-sky-blue"></span>Entertainment</td><td>10%</td><td id="exp-entertainment-limit" class="budget-limit">0</td><td id="exp-entertainment" class="exp-amount">0</td><td id="exp-entertainment-available" class="exp-amount-available">0</td></tr><tr class="medical"><td><span class="legend legend--medium bg-light-coral"></span>Medical</td><td>5%</td><td id="exp-medical-limit" class="budget-limit">0</td><td id="exp-medical" class="exp-amount">0</td><td id="exp-medical-available" class="exp-amount-available">0</td></tr><tr class="personal"><td><span class="legend legend--medium bg-teal"></span>Personal</td><td>10%</td><td id="exp-personal-limit" class="budget-limit">0</td><td id="exp-personal" class="exp-amount">0</td><td id="exp-personal-available" class="exp-amount-available">0</td></tr><tr class="utility"><td><span class="legend legend--medium bg-dark-orchid"></span>Utility</td><td>10%</td><td id="exp-utility-limit" class="budget-limit">0</td><td id="exp-utility" class="exp-amount">0</td><td id="exp-utility-available" class="exp-amount-available">0</td></tr><tr class="other"><td><span class="legend legend--medium bg-golden-rod"></span>Other</td><td>5%</td><td id="exp-other-limit" class="budget-limit">0</td><td id="exp-other" class="exp-amount">0</td><td id="exp-other-available" class="exp-amount-available">0</td></tr><tr class="total"><td>Total</td><td>100%</td><td id="exp-total-limit">0</td><td id="exp-total">0</td><td id="exp-total-available">0</td></tr></table></div></div></div></div></main><footer>Site designed by Ambreen Khan. All rights reserved.</footer><script src="index.js"></script></body></html>
- Add styling using CSS
- Style input & output fields
Here's what my styling looks like:
body {margin: 0;font-size: 16px;}h1 {font-size: 35px;}h2 {font-size: 30px;color: rgb(26, 79, 122);}h3 {font-size: 28px;}header {background-color: rgb(26, 79, 122);color: white;text-align: center;padding: 5px 0;margin-bottom: 15px;}.label {font-size: 20px;/* border: none; */width: 100px;color: rgb(26, 79, 122);}input {font-size: 16px;padding: 6px;}.input-text {margin-top: 30px;}select {padding: 8px 10px;}main {color: black;width: 1200px;margin: 0 auto;text-align: center;/* border: 1px solid red; */}footer {background-color: rgb(26, 79, 122);color: white;text-align: center;padding: 10px 0;}table {font-family: arial, sans-serif;border-collapse: collapse;/* border: 1px solid black; */width: 100%;/* margin: 50px 0px; */margin: 50px 100px;}.overlay-table {width: 80%;}td,th {border: 1px solid #dddddd;text-align: left;padding: 8px;}tr:nth-child(even) {background-color: rgb(198, 217, 236);}.expense-type-header,.total {color: rgb(26, 79, 122);font-weight: bold;}.columns {display: flex;}img {padding-top: 30px;}#message {font-size: 20px;font-weight: bold;}button {padding: 9px 12px;background-color: rgb(26, 79, 122);color: #fff;font-weight: bold;border: 1px solid gray;}button:hover {background-color: rgb(33, 62, 87);}/* Color pallete */.bg-green {background: ForestGreen;}.bg-yellow-green {background: YellowGreen;}.bg-orange {background: orange;}.bg-peach-puff {background: PeachPuff;}.bg-dodger-blue {background: DodgerBlue;}.bg-light-coral {background: LightCoral;}.bg-sky-blue {background: DeepSkyBlue;}.bg-dark-orchid {background: DarkOrchid;}.bg-teal {background: Teal;}.bg-golden-rod {background: DarkGoldenRod;}.legend {display: inline-block;width: 12px;height: 12px;border-radius: 16px;margin-right: 10px;}.chart-heading {margin-top: 50px;color: rgb(26, 79, 122);font-size: 25px;}#success,#failure,#overlay3 {display: none;width: 100%;height: 100%;position: absolute;top: 0;left: 0;right: 0;bottom: 0;background-color: rgba(0, 0, 0, 0.3);}#success-modal,#failure-modal {background-color: white;max-width: 600px;margin: 0 auto;padding: 20px;height: 200px;position: relative;top: 30%;}#modal3 {background-color: white;max-width: 800px;margin: 0 auto;padding: 10px;height: 500px;position: relative;top: 30%;}#open-modal,#close-modal-2,#close-modal-3,#show-favourite-list {background-color: rgb(26, 79, 122);color: white;padding: 10px 20px;font-size: 15px;cursor: pointer;}.sign-up-text {margin-top: 50px;color: rgb(214, 148, 24);font-size: 20px;font-weight: bold;}#total-income {width: 80px;}
- Add interaction using JavaScript. Perform these steps while adding interactivity to the html page:
- Link
js
file withhtml
file. - Grab elements from html & read values provided by user
- Validation of fields to ensure user provides all mandatory values
- Add event listenrs for button to perform action and dispaly results
Here is the JS Code:
let totalIncome = document.getElementById('total-income');let btnContinue = document.getElementById('continue');let btnCheckBudget = document.getElementById('btn-budget-check');let errorText = document.getElementById('error-text');let errorTextIncome = document.getElementById('error-text-income');// Initialize all expense amountslet userGroceryExpensesAmt = 0;let userHousingExpensesAmt = 0;let userTransportExpensesAmt = 0;let userEducationExpensesAmt = 0;let userEntertainmentExpensesAmt = 0;let userMedicalExpensesAmt = 0;let userPersonalExpensesAmt = 0;let userUtilityExpensesAmt = 0;let userOtherExpensesAmt = 0;let expGroceryLimit = 0;let expHousingLimit = 0;let expTransportLimit = 0;let expEducationLimit = 0;let expEntertainmentLimit = 0;let expMedicalLimit = 0;let expPersonalLimit = 0;let expUtilityLimit = 0;let expOtherLimit = 0;let userIncomeAmt = 0;//Grocerylet tblExpGroceryLimit = document.getElementById('exp-grocery-limit');let tblExpGrocery = document.getElementById('exp-grocery');let tblExpGroceryAvailable = document.getElementById('exp-grocery-available');//Housinglet tblHousingLimit = document.getElementById('exp-housing-limit');let tblExpHousing = document.getElementById('exp-housing');let tblExpHousingAvailable = document.getElementById('exp-housing-available');//Transportationlet tblTransportLimit = document.getElementById('exp-transportation-limit');let tblExpTransport = document.getElementById('exp-transportation');let tblExpTransportAvailable = document.getElementById('exp-transportation-available');//Educationallet tblEducationLimit = document.getElementById('exp-educational-limit');let tblExpEducation = document.getElementById('exp-educational');let tblExpEducationAvailable = document.getElementById('exp-educational-available');//Entertainmentlet tblEntertainmentLimit = document.getElementById('exp-entertainment-limit');let tblExpEntertainment = document.getElementById('exp-entertainment');let tblExpEntertainmentAvailable = document.getElementById('exp-entertainment-available');//Medicallet tblMedicalimit = document.getElementById('exp-medical-limit');let tblExpMedical = document.getElementById('exp-medical');let tblExpMedicalAvailable = document.getElementById('exp-medical-available');//Personallet tblPersonalLimit = document.getElementById('exp-personal-limit');let tblExpPersonal = document.getElementById('exp-personal');let tblExpPersonalAvailable = document.getElementById('exp-personal-available');//Utilitylet tblUtilityLimit = document.getElementById('exp-utility-limit');let tblExpUtility = document.getElementById('exp-utility');let tblExpUtilityAvailable = document.getElementById('exp-utility-available');//Otherlet tblOtherLimit = document.getElementById('exp-other-limit');let tblExpOther = document.getElementById('exp-other');let tblExpOtherAvailable = document.getElementById('exp-other-available');// Totallet tblTotal = document.getElementById('exp-total-limit');let tblExpTotal = document.getElementById('exp-total');let tblExpTotalAvailable = document.getElementById('exp-total-available');let allBudgetLimitCollection = document.getElementsByClassName('budget-limit');let allExpenseAmtCollection = document.getElementsByClassName('exp-amount');let allAvailableAmtCollection = document.getElementsByClassName('exp-amount-available');// Event Listener - Continue ButtonbtnContinue.addEventListener('click', function() {if (!totalIncome.value) {errorTextIncome.style.color = 'red';errorTextIncome.style.fontWeight = 'bold';errorTextIncome.textContent = `Please enter the income value to continue.`;} else {errorTextIncome.style.display = 'none';showMonthlyBudgetGrocery();showMonthlyBudgetHousing();showMonthlyBudgetTransportation();showMonthlyBudgetEducational();showMonthlyBudgetEntertainment();showMonthlyBudgetMedical();showMonthlyBudgetPersonal();showMonthlyBudgetUtility();showMonthlyBudgetOther();showTotalMonthlyBudgetValue();updateTotalAvailableAmt();}});// Event Listener - Budget Check ButtonbtnCheckBudget.addEventListener('click', function() {let prodDesc = document.getElementById('prod-desc').value;let prodValue = document.getElementById('prod-value').value;let expenseType = document.getElementById('expense-type').value.toLowerCase();if (!prodDesc || !prodValue) {errorText.style.color = 'red';errorText.style.fontWeight = 'bold';errorText.textContent = `Please enter the required input values.`;} else {errorText.style.display = 'none';availableLimitExpenseTypeSelector = `exp-${expenseType}-available`;let availableExpenseLimit = parseInt(document.getElementById(availableLimitExpenseTypeSelector).innerHTML);if (prodValue < availableExpenseLimit) {document.getElementById('success').style.display = 'block';} else {document.getElementById('failure').style.display = 'block';}updateTotalExpenses();}});// Event Listener - Close Button - Expense not alloweddocument.getElementById('close-sorry-modal').addEventListener('click', function() {document.getElementById('failure').style.display = 'none';});// Event Listener - Close Button - Expense is alloweddocument.getElementById('close-modal').addEventListener('click', function() {document.getElementById('success').style.display = 'none';});// Methodsfunction showMonthlyBudgetGrocery() {expGroceryLimit = (20 / 100) * totalIncome.value;tblExpGroceryLimit.innerText = expGroceryLimit;tblExpGrocery.innerText = userGroceryExpensesAmt;tblExpGroceryAvailable.innerText = expGroceryLimit - userGroceryExpensesAmt;}function showMonthlyBudgetHousing() {expHousingLimit = (20 / 100) * totalIncome.value;tblHousingLimit.innerText = expHousingLimit;tblExpHousing.innerText = userHousingExpensesAmt;tblExpHousingAvailable.innerText = expHousingLimit - userHousingExpensesAmt;}function showMonthlyBudgetTransportation() {expTransportLimit = (5 / 100) * totalIncome.value;tblTransportLimit.innerText = expTransportLimit;tblExpTransport.innerText = userTransportExpensesAmt;tblExpTransportAvailable.innerText = expTransportLimit - userTransportExpensesAmt;}function showMonthlyBudgetEducational() {expEducationLimit = (15 / 100) * totalIncome.value;tblEducationLimit.innerText = expEducationLimit;tblExpEducation.innerText = userEducationExpensesAmt;tblExpEducationAvailable.innerText = expEducationLimit - userEducationExpensesAmt;}function showMonthlyBudgetEntertainment() {expEntertainmentLimit = (10 / 100) * totalIncome.value;tblEntertainmentLimit.innerText = expEntertainmentLimit;tblExpEntertainment.innerText = userEntertainmentExpensesAmt;tblExpEntertainmentAvailable.innerText = expEntertainmentLimit - userEntertainmentExpensesAmt;}function showMonthlyBudgetMedical() {expMedicalLimit = (5 / 100) * totalIncome.value;tblMedicalimit.innerText = expMedicalLimit;tblExpMedical.innerText = userMedicalExpensesAmt;tblExpMedicalAvailable.innerText = expMedicalLimit - userMedicalExpensesAmt;}function showMonthlyBudgetPersonal() {expPersonalLimit = (10 / 100) * totalIncome.value;tblPersonalLimit.innerText = expPersonalLimit;tblExpPersonal.innerText = userPersonalExpensesAmt;tblExpPersonalAvailable.innerText = expPersonalLimit - userPersonalExpensesAmt;}function showMonthlyBudgetUtility() {expUtilityLimit = (10 / 100) * totalIncome.value;tblUtilityLimit.innerText = expUtilityLimit;tblExpUtility.innerText = userUtilityExpensesAmt;tblExpUtilityAvailable.innerText = expUtilityLimit - userUtilityExpensesAmt;}function showMonthlyBudgetOther() {expOtherLimit = (5 / 100) * totalIncome.value;tblOtherLimit.innerText = expOtherLimit;tblExpOther.innerText = userOtherExpensesAmt;tblExpOtherAvailable.innerText = expOtherLimit - userOtherExpensesAmt;}function updateTotalExpenses() {let totalExpensesAmt = 0;for (let counter = 0; counter < allExpenseAmtCollection.length; counter++) {let expensesAmt = parseInt(allExpenseAmtCollection[counter].textContent);totalExpensesAmt = totalExpensesAmt + expensesAmt;}tblExpTotal.innerText = totalExpensesAmt;return totalExpensesAmt;}function showTotalMonthlyBudgetValue() {let totalBudgetAmt = 0;for (let counter = 0; counter < allBudgetLimitCollection.length; counter++) {let budgetAmt = parseInt(allBudgetLimitCollection[counter].textContent);totalBudgetAmt = totalBudgetAmt + budgetAmt;}tblTotal.innerText = totalBudgetAmt;return totalBudgetAmt;}function updateTotalAvailableAmt() {let totalAvailableAmt = 0;for (let counter = 0; counter < allAvailableAmtCollection.length; counter++) {let availableAmt = parseInt(allAvailableAmtCollection[counter].textContent);totalAvailableAmt = totalAvailableAmt + availableAmt;}tblExpTotalAvailable.innerText = totalAvailableAmt;return totalAvailableAmt;}function updateExpenseAmt(expenseType, prodValue) {switch (expenseType) {case 'grocery':userGroceryExpensesAmt = userGroceryExpensesAmt + prodValue;tblExpGrocery.innerText = userGroceryExpensesAmt;tblExpGroceryAvailable.innerText = expGroceryLimit - userGroceryExpensesAmt;break;case 'housing':userHousingExpensesAmt = userHousingExpensesAmt + prodValue;tblExpHousing.innerText = userHousingExpensesAmt;tblExpHousingAvailable.innerText = expHousingLimit - userHousingExpensesAmt;break;case 'transportation':userTransportExpensesAmt = userTransportExpensesAmt + prodValue;tblExpTransport.innerText = userTransportExpensesAmt;tblExpTransportAvailable.innerText = expTransportLimit - userTransportExpensesAmt;break;case 'educational':userEducationExpensesAmt = userEducationExpensesAmt + prodValue;tblExpEducation.innerText = userEducationExpensesAmt;tblExpEducationAvailable.innerText = expEducationLimit - userEducationExpensesAmt;break;case 'entertainment':userEntertainmentExpensesAmt = userEntertainmentExpensesAmt + prodValue;tblExpEntertainment.innerText = userEntertainmentExpensesAmt;tblExpEntertainmentAvailable.innerText = expEntertainmentLimit - userEntertainmentExpensesAmt;break;case 'medical':userMedicalExpensesAmt = userMedicalExpensesAmt + prodValue;tblExpMedical.innerText = userMedicalExpensesAmt;tblExpMedicalAvailable.innerText = expMedicalLimit - userMedicalExpensesAmt;break;case 'personal':userPersonalExpensesAmt = userPersonalExpensesAmt + prodValue;tblExpPersonal.innerText = userPersonalExpensesAmt;tblExpPersonalAvailable.innerText = expPersonalLimit - userPersonalExpensesAmt;break;case 'utility':userUtilityExpensesAmt = userUtilityExpensesAmt + prodValue;tblExpUtility.innerText = userUtilityExpensesAmt;tblExpUtilityAvailable.innerText = expUtilityLimit - userUtilityExpensesAmt;break;case 'other':userOtherExpensesAmt = userOtherExpensesAmt + prodValue;tblExpOther.innerText = userOtherExpensesAmt;tblExpOtherAvailable.innerText = expOtherLimit - userOtherExpensesAmt;break;default:}updateTotalExpenses();updateTotalAvailableAmt();}document.getElementById('update-expense').addEventListener('click', function() {let prodValue = parseInt(document.getElementById('prod-value').value);let expenseType = document.getElementById('expense-type').value.toLowerCase();document.getElementById('success').style.display = 'none';updateExpenseAmt(expenseType, prodValue);});// Drawing the graph using Canvas for allowed expense valuesvar myCanvas = document.getElementById('myCanvas');myCanvas.width = 300;myCanvas.height = 300;var ctx = myCanvas.getContext('2d');var myVinyls = {Grocery: 20,Housing: 20,Transportation: 5,Educational: 15,Entertainment: 10,Medical: 5,Personal: 10,Savings: 10,Other: 5,};var Piechart = function(options) {this.options = options;this.canvas = options.canvas;this.ctx = this.canvas.getContext('2d');this.colors = options.colors;this.draw = function() {var total_value = 0;var color_index = 0;for (var categ in this.options.data) {var val = this.options.data[categ];total_value += val;}var start_angle = 0;for (categ in this.options.data) {val = this.options.data[categ];var slice_angle = (2 * Math.PI * val) / total_value;drawPieSlice(this.ctx,this.canvas.width / 2,this.canvas.height / 2,Math.min(this.canvas.width / 2, this.canvas.height / 2),start_angle,start_angle + slice_angle,this.colors[color_index % this.colors.length]);start_angle += slice_angle;color_index++;}};if (this.options.legend) {color_index = 0;var legendHTML = '';for (categ in this.options.data) {legendHTML +="<div><span style='display:inline-block;width:20px;background-color:" +this.colors[color_index++] +";'> </span> " +categ +'</div>';}this.options.legend.innerHTML = legendHTML;}};var myPiechart = new Piechart({canvas: myCanvas,data: myVinyls,colors: ['ForestGreen','DodgerBlue','YellowGreen','Orange','DeepSkyBlue','LightCoral','Teal','DarkOrchid','DarkGoldenRod',],// legend:myLegend});myPiechart.draw();function drawPieSlice(ctx, centerX, centerY, radius, startAngle, endAngle, color) {ctx.fillStyle = color;ctx.beginPath();ctx.moveTo(centerX, centerY);ctx.arc(centerX, centerY, radius, startAngle, endAngle);ctx.closePath();ctx.fill();}
- Publish the app
I used github pages to deploy my app by following these steps:
- Created a repository on github.
- Pushed my code.
- From repo settings, published my app using github pages. The html file with name index.html is published as the home page.