diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..cbe4613 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,308 @@ +/********************************************** +*** GENERAL +**********************************************/ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.clearfix::after { + content: ""; + display: table; + clear: both; +} + +body { + color: #555; + font-family: Open Sans; + font-size: 16px; + position: relative; + height: 100vh; + font-weight: 400; +} + +.right { float: right; } +.red { color: #FF5049 !important; } +.red-focus:focus { border: 1px solid #FF5049 !important; } + +/********************************************** +*** TOP PART +**********************************************/ + +.top { + height: 40vh; + background-image: linear-gradient(rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0.35)), url(../../images/back.png); + background-size: cover; + background-position: center; + position: relative; +} + +.budget { + position: absolute; + width: 350px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #fff; +} + +.budget__title { + font-size: 18px; + text-align: center; + margin-bottom: 10px; + font-weight: 300; +} + +.budget__value { + font-weight: 300; + font-size: 46px; + text-align: center; + margin-bottom: 25px; + letter-spacing: 2px; +} + +.budget__income, +.budget__expenses { + padding: 12px; + text-transform: uppercase; +} + +.budget__income { + margin-bottom: 10px; + background-color: #28B9B5; + font-weight: bold; +} + +.budget__expenses { + background-color: #FF5049; + animation: box; + animation-duration: 5s; +} + +.budget__expenses:hover{ + transform: scale(1.2); + transition: 0.5s ease; + font-weight: bold; + font-size: 20px; +} + + + + + + + + + +/* @keyframes box{ + to{ + transform: scale(1.2); + transition: 0.3s ease; + } + from{ + transform: scale(1.5); + transition: 0.5s ease-in; + } */ + + +} +.budget__income--text, +.budget__expenses--text { + float: left; + font-size: 13px; + color: #444; + margin-top: 2px; +} + +.budget__income--value, +.budget__expenses--value { + letter-spacing: 1px; + float: left; +} + +.budget__income--percentage, +.budget__expenses--percentage { + float: left; + width: 34px; + font-size: 11px; + padding: 3px 0; + margin-left: 10px; +} + +.budget__expenses--percentage { + background-color: rgba(255, 255, 255, 0.2); + text-align: center; + border-radius: 3px; +} + + +/********************************************** +*** BOTTOM PART +**********************************************/ + +/***** FORM *****/ +.add { + padding: 14px; + border-bottom: 1px solid #e7e7e7; + background-color: #f7f7f7; +} + +.add__container { + margin: 0 auto; + text-align: center; +} + +.add__type { + width: 55px; + border: 1px solid #e7e7e7; + height: 44px; + font-size: 18px; + color: inherit; + background-color: #fff; + margin-right: 10px; + font-weight: 300; + transition: border 0.3s; +} + +.add__description, +.add__value { + border: 1px solid #e7e7e7; + background-color: #fff; + color: inherit; + font-family: inherit; + font-size: 14px; + padding: 12px 15px; + margin-right: 10px; + border-radius: 5px; + transition: border 0.3s; +} + +.add__description { width: 400px;} +.add__value { width: 100px;} + +.add__btn { + font-size: 35px; + background: none; + border: none; + color: #28B9B5; + cursor: pointer; + display: inline-block; + vertical-align: middle; + line-height: 1.1; + margin-left: 10px; +} + +.add__btn:active { transform: translateY(2px); } + +.add__type:focus, +.add__description:focus, +.add__value:focus { + outline: none; + border: 1px solid #28B9B5; +} + +.add__btn:focus { outline: none; } + +/***** LISTS *****/ +.container { + width: 1000px; + margin: 60px auto; +} + +.income { + float: left; + width: 475px; + margin-right: 50px; +} + +.expenses { + float: left; + width: 475px; +} + +h2 { + text-transform: uppercase; + font-size: 18px; + font-weight: 400; + margin-bottom: 15px; +} + +.icome__title { color: #28B9B5; } +.expenses__title { color: #FF5049; } + +.item { + padding: 13px; + border-bottom: 1px solid #e7e7e7; +} + +.item:first-child { border-top: 1px solid #e7e7e7; } +.item:nth-child(even) { background-color: #f7f7f7; } + +.item__description { + float: left; +} + +.item__value { + float: left; + transition: transform 0.3s; +} + +.item__percentage { + float: left; + margin-left: 20px; + transition: transform 0.3s; + font-size: 11px; + background-color: #FFDAD9; + padding: 3px; + border-radius: 3px; + width: 32px; + text-align: center; +} + +.income .item__value, +.income .item__delete--btn { + color: #28B9B5; +} + +.expenses .item__value, +.expenses .item__percentage, +.expenses .item__delete--btn { + color: #FF5049; +} + + +.item__delete { + float: left; +} + +.item__delete--btn { + font-size: 22px; + background: none; + border: none; + cursor: pointer; + display: inline-block; + vertical-align: middle; + line-height: 1; + display: none; +} + +.item__delete--btn:focus { outline: none; } +.item__delete--btn:active { transform: translateY(2px); } + +.item:hover .item__delete--btn { display: block; } +.item:hover .item__value { transform: translateX(-20px); } +.item:hover .item__percentage { transform: translateX(-20px); } + + +.unpaid { + background-color: #FFDAD9 !important; + cursor: pointer; + color: #FF5049; + +} + +.unpaid .item__percentage { box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1); } +.unpaid:hover .item__description { font-weight: 900; } + diff --git a/assets/js/index.js b/assets/js/index.js new file mode 100644 index 0000000..0e2fe92 --- /dev/null +++ b/assets/js/index.js @@ -0,0 +1,402 @@ +// BUDGET CONTROLLER +var budgetController = (function(){ + + var Expense = function(id,description,value){ + this.id = id; + this.description = description; + this.value = value; + this.percentage = -1; + }; + + Expense.prototype.calcPercentage = function(totalIncome){ + if(totalIncome > 0){ + this.percentage = Math.round((this.value / totalIncome) * 100); + } else { + this.percentage = -1; + } + }; + + Expense.prototype.getPercentage = function(){ + return this.percentage; + }; + + var Income = function(id,description,value){ + this.id = id; + this.description = description; + this.value = value; + }; + + var calculateTotal = function(type){ + var sum = 0; + data.allItems[type].forEach(function(el){ + sum += el.value; + }); + data.totals[type] = sum; + }; + + var data = { + allItems: { + exp: [], + inc: [] + }, + totals: { + exp: 0, + inc: 0 + }, + budget: 0, + percentage: -1 + }; + + return { + addItem: function(type, des, val){ + var newItem, + ID; + + // Create new ID + if(data.allItems[type].length>0){ + ID = data.allItems[type][data.allItems[type].length-1].id+1; + } else { + ID = 0; + } + + // Create new item basec on "inc" or "exp" type + if(type === "exp"){ + newItem = new Expense(ID,des,val); + } else if(type === "inc") { + newItem = new Income(ID,des,val); + } + + // Push it into our data structure + data.allItems[type].push(newItem); + + // Return the new element + return newItem; + }, + + deleteItem: function(type,id){ + var ids, + index; + + ids = data.allItems[type].map(function(el){ + return el.id; + }); + + index = ids.indexOf(id); + + if(index !== -1){ + data.allItems[type].splice(index, 1); + } + + }, + + calculateBudget: function(){ + + // Calculate total income and expenses + calculateTotal('exp'); + calculateTotal('inc'); + // Calculate budget: income - expenses + data.budget = data.totals.inc - data.totals.exp; + // Calculate the percentage of income that we spent + if(data.totals.inc>0){ + data.percentage = Math.round((data.totals.exp / data.totals.inc) * 100); + } else { + data.percentage = -1; + } + }, + + calculatePercentage: function(){ + data.allItems.exp.forEach(function(el){ + el.calcPercentage(data.totals.inc); + }); + }, + + getPercentage: function(){ + var allPerc = data.allItems.exp.map(function(el){ + return el.getPercentage(); + }); + return allPerc; + }, + + getBudget: function(){ + return { + budget: data.budget, + totalInc: data.totals.inc, + totalExp: data.totals.exp, + percentage: data.percentage + } + }, + + testitem: function(){ + console.log(data); + } + } + +})(); + + +// UI CONTROLLER +var UIController = (function(){ + + var DOMstrings = { + inputType: ".add__type", + inputDescription: ".add__description", + inputValue: ".add__value", + inputBtn: ".add__btn", + incomeContainer: ".income__list", + expensesContainer: ".expenses__list", + budgetLabel: ".budget__value", + incomeLabel: ".budget__income--value", + expensesLabel: ".budget__expenses--value", + percentageLabel: ".budget__expenses--percentage", + container: ".container", + expensesPercLabel: ".item__percentage", + dateLabel: ".budget__title--month" + }; + + var formatNumber = function(num,type){ + var numSplit, + int, + dec, + sign; + + num = Math.abs(num); + num = num.toFixed(2); + numSplit = num.split("."); + int = numSplit[0]; + if(int.length>3){ + int = int.substr(0,int.length - 3)+"," + int.substr(int.length - 3,int.length); + } + + dec = numSplit[1]; + + return (type === "exp" ? sign = "-" : sign = "+") + " " + int + "." + dec; + + }; + + var nodeListForEach = function(list,callback){ + for(var i=0; i 0 ? type = "inc" : type = "exp"; + + document.querySelector(DOMstrings.budgetLabel).textContent = formatNumber(obj.budget,type); + document.querySelector(DOMstrings.incomeLabel).textContent = formatNumber(obj.totalInc,"inc"); + document.querySelector(DOMstrings.expensesLabel).textContent = formatNumber(obj.totalExp,"exp"); + + if(obj.percentage>0){ + document.querySelector(DOMstrings.percentageLabel).textContent = obj.percentage+"%"; + } else { + document.querySelector(DOMstrings.percentageLabel).textContent = "---"; + } + + }, + + displayPercentages: function(percentages) { + var fields = document.querySelectorAll(DOMstrings.expensesPercLabel); + + nodeListForEach(fields, function(el,i){ + if(percentages[i]>0){ + el.textContent = percentages[i] + "%"; + } else { + el.textContent = "---"; + } + }); + + }, + + displayMonth: function() { + var now, + month, + months, + year; + + now = new Date(); + + months = ["January","February","March","April","May","June","July","August","September","October","November","December"]; + month = now.getMonth(); + year = now.getFullYear(); + document.querySelector(DOMstrings.dateLabel).textContent = months[month] + " " + year; + }, + + changedType: function(){ + var fields = document.querySelectorAll( + DOMstrings.inputType + "," + + DOMstrings.inputDescription + ',' + + DOMstrings.inputValue + ); + + nodeListForEach(fields, function(el){ + el.classList.toggle("red-focus"); + }); + + document.querySelector(DOMstrings.inputBtn).classList.toggle("red"); + + }, + + getDOMstrings: function(){ + return DOMstrings; + } + }; + +})(); + + +//GLOBAL APP CONTROLLER +var controller = (function(budgetCtrl,UICtrl){ + + var setupEventListeners = function(){ + var DOM = UICtrl.getDOMstrings(); + + document.querySelector(DOM.inputBtn).addEventListener("click",ctrlAddItem); + + document.addEventListener("keypress",function(event){ + if(event.keyCode === 13 || event.which === 13){ + ctrlAddItem(); + } + }); + + document.querySelector(DOM.container).addEventListener("click", ctrlDeleteItem); + document.querySelector(DOM.inputType).addEventListener("change",UICtrl.changedType); + }; + + var updateBudget = function(){ + // 1. Calculate the budget + budgetController.calculateBudget(); + + // 2. Return the budget + var budget = budgetController.getBudget(); + + // 3. Display the budget on the UI + UICtrl.displayBudget(budget); + }; + + var updatePercentages = function(){ + // 1. Calculate percentages + budgetCtrl.calculatePercentage(); + // 2. Read percentages from the budget controller + var percentages = budgetCtrl.getPercentage(); + // 3. Update the UI with the new percentages + UICtrl.displayPercentages(percentages); + }; + + var ctrlAddItem = function(){ + var input, + newItem, + addItem; + + // 1. Get the field input data + input = UICtrl.getInput(); + + if(input.description !== "" && !isNaN(input.value) && input.value>0){ + // 2. Add the item to the budget controller + newItem = budgetCtrl.addItem(input.type,input.description,input.value); + // 3. Add the item to the UI + addItem = UICtrl.addListItem(newItem,input.type); + // 4. Clear the fields + UICtrl.clearFields(); + // 5. Calculate and update budget + updateBudget(); + // 6. Calculate and update percentages + updatePercentages(); + } + }; + + var ctrlDeleteItem = function(event){ + var itemID, + splitID, + type, + ID; + + itemID = event.target.parentNode.parentNode.parentNode.parentNode.id; + + if(itemID){ + + splitID = itemID.split("-"); + type = splitID[0]; + ID = parseInt(splitID[1]); + + // 1. Delete the item from the data structure + budgetController.deleteItem(type, ID); + // 2. Delete the item from the UI + UICtrl.deleteListItem(itemID); + // 3. Update and show the new budget + updateBudget(); + // 4. Calculate and update percentages + updatePercentages(); + } + }; + + return { + init: function(){ + console.log("Application has started"); + UICtrl.displayMonth(); + UICtrl.displayBudget({ + budget: 0, + totalInc: 0, + totalExp: 0, + percentage: -1 + }); + setupEventListeners(); + } + } + +})(budgetController,UIController); + + + +controller.init(); \ No newline at end of file diff --git a/images/back.png b/images/back.png new file mode 100644 index 0000000..8527293 Binary files /dev/null and b/images/back.png differ diff --git a/images/favicon-cash.png b/images/favicon-cash.png new file mode 100644 index 0000000..dcacba9 Binary files /dev/null and b/images/favicon-cash.png differ