mirror of
https://github.com/Nezumi-2711/typescript-training.git
synced 2026-09-22 13:48:29 +00:00
Add base model, service and view
- Add wallet, transaction model - Add wallet, transaction service - Add home view, controller - Additional some type and interface
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import HomeView from 'views/home/homeView';
|
||||
import Transform from '../helpers/transform';
|
||||
import Service from 'services';
|
||||
import View from 'views';
|
||||
import Wallet from 'models/wallet';
|
||||
import Transaction from 'models/transaction';
|
||||
|
||||
export default class HomeController {
|
||||
public homeView: HomeView | null = null;
|
||||
|
||||
constructor(
|
||||
public service: Service,
|
||||
public view: View,
|
||||
) {
|
||||
this.service = service;
|
||||
this.homeView = view.homeView;
|
||||
}
|
||||
|
||||
handlerGetInfoUserLogin() {
|
||||
return this.service.userService.getInfoUserLogin();
|
||||
}
|
||||
|
||||
handlerGetWalletByIdUser(idUser: number) {
|
||||
return this.service.walletService.getWalletByIdUser(idUser);
|
||||
}
|
||||
|
||||
handlerSaveWallet(wallet: Wallet) {
|
||||
return this.service.walletService.saveWallet(wallet);
|
||||
}
|
||||
|
||||
handlerSaveTransaction(transaction: Transaction) {
|
||||
return this.service.transactionService.saveTransaction(transaction);
|
||||
}
|
||||
|
||||
// handlerGetAllCategory() {
|
||||
// return this.service.categoryService.getAllCategory();
|
||||
// }
|
||||
|
||||
// handlerGetAllTransactions(idUser) {
|
||||
// return this.service.transactionService.getListTransactionByIdUser(idUser);
|
||||
// }
|
||||
|
||||
// handlerDeleteTransaction(idTransaction) {
|
||||
// return this.service.transactionService.deleteTransaction(idTransaction);
|
||||
// }
|
||||
|
||||
init() {
|
||||
if (this.homeView) {
|
||||
this.homeView.initFunction(
|
||||
this.handlerGetInfoUserLogin.bind(this),
|
||||
this.handlerGetWalletByIdUser.bind(this),
|
||||
this.handlerSaveWallet.bind(this),
|
||||
this.handlerSaveTransaction.bind(this),
|
||||
new Transform(),
|
||||
);
|
||||
|
||||
this.homeView.loadPage();
|
||||
|
||||
// Subscribe listener data update
|
||||
this.homeView.subscribeListenerData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import Wallet from 'models/wallet';
|
||||
|
||||
export interface IDataObject<T> {
|
||||
id: string;
|
||||
data: T;
|
||||
@@ -14,10 +16,10 @@ export class DataObject<T> {
|
||||
}
|
||||
}
|
||||
|
||||
export type TError = {
|
||||
export interface TError {
|
||||
title: string;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class CustomError extends Error {
|
||||
constructor(
|
||||
@@ -27,3 +29,14 @@ export class CustomError extends Error {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export type TSignal = {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
handler: (value: Data) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export interface Data {
|
||||
wallet: Wallet;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import localStorageService from 'services/localStorageService';
|
||||
import { DataObject, IDataObject } from '../global/types';
|
||||
import { LOCAL_STORAGE } from 'constants/config';
|
||||
|
||||
export const createIdUser = (): number => {
|
||||
export const generateId = (): number => {
|
||||
return new Date().getTime();
|
||||
};
|
||||
|
||||
@@ -30,3 +32,13 @@ export const createToken = (): string => {
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
export const formatNumber = (number: number): string => {
|
||||
return number.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
};
|
||||
|
||||
export const clearAccessToken = (): void => {
|
||||
localStorageService.remove(LOCAL_STORAGE.ACCESS_TOKEN);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { TSignal, Data } from 'global/types';
|
||||
|
||||
export default class Transform {
|
||||
public signal: TSignal;
|
||||
|
||||
constructor() {
|
||||
this.signal = {};
|
||||
}
|
||||
|
||||
onSendSignal(fromClass: string, value: Data) {
|
||||
Object.keys(this.signal).forEach((key) => {
|
||||
const item = this.signal[key as keyof TSignal];
|
||||
|
||||
// Same receiver
|
||||
if (item.name === fromClass) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send signal
|
||||
const { handler } = this.signal[key];
|
||||
if (handler) {
|
||||
handler(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
create(
|
||||
className: string,
|
||||
handler: ((value: Data) => void) | null = null,
|
||||
): void {
|
||||
if (handler) this.signal[className] = { name: className, handler };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createIdUser } from '../helpers/data';
|
||||
import { generateId } from '../helpers/data';
|
||||
|
||||
export default class User {
|
||||
private readonly _id: number;
|
||||
@@ -10,7 +10,7 @@ export default class User {
|
||||
private _accessToken: string;
|
||||
|
||||
constructor(email: string, password: string, accessToken?: string) {
|
||||
this._id = createIdUser();
|
||||
this._id = generateId();
|
||||
this._email = email;
|
||||
this._password = password || '';
|
||||
this._accessToken = accessToken || '';
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { generateId } from '../helpers/data';
|
||||
|
||||
export default class Wallet {
|
||||
private readonly _id: number;
|
||||
|
||||
private _walletName: string;
|
||||
|
||||
private _inflow: number;
|
||||
|
||||
private _outflow: number;
|
||||
|
||||
private _idUser: number;
|
||||
|
||||
constructor(
|
||||
walletName: string,
|
||||
inflow: number,
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export default class CommonService<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async getAllDataFromPath(path = this.defaultPath): Promise<object[] | 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 object[];
|
||||
}) as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -98,9 +98,9 @@ export default class CommonService<T> {
|
||||
|
||||
async getListDataFromProp(
|
||||
property: string,
|
||||
value: object,
|
||||
value: string,
|
||||
path: string = this.defaultPath,
|
||||
): Promise<object[] | null> {
|
||||
): Promise<T | null> {
|
||||
this.connectToDb();
|
||||
|
||||
const results = await timeOutConnect(
|
||||
@@ -114,14 +114,14 @@ export default class CommonService<T> {
|
||||
const tempData = data as DataObject<T>;
|
||||
|
||||
return convertDataObjectToModel(tempData);
|
||||
}) as object[];
|
||||
}) as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async deleteData(
|
||||
id: string,
|
||||
id: number,
|
||||
path = this.defaultPath,
|
||||
): Promise<string | void> {
|
||||
this.connectToDb();
|
||||
|
||||
@@ -34,7 +34,7 @@ class FirebaseService {
|
||||
return set(ref(this._db, path), data);
|
||||
}
|
||||
|
||||
delete(id: string, path: string): Promise<void> {
|
||||
delete(id: number, path: string): Promise<void> {
|
||||
return remove(ref(this._db, path + id));
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class FirebaseService {
|
||||
getListDataFromProp(
|
||||
path: string,
|
||||
property: string,
|
||||
value: object,
|
||||
value: string,
|
||||
): Promise<object[]> {
|
||||
return new Promise((resolve) => {
|
||||
onValue(
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import TransactionService from './transactionService';
|
||||
import UserService from './userService';
|
||||
import WalletService from './walletService';
|
||||
|
||||
export default class Service {
|
||||
public userService: UserService;
|
||||
|
||||
public walletService: WalletService;
|
||||
|
||||
public transactionService: TransactionService;
|
||||
|
||||
constructor() {
|
||||
this.userService = new UserService();
|
||||
this.walletService = new WalletService();
|
||||
this.transactionService = new TransactionService();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import Transaction from 'models/transaction';
|
||||
import CommonService from './commonService';
|
||||
|
||||
export default class TransactionService extends CommonService<Transaction> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.defaultPath = 'transactions/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save transaction into database
|
||||
* @param {Object} transaction The wallet object need to be saved into database
|
||||
*/
|
||||
async saveTransaction(transaction: Transaction): Promise<void> {
|
||||
await this.save(transaction);
|
||||
}
|
||||
|
||||
async getListTransactionByIdUser(
|
||||
idUser: number,
|
||||
): Promise<Transaction | null> {
|
||||
const results = this.getListDataFromProp(
|
||||
'idUser',
|
||||
idUser.toString(),
|
||||
this.defaultPath,
|
||||
);
|
||||
|
||||
return results || null;
|
||||
}
|
||||
|
||||
async deleteTransaction(idTransaction: number) {
|
||||
await this.deleteData(idTransaction);
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,4 @@ export default class UserService {
|
||||
|
||||
return result || null;
|
||||
}
|
||||
|
||||
static clearAccessToken(): void {
|
||||
LocalStorageService.remove(LOCAL_STORAGE.ACCESS_TOKEN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import Wallet from '../models/wallet';
|
||||
import CommonService from './commonService';
|
||||
|
||||
export default class WalletService extends CommonService<Wallet> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.defaultPath = 'wallets/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save wallet into database
|
||||
* @param {Object} wallet The wallet object need to be saved into database
|
||||
*/
|
||||
async saveWallet(wallet: Wallet): Promise<void> {
|
||||
await this.save(wallet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wallet exist in database
|
||||
* @param {string} idUser The id user to find user's wallet
|
||||
* @returns {boolean} Return true if find, otherwise return false
|
||||
*/
|
||||
async isValidWallet(idUser: number): Promise<boolean> {
|
||||
const wallet = await this.getWalletByIdUser(idUser);
|
||||
|
||||
return !!wallet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get wallet data from idUser
|
||||
* @param {string} email The id user to find user's wallet
|
||||
* @returns {Object || null} Return new Wallet Object if find, otherwise return null.
|
||||
*/
|
||||
async getWalletByIdUser(idUser: number): Promise<Wallet | null> {
|
||||
const result = (await this.getDataFromProp(
|
||||
'idUser',
|
||||
idUser.toString(),
|
||||
)) as Wallet;
|
||||
|
||||
return result || null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
import { BTN_CONTENT, TypeToast } from '../../constants/config';
|
||||
import CommonView from '../commonView';
|
||||
import { clearAccessToken, formatNumber } from '../../helpers/data';
|
||||
|
||||
import WalletView from './walletView';
|
||||
import Wallet from 'models/wallet';
|
||||
import Transform from 'helpers/transform';
|
||||
import Transaction from 'models/transaction';
|
||||
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';
|
||||
|
||||
export default class HomeView extends CommonView {
|
||||
private _tabs: NodeListOf<Element>;
|
||||
|
||||
private _allContent: NodeListOf<Element>;
|
||||
|
||||
private _cancelBtns: NodeListOf<Element>;
|
||||
|
||||
private _saveBtns: NodeListOf<Element>;
|
||||
|
||||
private _dialogs: NodeListOf<Element>;
|
||||
|
||||
private _amountInputs: NodeListOf<Element>;
|
||||
|
||||
private _transactionDialog: HTMLElement | null;
|
||||
|
||||
private _walletView: WalletView;
|
||||
|
||||
private _user: User | null = null;
|
||||
|
||||
private _wallet: Wallet | null = null;
|
||||
|
||||
private _getInfoUserLogin: (() => Promise<User | null>) | null = null;
|
||||
|
||||
private _getWalletByIdUser:
|
||||
| ((idUser: number) => Promise<Wallet | null>)
|
||||
| null = null;
|
||||
|
||||
private _saveWallet: ((wallet: Wallet) => Promise<void>) | null = null;
|
||||
|
||||
private _saveTransaction:
|
||||
| ((transaction: Transaction) => Promise<void>)
|
||||
| null = null;
|
||||
|
||||
private _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._saveBtns = document.querySelectorAll('.form__save-btn');
|
||||
this._dialogs = document.querySelectorAll('.dialog');
|
||||
|
||||
this._amountInputs = document.querySelectorAll('.form__input-balance');
|
||||
|
||||
this._transactionDialog = document.getElementById('transactionDialog');
|
||||
|
||||
this._walletView = new WalletView();
|
||||
}
|
||||
|
||||
initFunction(
|
||||
getInfoUserLogin: () => Promise<User | null>,
|
||||
getWalletByIdUser: (idUser: number) => Promise<Wallet | null>,
|
||||
saveWallet: (wallet: Wallet) => Promise<void>,
|
||||
saveTransaction: (transaction: Transaction) => Promise<void>,
|
||||
transform: Transform,
|
||||
) {
|
||||
this._getInfoUserLogin = getInfoUserLogin;
|
||||
this._getWalletByIdUser = getWalletByIdUser;
|
||||
this._saveWallet = saveWallet;
|
||||
this._saveTransaction = saveTransaction;
|
||||
|
||||
this._transform = transform;
|
||||
|
||||
// Init function for child view
|
||||
this.initWalletViewFunction();
|
||||
}
|
||||
|
||||
initWalletViewFunction() {
|
||||
this._walletView.initFunction(
|
||||
this._transform,
|
||||
this.toggleLoaderSpinner.bind(this),
|
||||
this._saveWallet,
|
||||
this._saveTransaction,
|
||||
this.loadData.bind(this),
|
||||
this.loadEvent.bind(this),
|
||||
this.showSuccessToast.bind(this),
|
||||
this.showErrorToast.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
subscribeListenerData() {
|
||||
this.subscribe();
|
||||
this._walletView.subscribe();
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
// Send data to other class
|
||||
this.sendData();
|
||||
|
||||
await this.loadWalletUser();
|
||||
}
|
||||
|
||||
async loadPage() {
|
||||
this.toggleLoaderSpinner();
|
||||
const user = await this._getInfoUserLogin!();
|
||||
|
||||
if (!user) {
|
||||
// If user not login yet
|
||||
clearAccessToken();
|
||||
window.location.replace('/login');
|
||||
} else {
|
||||
// If user already login
|
||||
this._user = user;
|
||||
this._wallet = await this._getWalletByIdUser!(user.id);
|
||||
|
||||
this.sendData();
|
||||
// Check user's wallet if have or not
|
||||
if (!this._wallet) {
|
||||
// Show add wallet dialog
|
||||
this._walletView.showDialog();
|
||||
} else {
|
||||
// Init data
|
||||
await this.loadData();
|
||||
|
||||
// Load event page
|
||||
this.loadEvent();
|
||||
}
|
||||
}
|
||||
|
||||
this.toggleLoaderSpinner();
|
||||
}
|
||||
|
||||
// ---------------------LOAD DATA---------------------//
|
||||
|
||||
subscribe() {
|
||||
this._transform!.create('homeView', this.updateData.bind(this));
|
||||
}
|
||||
|
||||
sendData() {
|
||||
const data: Data = {
|
||||
wallet: this._wallet!,
|
||||
};
|
||||
|
||||
this._transform!.onSendSignal('homeView', data);
|
||||
}
|
||||
|
||||
updateData(data: Data) {
|
||||
if (data.wallet) this._wallet = data.wallet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load wallet user
|
||||
*/
|
||||
async loadWalletUser() {
|
||||
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;
|
||||
const walletAmountValue = wallet!.amountWallet;
|
||||
|
||||
const sign = walletAmountValue >= 0 ? '+' : '-';
|
||||
|
||||
this._wallet = wallet; // Make wallet into global variable
|
||||
|
||||
walletName!.textContent = walletNameValue;
|
||||
walletPrice!.textContent = `${sign}$ ${formatNumber(
|
||||
Math.abs(walletAmountValue),
|
||||
)}`; // Math.abs(walletAmountValue) to keep the value always > 0
|
||||
|
||||
// Update amount wallet into database
|
||||
this._saveWallet!(this._wallet!);
|
||||
}
|
||||
|
||||
// ---------------------END---------------------//
|
||||
|
||||
showSuccessToast(title: string, message: string) {
|
||||
const typeToast = TypeToast.success;
|
||||
const btnContent = BTN_CONTENT.OK;
|
||||
|
||||
this.initToastContent(typeToast, title, message, btnContent);
|
||||
|
||||
this.toastDialog!.showModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement error toast in site
|
||||
* @param {string} content The content will show in error toast
|
||||
*/
|
||||
showErrorToast(error: TError | string): void {
|
||||
const title =
|
||||
typeof error === 'object' && error.title
|
||||
? error.title
|
||||
: DEFAULT_TITLE_ERROR_TOAST;
|
||||
|
||||
const content =
|
||||
typeof error === 'object' && error.message
|
||||
? error.message
|
||||
: (error as string);
|
||||
|
||||
this.initToastContent(TypeToast.error, title, content, BTN_CONTENT.OK);
|
||||
|
||||
if (this.toastDialog && this.toastBtn) {
|
||||
// Show toast
|
||||
this.toastDialog.showModal();
|
||||
|
||||
// Remove event for toast button
|
||||
this.toastBtn.removeEventListener('click', redirectToLoginPage);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------- HANDLER EVENT ------------------------------- //
|
||||
|
||||
loadEvent() {
|
||||
this.addCommonEventPage();
|
||||
this.handlerTabsTransfer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle event when click on tabs
|
||||
*/
|
||||
handlerTabsTransfer() {
|
||||
this._tabs.forEach((tab, index) => {
|
||||
tab.addEventListener('click', (e: Event) => {
|
||||
this.removeActiveTab();
|
||||
tab.classList.add('active');
|
||||
|
||||
const line = document.querySelector<HTMLElement>(
|
||||
'.app__line',
|
||||
) as HTMLElement;
|
||||
|
||||
const eventTarget = e.target as HTMLElement;
|
||||
|
||||
line.style.width = `${eventTarget.offsetWidth}px`;
|
||||
line.style.left = `${eventTarget.offsetLeft}px`;
|
||||
|
||||
this._allContent.forEach((content) => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
this._allContent[index].classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addCommonEventPage() {
|
||||
// Add event close dialog when click outside
|
||||
this._dialogs.forEach((dialog) => {
|
||||
dialog.addEventListener('click', (e) => {
|
||||
const dialogDimensions = dialog.getBoundingClientRect();
|
||||
const mouseEvent = e as MouseEvent;
|
||||
if (
|
||||
mouseEvent.clientX < dialogDimensions.left ||
|
||||
mouseEvent.clientX > dialogDimensions.right ||
|
||||
mouseEvent.clientY < dialogDimensions.top ||
|
||||
mouseEvent.clientY > dialogDimensions.bottom
|
||||
) {
|
||||
(<HTMLDialogElement>dialog).close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._cancelBtns.forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
this.closeAllDialog();
|
||||
});
|
||||
});
|
||||
|
||||
// Prevent input =,-,e into amount input
|
||||
this._amountInputs.forEach((item) => {
|
||||
item.addEventListener(
|
||||
'keypress',
|
||||
(e) =>
|
||||
['+', '-', 'e'].includes((<KeyboardEvent>e).key) &&
|
||||
e.preventDefault(),
|
||||
);
|
||||
});
|
||||
|
||||
// Prevent user input at category search
|
||||
const categorySearchEl = document.querySelector(
|
||||
'.category-name',
|
||||
) as Element;
|
||||
categorySearchEl.addEventListener('keydown', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
const logoutBtn = document.querySelector('.logout-text') as Element;
|
||||
logoutBtn.addEventListener('click', () => {
|
||||
clearAccessToken();
|
||||
|
||||
window.location.replace('/login');
|
||||
});
|
||||
}
|
||||
|
||||
closeAllDialog() {
|
||||
this._dialogs.forEach((dialog) => {
|
||||
(<HTMLDialogElement>dialog).close();
|
||||
});
|
||||
}
|
||||
|
||||
removeActiveTab() {
|
||||
this._tabs.forEach((tab) => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
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';
|
||||
|
||||
export default class WalletView {
|
||||
constructor() {
|
||||
this.walletDialog = document.getElementById('walletDialog');
|
||||
|
||||
this.addHandlerEventWalletForm();
|
||||
}
|
||||
|
||||
initFunction(
|
||||
transform,
|
||||
toggleLoaderSpinner,
|
||||
saveWallet,
|
||||
saveTransaction,
|
||||
loadData,
|
||||
loadEvent,
|
||||
showSuccessToast,
|
||||
showErrorToast,
|
||||
) {
|
||||
this.transform = transform;
|
||||
this.toggleLoaderSpinner = toggleLoaderSpinner;
|
||||
this.saveWallet = saveWallet;
|
||||
this.saveTransaction = saveTransaction;
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ import { getSubdirectoryURL } from 'helpers/url';
|
||||
import LoginView from './loginView';
|
||||
import RegisterView from './registerView';
|
||||
import { URL } from 'constants/config';
|
||||
import HomeView from './home/homeView';
|
||||
|
||||
export default class View {
|
||||
public registerView: RegisterView | null = null;
|
||||
|
||||
public loginView: LoginView | null = null;
|
||||
|
||||
public homeView: HomeView | null = null;
|
||||
|
||||
constructor() {
|
||||
switch (getSubdirectoryURL()) {
|
||||
case URL.LOGIN:
|
||||
@@ -16,6 +19,8 @@ export default class View {
|
||||
case URL.REGISTER:
|
||||
this.registerView = new RegisterView();
|
||||
break;
|
||||
case URL.HOME:
|
||||
this.homeView = new HomeView();
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user