diff --git a/javascript-practice/src/js/controllers/homeController.js b/javascript-practice/src/js/controllers/homeController.js index acad97b..422ab52 100644 --- a/javascript-practice/src/js/controllers/homeController.js +++ b/javascript-practice/src/js/controllers/homeController.js @@ -28,30 +28,30 @@ export default class HomeController { return this.service.categoryService.getAllCategory(); } + handlerGetAllTransactions(idUser) { + return this.service.transactionService.getListTransactionByIdUser(idUser); + } + init() { if (this.homeView) { - this.homeView.addHandlerEventWalletForm( - this.handlerSaveWallet.bind(this), - this.handlerGetAllCategory.bind(this), - ); - - this.homeView.loadPage( + this.homeView.initFunction( this.handlerGetInfoUserLogin.bind(this), this.handlerCheckWalletValid.bind(this), this.handlerGetWalletUser.bind(this), this.handlerGetAllCategory.bind(this), - ); - - this.homeView.addHandlerEventBudgetForm( - this.handlerSaveTransaction.bind(this), + this.handlerGetAllTransactions.bind(this), this.handlerSaveWallet.bind(this), + this.handlerSaveTransaction.bind(this), ); + this.homeView.addHandlerEventWalletForm(); + + this.homeView.loadPage(); + + this.homeView.addHandlerEventBudgetForm(); this.homeView.handlerEventCategoryDialog(); - this.homeView.handlerEventTransactionDialog( - this.handlerSaveTransaction.bind(this), - ); + this.homeView.handlerEventTransactionDialog(); } } } diff --git a/javascript-practice/src/js/helpers/helpers.js b/javascript-practice/src/js/helpers/helpers.js index 10c5701..ea0b6b3 100644 --- a/javascript-practice/src/js/helpers/helpers.js +++ b/javascript-practice/src/js/helpers/helpers.js @@ -92,3 +92,64 @@ export const changeDateFormat = (oldFormatDate) => { return `${day}, ${date}, ${month}, ${year}`; }; + +export const createTransactionDetailObject = (category, transactions) => { + const totalTransaction = transactions.length; + const totalAmount = () => { + let amount = 0; + + transactions.forEach((transaction) => { + amount += transaction.amount; + }); + + return amount; + }; + const listTransaction = () => { + const results = []; + + transactions.forEach((transaction) => { + const dateParts = transaction.date.split(','); // ['Monday', '14', 'September', '2023'] + const day = dateParts[1]; + const fullDateString = `${dateParts[0]}, ${dateParts[2]} ${dateParts[3]}`; + const tempData = { + id: transaction.id, + day, + fullDateString, + note: transaction.note, + amount: transaction.amount, + }; + + results.push(tempData); + }); + + results.sort((a, b) => parseInt(b.id, 10) - parseInt(a.id, 10)); + + return results; + }; + + return { + categoryName: category.name, + url: category.url, + totalTransaction, + totalAmount: totalAmount(), + transactions: listTransaction(), + }; +}; + +export const getAllCategoryNameInTransactions = (transactions) => { + const categoryName = new Set(); + + transactions.forEach((transaction) => + categoryName.add(transaction.categoryName), + ); + + return Array.from(categoryName); +}; + +export const getAllTransactionByCategoryName = (categoryName, transactions) => { + const results = transactions.filter((transaction) => { + return transaction.categoryName === categoryName; + }); + + return results; +}; diff --git a/javascript-practice/src/js/models/transaction.js b/javascript-practice/src/js/models/transaction.js index ca598df..32065ba 100644 --- a/javascript-practice/src/js/models/transaction.js +++ b/javascript-practice/src/js/models/transaction.js @@ -3,14 +3,16 @@ export default class Transaction { id = Transaction.createIdTransactions(), categoryName = '', date = '', - note = 'None', + note = '', amount = 0, + idUser, }) { this.id = id; this.categoryName = categoryName; this.date = date; this.note = note; this.amount = amount; + this.idUser = idUser; } static createIdTransactions() { diff --git a/javascript-practice/src/js/services/categoryService.js b/javascript-practice/src/js/services/categoryService.js index 86b0db3..c0d48a3 100644 --- a/javascript-practice/src/js/services/categoryService.js +++ b/javascript-practice/src/js/services/categoryService.js @@ -22,4 +22,16 @@ export default class CategoryService extends CommonService { return null; } + + async getCategoryByName(nameCategory) { + const data = await this.getDataFromProp( + 'name', + nameCategory, + this.defaultPath, + ); + + if (data) return data; + + return null; + } } diff --git a/javascript-practice/src/js/services/commonService.js b/javascript-practice/src/js/services/commonService.js index ee96671..3591ef6 100644 --- a/javascript-practice/src/js/services/commonService.js +++ b/javascript-practice/src/js/services/commonService.js @@ -66,11 +66,39 @@ export default class CommonService { async getAllDataFromPath(path = this.defaultPath) { this.connectToDb(); - const result = await this.firebaseService.getAllDataFromPath(path); + const results = await timeOutConnect( + this.firebaseService.getAllDataFromPath(path), + ); + const listData = []; - const data = await timeOutConnect(result); + if (results) { + // Convert format object + results.forEach((data) => { + listData.push(convertDataObjectToModel(data)); + }); - if (data) return data; + return listData; + } + + return null; + } + + async getListDataFromProp(property, value, path = this.defaultPath) { + this.connectToDb(); + + const results = await timeOutConnect( + this.firebaseService.getListDataFromProp(path, property, value), + ); + const listData = []; + + if (results) { + // Convert format object + results.forEach((data) => { + listData.push(convertDataObjectToModel(data)); + }); + + return listData; + } return null; } diff --git a/javascript-practice/src/js/services/firebaseService.js b/javascript-practice/src/js/services/firebaseService.js index a5a40f7..ca7bcbf 100644 --- a/javascript-practice/src/js/services/firebaseService.js +++ b/javascript-practice/src/js/services/firebaseService.js @@ -100,7 +100,7 @@ class FirebaseService { onValue( ref(this.db, path), (snapshot) => { - const data = []; + const listData = []; // snapshot is a type of data by Firebase define snapshot.forEach((childSnapshot) => { @@ -111,11 +111,40 @@ class FirebaseService { // data = dataTemp; // } const id = childSnapshot.key; - const val = childSnapshot.val(); + const data = childSnapshot.val(); - data.push({ id, ...val }); + listData.push({ id, data }); }); - resolve(data); + resolve(listData); + }, + { + onlyOnce: true, + }, + ); + }); + } + + getListDataFromProp(path, property, value) { + return new Promise((resolve) => { + onValue( + ref(this.db, path), + (snapshot) => { + let id; + let data; + const listData = []; + + // snapshot is a type of data by Firebase define + snapshot.forEach((childSnapshot) => { + const dataTemp = childSnapshot.val(); + + if (dataTemp[property] === value) { + id = childSnapshot.key; + data = dataTemp; + + listData.push({ id, data }); + } + }); + resolve(listData); }, { onlyOnce: true, diff --git a/javascript-practice/src/js/services/transactionService.js b/javascript-practice/src/js/services/transactionService.js index 15f8210..1fd2d35 100644 --- a/javascript-practice/src/js/services/transactionService.js +++ b/javascript-practice/src/js/services/transactionService.js @@ -14,4 +14,18 @@ export default class TransactionService extends CommonService { async saveTransaction(transaction) { await this.save(transaction); } + + async getListTransactionByIdUser(idUser) { + const results = this.getListDataFromProp( + 'idUser', + idUser, + this.defaultPath, + ); + + if (results) { + return results; + } + + return null; + } } diff --git a/javascript-practice/src/js/views/homeView.js b/javascript-practice/src/js/views/homeView.js index 4f1ce6c..e5c9beb 100644 --- a/javascript-practice/src/js/views/homeView.js +++ b/javascript-practice/src/js/views/homeView.js @@ -4,7 +4,13 @@ import * as MESSAGE from '../constants/message'; import CommonView from './commonView'; import Wallet from '../models/wallet'; import Transaction from '../models/transaction'; -import { changeDateFormat, formatNumber } from '../helpers/helpers'; +import { + changeDateFormat, + createTransactionDetailObject, + formatNumber, + getAllCategoryNameInTransactions, + getAllTransactionByCategoryName, +} from '../helpers/helpers'; import defaultCategoryIcon from '../../assets/images/question-icon.svg'; export default class HomeView extends CommonView { @@ -30,21 +36,35 @@ export default class HomeView extends CommonView { this.walletDialog = document.getElementById('walletDialog'); } - async loadPage( + initFunction( getInfoUserLogin, isValidWallet, getWalletByIdUser, getAllCategory, + getAllTransactions, + saveWallet, + saveTransaction, ) { - this.getWalletByIdUser = getWalletByIdUser; // Init function + this.getInfoUserLogin = getInfoUserLogin; + this.isValidWallet = isValidWallet; + this.getWalletByIdUser = getWalletByIdUser; + this.getAllCategory = getAllCategory; + this.getAllTransactions = getAllTransactions; + this.saveWallet = saveWallet; + this.saveTransaction = saveTransaction; + } + + async loadPage() { + // Init function + this.toggleLoaderSpinner(); - const user = await getInfoUserLogin(); + const user = await this.getInfoUserLogin(); if (!user) { window.location.replace('/login'); } else { this.user = user; - const wallet = await isValidWallet(user.id); + const wallet = await this.isValidWallet(user.id); // Check user's wallet if have or not if (!wallet) { @@ -55,18 +75,22 @@ export default class HomeView extends CommonView { this.loadEvent(); // Init data - this.loadData(getAllCategory); + this.loadData(); } } this.toggleLoaderSpinner(); } - async loadData(getAllCategory) { + async loadData() { await this.loadWalletUser(); - await this.loadCategory(getAllCategory); + await this.loadTransactionData(); + await this.loadCategory(this.getAllCategory); + this.loadSummaryTab(); + this.loadTransactionTab(); } + // ---------------------LOAD DATA---------------------// /** * Load wallet user */ @@ -86,16 +110,173 @@ export default class HomeView extends CommonView { } async updateAmountWallet(amount, saveWallet) { - this.wallet.amount += +amount; + this.wallet.amount += amount; + + if (amount > 0) { + this.wallet.inflow += amount; + } else { + this.wallet.outflow -= amount; + } await saveWallet(this.wallet); } - // ---------------------TRANSACTIONS DIALOG---------------------// - handlerEventTransactionDialog(saveTransaction) { + /** + * Load category data + * @param {function} getAllCategory Get all category function + */ + async loadCategory(getAllCategory) { + this.listCategory = await getAllCategory(); + + if (this.listCategory) { + this.renderCategoryItem(); + } + } + + loadSummaryTab() { + const inflowValue = document.querySelector('.inflow__text--income'); + const outflowValue = document.querySelector('.outflow__text--outcome'); + const totalValue = document.querySelector('.summary__total'); + + const { inflow } = this.wallet; + const { outflow } = this.wallet; + const total = inflow - outflow; + + inflowValue.textContent = `+$ ${formatNumber(inflow)}`; + outflowValue.textContent = `-$ ${formatNumber(outflow)}`; + totalValue.textContent = `${total >= 0 ? '+' : '-'}$ ${formatNumber( + Math.abs(total), + )}`; + } + + async loadTransactionData() { + this.listTransactions = await this.getAllTransactions(this.wallet.idUser); + } + + async loadTransactionTab() { + // Init data first + const transactionDetails = this.loadTransactionDetailsData(); + const transactionEl = document.querySelector('.transaction'); + const listTransactionDetailEl = + transactionEl.querySelector('.transaction__list'); + + const markup = []; + + if (transactionDetails) { + transactionDetails.forEach((transactionDetail) => { + markup.push(this.transactionDetailMarkup(transactionDetail)); + }); + } + + listTransactionDetailEl.innerHTML = ''; + listTransactionDetailEl.insertAdjacentHTML('afterbegin', markup.join('\n')); + } + + loadTransactionDetailsData() { + // Get all category user have in transactions + const listCategoryInTransaction = getAllCategoryNameInTransactions( + this.listTransactions, + ); + + // Create transactions details object + const tempList = []; + + listCategoryInTransaction.forEach((categoryName) => { + // Get category object from list category has been loaded. + const category = this.listCategory.filter( + (item) => item.name === categoryName, + ); + + const transactions = getAllTransactionByCategoryName( + categoryName, + this.listTransactions, + ); + + tempList.push( + createTransactionDetailObject( + Object.assign({}, ...category), + transactions, + ), + ); + }); + + tempList.sort( + (a, b) => + parseInt(b.transactions[0].id, 10) - parseInt(a.transactions[0].id, 10), + ); + + return tempList; + } + + transactionDetailMarkup(transactionDetail) { + const transactionTimeMarkup = () => { + const listMarkup = []; + transactionDetail.transactions.forEach((transaction) => { + const markup = ` +
${transaction.day}
++ ${transaction.fullDateString} +
+${ + transaction.note === '' ? 'None' : transaction.note + }
+${transaction.amount >= 0 ? '+' : '-'}$ ${formatNumber( + Math.abs(transaction.amount), + )}
++ ${transactionDetail.categoryName} +
+${ + transactionDetail.totalTransaction + } Transactions
+${ + transactionDetail.totalAmount >= 0 ? '+' : '-' + }$ ${formatNumber(Math.abs(transactionDetail.totalAmount))}
+Inflow
-+$ 500,000,000.00
++$ 0.00
Outflow
--$ 100,000.00
+$ 0.00
+$ 499,900,000.00
++$ 0.00
- Transportation -
-1 Transactions
--$ 100,000.00
-18
-- Friday, August 2023 -
-Test Note
--$ 100,000.00
-- Transportation -
-1 Transactions
-- +$ 500,000,000.00 -
-18
-- Friday, August 2023 -
-Initial Balance
-+$ 500,000,000.00
-