mirror of
https://github.com/Nezumi-2711/javascript-training.git
synced 2026-09-22 20:01:31 +00:00
Refactor code
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import Transform from '../helpers/transform';
|
||||
import UserService from '../services/userService';
|
||||
|
||||
export default class HomeController {
|
||||
@@ -49,16 +50,13 @@ export default class HomeController {
|
||||
this.handlerSaveTransaction.bind(this),
|
||||
HomeController.handlerClearAccessToken.bind(this),
|
||||
this.handlerDeleteTransaction.bind(this),
|
||||
new Transform(),
|
||||
);
|
||||
this.homeView.addHandlerEventWalletForm();
|
||||
|
||||
this.homeView.loadPage();
|
||||
|
||||
this.homeView.addHandlerEventBudgetForm();
|
||||
|
||||
this.homeView.handlerEventCategoryDialog();
|
||||
|
||||
this.homeView.handlerEventTransactionDialog();
|
||||
// Subscribe listener data update
|
||||
this.homeView.subscribeListenerData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
export default class Transform {
|
||||
constructor() {
|
||||
this.signal = {};
|
||||
}
|
||||
|
||||
onSendSignal(fromClass, value) {
|
||||
Object.keys(this.signal).forEach((key) => {
|
||||
const item = this.signal[key];
|
||||
|
||||
// Same receiver
|
||||
if (item.name === fromClass) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send signal
|
||||
const { handler } = this.signal[key];
|
||||
if (handler) {
|
||||
handler(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
create(className, handler = null) {
|
||||
this.signal[className] = { name: className, handler };
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,10 @@ export default class WalletService extends CommonService {
|
||||
async getWalletByIdUser(idUser) {
|
||||
const result = await this.getDataFromProp('idUser', idUser);
|
||||
|
||||
return new Wallet(result) || null;
|
||||
if (result) {
|
||||
return new Wallet(result);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import { DEFAULT_CATEGORY } from '../constants/config';
|
||||
import Transaction from '../models/transaction';
|
||||
import * as MESSAGE from '../constants/message';
|
||||
import { renderRequiredText } from '../helpers/validateForm';
|
||||
|
||||
class BudgetView {
|
||||
constructor() {
|
||||
this.budgetDialog = document.getElementById('budgetDialog');
|
||||
this.addBudgetBtn = document.getElementById('addBudget');
|
||||
|
||||
this.handlerEventBudgetView();
|
||||
}
|
||||
|
||||
initFunction(
|
||||
showErrorToast,
|
||||
showSuccessToast,
|
||||
toggleLoaderSpinner,
|
||||
saveTransaction,
|
||||
loadTransactionData,
|
||||
updateAmountWallet,
|
||||
loadData,
|
||||
transform,
|
||||
) {
|
||||
this.showErrorToast = showErrorToast;
|
||||
this.showSuccessToast = showSuccessToast;
|
||||
this.toggleLoaderSpinner = toggleLoaderSpinner;
|
||||
this.saveTransaction = saveTransaction;
|
||||
this.loadTransactionData = loadTransactionData;
|
||||
this.updateAmountWallet = updateAmountWallet;
|
||||
this.loadData = loadData;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.transform.create('budgetView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
sendData() {
|
||||
const data = { wallet: this.wallet, listTransaction: this.listTransaction };
|
||||
|
||||
this.transform.onSendSignal('budgetView', data);
|
||||
}
|
||||
|
||||
updateData(data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.listTransactions) this.listTransaction = data.listTransactions;
|
||||
|
||||
if (data.listCategory) this.listCategory = data.listCategory;
|
||||
}
|
||||
|
||||
addHandlerEventBudgetForm() {
|
||||
this.budgetDialog.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.clearErrorStyleBudgetForm();
|
||||
this.submitBudgetForm();
|
||||
});
|
||||
|
||||
this.budgetDialog.addEventListener('input', () => {
|
||||
this.changeBtnStyle();
|
||||
this.clearErrorStyleBudgetForm();
|
||||
});
|
||||
}
|
||||
|
||||
clearErrorStyleBudgetForm() {
|
||||
// Clear error style
|
||||
const inputFieldEls =
|
||||
this.budgetDialog.querySelectorAll('.form__input-field');
|
||||
const errorTexts = this.budgetDialog.querySelectorAll('.error-text');
|
||||
|
||||
inputFieldEls.forEach((item) => {
|
||||
if (item.classList.contains('error-input'))
|
||||
item.classList.remove('error-input');
|
||||
});
|
||||
|
||||
errorTexts.forEach((item) => {
|
||||
if (item) item.remove();
|
||||
});
|
||||
}
|
||||
|
||||
async submitBudgetForm() {
|
||||
try {
|
||||
const { formAddBudget } = document.forms;
|
||||
const form = new FormData(formAddBudget);
|
||||
const date = form.get('selected_date');
|
||||
const amount = form.get('amount');
|
||||
const note = form.get('note');
|
||||
|
||||
// Validate data user input
|
||||
|
||||
if (this.validateBudgetForm(date, amount)) {
|
||||
this.toggleLoaderSpinner(); // Enable loader spinner
|
||||
this.budgetDialog.close(); // Close dialog
|
||||
|
||||
const transaction = new Transaction({
|
||||
categoryName: DEFAULT_CATEGORY.INCOME,
|
||||
date,
|
||||
amount: +amount,
|
||||
note,
|
||||
idUser: this.wallet.idUser,
|
||||
});
|
||||
|
||||
await this.saveTransaction(transaction);
|
||||
|
||||
// Reload data
|
||||
await this.loadTransactionData();
|
||||
await this.updateAmountWallet();
|
||||
await this.loadData();
|
||||
|
||||
// Hide loader spinner
|
||||
this.toggleLoaderSpinner();
|
||||
|
||||
// Show success message
|
||||
this.showSuccessToast(
|
||||
MESSAGE.ADD_TRANSACTION_SUCCESS,
|
||||
MESSAGE.DEFAULT_MESSAGE,
|
||||
);
|
||||
|
||||
document.getElementById('formAddBudget').reset();
|
||||
}
|
||||
} catch (error) {
|
||||
this.showErrorToast(error);
|
||||
}
|
||||
}
|
||||
|
||||
validateBudgetForm(date, amount) {
|
||||
const inputFieldEl =
|
||||
this.budgetDialog.querySelectorAll('.form__input-field');
|
||||
|
||||
if (!date || !amount) {
|
||||
if (!date) {
|
||||
renderRequiredText('date', inputFieldEl[0]);
|
||||
inputFieldEl[0].classList.add('error-input');
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
renderRequiredText('amount', inputFieldEl[1]);
|
||||
inputFieldEl[1].classList.add('error-input');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
changeBtnStyle() {
|
||||
const date = this.budgetDialog.querySelector('.input-date').value;
|
||||
const amount = +this.budgetDialog.querySelector('.form__input-balance')
|
||||
.value;
|
||||
const saveBtn = this.budgetDialog.querySelector('.form__save-btn');
|
||||
|
||||
if (date.trim() && amount >= 1) {
|
||||
saveBtn.classList.add('active');
|
||||
} else {
|
||||
saveBtn.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
handlerEventBudgetView() {
|
||||
this.addBudgetBtn.addEventListener('click', () => {
|
||||
// Set default value for date input
|
||||
this.budgetDialog.querySelector("[name='selected_date']").valueAsDate =
|
||||
new Date();
|
||||
|
||||
this.budgetDialog.showModal();
|
||||
});
|
||||
|
||||
this.addHandlerEventBudgetForm();
|
||||
}
|
||||
}
|
||||
|
||||
export default new BudgetView();
|
||||
@@ -0,0 +1,136 @@
|
||||
import { REMOVE_CATEGORY } from '../constants/config';
|
||||
|
||||
class CategoryView {
|
||||
constructor() {
|
||||
this.categoryDialog = document.getElementById('categoryDialog');
|
||||
this.categoryField = document.getElementById('selectCategory');
|
||||
this.closeIcon = document.querySelector('.close-icon');
|
||||
|
||||
this.handlerEventCategoryDialog();
|
||||
this.addEventSelectCategoryDialog();
|
||||
}
|
||||
|
||||
initFunction(getAllCategory, transform) {
|
||||
this.getAllCategory = getAllCategory;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
sendData() {
|
||||
const data = { listCategory: this.listCategory };
|
||||
|
||||
this.transform.onSendSignal('categoryView', data);
|
||||
}
|
||||
|
||||
handlerEventCategoryDialog() {
|
||||
this.categoryDialog.addEventListener('input', () => {
|
||||
setTimeout(() => {
|
||||
this.searchCategory();
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load category data
|
||||
* @param {function} getAllCategory Get all category function
|
||||
*/
|
||||
async loadCategory() {
|
||||
if (!this.listCategory) {
|
||||
this.listCategory = await this.getAllCategory();
|
||||
|
||||
this.sendData();
|
||||
}
|
||||
|
||||
if (this.listCategory) {
|
||||
this.renderCategoryList();
|
||||
}
|
||||
}
|
||||
|
||||
searchCategory() {
|
||||
const searchCategoryEl =
|
||||
this.categoryDialog.querySelector("[name='category']");
|
||||
const searchValue = searchCategoryEl.value.trim().toLowerCase();
|
||||
let newListCategory = [];
|
||||
|
||||
if (searchValue) {
|
||||
this.listCategory.forEach((category) => {
|
||||
const categoryName = category.name.trim().toLowerCase();
|
||||
|
||||
if (categoryName.includes(searchValue)) {
|
||||
newListCategory.unshift(category);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newListCategory = this.listCategory;
|
||||
}
|
||||
|
||||
// Render category item
|
||||
this.renderCategoryList(this.keySearchCategory, newListCategory);
|
||||
}
|
||||
|
||||
renderCategoryList(categorySelected, listCategory = this.listCategory) {
|
||||
// Remove category name unnecessary
|
||||
const newListCategory = listCategory.filter(
|
||||
(item) => !REMOVE_CATEGORY.includes(item.name),
|
||||
);
|
||||
|
||||
const listCategoryEl = document.querySelector('.list-category');
|
||||
|
||||
listCategoryEl.innerHTML = ''; // Remove old category item
|
||||
|
||||
newListCategory.forEach((category) => {
|
||||
const markup = `
|
||||
<div class="category-item ${
|
||||
category.name === categorySelected ? 'selected' : ''
|
||||
}" data-value='${category.name}' data-url='${category.url}'>
|
||||
<img
|
||||
class="icon-category"
|
||||
src="${category.url}"
|
||||
alt="${category.name} Icon"
|
||||
/>
|
||||
<p class="name-category">${category.name}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
listCategoryEl.insertAdjacentHTML('afterbegin', markup);
|
||||
});
|
||||
}
|
||||
|
||||
addEventSelectCategoryDialog() {
|
||||
const categoryListEl = document.querySelector('.list-category');
|
||||
const categoryIconEl = this.categoryField.querySelector('.category-icon');
|
||||
const categoryNameEl = this.categoryField.querySelector('.category-name');
|
||||
|
||||
categoryListEl.addEventListener('click', (e) => {
|
||||
const categoryItem = e.target.closest('.category-item');
|
||||
|
||||
if (categoryItem) {
|
||||
const { url } = categoryItem.dataset;
|
||||
const { value } = categoryItem.dataset;
|
||||
|
||||
// Set url and value into category field in transaction dialog
|
||||
categoryIconEl.src = url;
|
||||
categoryNameEl.value = value;
|
||||
|
||||
// Close select category dialog
|
||||
this.categoryDialog.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Pass value selected to category dialog
|
||||
categoryNameEl.addEventListener('click', () => {
|
||||
if (categoryNameEl.value) {
|
||||
// Make the keyword search category name into global
|
||||
this.keySearchCategory = categoryNameEl.value;
|
||||
|
||||
this.renderCategoryList(this.keySearchCategory);
|
||||
}
|
||||
this.categoryDialog.showModal();
|
||||
});
|
||||
|
||||
this.closeIcon.addEventListener('click', () => {
|
||||
this.categoryDialog.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new CategoryView();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
import { formatNumber } from '../helpers/helpers';
|
||||
|
||||
class SummaryTabView {
|
||||
initFunction(transform) {
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.transform.create('summaryTabView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
updateData(data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.listTransactions) this.listTransaction = data.listTransactions;
|
||||
|
||||
if (data.listCategory) this.listCategory = data.listCategory;
|
||||
}
|
||||
|
||||
load() {
|
||||
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(Math.abs(outflow))}`;
|
||||
totalValue.textContent = `${total >= 0 ? '+' : '-'}$ ${formatNumber(
|
||||
Math.abs(total),
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default new SummaryTabView();
|
||||
@@ -0,0 +1,169 @@
|
||||
import {
|
||||
createTransactionDetailObject,
|
||||
getAllCategoryNameInTransactions,
|
||||
getAllTransactionByCategoryName,
|
||||
} from '../helpers/dataProcess';
|
||||
import { formatNumber } from '../helpers/helpers';
|
||||
import TransactionView from './transactionView';
|
||||
|
||||
class TransactionTabView {
|
||||
constructor() {
|
||||
this.addEventTransactionItem();
|
||||
}
|
||||
|
||||
initFunction(transform) {
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.transform.create('transactionTabView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
updateData(data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.listTransactions) this.listTransactions = data.listTransactions;
|
||||
|
||||
if (data.listCategory) this.listCategory = data.listCategory;
|
||||
}
|
||||
|
||||
async loadTransactionTab() {
|
||||
// Load category
|
||||
// Init data first
|
||||
this.transactionDetails = this.loadTransactionDetailsData();
|
||||
const transactionEl = document.querySelector('.transaction');
|
||||
const listTransactionDetailEl =
|
||||
transactionEl.querySelector('.transaction__list');
|
||||
const markup = [];
|
||||
if (this.transactionDetails) {
|
||||
this.transactionDetails.forEach((transactionDetail) => {
|
||||
markup.push(this.transactionDetailMarkup(transactionDetail));
|
||||
});
|
||||
}
|
||||
listTransactionDetailEl.innerHTML = '';
|
||||
listTransactionDetailEl.insertAdjacentHTML('afterbegin', markup.join('\n'));
|
||||
// Load event
|
||||
this.addEventTransactionItem();
|
||||
}
|
||||
|
||||
loadTransactionDetailsData() {
|
||||
// Get all category user have in transactions
|
||||
const listCategoryInTransaction = getAllCategoryNameInTransactions(
|
||||
this.listTransactions,
|
||||
);
|
||||
|
||||
// Create transactions details object
|
||||
const tempList = listCategoryInTransaction.map((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,
|
||||
);
|
||||
|
||||
return createTransactionDetailObject(
|
||||
Object.assign({}, ...category),
|
||||
transactions,
|
||||
);
|
||||
});
|
||||
|
||||
tempList.sort(
|
||||
(a, b) =>
|
||||
parseInt(b.transactions[0].id, 10) - parseInt(a.transactions[0].id, 10),
|
||||
);
|
||||
|
||||
return tempList;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
transactionDetailMarkup(transactionDetail) {
|
||||
const transactionTimeMarkup = () => {
|
||||
const listMarkup = [];
|
||||
transactionDetail.transactions.forEach((transaction) => {
|
||||
const markup = `
|
||||
<div class="transaction__time" data-id=${transaction.id}>
|
||||
<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>
|
||||
`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
addEventTransactionItem() {
|
||||
const transactionItemEl = document.querySelectorAll('.transaction__item');
|
||||
|
||||
if (transactionItemEl) {
|
||||
transactionItemEl.forEach((item) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const transactionTime = e.target.closest('.transaction__time');
|
||||
const categoryNameEl = item.querySelector(
|
||||
'.transaction__category-name',
|
||||
);
|
||||
|
||||
if (transactionTime) {
|
||||
const idTransaction = transactionTime.dataset.id;
|
||||
|
||||
// If it is income transaction, don't show dialog
|
||||
if (categoryNameEl.textContent.trim() !== 'Income')
|
||||
TransactionView.showTransactionDialog(idTransaction);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new TransactionTabView();
|
||||
@@ -1,19 +1,312 @@
|
||||
export default class TransactionView {
|
||||
constructor() {}
|
||||
import * as MESSAGE from '../constants/message';
|
||||
import { renderRequiredText } from '../helpers/validateForm';
|
||||
import Transaction from '../models/transaction';
|
||||
import CategoryView from './categoryView';
|
||||
|
||||
onTransitionChange() {
|
||||
this.onTransitionChangeHandler();
|
||||
import defaultCategoryIcon from '../../assets/images/question-icon.svg';
|
||||
|
||||
class TransactionView {
|
||||
constructor() {
|
||||
this.addTransactionBtn = document.getElementById('addTransaction');
|
||||
this.transactionDialog = document.getElementById('transactionDialog');
|
||||
this.transactionForm = document.getElementById('formAddTransaction');
|
||||
|
||||
this.handlerEventTransactionDialog();
|
||||
}
|
||||
|
||||
initTransitionChange(handler) {
|
||||
this.onTransitionChangeHandler = handler;
|
||||
initFunction(
|
||||
toggleLoaderSpinner,
|
||||
deleteTransaction,
|
||||
loadTransactionData,
|
||||
updateAmountWallet,
|
||||
loadData,
|
||||
showSuccessToast,
|
||||
showErrorToast,
|
||||
saveTransaction,
|
||||
transform,
|
||||
) {
|
||||
this.toggleLoaderSpinner = toggleLoaderSpinner;
|
||||
this.deleteTransaction = deleteTransaction;
|
||||
this.loadTransactionData = loadTransactionData;
|
||||
this.updateAmountWallet = updateAmountWallet;
|
||||
this.loadData = loadData;
|
||||
this.showSuccessToast = showSuccessToast;
|
||||
this.showErrorToast = showErrorToast;
|
||||
this.saveTransaction = saveTransaction;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
submitTransaction() {
|
||||
const transactionDialog = document.getElementById('transactionDialog');
|
||||
subscribe() {
|
||||
this.transform.create('transactionView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
const saveBtn = transactionDialog.querySelector('.form__save-btn');
|
||||
sendData() {
|
||||
const data = { wallet: this.wallet, listTransaction: this.listTransaction };
|
||||
|
||||
saveBtn.addEventListener('click', this.onTransitionChange);
|
||||
this.transform.onSendSignal('transactionView', data);
|
||||
}
|
||||
|
||||
updateData(data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.listTransactions) this.listTransactions = data.listTransactions;
|
||||
|
||||
if (data.listCategory) this.listCategory = data.listCategory;
|
||||
}
|
||||
|
||||
handlerEventTransactionDialog() {
|
||||
this.transactionDialog.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.clearErrorTransactionDialog();
|
||||
this.submitTransactionDialog();
|
||||
});
|
||||
|
||||
this.transactionDialog.addEventListener('input', () => {
|
||||
this.changeBtnStyleTransactionDialog();
|
||||
this.clearErrorTransactionDialog();
|
||||
});
|
||||
|
||||
// Add delete transaction event
|
||||
this.deleteTransactionEvent();
|
||||
|
||||
this.addTransactionBtn.addEventListener('click', () => {
|
||||
this.showTransactionDialog();
|
||||
});
|
||||
|
||||
this.handlerCategoryFieldEvent();
|
||||
}
|
||||
|
||||
handlerCategoryFieldEvent() {
|
||||
const categoryField = this.transactionForm.querySelector(
|
||||
"[name='category_name']",
|
||||
);
|
||||
|
||||
categoryField.addEventListener('click', () => {
|
||||
this.clearErrorTransactionDialog();
|
||||
});
|
||||
}
|
||||
|
||||
clearErrorTransactionDialog() {
|
||||
// Clear error style
|
||||
const inputFieldEls =
|
||||
this.transactionDialog.querySelectorAll('.form__input-field');
|
||||
const errorTextEls = this.transactionDialog.querySelectorAll('.error-text');
|
||||
|
||||
inputFieldEls.forEach((item) => {
|
||||
if (item.classList.contains('error-input'))
|
||||
item.classList.remove('error-input');
|
||||
});
|
||||
errorTextEls.forEach((item) => {
|
||||
if (item) item.remove();
|
||||
});
|
||||
}
|
||||
|
||||
deleteTransactionEvent() {
|
||||
const deleteTransactionBtn =
|
||||
this.transactionDialog.querySelector('.form__delete-btn');
|
||||
|
||||
deleteTransactionBtn.addEventListener('click', async () => {
|
||||
const idEl = this.transactionDialog.querySelector(
|
||||
"[name='id_transaction']",
|
||||
);
|
||||
|
||||
if (idEl.value) {
|
||||
try {
|
||||
this.transactionDialog.close();
|
||||
this.toggleLoaderSpinner();
|
||||
|
||||
await this.deleteTransaction(idEl.value);
|
||||
|
||||
// Reload data
|
||||
await this.loadTransactionData();
|
||||
await this.updateAmountWallet();
|
||||
await this.loadData();
|
||||
|
||||
this.showSuccessToast('Delete success!', MESSAGE.DEFAULT_MESSAGE);
|
||||
} catch (error) {
|
||||
this.showErrorToast(error);
|
||||
}
|
||||
this.toggleLoaderSpinner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changeBtnStyleTransactionDialog() {
|
||||
const dateInput = this.transactionDialog.querySelector(
|
||||
"[name='selected_date']",
|
||||
).value;
|
||||
const categoryName = this.transactionDialog.querySelector(
|
||||
"[name='category_name']",
|
||||
).value;
|
||||
const amountInput =
|
||||
this.transactionDialog.querySelector("[name='amount']").value;
|
||||
const saveBtn = this.transactionDialog.querySelector('.form__save-btn');
|
||||
|
||||
saveBtn.classList.toggle(
|
||||
'active',
|
||||
dateInput && categoryName && amountInput >= 1,
|
||||
);
|
||||
}
|
||||
|
||||
initValueTransactionDialog(idTransaction) {
|
||||
let categoryName;
|
||||
|
||||
if (idTransaction) {
|
||||
// Get transaction object
|
||||
const transactionArr = this.listTransactions.filter(
|
||||
(obj) => obj.id === idTransaction,
|
||||
);
|
||||
const transaction = Object.assign({}, ...transactionArr);
|
||||
// Get category object
|
||||
const categoryArr = this.listCategory.filter(
|
||||
(obj) => obj.name === transaction.categoryName,
|
||||
);
|
||||
const category = Object.assign({}, ...categoryArr);
|
||||
|
||||
const idEl = this.transactionDialog.querySelector(
|
||||
"[name='id_transaction']",
|
||||
);
|
||||
const dateEl = this.transactionDialog.querySelector(
|
||||
"[name='selected_date']",
|
||||
);
|
||||
const categoryEl = this.transactionDialog.querySelector(
|
||||
"[name='category_name']",
|
||||
);
|
||||
const amountEl = this.transactionDialog.querySelector("[name='amount']");
|
||||
const noteEl = this.transactionDialog.querySelector("[name='note']");
|
||||
const iconEl = this.transactionDialog.querySelector('.category-icon');
|
||||
|
||||
idEl.value = transaction.id;
|
||||
dateEl.value = transaction.date;
|
||||
categoryEl.value = transaction.categoryName;
|
||||
amountEl.value = Math.abs(transaction.amount);
|
||||
noteEl.value = transaction.note;
|
||||
iconEl.src = category.url;
|
||||
|
||||
categoryName = categoryEl.value;
|
||||
}
|
||||
|
||||
// Show delete button only if it is a edit form and not a income transaction
|
||||
const deleteBtn = this.transactionDialog.querySelector('.form__delete-btn');
|
||||
const showDeleteBtn = () => {
|
||||
return idTransaction && categoryName !== 'Income';
|
||||
};
|
||||
|
||||
deleteBtn.classList.toggle('hide', !showDeleteBtn());
|
||||
}
|
||||
|
||||
async submitTransactionDialog() {
|
||||
try {
|
||||
const dateEl = this.transactionForm.querySelector(
|
||||
"[name='selected_date']",
|
||||
);
|
||||
const categoryNameEl = this.transactionForm.querySelector(
|
||||
"[name='category_name']",
|
||||
);
|
||||
const amountEl = this.transactionForm.querySelector("[name='amount']");
|
||||
const noteEl = this.transactionForm.querySelector("[name='note']");
|
||||
const idEl = this.transactionForm.querySelector(
|
||||
"[name='id_transaction']",
|
||||
);
|
||||
const amount =
|
||||
categoryNameEl.value === 'Income' ? +amountEl.value : -+amountEl.value; // Check if this is a income transaction, amount must plus
|
||||
|
||||
// Validate data user input
|
||||
if (
|
||||
this.validateTransactionForm(dateEl.value, categoryNameEl.value, amount)
|
||||
) {
|
||||
this.toggleLoaderSpinner();
|
||||
this.transactionDialog.close();
|
||||
|
||||
const transaction = new Transaction({
|
||||
id: idEl.value,
|
||||
categoryName: categoryNameEl.value,
|
||||
date: dateEl.value,
|
||||
amount,
|
||||
note: noteEl.value,
|
||||
idUser: this.wallet.idUser,
|
||||
});
|
||||
|
||||
await this.saveTransaction(transaction);
|
||||
|
||||
// Reload data
|
||||
await this.loadTransactionData();
|
||||
this.updateAmountWallet();
|
||||
await this.loadData();
|
||||
|
||||
if (!idEl.value) {
|
||||
// Add success
|
||||
this.showSuccessToast(
|
||||
MESSAGE.ADD_TRANSACTION_SUCCESS,
|
||||
MESSAGE.DEFAULT_MESSAGE,
|
||||
);
|
||||
} else {
|
||||
// Update success
|
||||
this.showSuccessToast(
|
||||
MESSAGE.UPDATE_TRANSACTION_SUCCESS,
|
||||
MESSAGE.DEFAULT_MESSAGE,
|
||||
);
|
||||
}
|
||||
|
||||
this.toggleLoaderSpinner();
|
||||
this.clearInputTransactionForm(this.transactionForm);
|
||||
}
|
||||
} catch (error) {
|
||||
this.showErrorToast(error);
|
||||
this.toggleLoaderSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
validateTransactionForm(date, categoryName, amount) {
|
||||
const inputFieldEls =
|
||||
this.transactionDialog.querySelectorAll('.form__input-field');
|
||||
|
||||
if (!date || !categoryName || !amount) {
|
||||
if (!date) {
|
||||
renderRequiredText('date', inputFieldEls[0]);
|
||||
inputFieldEls[0].classList.add('error-input');
|
||||
}
|
||||
|
||||
if (!categoryName) {
|
||||
renderRequiredText('category', inputFieldEls[1]);
|
||||
inputFieldEls[1].classList.add('error-input');
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
renderRequiredText('amount', inputFieldEls[2]);
|
||||
inputFieldEls[2].classList.add('error-input');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
clearInputTransactionForm() {
|
||||
const categoryIcon = this.transactionForm.querySelector('.category-icon');
|
||||
|
||||
categoryIcon.src = defaultCategoryIcon;
|
||||
this.keySearchCategory = null; // Delete keyword search
|
||||
CategoryView.renderCategoryList(this.keySearchCategory);
|
||||
this.transactionForm.reset();
|
||||
}
|
||||
|
||||
showTransactionDialog(idTransaction = null) {
|
||||
this.clearInputTransactionForm();
|
||||
|
||||
// Init data transaction to dialog
|
||||
this.initValueTransactionDialog(idTransaction);
|
||||
|
||||
if (!idTransaction)
|
||||
this.transactionDialog.querySelector(
|
||||
"[name='selected_date']",
|
||||
).valueAsDate = new Date(); // Set default value for date input
|
||||
|
||||
// Change style submit btn
|
||||
this.changeBtnStyleTransactionDialog();
|
||||
this.transactionDialog.showModal();
|
||||
}
|
||||
}
|
||||
|
||||
export default new TransactionView();
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import { FIRST_ADD_WALLET_NOTE } from '../constants/config';
|
||||
import Transaction from '../models/transaction';
|
||||
import Wallet from '../models/wallet';
|
||||
import * as MESSAGE from '../constants/message';
|
||||
import { renderRequiredText } from '../helpers/validateForm';
|
||||
|
||||
class WalletView {
|
||||
constructor() {
|
||||
this.walletDialog = document.getElementById('walletDialog');
|
||||
|
||||
this.addHandlerEventWalletForm();
|
||||
}
|
||||
|
||||
initFunction(
|
||||
transform,
|
||||
toggleLoaderSpinner,
|
||||
saveWallet,
|
||||
saveTransaction,
|
||||
loadTransactionData,
|
||||
loadData,
|
||||
loadEvent,
|
||||
showSuccessToast,
|
||||
showErrorToast,
|
||||
) {
|
||||
this.transform = transform;
|
||||
this.toggleLoaderSpinner = toggleLoaderSpinner;
|
||||
this.saveWallet = saveWallet;
|
||||
this.saveTransaction = saveTransaction;
|
||||
this.loadTransactionData = loadTransactionData;
|
||||
this.loadData = loadData;
|
||||
this.loadEvent = loadEvent;
|
||||
this.showSuccessToast = showSuccessToast;
|
||||
this.showErrorToast = showErrorToast;
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.transform.create('walletView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
sendData() {
|
||||
const data = {
|
||||
wallet: this.wallet,
|
||||
listTransactions: this.listTransactions,
|
||||
user: this.user,
|
||||
};
|
||||
|
||||
this.transform.onSendSignal('walletView', data);
|
||||
}
|
||||
|
||||
updateData(data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.listTransactions) this.listTransaction = data.listTransactions;
|
||||
|
||||
if (data.listCategory) this.listCategory = data.listCategory;
|
||||
|
||||
if (data.user) this.user = data.user;
|
||||
}
|
||||
|
||||
showDialog() {
|
||||
this.walletDialog.showModal();
|
||||
}
|
||||
|
||||
addHandlerEventWalletForm() {
|
||||
this.walletDialog.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.clearErrorStyleWalletDialog();
|
||||
this.submitWalletForm();
|
||||
});
|
||||
|
||||
this.walletDialog.addEventListener('input', (e) => {
|
||||
const bodyDialog = e.target.closest('.dialog__body');
|
||||
|
||||
this.changeBtnStyleWalletDialog(bodyDialog);
|
||||
this.clearErrorStyleWalletDialog();
|
||||
});
|
||||
}
|
||||
|
||||
clearErrorStyleWalletDialog() {
|
||||
const inputFieldEls =
|
||||
this.walletDialog.querySelectorAll('.form__input-field');
|
||||
const errorTextEls = this.walletDialog.querySelectorAll('.error-text');
|
||||
|
||||
inputFieldEls.forEach((item) => {
|
||||
if (item.classList.contains('error-input'))
|
||||
item.classList.remove('error-input');
|
||||
});
|
||||
|
||||
errorTextEls.forEach((item) => {
|
||||
if (item) item.remove();
|
||||
});
|
||||
}
|
||||
|
||||
async submitWalletForm() {
|
||||
try {
|
||||
// Wallet info
|
||||
const { walletForm } = document.forms;
|
||||
const form = new FormData(walletForm);
|
||||
const walletName = form.get('walletName');
|
||||
const amount = form.get('amount');
|
||||
|
||||
if (this.validateWalletDialog(walletName, amount)) {
|
||||
this.walletDialog.close();
|
||||
this.toggleLoaderSpinner();
|
||||
|
||||
this.wallet = new Wallet({
|
||||
walletName,
|
||||
amount: +amount,
|
||||
idUser: this.user.id,
|
||||
inflow: +amount,
|
||||
});
|
||||
|
||||
this.sendData();
|
||||
|
||||
await this.saveWallet(this.wallet);
|
||||
// Transaction info
|
||||
const transaction = new Transaction({
|
||||
categoryName: 'Income',
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
note: FIRST_ADD_WALLET_NOTE,
|
||||
amount: +amount,
|
||||
idUser: this.user.id,
|
||||
});
|
||||
await this.saveTransaction(transaction);
|
||||
// Load data and event
|
||||
await this.loadTransactionData();
|
||||
await this.loadData();
|
||||
this.loadEvent();
|
||||
this.showSuccessToast(
|
||||
MESSAGE.ADD_WALLET_SUCCESS,
|
||||
MESSAGE.DEFAULT_MESSAGE,
|
||||
);
|
||||
await this.loadData(); // Load data from database into page
|
||||
|
||||
this.toggleLoaderSpinner();
|
||||
}
|
||||
} catch (error) {
|
||||
// Show toast error
|
||||
this.showErrorToast(error);
|
||||
this.toggleLoaderSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
validateWalletDialog(walletName, amount) {
|
||||
const inputFieldEls =
|
||||
this.walletDialog.querySelectorAll('.form__input-field');
|
||||
|
||||
if (!walletName || !amount) {
|
||||
if (!walletName) {
|
||||
renderRequiredText('wallet name', inputFieldEls[0]);
|
||||
inputFieldEls[0].classList.add('error-input');
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
renderRequiredText('amount', inputFieldEls[2]);
|
||||
inputFieldEls[2].classList.add('error-input');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
changeBtnStyleWalletDialog(bodyDialog) {
|
||||
const walletName = bodyDialog.querySelector('.form__input-text').value;
|
||||
const amount = bodyDialog.querySelector('.form__input-balance').value;
|
||||
const saveBtn = bodyDialog.querySelector('.form__save-btn');
|
||||
|
||||
if (walletName.length >= 3 && amount >= 1) {
|
||||
saveBtn.classList.add('active');
|
||||
} else {
|
||||
saveBtn.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new WalletView();
|
||||
Reference in New Issue
Block a user