Merge pull request #24 from Nez27/feat/show-transaction-tabs

Implement show data in transaction data
This commit is contained in:
Loi Phan
2023-09-15 15:13:01 +07:00
committed by GitHub
10 changed files with 395 additions and 142 deletions
@@ -28,30 +28,30 @@ export default class HomeController {
return this.service.categoryService.getAllCategory(); return this.service.categoryService.getAllCategory();
} }
handlerGetAllTransactions(idUser) {
return this.service.transactionService.getListTransactionByIdUser(idUser);
}
init() { init() {
if (this.homeView) { if (this.homeView) {
this.homeView.addHandlerEventWalletForm( this.homeView.initFunction(
this.handlerSaveWallet.bind(this),
this.handlerGetAllCategory.bind(this),
);
this.homeView.loadPage(
this.handlerGetInfoUserLogin.bind(this), this.handlerGetInfoUserLogin.bind(this),
this.handlerCheckWalletValid.bind(this), this.handlerCheckWalletValid.bind(this),
this.handlerGetWalletUser.bind(this), this.handlerGetWalletUser.bind(this),
this.handlerGetAllCategory.bind(this), this.handlerGetAllCategory.bind(this),
); this.handlerGetAllTransactions.bind(this),
this.homeView.addHandlerEventBudgetForm(
this.handlerSaveTransaction.bind(this),
this.handlerSaveWallet.bind(this), this.handlerSaveWallet.bind(this),
this.handlerSaveTransaction.bind(this),
); );
this.homeView.addHandlerEventWalletForm();
this.homeView.loadPage();
this.homeView.addHandlerEventBudgetForm();
this.homeView.handlerEventCategoryDialog(); this.homeView.handlerEventCategoryDialog();
this.homeView.handlerEventTransactionDialog( this.homeView.handlerEventTransactionDialog();
this.handlerSaveTransaction.bind(this),
);
} }
} }
} }
@@ -92,3 +92,64 @@ export const changeDateFormat = (oldFormatDate) => {
return `${day}, ${date}, ${month}, ${year}`; 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;
};
@@ -3,14 +3,16 @@ export default class Transaction {
id = Transaction.createIdTransactions(), id = Transaction.createIdTransactions(),
categoryName = '', categoryName = '',
date = '', date = '',
note = 'None', note = '',
amount = 0, amount = 0,
idUser,
}) { }) {
this.id = id; this.id = id;
this.categoryName = categoryName; this.categoryName = categoryName;
this.date = date; this.date = date;
this.note = note; this.note = note;
this.amount = amount; this.amount = amount;
this.idUser = idUser;
} }
static createIdTransactions() { static createIdTransactions() {
@@ -22,4 +22,16 @@ export default class CategoryService extends CommonService {
return null; return null;
} }
async getCategoryByName(nameCategory) {
const data = await this.getDataFromProp(
'name',
nameCategory,
this.defaultPath,
);
if (data) return data;
return null;
}
} }
@@ -66,11 +66,39 @@ export default class CommonService {
async getAllDataFromPath(path = this.defaultPath) { async getAllDataFromPath(path = this.defaultPath) {
this.connectToDb(); 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; return null;
} }
@@ -100,7 +100,7 @@ class FirebaseService {
onValue( onValue(
ref(this.db, path), ref(this.db, path),
(snapshot) => { (snapshot) => {
const data = []; const listData = [];
// snapshot is a type of data by Firebase define // snapshot is a type of data by Firebase define
snapshot.forEach((childSnapshot) => { snapshot.forEach((childSnapshot) => {
@@ -111,11 +111,40 @@ class FirebaseService {
// data = dataTemp; // data = dataTemp;
// } // }
const id = childSnapshot.key; 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, onlyOnce: true,
@@ -14,4 +14,18 @@ export default class TransactionService extends CommonService {
async saveTransaction(transaction) { async saveTransaction(transaction) {
await this.save(transaction); await this.save(transaction);
} }
async getListTransactionByIdUser(idUser) {
const results = this.getListDataFromProp(
'idUser',
idUser,
this.defaultPath,
);
if (results) {
return results;
}
return null;
}
} }
+223 -45
View File
@@ -4,7 +4,13 @@ import * as MESSAGE from '../constants/message';
import CommonView from './commonView'; import CommonView from './commonView';
import Wallet from '../models/wallet'; import Wallet from '../models/wallet';
import Transaction from '../models/transaction'; 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'; import defaultCategoryIcon from '../../assets/images/question-icon.svg';
export default class HomeView extends CommonView { export default class HomeView extends CommonView {
@@ -30,21 +36,35 @@ export default class HomeView extends CommonView {
this.walletDialog = document.getElementById('walletDialog'); this.walletDialog = document.getElementById('walletDialog');
} }
async loadPage( initFunction(
getInfoUserLogin, getInfoUserLogin,
isValidWallet, isValidWallet,
getWalletByIdUser, getWalletByIdUser,
getAllCategory, 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(); this.toggleLoaderSpinner();
const user = await getInfoUserLogin(); const user = await this.getInfoUserLogin();
if (!user) { if (!user) {
window.location.replace('/login'); window.location.replace('/login');
} else { } else {
this.user = user; this.user = user;
const wallet = await isValidWallet(user.id); const wallet = await this.isValidWallet(user.id);
// Check user's wallet if have or not // Check user's wallet if have or not
if (!wallet) { if (!wallet) {
@@ -55,18 +75,22 @@ export default class HomeView extends CommonView {
this.loadEvent(); this.loadEvent();
// Init data // Init data
this.loadData(getAllCategory); this.loadData();
} }
} }
this.toggleLoaderSpinner(); this.toggleLoaderSpinner();
} }
async loadData(getAllCategory) { async loadData() {
await this.loadWalletUser(); await this.loadWalletUser();
await this.loadCategory(getAllCategory); await this.loadTransactionData();
await this.loadCategory(this.getAllCategory);
this.loadSummaryTab();
this.loadTransactionTab();
} }
// ---------------------LOAD DATA---------------------//
/** /**
* Load wallet user * Load wallet user
*/ */
@@ -86,16 +110,173 @@ export default class HomeView extends CommonView {
} }
async updateAmountWallet(amount, saveWallet) { 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); 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 = `
<div class="transaction__time">
<div class="transaction__details">
<p class="transaction__day">${transaction.day}</p>
<div class="transaction__time-details">
<p class="transaction__date-time">
${transaction.fullDateString}
</p>
<p class="transaction__note">${
transaction.note === '' ? 'None' : transaction.note
}</p>
</div>
</div>
<p class="transaction__${
transaction.amount >= 0 ? 'income' : 'outcome'
}">${transaction.amount >= 0 ? '+' : '-'}$ ${formatNumber(
Math.abs(transaction.amount),
)}</p>
</div>
`;
listMarkup.push(markup);
});
return listMarkup.join('\n');
};
return `
<div class="transaction__item">
<div class="transaction__category">
<div class="transaction__category-infor">
<div class="transaction__category-icon-container">
<img
class="icon-category"
src="${transactionDetail.url}"
alt="Transportation icon category"
/>
</div>
<div class="transaction__category-content">
<p class="transaction__category-name">
${transactionDetail.categoryName}
</p>
<p class="transaction__total">${
transactionDetail.totalTransaction
} Transactions</p>
</div>
</div>
<p class="transaction__category-total">${
transactionDetail.totalAmount >= 0 ? '+' : '-'
}$ ${formatNumber(Math.abs(transactionDetail.totalAmount))}</p>
</div>
<div class="transaction__line"></div>
<!-- Transaction time item -->
${transactionTimeMarkup()}
</div>
`;
}
// ---------------------END---------------------//
// ---------------------TRANSACTION DIALOG---------------------//
handlerEventTransactionDialog() {
this.transactionDialog.addEventListener('submit', (e) => { this.transactionDialog.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
this.submitTransactionDialog(saveTransaction); this.submitTransactionDialog();
}); });
this.transactionDialog.addEventListener('input', () => { this.transactionDialog.addEventListener('input', () => {
@@ -116,26 +297,30 @@ export default class HomeView extends CommonView {
}); });
} }
async submitTransactionDialog(saveTransaction) { async submitTransactionDialog() {
this.toggleLoaderSpinner(); this.toggleLoaderSpinner();
this.transactionDialog.close(); this.transactionDialog.close();
try { try {
const transactionForm = document.getElementById('formAddTransaction'); const transactionForm = document.getElementById('formAddTransaction');
const dateEl = transactionForm.querySelector("[name='selected_date']"); const date = transactionForm.querySelector("[name='selected_date']");
const categoryNameEl = transactionForm.querySelector( const categoryName = transactionForm.querySelector(
"[name='category_name']", "[name='category_name']",
); );
const amountEl = transactionForm.querySelector("[name='amount']"); const amount = transactionForm.querySelector("[name='amount']");
const noteEl = transactionForm.querySelector("[name='note']"); const note = transactionForm.querySelector("[name='note']");
const transaction = new Transaction({ const transaction = new Transaction({
categoryName: categoryNameEl.value, categoryName: categoryName.value,
date: dateEl.value, date: changeDateFormat(date.value),
amount: +amountEl.value, amount: -+amount.value,
note: noteEl.value, note: note.value,
idUser: this.wallet.idUser,
}); });
await saveTransaction(transaction); await this.saveTransaction(transaction);
// Update wallet info
this.updateAmountWallet(-+amount.value, this.saveWallet);
this.showSuccessToast( this.showSuccessToast(
MESSAGE.ADD_TRANSACTION_SUCCESS, MESSAGE.ADD_TRANSACTION_SUCCESS,
@@ -143,6 +328,9 @@ export default class HomeView extends CommonView {
); );
this.clearInputTransactionForm(transactionForm); this.clearInputTransactionForm(transactionForm);
// Reload data
this.loadData();
} catch (error) { } catch (error) {
this.showErrorToast(error); this.showErrorToast(error);
} }
@@ -164,18 +352,6 @@ export default class HomeView extends CommonView {
// ---------------------END DIALOG---------------------// // ---------------------END DIALOG---------------------//
// ---------------------SELECTED CATEGORY DIALOG---------------------// // ---------------------SELECTED CATEGORY DIALOG---------------------//
/**
* Load category data
* @param {function} getAllCategory Get all category function
*/
async loadCategory(getAllCategory) {
this.listCategory = await getAllCategory();
if (this.listCategory) {
this.renderCategoryItem();
}
}
handlerEventCategoryDialog() { handlerEventCategoryDialog() {
this.categoryDialog.addEventListener('input', () => { this.categoryDialog.addEventListener('input', () => {
setTimeout(() => { setTimeout(() => {
@@ -232,11 +408,11 @@ export default class HomeView extends CommonView {
// ---------------------END DIALOG---------------------// // ---------------------END DIALOG---------------------//
// ---------------------ADD BUDGET DIALOG---------------------// // ---------------------ADD BUDGET DIALOG---------------------//
addHandlerEventBudgetForm(saveTransaction, saveWallet) { addHandlerEventBudgetForm() {
this.budgetDialog.addEventListener('submit', (e) => { this.budgetDialog.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
this.submitBudgetForm(saveTransaction, saveWallet); this.submitBudgetForm();
}); });
this.budgetDialog.addEventListener('input', (e) => { this.budgetDialog.addEventListener('input', (e) => {
@@ -245,7 +421,7 @@ export default class HomeView extends CommonView {
}); });
} }
async submitBudgetForm(saveTransaction, saveWallet) { async submitBudgetForm() {
try { try {
this.toggleLoaderSpinner(); // Enable loader spinner this.toggleLoaderSpinner(); // Enable loader spinner
this.budgetDialog.close(); // Close dialog this.budgetDialog.close(); // Close dialog
@@ -261,13 +437,14 @@ export default class HomeView extends CommonView {
date: changeDateFormat(date), date: changeDateFormat(date),
amount: +amount, amount: +amount,
note, note,
idUser: this.wallet.idUser,
}); });
await saveTransaction(transaction); await this.saveTransaction(transaction);
await this.updateAmountWallet(+amount, saveWallet); // Update wallet await this.updateAmountWallet(+amount, this.saveWallet); // Update wallet
this.loadWalletUser(); this.loadData();
// Hide loader spinner // Hide loader spinner
this.toggleLoaderSpinner(); this.toggleLoaderSpinner();
@@ -298,11 +475,11 @@ export default class HomeView extends CommonView {
// ---------------------END DIALOG---------------------// // ---------------------END DIALOG---------------------//
// ---------------------WALLET DIALOG--------------------- // // ---------------------WALLET DIALOG--------------------- //
addHandlerEventWalletForm(saveWallet, getAllCategory) { addHandlerEventWalletForm() {
this.walletDialog.addEventListener('submit', (e) => { this.walletDialog.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
this.submitWalletForm(saveWallet, getAllCategory); this.submitWalletForm();
}); });
this.walletDialog.addEventListener('input', (e) => { this.walletDialog.addEventListener('input', (e) => {
@@ -311,7 +488,7 @@ export default class HomeView extends CommonView {
}); });
} }
async submitWalletForm(saveWallet, getAllCategory) { async submitWalletForm() {
this.walletDialog.close(); this.walletDialog.close();
this.toggleLoaderSpinner(); this.toggleLoaderSpinner();
@@ -325,9 +502,10 @@ export default class HomeView extends CommonView {
walletName, walletName,
amount: +amount, amount: +amount,
idUser: this.user.id, idUser: this.user.id,
inflow: +amount,
}); });
await saveWallet(wallet); await this.saveWallet(wallet);
this.showSuccessToast( this.showSuccessToast(
MESSAGE.ADD_WALLET_SUCCESS, MESSAGE.ADD_WALLET_SUCCESS,
@@ -335,7 +513,7 @@ export default class HomeView extends CommonView {
); );
this.loadEvent(); // Load event page this.loadEvent(); // Load event page
this.loadData(getAllCategory); // Load data from database into page this.loadData(); // Load data from database into page
} catch (error) { } catch (error) {
// Show toast error // Show toast error
this.showErrorToast(error); this.showErrorToast(error);
+4 -76
View File
@@ -251,93 +251,21 @@
<div class="summary"> <div class="summary">
<div class="inflow"> <div class="inflow">
<p class="inflow__text">Inflow</p> <p class="inflow__text">Inflow</p>
<p class="inflow__text--income">+$ 500,000,000.00</p> <p class="inflow__text--income">+$ 0.00</p>
</div> </div>
<div class="outflow"> <div class="outflow">
<p class="outflow__text">Outflow</p> <p class="outflow__text">Outflow</p>
<p class="outflow__text--outcome">-$ 100,000.00</p> <p class="outflow__text--outcome">$ 0.00</p>
</div> </div>
<div class="summary__line"></div> <div class="summary__line"></div>
<p class="summary__total">+$ 499,900,000.00</p> <p class="summary__total">+$ 0.00</p>
</div> </div>
<!-- End content summary --> <!-- End content summary -->
</div> </div>
<div class="app__content-item"> <div class="app__content-item">
<!-- Start content transaction --> <!-- Start content transaction -->
<div class="transaction"> <div class="transaction">
<div class="transaction__list"> <div class="transaction__list"></div>
<!-- Transaction item -->
<!-- Outcome transaction -->
<div class="transaction__item">
<div class="transaction__category">
<div class="transaction__category-infor">
<div class="transaction__category-icon-container">
<img
src="../assets/images/ic_category_transport.png.png"
alt="Transportation icon category"
/>
</div>
<div class="transaction__category-content">
<p class="transaction__category-name">
Transportation
</p>
<p class="transaction__total">1 Transactions</p>
</div>
</div>
<p class="transaction__category-total">-$ 100,000.00</p>
</div>
<div class="transaction__line"></div>
<!-- Transaction time item -->
<div class="transaction__time">
<div class="transaction__details">
<p class="transaction__day">18</p>
<div class="transaction__time-details">
<p class="transaction__date-time">
Friday, August 2023
</p>
<p class="transaction__note">Test Note</p>
</div>
</div>
<p class="transaction__outcome">-$ 100,000.00</p>
</div>
</div>
<!-- Income transaction -->
<div class="transaction__item">
<div class="transaction__category">
<div class="transaction__category-infor">
<div class="transaction__category-icon-container">
<img
src="../assets/images/ic_category_other_income.png.png"
alt="Transportation icon category"
/>
</div>
<div class="transaction__category-content">
<p class="transaction__category-name">
Transportation
</p>
<p class="transaction__total">1 Transactions</p>
</div>
</div>
<p class="transaction__category-total">
+$ 500,000,000.00
</p>
</div>
<div class="transaction__line"></div>
<!-- Transaction time item -->
<div class="transaction__time">
<div class="transaction__details">
<p class="transaction__day">18</p>
<div class="transaction__time-details">
<p class="transaction__date-time">
Friday, August 2023
</p>
<p class="transaction__note">Initial Balance</p>
</div>
</div>
<p class="transaction__income">+$ 500,000,000.00</p>
</div>
</div>
</div>
</div> </div>
<!-- End content transaction --> <!-- End content transaction -->
</div> </div>
@@ -2,6 +2,7 @@
.background { .background {
height: 100vh; height: 100vh;
background: $gray; background: $gray;
overflow: auto;
} }
.wrapper { .wrapper {