mirror of
https://github.com/Nezumi-2711/typescript-training.git
synced 2026-09-22 20:02:19 +00:00
Add transaction, budget, category view
This commit is contained in:
Vendored
+1
@@ -0,0 +1 @@
|
||||
declare module '*.svg';
|
||||
@@ -32,3 +32,34 @@ export const URL = {
|
||||
REGISTER: 'register',
|
||||
HOME: '',
|
||||
};
|
||||
|
||||
export const DEFAULT_CATEGORY = {
|
||||
INCOME: 'Income',
|
||||
};
|
||||
|
||||
export const REMOVE_CATEGORY = ['Income'];
|
||||
|
||||
export const DAY = [
|
||||
'Sunday',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday',
|
||||
];
|
||||
|
||||
export const MONTH = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
|
||||
@@ -4,6 +4,7 @@ import Service from 'services';
|
||||
import View from 'views';
|
||||
import Wallet from 'models/wallet';
|
||||
import Transaction from 'models/transaction';
|
||||
import Category from 'models/category';
|
||||
|
||||
export default class HomeController {
|
||||
public homeView: HomeView | null = null;
|
||||
@@ -20,25 +21,40 @@ export default class HomeController {
|
||||
return this.service.userService.getInfoUserLogin();
|
||||
}
|
||||
|
||||
handlerGetWalletByIdUser(idUser: number) {
|
||||
handlerGetWalletByIdUser(idUser: number): Promise<Wallet | null> {
|
||||
return this.service.walletService.getWalletByIdUser(idUser);
|
||||
}
|
||||
|
||||
handlerSaveWallet(wallet: Wallet) {
|
||||
handlerSaveWallet(wallet: Wallet): Promise<void> {
|
||||
return this.service.walletService.saveWallet(wallet);
|
||||
}
|
||||
|
||||
handlerSaveTransaction(transaction: Transaction) {
|
||||
handlerSaveTransaction(transaction: Transaction): Promise<void> {
|
||||
return this.service.transactionService.saveTransaction(transaction);
|
||||
}
|
||||
|
||||
handlerGetAllCategory(): Promise<Category[] | null> {
|
||||
return this.service.categoryService.getAllCategory();
|
||||
}
|
||||
|
||||
handlerGetAllTransactions(idUser: number): Promise<Transaction[] | null> {
|
||||
return this.service.transactionService.getListTransactionByIdUser(idUser);
|
||||
}
|
||||
|
||||
handlerDeleteTransaction(idTransaction: number): Promise<void> {
|
||||
return this.service.transactionService.deleteTransaction(idTransaction);
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.homeView) {
|
||||
this.homeView.initFunction(
|
||||
this.handlerGetInfoUserLogin.bind(this),
|
||||
this.handlerGetWalletByIdUser.bind(this),
|
||||
this.handlerGetAllCategory.bind(this),
|
||||
this.handlerGetAllTransactions.bind(this),
|
||||
this.handlerSaveWallet.bind(this),
|
||||
this.handlerSaveTransaction.bind(this),
|
||||
this.handlerDeleteTransaction.bind(this),
|
||||
new Transform(),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import Category from 'models/category';
|
||||
import Transaction from 'models/transaction';
|
||||
import User from 'models/user';
|
||||
import Wallet from 'models/wallet';
|
||||
|
||||
@@ -13,7 +15,7 @@ export class DataObject<T> {
|
||||
|
||||
constructor(dataObject: IDataObject<T>) {
|
||||
this.id = dataObject?.id ?? null;
|
||||
this.data = dataObject.data as T;
|
||||
this.data = <T>dataObject.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +41,16 @@ export type TSignal = {
|
||||
};
|
||||
|
||||
export interface Data {
|
||||
wallet: Wallet;
|
||||
user: User;
|
||||
wallet?: Wallet;
|
||||
listTransactions?: Transaction[];
|
||||
listCategories?: Category[];
|
||||
user?: User;
|
||||
}
|
||||
|
||||
export interface ItemTransaction {
|
||||
id: number;
|
||||
day: string;
|
||||
fullDateString: string;
|
||||
note: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import localStorageService from 'services/localStorageService';
|
||||
import { DataObject, IDataObject } from '../global/types';
|
||||
import { LOCAL_STORAGE } from 'constants/config';
|
||||
import { DataObject, IDataObject, ItemTransaction } from '../global/types';
|
||||
import { DAY, LOCAL_STORAGE, MONTH } from 'constants/config';
|
||||
import Transaction from 'models/transaction';
|
||||
import TransactionDetail from 'models/transactionDetail';
|
||||
import Category from 'models/category';
|
||||
|
||||
export const generateId = (): number => {
|
||||
return new Date().getTime();
|
||||
@@ -39,6 +42,84 @@ export const formatNumber = (number: number): string => {
|
||||
});
|
||||
};
|
||||
|
||||
export const changeDateFormat = (oldFormatDate: string) => {
|
||||
const tempDate = new Date(oldFormatDate);
|
||||
|
||||
const day = DAY[tempDate.getDay()];
|
||||
const date = tempDate.getDate();
|
||||
const month = MONTH[tempDate.getMonth()];
|
||||
const year = tempDate.getFullYear();
|
||||
|
||||
return `${day}, ${date}, ${month}, ${year}`;
|
||||
};
|
||||
|
||||
export const clearAccessToken = (): void => {
|
||||
localStorageService.remove(LOCAL_STORAGE.ACCESS_TOKEN);
|
||||
};
|
||||
|
||||
export const getAllCategoryNameInTransactions = (
|
||||
transactions: Transaction[],
|
||||
): string[] => {
|
||||
const categoryName = new Set();
|
||||
|
||||
transactions.forEach((transaction) =>
|
||||
categoryName.add(transaction.categoryName),
|
||||
);
|
||||
|
||||
return Array.from(categoryName) as string[];
|
||||
};
|
||||
|
||||
export const getAllTransactionByCategoryName = (
|
||||
categoryName: string,
|
||||
transactions: Transaction[],
|
||||
) => {
|
||||
const results = transactions.filter((transaction) => {
|
||||
return transaction.categoryName === categoryName;
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
export const createTransactionDetailObject = (
|
||||
category: Category,
|
||||
transactions: Transaction[],
|
||||
): TransactionDetail => {
|
||||
const totalTransaction = transactions.length;
|
||||
const totalAmount = () => {
|
||||
let amount = 0;
|
||||
|
||||
transactions.forEach((transaction) => {
|
||||
amount += transaction.amount;
|
||||
});
|
||||
|
||||
return amount;
|
||||
};
|
||||
const listTransaction = (): ItemTransaction[] => {
|
||||
const results: ItemTransaction[] = transactions.map((transaction) => {
|
||||
const dateParts = changeDateFormat(transaction.date).split(','); // ['Monday', '14', 'September', '2023']
|
||||
const day = dateParts[1];
|
||||
const fullDateString = `${dateParts[0]}, ${dateParts[2]} ${dateParts[3]}`;
|
||||
const tempData: ItemTransaction = {
|
||||
id: transaction.id,
|
||||
day,
|
||||
fullDateString,
|
||||
note: transaction.note,
|
||||
amount: transaction.amount,
|
||||
};
|
||||
|
||||
return tempData;
|
||||
});
|
||||
|
||||
results.sort((a, b) => b.id - a.id);
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
return new TransactionDetail(
|
||||
category.name,
|
||||
category.url,
|
||||
totalTransaction,
|
||||
totalAmount(),
|
||||
listTransaction(),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
export default class Category {
|
||||
private readonly _id: number;
|
||||
|
||||
private _url: string;
|
||||
|
||||
private _name: string;
|
||||
|
||||
constructor(id: number, url: string, name: string) {
|
||||
this._id = id;
|
||||
this._url = url;
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,19 @@
|
||||
import { generateId } from 'helpers/data';
|
||||
|
||||
export default class Transaction {
|
||||
private readonly _id: number;
|
||||
|
||||
private _categoryName: string;
|
||||
|
||||
private _date: string;
|
||||
|
||||
private _note: string;
|
||||
|
||||
private _amount: number;
|
||||
|
||||
private _idUser: number;
|
||||
|
||||
constructor(
|
||||
categoryName: string,
|
||||
date: string,
|
||||
note: string,
|
||||
amount: number,
|
||||
idUser: number,
|
||||
public id: number,
|
||||
public categoryName: string,
|
||||
public date: string,
|
||||
public note: string,
|
||||
public amount: number,
|
||||
public idUser: number,
|
||||
) {
|
||||
this._id = generateId();
|
||||
this._categoryName = categoryName;
|
||||
this._date = date;
|
||||
this._note = note;
|
||||
this._amount = amount;
|
||||
this._idUser = idUser;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get categoryName() {
|
||||
return this._categoryName;
|
||||
}
|
||||
|
||||
get date() {
|
||||
return this._date;
|
||||
}
|
||||
|
||||
get note() {
|
||||
return this._note;
|
||||
}
|
||||
|
||||
get amount() {
|
||||
return this._amount;
|
||||
}
|
||||
|
||||
get idUser() {
|
||||
return this._idUser;
|
||||
}
|
||||
|
||||
get toObject() {
|
||||
return {
|
||||
id: this._id,
|
||||
categoryName: this._categoryName,
|
||||
date: this._date,
|
||||
note: this._note,
|
||||
amount: this._amount,
|
||||
idUser: this._idUser,
|
||||
} as Transaction;
|
||||
this.id = id !== 0 ? id : generateId();
|
||||
this.categoryName = categoryName;
|
||||
this.date = date;
|
||||
this.note = note;
|
||||
this.amount = amount;
|
||||
this.idUser = idUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ItemTransaction } from 'global/types';
|
||||
|
||||
export default class TransactionDetail {
|
||||
constructor(
|
||||
public categoryName: string,
|
||||
public url: string,
|
||||
public totalTransaction: number,
|
||||
public totalAmount: number,
|
||||
public transactions: ItemTransaction[],
|
||||
) {
|
||||
this.categoryName = categoryName;
|
||||
this.url = url;
|
||||
this.totalTransaction = totalTransaction;
|
||||
this.totalAmount = totalAmount;
|
||||
this.transactions = transactions;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import { generateId } from '../helpers/data';
|
||||
|
||||
export default class Wallet {
|
||||
private readonly _id: number;
|
||||
id: number;
|
||||
|
||||
private _walletName: string;
|
||||
walletName: string;
|
||||
|
||||
private _inflow: number;
|
||||
inflow: number;
|
||||
|
||||
private _outflow: number;
|
||||
outflow: number;
|
||||
|
||||
private _idUser: number;
|
||||
idUser: number;
|
||||
|
||||
constructor(
|
||||
walletName: string,
|
||||
@@ -17,44 +17,10 @@ export default class Wallet {
|
||||
outflow: number,
|
||||
idUser: number,
|
||||
) {
|
||||
this._id = generateId();
|
||||
this._walletName = walletName;
|
||||
this._inflow = inflow;
|
||||
this._outflow = outflow;
|
||||
this._idUser = idUser;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get walletName() {
|
||||
return this._walletName;
|
||||
}
|
||||
|
||||
get inflow() {
|
||||
return this._inflow;
|
||||
}
|
||||
|
||||
get outflow() {
|
||||
return this._outflow;
|
||||
}
|
||||
|
||||
get idUser() {
|
||||
return this._idUser;
|
||||
}
|
||||
|
||||
get amountWallet() {
|
||||
return this._inflow + this._outflow;
|
||||
}
|
||||
|
||||
get toObject() {
|
||||
return {
|
||||
id: this._id,
|
||||
walletName: this._walletName,
|
||||
inflow: this._inflow,
|
||||
outflow: this._outflow,
|
||||
idUser: this._idUser,
|
||||
};
|
||||
this.id = generateId();
|
||||
this.walletName = walletName;
|
||||
this.inflow = inflow;
|
||||
this.outflow = outflow;
|
||||
this.idUser = idUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import CommonService from './commonService';
|
||||
import Category from '../models/category';
|
||||
|
||||
export default class CategoryService extends CommonService<Category> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.defaultPath = 'categories/';
|
||||
}
|
||||
|
||||
async getAllCategory() {
|
||||
const data = await this.getAllDataFromPath(this.defaultPath);
|
||||
|
||||
if (data) {
|
||||
return data.reverse().map((category): Category => category);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async getCategoryByName(nameCategory: string) {
|
||||
const data = await this.getDataFromProp(
|
||||
'name',
|
||||
nameCategory,
|
||||
this.defaultPath,
|
||||
);
|
||||
|
||||
if (data) return data;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export default class CommonService<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async getAllDataFromPath(path = this.defaultPath): Promise<T | null> {
|
||||
async getAllDataFromPath(path = this.defaultPath): Promise<T[] | null> {
|
||||
this.connectToDb();
|
||||
|
||||
const results = await timeOutConnect(
|
||||
@@ -90,7 +90,7 @@ export default class CommonService<T> {
|
||||
const tempData = data as DataObject<T>;
|
||||
|
||||
return convertDataObjectToModel(tempData);
|
||||
}) as T;
|
||||
}) as T[];
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -100,7 +100,7 @@ export default class CommonService<T> {
|
||||
property: string,
|
||||
value: string,
|
||||
path: string = this.defaultPath,
|
||||
): Promise<T | null> {
|
||||
): Promise<T[] | null> {
|
||||
this.connectToDb();
|
||||
|
||||
const results = await timeOutConnect(
|
||||
@@ -114,7 +114,7 @@ export default class CommonService<T> {
|
||||
const tempData = data as DataObject<T>;
|
||||
|
||||
return convertDataObjectToModel(tempData);
|
||||
}) as T;
|
||||
}) as T[];
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import CategoryService from './categoryService';
|
||||
import TransactionService from './transactionService';
|
||||
import UserService from './userService';
|
||||
import WalletService from './walletService';
|
||||
@@ -9,9 +10,12 @@ export default class Service {
|
||||
|
||||
public transactionService: TransactionService;
|
||||
|
||||
public categoryService: CategoryService;
|
||||
|
||||
constructor() {
|
||||
this.userService = new UserService();
|
||||
this.walletService = new WalletService();
|
||||
this.transactionService = new TransactionService();
|
||||
this.categoryService = new CategoryService();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class TransactionService extends CommonService<Transaction> {
|
||||
|
||||
async getListTransactionByIdUser(
|
||||
idUser: number,
|
||||
): Promise<Transaction | null> {
|
||||
): Promise<Transaction[] | null> {
|
||||
const results = this.getListDataFromProp(
|
||||
'idUser',
|
||||
idUser.toString(),
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import Transaction from '../../models/transaction';
|
||||
import { renderRequiredText } from '../../helpers/validatorForm';
|
||||
import { Data, TError } from 'global/types';
|
||||
import Transform from 'helpers/transform';
|
||||
import Wallet from 'models/wallet';
|
||||
import User from 'models/user';
|
||||
import { DEFAULT_CATEGORY } from 'constants/config';
|
||||
import {
|
||||
ADD_TRANSACTION_SUCCESS,
|
||||
DEFAULT_MESSAGE,
|
||||
} from 'constants/messages/dialog';
|
||||
|
||||
export default class BudgetView {
|
||||
budgetDialog: HTMLDialogElement | null = null;
|
||||
|
||||
addBudgetBtn: HTMLElement | null = null;
|
||||
|
||||
transform: Transform | null = null;
|
||||
|
||||
toggleLoaderSpinner: (() => void) | null = null;
|
||||
|
||||
saveTransaction: ((transaction: Transaction) => Promise<void>) | null = null;
|
||||
|
||||
loadTransactionData: (() => Promise<void>) | null = null;
|
||||
|
||||
updateAmountWallet: (() => Promise<void>) | null = null;
|
||||
|
||||
loadData: (() => Promise<void>) | null = null;
|
||||
|
||||
showSuccessToast: ((title: string, message: string) => void) | null = null;
|
||||
|
||||
showErrorToast: ((error: string | TError) => void) | null = null;
|
||||
|
||||
wallet: Wallet | null = null;
|
||||
|
||||
user: User | null = null;
|
||||
|
||||
constructor() {
|
||||
this.budgetDialog = document.getElementById(
|
||||
'budgetDialog',
|
||||
) as HTMLDialogElement;
|
||||
this.addBudgetBtn = document.getElementById('addBudget');
|
||||
|
||||
this.handlerEventBudgetView();
|
||||
}
|
||||
|
||||
initFunction(
|
||||
showErrorToast: (error: string | TError) => void,
|
||||
showSuccessToast: (title: string, message: string) => void,
|
||||
toggleLoaderSpinner: () => void,
|
||||
saveTransaction: ((transaction: Transaction) => Promise<void>) | null,
|
||||
loadTransactionData: () => Promise<void>,
|
||||
updateAmountWallet: () => Promise<void>,
|
||||
loadData: () => Promise<void>,
|
||||
transform: Transform | null,
|
||||
) {
|
||||
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! };
|
||||
|
||||
this.transform!.onSendSignal('budgetView', data);
|
||||
}
|
||||
|
||||
updateData(data: Data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.user) this.user = data.user;
|
||||
}
|
||||
|
||||
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 form = document.getElementById('formAddBudget') as HTMLFormElement;
|
||||
const formAddBudget = new FormData(form);
|
||||
const date = formAddBudget.get('selected_date') as string;
|
||||
const amount = formAddBudget.get('amount') as string;
|
||||
const note = formAddBudget.get('note') as string;
|
||||
|
||||
// Validate data user input
|
||||
|
||||
if (this.validateBudgetForm(date, +amount)) {
|
||||
this.toggleLoaderSpinner!(); // Enable loader spinner
|
||||
this.budgetDialog!.close(); // Close dialog
|
||||
|
||||
const transaction = new Transaction(
|
||||
0,
|
||||
DEFAULT_CATEGORY.INCOME,
|
||||
date,
|
||||
note,
|
||||
+amount,
|
||||
+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!(ADD_TRANSACTION_SUCCESS, DEFAULT_MESSAGE);
|
||||
|
||||
(<HTMLFormElement>document.getElementById('formAddBudget')!).reset();
|
||||
}
|
||||
} catch (error) {
|
||||
this.showErrorToast!(error as string | TError);
|
||||
}
|
||||
}
|
||||
|
||||
validateBudgetForm(date: string, amount: number) {
|
||||
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 = (<HTMLDataElement>(
|
||||
this.budgetDialog!.querySelector('.input-date')!
|
||||
)).value;
|
||||
const amount = +(<HTMLDataElement>(
|
||||
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
|
||||
(<HTMLInputElement>(
|
||||
this.budgetDialog!.querySelector("[name='selected_date']")
|
||||
))!.valueAsDate = new Date();
|
||||
|
||||
this.budgetDialog!.showModal();
|
||||
});
|
||||
|
||||
this.addHandlerEventBudgetForm();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import Transform from 'helpers/transform';
|
||||
import { REMOVE_CATEGORY } from '../../constants/config';
|
||||
import Category from 'models/category';
|
||||
import { Data } from 'global/types';
|
||||
|
||||
export default class CategoryView {
|
||||
categoryDialog: HTMLDialogElement | null = null;
|
||||
|
||||
categoryField: HTMLElement | null = null;
|
||||
|
||||
closeIcon: HTMLElement | null = null;
|
||||
|
||||
getAllCategory: (() => Promise<Category[] | null>) | null = null;
|
||||
|
||||
transform: Transform | null = null;
|
||||
|
||||
listCategory: Category[] | null = null;
|
||||
|
||||
categorySelected: string;
|
||||
|
||||
constructor() {
|
||||
this.categorySelected = '';
|
||||
|
||||
this.categoryDialog = document.getElementById(
|
||||
'categoryDialog',
|
||||
) as HTMLDialogElement;
|
||||
this.categoryField = document.getElementById('selectCategory');
|
||||
this.closeIcon = document.querySelector('.close-icon') as HTMLElement;
|
||||
|
||||
this.handlerEventCategoryDialog();
|
||||
this.addEventSelectCategoryDialog();
|
||||
}
|
||||
|
||||
initFunction(
|
||||
getAllCategory: () => Promise<Category[] | null>,
|
||||
transform: Transform,
|
||||
) {
|
||||
this.getAllCategory = getAllCategory;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
sendData() {
|
||||
const data: Data = { listCategories: 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: HTMLDataElement =
|
||||
this.categoryDialog!.querySelector("[name='category']")!;
|
||||
const searchValue = searchCategoryEl.value.trim().toLowerCase();
|
||||
let newListCategory: Category[] = [];
|
||||
|
||||
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.categorySelected, newListCategory);
|
||||
}
|
||||
|
||||
renderCategoryList(
|
||||
categorySelected?: string,
|
||||
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',
|
||||
) as HTMLImageElement;
|
||||
const categoryNameEl = this.categoryField!.querySelector(
|
||||
'.category-name',
|
||||
) as HTMLDataElement;
|
||||
|
||||
categoryListEl!.addEventListener('click', (e) => {
|
||||
const categoryItem = (<Element>e.target).closest(
|
||||
'.category-item',
|
||||
) as HTMLElement;
|
||||
|
||||
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.categorySelected = categoryNameEl.value;
|
||||
|
||||
this.renderCategoryList(this.categorySelected);
|
||||
}
|
||||
this.categoryDialog!.showModal();
|
||||
});
|
||||
|
||||
this.closeIcon!.addEventListener('click', () => {
|
||||
this.categoryDialog!.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,49 +10,79 @@ import User from 'models/user';
|
||||
import { Data, TError } from 'global/types';
|
||||
import { DEFAULT_TITLE_ERROR_TOAST } from 'constants/messages/dialog';
|
||||
import { redirectToLoginPage } from 'helpers/url';
|
||||
import Category from 'models/category';
|
||||
import CategoryView from './categoryView';
|
||||
import BudgetView from './budgetView';
|
||||
import TransactionView from './transactionView';
|
||||
import SummaryTabView from './summaryTabView';
|
||||
import TransactionDetail from 'models/transactionDetail';
|
||||
import TransactionTabView from './transactionTabView';
|
||||
|
||||
export default class HomeView extends CommonView {
|
||||
private _tabs: NodeListOf<Element>;
|
||||
walletView: WalletView;
|
||||
|
||||
private _allContent: NodeListOf<Element>;
|
||||
categoryView: CategoryView;
|
||||
|
||||
private _cancelBtns: NodeListOf<Element>;
|
||||
budgetView: BudgetView;
|
||||
|
||||
private _dialogs: NodeListOf<Element>;
|
||||
transactionView: TransactionView;
|
||||
|
||||
private _amountInputs: NodeListOf<Element>;
|
||||
summaryTabView: SummaryTabView;
|
||||
|
||||
private _walletView: WalletView;
|
||||
transactionTabView: TransactionTabView;
|
||||
|
||||
private _user: User | null = null;
|
||||
tabs: NodeListOf<Element>;
|
||||
|
||||
private _wallet: Wallet | null = null;
|
||||
allContent: NodeListOf<Element>;
|
||||
|
||||
private _getInfoUserLogin: (() => Promise<User | null>) | null = null;
|
||||
cancelBtns: NodeListOf<Element>;
|
||||
|
||||
private _getWalletByIdUser:
|
||||
| ((idUser: number) => Promise<Wallet | null>)
|
||||
dialogs: NodeListOf<Element>;
|
||||
|
||||
amountInputs: NodeListOf<Element>;
|
||||
|
||||
user: User | null = null;
|
||||
|
||||
wallet: Wallet | null = null;
|
||||
|
||||
listTransactions: Transaction[] | null = null;
|
||||
|
||||
transactionDetails: TransactionDetail[] = [];
|
||||
|
||||
getInfoUserLogin: (() => Promise<User | null>) | null = null;
|
||||
|
||||
getWalletByIdUser: ((idUser: number) => Promise<Wallet | null>) | null = null;
|
||||
|
||||
getAllCategory: (() => Promise<Category[] | null>) | null = null;
|
||||
|
||||
getAllTransactions:
|
||||
| ((idUser: number) => Promise<Transaction[] | null>)
|
||||
| null = null;
|
||||
|
||||
private _saveWallet: ((wallet: Wallet) => Promise<void>) | null = null;
|
||||
deleteTransaction: ((idTransaction: number) => Promise<void>) | null = null;
|
||||
|
||||
private _saveTransaction:
|
||||
| ((transaction: Transaction) => Promise<void>)
|
||||
| null = null;
|
||||
saveWallet: ((wallet: Wallet) => Promise<void>) | null = null;
|
||||
|
||||
private _transform: Transform | null = null;
|
||||
saveTransaction: ((transaction: Transaction) => Promise<void>) | null = null;
|
||||
|
||||
transform: Transform | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._tabs = document.querySelectorAll('.app__tab-item');
|
||||
this._allContent = document.querySelectorAll('.app__content-item');
|
||||
this._cancelBtns = document.querySelectorAll('.form__cancel-btn');
|
||||
this._dialogs = document.querySelectorAll('.dialog');
|
||||
this.tabs = document.querySelectorAll('.app__tab-item');
|
||||
this.allContent = document.querySelectorAll('.app__content-item');
|
||||
this.cancelBtns = document.querySelectorAll('.form__cancel-btn');
|
||||
this.dialogs = document.querySelectorAll('.dialog');
|
||||
|
||||
this._amountInputs = document.querySelectorAll('.form__input-balance');
|
||||
this.amountInputs = document.querySelectorAll('.form__input-balance');
|
||||
|
||||
this._walletView = new WalletView();
|
||||
this.walletView = new WalletView();
|
||||
this.categoryView = new CategoryView();
|
||||
this.budgetView = new BudgetView();
|
||||
this.transactionView = new TransactionView(this.categoryView);
|
||||
this.summaryTabView = new SummaryTabView();
|
||||
this.transactionTabView = new TransactionTabView(this.transactionView);
|
||||
|
||||
this.initToast();
|
||||
this.initLoader();
|
||||
@@ -62,27 +92,78 @@ export default class HomeView extends CommonView {
|
||||
initFunction(
|
||||
getInfoUserLogin: () => Promise<User | null>,
|
||||
getWalletByIdUser: (idUser: number) => Promise<Wallet | null>,
|
||||
getAllCategory: () => Promise<Category[] | null>,
|
||||
getAllTransactions: (idUser: number) => Promise<Transaction[] | null>,
|
||||
saveWallet: (wallet: Wallet) => Promise<void>,
|
||||
saveTransaction: (transaction: Transaction) => Promise<void>,
|
||||
deleteTransaction: (idTransaction: number) => Promise<void>,
|
||||
transform: Transform,
|
||||
) {
|
||||
this._getInfoUserLogin = getInfoUserLogin;
|
||||
this._getWalletByIdUser = getWalletByIdUser;
|
||||
this._saveWallet = saveWallet;
|
||||
this._saveTransaction = saveTransaction;
|
||||
this.getInfoUserLogin = getInfoUserLogin;
|
||||
this.getWalletByIdUser = getWalletByIdUser;
|
||||
this.getAllCategory = getAllCategory;
|
||||
this.getAllTransactions = getAllTransactions;
|
||||
this.saveWallet = saveWallet;
|
||||
this.saveTransaction = saveTransaction;
|
||||
this.deleteTransaction = deleteTransaction;
|
||||
|
||||
this._transform = transform;
|
||||
this.transform = transform;
|
||||
|
||||
// Init function for child view
|
||||
this.initWalletViewFunction();
|
||||
this.initBudgetViewFunction();
|
||||
this.initTransactionViewFunction();
|
||||
this.initCategoryViewFunction();
|
||||
this.initSummaryTabViewFunction();
|
||||
this.initTransactionTabViewFunction();
|
||||
}
|
||||
|
||||
initTransactionViewFunction() {
|
||||
this.transactionView.initFunction(
|
||||
this.toggleLoaderSpinner.bind(this),
|
||||
this.deleteTransaction!,
|
||||
this.loadTransactionData.bind(this),
|
||||
this.updateAmountWallet.bind(this),
|
||||
this.loadData.bind(this),
|
||||
this.showSuccessToast.bind(this),
|
||||
this.showErrorToast.bind(this),
|
||||
this.saveTransaction!,
|
||||
this.transform,
|
||||
);
|
||||
}
|
||||
|
||||
initBudgetViewFunction() {
|
||||
this.budgetView.initFunction(
|
||||
this.showErrorToast.bind(this),
|
||||
this.showSuccessToast.bind(this),
|
||||
this.toggleLoaderSpinner.bind(this),
|
||||
this.saveTransaction!.bind(this),
|
||||
this.loadTransactionData.bind(this),
|
||||
this.updateAmountWallet.bind(this),
|
||||
this.loadData.bind(this),
|
||||
this.transform,
|
||||
);
|
||||
}
|
||||
|
||||
initCategoryViewFunction() {
|
||||
this.categoryView.initFunction(this.getAllCategory!, this.transform!);
|
||||
}
|
||||
|
||||
initSummaryTabViewFunction() {
|
||||
this.summaryTabView.initFunction(this.transform!);
|
||||
}
|
||||
|
||||
initTransactionTabViewFunction() {
|
||||
this.transactionTabView.initFunction(this.transform!);
|
||||
}
|
||||
|
||||
initWalletViewFunction() {
|
||||
this._walletView.initFunction(
|
||||
this._transform,
|
||||
this.walletView.initFunction(
|
||||
this.transform,
|
||||
this.toggleLoaderSpinner.bind(this),
|
||||
this._saveWallet,
|
||||
this._saveTransaction,
|
||||
this.saveWallet,
|
||||
this.saveTransaction,
|
||||
this.loadTransactionData.bind(this),
|
||||
this.loadData.bind(this),
|
||||
this.loadEvent.bind(this),
|
||||
this.showSuccessToast.bind(this),
|
||||
@@ -92,19 +173,29 @@ export default class HomeView extends CommonView {
|
||||
|
||||
subscribeListenerData() {
|
||||
this.subscribe();
|
||||
this._walletView.subscribe();
|
||||
this.transactionView.subscribe();
|
||||
this.budgetView.subscribe();
|
||||
this.summaryTabView.subscribe();
|
||||
this.transactionTabView.subscribe();
|
||||
this.walletView.subscribe();
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
// Send data to other class
|
||||
this.sendData();
|
||||
|
||||
await this.categoryView.loadCategory();
|
||||
|
||||
await this.loadWalletUser();
|
||||
|
||||
this.summaryTabView.load();
|
||||
|
||||
this.transactionTabView.loadTransactionTab();
|
||||
}
|
||||
|
||||
async loadPage() {
|
||||
this.toggleLoaderSpinner();
|
||||
const user = await this._getInfoUserLogin!();
|
||||
const user = await this.getInfoUserLogin!();
|
||||
|
||||
if (!user) {
|
||||
// If user not login yet
|
||||
@@ -112,16 +203,17 @@ export default class HomeView extends CommonView {
|
||||
window.location.replace('/login');
|
||||
} else {
|
||||
// If user already login
|
||||
this._user = user;
|
||||
this._wallet = await this._getWalletByIdUser!(user.id);
|
||||
this.user = user;
|
||||
this.wallet = await this.getWalletByIdUser!(user.id);
|
||||
|
||||
this.sendData();
|
||||
// Check user's wallet if have or not
|
||||
if (!this._wallet) {
|
||||
if (!this.wallet) {
|
||||
// Show add wallet dialog
|
||||
this._walletView.showDialog();
|
||||
this.walletView.showDialog();
|
||||
} else {
|
||||
// Init data
|
||||
await this.loadTransactionData();
|
||||
await this.loadData();
|
||||
|
||||
// Load event page
|
||||
@@ -135,31 +227,32 @@ export default class HomeView extends CommonView {
|
||||
// ---------------------LOAD DATA---------------------//
|
||||
|
||||
subscribe() {
|
||||
this._transform!.create('homeView', this.updateData.bind(this));
|
||||
this.transform!.create('homeView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
sendData() {
|
||||
const data: Data = {
|
||||
wallet: this._wallet!,
|
||||
user: this._user!,
|
||||
wallet: this.wallet!,
|
||||
listTransactions: this.listTransactions!,
|
||||
user: this.user!,
|
||||
};
|
||||
|
||||
this._transform!.onSendSignal('homeView', data);
|
||||
this.transform!.onSendSignal('homeView', data);
|
||||
}
|
||||
|
||||
updateData(data: Data) {
|
||||
if (data.wallet) this._wallet = data.wallet;
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.user) this._user = data.user;
|
||||
if (data.user) this.user = data.user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load wallet user
|
||||
*/
|
||||
async loadWalletUser() {
|
||||
const wallet = this._wallet
|
||||
? this._wallet
|
||||
: await this._getWalletByIdUser!(this._user!.id);
|
||||
const wallet = this.wallet
|
||||
? this.wallet
|
||||
: await this.getWalletByIdUser!(this.user!.id);
|
||||
const walletName = document.querySelector('.wallet__name');
|
||||
const walletPrice = document.querySelector('.wallet__price');
|
||||
const walletNameValue = wallet!.walletName;
|
||||
@@ -167,7 +260,7 @@ export default class HomeView extends CommonView {
|
||||
|
||||
const sign = walletAmountValue >= 0 ? '+' : '-';
|
||||
|
||||
this._wallet = wallet; // Make wallet into global variable
|
||||
this.wallet = wallet; // Make wallet into global variable
|
||||
|
||||
walletName!.textContent = walletNameValue;
|
||||
walletPrice!.textContent = `${sign}$ ${formatNumber(
|
||||
@@ -175,11 +268,38 @@ export default class HomeView extends CommonView {
|
||||
)}`; // Math.abs(walletAmountValue) to keep the value always > 0
|
||||
|
||||
// Update amount wallet into database
|
||||
this._saveWallet!(this._wallet!);
|
||||
this.saveWallet!(this.wallet!);
|
||||
}
|
||||
|
||||
// ---------------------END---------------------//
|
||||
async loadTransactionData() {
|
||||
this.listTransactions = await this.getAllTransactions!(this.wallet!.idUser);
|
||||
|
||||
this.sendData();
|
||||
}
|
||||
|
||||
async updateAmountWallet() {
|
||||
let inflow = 0;
|
||||
let outflow = 0;
|
||||
// Init data first
|
||||
this.transactionDetails =
|
||||
this.transactionTabView!.loadTransactionDetailsData();
|
||||
|
||||
this.transactionDetails.forEach((transaction) => {
|
||||
if (transaction.totalAmount >= 0) {
|
||||
inflow += transaction.totalAmount;
|
||||
} else {
|
||||
outflow -= transaction.totalAmount;
|
||||
}
|
||||
});
|
||||
// Reassign value for wallet user;
|
||||
this.wallet!.inflow = inflow;
|
||||
this.wallet!.outflow = -outflow;
|
||||
await this.saveWallet!(this.wallet!);
|
||||
}
|
||||
|
||||
// ---------------------END--------------------- //
|
||||
|
||||
// ---------------------TOAST--------------------- //
|
||||
showSuccessToast(title: string, message: string) {
|
||||
const typeToast = TypeToast.success;
|
||||
const btnContent = BTN_CONTENT.OK;
|
||||
@@ -214,8 +334,9 @@ export default class HomeView extends CommonView {
|
||||
this.toastBtn.removeEventListener('click', redirectToLoginPage);
|
||||
}
|
||||
}
|
||||
// ---------------------END--------------------- //
|
||||
|
||||
// ------------------------------- HANDLER EVENT ------------------------------- //
|
||||
// --------------------- HANDLER EVENT --------------------- //
|
||||
|
||||
loadEvent() {
|
||||
this.addCommonEventPage();
|
||||
@@ -226,7 +347,7 @@ export default class HomeView extends CommonView {
|
||||
* Handle event when click on tabs
|
||||
*/
|
||||
handlerTabsTransfer() {
|
||||
this._tabs.forEach((tab, index) => {
|
||||
this.tabs.forEach((tab, index) => {
|
||||
tab.addEventListener('click', (e: Event) => {
|
||||
this.removeActiveTab();
|
||||
tab.classList.add('active');
|
||||
@@ -240,17 +361,17 @@ export default class HomeView extends CommonView {
|
||||
line.style.width = `${eventTarget.offsetWidth}px`;
|
||||
line.style.left = `${eventTarget.offsetLeft}px`;
|
||||
|
||||
this._allContent.forEach((content) => {
|
||||
this.allContent.forEach((content) => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
this._allContent[index].classList.add('active');
|
||||
this.allContent[index].classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addCommonEventPage() {
|
||||
// Add event close dialog when click outside
|
||||
this._dialogs.forEach((dialog) => {
|
||||
this.dialogs.forEach((dialog) => {
|
||||
dialog.addEventListener('click', (e) => {
|
||||
const dialogDimensions = dialog.getBoundingClientRect();
|
||||
const mouseEvent = e as MouseEvent;
|
||||
@@ -265,14 +386,14 @@ export default class HomeView extends CommonView {
|
||||
});
|
||||
});
|
||||
|
||||
this._cancelBtns.forEach((btn) => {
|
||||
this.cancelBtns.forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
this.closeAllDialog();
|
||||
});
|
||||
});
|
||||
|
||||
// Prevent input =,-,e into amount input
|
||||
this._amountInputs.forEach((item) => {
|
||||
this.amountInputs.forEach((item) => {
|
||||
item.addEventListener(
|
||||
'keypress',
|
||||
(e) =>
|
||||
@@ -298,13 +419,13 @@ export default class HomeView extends CommonView {
|
||||
}
|
||||
|
||||
closeAllDialog() {
|
||||
this._dialogs.forEach((dialog) => {
|
||||
this.dialogs.forEach((dialog) => {
|
||||
(<HTMLDialogElement>dialog).close();
|
||||
});
|
||||
}
|
||||
|
||||
removeActiveTab() {
|
||||
this._tabs.forEach((tab) => {
|
||||
this.tabs.forEach((tab) => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import Transform from 'helpers/transform';
|
||||
import { formatNumber } from '../../helpers/data';
|
||||
import { Data } from 'global/types';
|
||||
import Wallet from 'models/wallet';
|
||||
import Transaction from 'models/transaction';
|
||||
import Category from 'models/category';
|
||||
|
||||
export default class SummaryTabView {
|
||||
transform: Transform | null = null;
|
||||
|
||||
wallet: Wallet | null = null;
|
||||
|
||||
listTransactions: Transaction[] = [];
|
||||
|
||||
listCategories: Category[] = [];
|
||||
|
||||
initFunction(transform: Transform) {
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.transform!.create('summaryTabView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
updateData(data: Data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.listTransactions) this.listTransactions = data.listTransactions;
|
||||
|
||||
if (data.listCategories) this.listCategories = data.listCategories;
|
||||
}
|
||||
|
||||
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),
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import Transform from 'helpers/transform';
|
||||
import {
|
||||
createTransactionDetailObject,
|
||||
getAllCategoryNameInTransactions,
|
||||
getAllTransactionByCategoryName,
|
||||
} from '../../helpers/data';
|
||||
import { formatNumber } from '../../helpers/data';
|
||||
import TransactionView from './transactionView';
|
||||
import { Data } from 'global/types';
|
||||
import Wallet from 'models/wallet';
|
||||
import Transaction from 'models/transaction';
|
||||
import Category from 'models/category';
|
||||
import TransactionDetail from 'models/transactionDetail';
|
||||
|
||||
export default class TransactionTabView {
|
||||
transactionView: TransactionView;
|
||||
|
||||
transform: Transform | null = null;
|
||||
|
||||
wallet: Wallet | null = null;
|
||||
|
||||
listTransactions: Transaction[] = [];
|
||||
|
||||
listCategories: Category[] = [];
|
||||
|
||||
transactionDetails: TransactionDetail[] = [];
|
||||
|
||||
constructor(transactionView: TransactionView) {
|
||||
this.transactionView = transactionView;
|
||||
this.addEventTransactionItem();
|
||||
}
|
||||
|
||||
initFunction(transform: Transform) {
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.transform!.create('transactionTabView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
updateData(data: Data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.listTransactions) this.listTransactions = data.listTransactions;
|
||||
|
||||
if (data.listCategories) this.listCategories = data.listCategories;
|
||||
}
|
||||
|
||||
async loadTransactionTab() {
|
||||
// Load category
|
||||
// Init data first
|
||||
this.transactionDetails = this.loadTransactionDetailsData();
|
||||
const transactionEl = document.querySelector('.transaction')!;
|
||||
const listTransactionDetailEl =
|
||||
transactionEl.querySelector('.transaction__list')!;
|
||||
const markup: string[] = [];
|
||||
|
||||
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(): TransactionDetail[] {
|
||||
// Get all category user have in transactions
|
||||
const listCategoryInTransaction = getAllCategoryNameInTransactions(
|
||||
this.listTransactions,
|
||||
);
|
||||
|
||||
// Create transactions details object
|
||||
const tempList: TransactionDetail[] = listCategoryInTransaction.map(
|
||||
(categoryName) => {
|
||||
// Get category object from list category has been loaded.
|
||||
const category = this.listCategories.filter(
|
||||
(item) => item.name === categoryName,
|
||||
);
|
||||
|
||||
const transactions = getAllTransactionByCategoryName(
|
||||
<string>categoryName,
|
||||
this.listTransactions,
|
||||
);
|
||||
|
||||
return createTransactionDetailObject(
|
||||
Object.assign({}, ...category),
|
||||
transactions,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
tempList.sort((a, b) => b.transactions[0].id - a.transactions[0].id);
|
||||
|
||||
return tempList;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
transactionDetailMarkup(transactionDetail: TransactionDetail) {
|
||||
const itemTransaction = () => {
|
||||
const listMarkup: string[] = [];
|
||||
|
||||
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 -->
|
||||
${itemTransaction()}
|
||||
</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 = (<Element>e.target).closest(
|
||||
'.transaction__time',
|
||||
);
|
||||
const categoryNameEl: HTMLInputElement = item.querySelector(
|
||||
'.transaction__category-name',
|
||||
)!;
|
||||
|
||||
if (transactionTime) {
|
||||
const idTransaction = +(<HTMLElement>transactionTime).dataset.id!;
|
||||
|
||||
// If it is income transaction, don't show dialog
|
||||
if (
|
||||
categoryNameEl.textContent &&
|
||||
categoryNameEl.textContent.trim() !== 'Income'
|
||||
)
|
||||
this.transactionView.showTransactionDialog(idTransaction);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
import Transaction from 'models/transaction';
|
||||
import defaultCategoryIcon from '../../../assets/images/question-icon.svg';
|
||||
import CategoryView from './categoryView';
|
||||
import Transform from 'helpers/transform';
|
||||
import { Data, TError } from 'global/types';
|
||||
import Wallet from 'models/wallet';
|
||||
import Category from 'models/category';
|
||||
import {
|
||||
ADD_TRANSACTION_SUCCESS,
|
||||
DEFAULT_MESSAGE,
|
||||
UPDATE_TRANSACTION_SUCCESS,
|
||||
} from 'constants/messages/dialog';
|
||||
import { renderRequiredText } from 'helpers/validatorForm';
|
||||
|
||||
export default class TransactionView {
|
||||
wallet: Wallet | null = null;
|
||||
|
||||
listTransactions: Transaction[] = [];
|
||||
|
||||
listCategories: Category[] = [];
|
||||
|
||||
categoryView: CategoryView;
|
||||
|
||||
addTransactionBtn: HTMLElement | null = null;
|
||||
|
||||
transactionDialog: HTMLDialogElement | null = null;
|
||||
|
||||
transactionForm: HTMLFormElement | null = null;
|
||||
|
||||
toggleLoaderSpinner: (() => void) | null = null;
|
||||
|
||||
deleteTransaction: ((idTransaction: number) => Promise<void>) | null = null;
|
||||
|
||||
loadTransactionData: (() => Promise<void>) | null = null;
|
||||
|
||||
updateAmountWallet: (() => Promise<void>) | null = null;
|
||||
|
||||
loadData: (() => Promise<void>) | null = null;
|
||||
|
||||
showSuccessToast: ((title: string, message: string) => void) | null = null;
|
||||
|
||||
showErrorToast: ((error: string | TError) => void) | null = null;
|
||||
|
||||
saveTransaction: ((transaction: Transaction) => Promise<void>) | null = null;
|
||||
|
||||
transform: Transform | null = null;
|
||||
|
||||
constructor(categoryView: CategoryView) {
|
||||
this.addTransactionBtn = document.getElementById('addTransaction');
|
||||
this.transactionDialog = document.getElementById(
|
||||
'transactionDialog',
|
||||
) as HTMLDialogElement;
|
||||
this.transactionForm = <HTMLFormElement>(
|
||||
document.getElementById('formAddTransaction')
|
||||
);
|
||||
|
||||
this.handlerEventTransactionDialog();
|
||||
|
||||
this.categoryView = categoryView;
|
||||
}
|
||||
|
||||
initFunction(
|
||||
toggleLoaderSpinner: () => void,
|
||||
deleteTransaction: (idTransaction: number) => Promise<void>,
|
||||
loadTransactionData: () => Promise<void>,
|
||||
updateAmountWallet: () => Promise<void>,
|
||||
loadData: () => Promise<void>,
|
||||
showSuccessToast: (title: string, message: string) => void,
|
||||
showErrorToast: (error: string | TError) => void,
|
||||
saveTransaction: (transaction: Transaction) => Promise<void>,
|
||||
transform: Transform | null,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.transform!.create('transactionView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
sendData() {
|
||||
const data: Data = {
|
||||
wallet: this.wallet!,
|
||||
listTransactions: this.listTransactions!,
|
||||
};
|
||||
|
||||
this.transform!.onSendSignal('transactionView', data);
|
||||
}
|
||||
|
||||
updateData(data: Data) {
|
||||
if (data.wallet) this.wallet = data.wallet;
|
||||
|
||||
if (data.listTransactions) this.listTransactions = data.listTransactions;
|
||||
|
||||
if (data.listCategories) this.listCategories = data.listCategories;
|
||||
}
|
||||
|
||||
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',
|
||||
) as Element;
|
||||
|
||||
deleteTransactionBtn.addEventListener('click', async () => {
|
||||
const idEl = this.transactionDialog!.querySelector(
|
||||
"[name='id_transaction']",
|
||||
) as HTMLDataElement;
|
||||
|
||||
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!', DEFAULT_MESSAGE);
|
||||
} catch (error) {
|
||||
this.showErrorToast!(error as string | TError);
|
||||
}
|
||||
this.toggleLoaderSpinner!();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changeBtnStyleTransactionDialog() {
|
||||
const dateInput = (<HTMLDataElement>(
|
||||
this.transactionDialog!.querySelector("[name='selected_date']")
|
||||
)).value;
|
||||
const categoryName = (<HTMLDataElement>(
|
||||
this.transactionDialog!.querySelector("[name='category_name']")
|
||||
)).value;
|
||||
const amountInput = +(<HTMLDataElement>(
|
||||
this.transactionDialog!.querySelector("[name='amount']")
|
||||
)).value;
|
||||
const saveBtn = this.transactionDialog!.querySelector('.form__save-btn')!;
|
||||
|
||||
saveBtn.classList.toggle(
|
||||
'active',
|
||||
<boolean>(dateInput && categoryName && amountInput >= 1),
|
||||
);
|
||||
}
|
||||
|
||||
initValueTransactionDialog(idTransaction: number | null) {
|
||||
let categoryName: string;
|
||||
|
||||
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.listCategories.filter(
|
||||
(obj) => obj.name === transaction.categoryName,
|
||||
);
|
||||
const category = Object.assign({}, ...categoryArr);
|
||||
|
||||
const idEl = <HTMLDataElement>(
|
||||
this.transactionDialog!.querySelector("[name='id_transaction']")
|
||||
);
|
||||
const dateEl = <HTMLDataElement>(
|
||||
this.transactionDialog!.querySelector("[name='selected_date']"!)
|
||||
);
|
||||
const categoryEl = <HTMLDataElement>(
|
||||
this.transactionDialog!.querySelector("[name='category_name']")
|
||||
);
|
||||
const amountEl = <HTMLDataElement>(
|
||||
this.transactionDialog!.querySelector("[name='amount']")
|
||||
);
|
||||
const noteEl = <HTMLDataElement>(
|
||||
this.transactionDialog!.querySelector("[name='note']")
|
||||
);
|
||||
const iconEl = <HTMLImageElement>(
|
||||
this.transactionDialog!.querySelector('.category-icon')
|
||||
);
|
||||
|
||||
idEl.value = transaction.id;
|
||||
dateEl.value = transaction.date;
|
||||
categoryEl.value = transaction.categoryName;
|
||||
amountEl.value = Math.abs(transaction.amount).toString();
|
||||
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 = <HTMLDataElement>(
|
||||
this.transactionForm!.querySelector("[name='selected_date']")
|
||||
);
|
||||
const categoryNameEl = <HTMLDataElement>(
|
||||
this.transactionForm!.querySelector("[name='category_name']")
|
||||
);
|
||||
const amountEl = <HTMLDataElement>(
|
||||
this.transactionForm!.querySelector("[name='amount']")
|
||||
);
|
||||
const noteEl = <HTMLDataElement>(
|
||||
this.transactionForm!.querySelector("[name='note']")
|
||||
);
|
||||
const idEl = <HTMLDataElement>(
|
||||
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(
|
||||
+idEl.value,
|
||||
categoryNameEl.value,
|
||||
dateEl.value,
|
||||
noteEl.value,
|
||||
+amount,
|
||||
+this.wallet!.idUser,
|
||||
);
|
||||
|
||||
await this.saveTransaction!(transaction);
|
||||
|
||||
// Reload data
|
||||
await this.loadTransactionData!();
|
||||
await this.updateAmountWallet!();
|
||||
await this.loadData!();
|
||||
|
||||
if (!idEl.value) {
|
||||
// Add success
|
||||
this.showSuccessToast!(ADD_TRANSACTION_SUCCESS, DEFAULT_MESSAGE);
|
||||
} else {
|
||||
// Update success
|
||||
this.showSuccessToast!(UPDATE_TRANSACTION_SUCCESS, DEFAULT_MESSAGE);
|
||||
}
|
||||
|
||||
this.toggleLoaderSpinner!();
|
||||
this.clearInputTransactionForm();
|
||||
}
|
||||
} catch (error) {
|
||||
this.showErrorToast!(error as string | TError);
|
||||
this.toggleLoaderSpinner!();
|
||||
}
|
||||
}
|
||||
|
||||
validateTransactionForm(date: string, categoryName: string, amount: number) {
|
||||
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 = <HTMLImageElement>(
|
||||
this.transactionForm!.querySelector('.category-icon')
|
||||
);
|
||||
|
||||
categoryIcon.src = defaultCategoryIcon;
|
||||
const keySearchCategory: string = ''; // Delete keyword search
|
||||
this.categoryView.renderCategoryList(
|
||||
keySearchCategory,
|
||||
this.listCategories,
|
||||
);
|
||||
this.transactionForm!.reset();
|
||||
}
|
||||
|
||||
showTransactionDialog(idTransaction: number | null = null) {
|
||||
this.clearInputTransactionForm();
|
||||
|
||||
// Init data transaction to dialog
|
||||
this.initValueTransactionDialog(idTransaction);
|
||||
|
||||
if (!idTransaction)
|
||||
(<HTMLInputElement>(
|
||||
this.transactionDialog!.querySelector("[name='selected_date']")
|
||||
)).valueAsDate = new Date(); // Set default value for date input
|
||||
|
||||
// Change style submit btn
|
||||
this.changeBtnStyleTransactionDialog();
|
||||
this.transactionDialog!.showModal();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ export default class WalletView {
|
||||
| ((transaction: Transaction) => Promise<void>)
|
||||
| null = null;
|
||||
|
||||
private _loadTransactionData: (() => Promise<void>) | null = null;
|
||||
|
||||
private _loadData: (() => Promise<void>) | null = null;
|
||||
|
||||
private _loadEvent: (() => void) | null = null;
|
||||
@@ -46,6 +48,7 @@ export default class WalletView {
|
||||
toggleLoaderSpinner: () => void,
|
||||
saveWallet: ((wallet: Wallet) => Promise<void>) | null,
|
||||
saveTransaction: ((transaction: Transaction) => Promise<void>) | null,
|
||||
loadTransactionData: () => Promise<void>,
|
||||
loadData: () => Promise<void>,
|
||||
loadEvent: () => void,
|
||||
showSuccessToast: (title: string, message: string) => void,
|
||||
@@ -55,6 +58,7 @@ export default class WalletView {
|
||||
this._toggleLoaderSpinner = toggleLoaderSpinner;
|
||||
this._saveWallet = saveWallet;
|
||||
this._saveTransaction = saveTransaction;
|
||||
this._loadTransactionData = loadTransactionData;
|
||||
this._loadData = loadData;
|
||||
this._loadEvent = loadEvent;
|
||||
this._showSuccessToast = showSuccessToast;
|
||||
@@ -127,8 +131,7 @@ export default class WalletView {
|
||||
this._walletDialog!.close();
|
||||
this._toggleLoaderSpinner!();
|
||||
|
||||
const wallet = new Wallet(walletName, +amount, 0, this._user!.id)
|
||||
.toObject as Wallet;
|
||||
const wallet = new Wallet(walletName, +amount, 0, this._user!.id);
|
||||
this._wallet = wallet;
|
||||
|
||||
this.sendData();
|
||||
@@ -136,17 +139,20 @@ export default class WalletView {
|
||||
await this._saveWallet!(wallet);
|
||||
// Transaction info
|
||||
const transaction = new Transaction(
|
||||
0,
|
||||
'Income',
|
||||
new Date().toISOString().slice(0, 10),
|
||||
FIRST_ADD_WALLET_NOTE,
|
||||
+amount,
|
||||
this._user!.id,
|
||||
).toObject;
|
||||
);
|
||||
|
||||
await this._saveTransaction!(transaction);
|
||||
// Load data and event
|
||||
await this._loadTransactionData!();
|
||||
await this._loadData!();
|
||||
this._loadEvent!();
|
||||
|
||||
this._showSuccessToast!(ADD_WALLET_SUCCESS, DEFAULT_MESSAGE);
|
||||
|
||||
this._toggleLoaderSpinner!();
|
||||
|
||||
Reference in New Issue
Block a user