diff --git a/.eslintrc.json b/.eslintrc.json index 7900fa6..314b170 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -32,6 +32,8 @@ { "ignoreRestSiblings": true } - ] + ], + "no-console": "warn", + "no-debugger": "warn" } } \ No newline at end of file diff --git a/src/common/pages/BasePageElement/BasePageElement.ts b/src/common/pages/BasePageElement/BasePageElement.ts index c13dedd..b1df69c 100644 --- a/src/common/pages/BasePageElement/BasePageElement.ts +++ b/src/common/pages/BasePageElement/BasePageElement.ts @@ -5,6 +5,7 @@ import { isTrue } from 'core/utils'; class BasePageElement extends BaseElement { public _pageTitle: string = ''; + public hideTitleHead: boolean = false; @attr hidetitle: string; @attr customtitle: string; private _data: any; @@ -13,6 +14,7 @@ class BasePageElement extends BaseElement { if (options?.title) { this._pageTitle = options?.title; } + this.hideTitleHead = options?.hideTitleHead || false; this.connectedCallback = this.connectedCallback.bind(this); this.disconnectedCallback = this.disconnectedCallback.bind(this); } @@ -36,7 +38,9 @@ class BasePageElement extends BaseElement { }; connectedCallback() { - this.appMain.setTitle(this.pageTitle); + if (!this.hideTitleHead) { + this.appMain.setTitle(this.pageTitle); + } super.connectedCallback(); } @@ -53,4 +57,5 @@ export default BasePageElement; export type OptionType = { title?: string; + hideTitleHead?: boolean; }; diff --git a/src/components/app-main/AppMainElement.ts b/src/components/app-main/AppMainElement.ts index 5628263..64b19d0 100644 --- a/src/components/app-main/AppMainElement.ts +++ b/src/components/app-main/AppMainElement.ts @@ -5,6 +5,7 @@ import { closest, controller, target } from 'core/utils'; import { AppLoaderElement } from 'components/app-loader/AppLoaderElement'; import { ToastPortalElement } from 'components/toast-portal/ToastPortalElement'; import { BasePageElement } from 'common/'; +import { TransactionsService } from 'services/'; @controller('app-main') class AppMainElement extends HTMLElement { @@ -12,6 +13,8 @@ class AppMainElement extends HTMLElement { public authStore: AuthStore; private httpClient: HttpClient; public appService: AppService; + private transactionsService: TransactionsService; + private subscriptionChecked: boolean = false; //public shadow: any; @target appModal: AppModalElement; @target mainRoot: AppRootElement; @@ -36,6 +39,7 @@ class AppMainElement extends HTMLElement { this.httpClient = new HttpClient(); this.appService = new AppService(this, this.httpClient); this.routerService = new RouterService(this, mainRoot); + this.transactionsService = new TransactionsService(this.appService); this.authStore = new AuthStore(this, this.appService); this.routerService.setRoutes([ { @@ -113,6 +117,8 @@ class AppMainElement extends HTMLElement { this.routerService.init(); this.addEventListener('mousedown', this.setActiveElement, false); this.addEventListener('tokenchange', this.closeOffToken); + this.addEventListener('routechanged', this.checkSubscriptions); + this.checkSubscriptions(); } closeOffToken = () => { @@ -121,9 +127,22 @@ class AppMainElement extends HTMLElement { } }; + checkSubscriptions = async () => { + if (this.isAuth && !this.subscriptionChecked) { + const checked = await this.transactionsService.check({ sortBy: 'transactionDate asc', rpp: 10 }); + this.createModal('transaction-check', { + data: checked, + autoInit: false, + }); + this.subscriptionChecked = true; + this.removeEventListener('routechanged', this.checkSubscriptions); + } + }; + disconnectedCallback = () => { this.removeEventListener('mousedown', this.setActiveElement); this.removeEventListener('tokenchange', this.closeOffToken); + this.removeEventListener('routechanged', this.checkSubscriptions); }; setActiveElement = (e) => { diff --git a/src/components/app-pagination/AppPaginationElement.ts b/src/components/app-pagination/AppPaginationElement.ts index 4875b03..e3e84a4 100644 --- a/src/components/app-pagination/AppPaginationElement.ts +++ b/src/components/app-pagination/AppPaginationElement.ts @@ -60,7 +60,7 @@ class AppPaginationElement extends BaseComponentElement { this.update(); }; - executeFetch = async (options?): Promise => { + executeFetch = async (options?, fetchFunc = this.fetchFunc): Promise => { if (!options) { options = { rpp: this.rpp || 5, @@ -70,7 +70,7 @@ class AppPaginationElement extends BaseComponentElement { try { this.loader?.start?.(); - const response = await this.fetchFunc(options); + const response = await fetchFunc(options); this.loader?.stop?.(); this.setItems(response?.items); this.totalItems = response?.totalRecords; diff --git a/src/components/input-field/InputFieldElement.ts b/src/components/input-field/InputFieldElement.ts index 70ae9b4..85c9758 100644 --- a/src/components/input-field/InputFieldElement.ts +++ b/src/components/input-field/InputFieldElement.ts @@ -12,6 +12,7 @@ class InputFieldElement extends BaseComponentElement { @attr rules: string; @attr pattern: string; @attr customAction: string; + @attr initialValue: string; @target main: HTMLElement; @target inp: HTMLElement; @closest appForm: AppFormElement; @@ -31,6 +32,9 @@ class InputFieldElement extends BaseComponentElement { this.validator = new Validator(this, this.appForm, this.rules); this.randId = `${name}${randomId()}`; this.update(); + if (this.initialValue) { + this._value = this.initialValue; + } }; attributeChangedCallback() { diff --git a/src/pages/history-page/HistoryPageElement.ts b/src/pages/history-page/HistoryPageElement.ts index 7be7d33..1890809 100644 --- a/src/pages/history-page/HistoryPageElement.ts +++ b/src/pages/history-page/HistoryPageElement.ts @@ -41,6 +41,7 @@ class HistoryPageElement extends BasePageElement { } options.embed = 'TransactionType'; options.sortBy = 'transactionDate|desc'; + options.noPending = true; const response = await this.transactionsService.getAll(options); return response; } catch (err) { @@ -48,6 +49,12 @@ class HistoryPageElement extends BasePageElement { } }; + transactionCheck = () => { + this.appMain.createModal('transaction-check', { + autoInit: true, + }); + }; + render = (): TemplateResult => HistoryPageElementTemplate({ walletId: this.routerService?.routerState?.data?.walletId }); } diff --git a/src/pages/history-page/HistoryPageElementTemplate.ts b/src/pages/history-page/HistoryPageElementTemplate.ts index 17557d4..dedf3d8 100644 --- a/src/pages/history-page/HistoryPageElementTemplate.ts +++ b/src/pages/history-page/HistoryPageElementTemplate.ts @@ -1,16 +1,12 @@ import { html, nothing, TemplateResult } from 'core/utils'; export default (props): TemplateResult => { - const { walletId } = props; - const renderWallet = () => { - if (walletId) { - return html`${walletId}`; - } - return nothing; - }; - return html`
- ${renderWallet()} +
+ +
`; }; diff --git a/src/pages/index.ts b/src/pages/index.ts index 6766e30..5837b23 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -13,3 +13,4 @@ export * from './wallet-page'; export * from './subscription-edit'; export * from './transaction-edit'; export * from './wallet-edit'; +export * from './transaction-check'; diff --git a/src/pages/transaction-check/TransactionCheckElement.ts b/src/pages/transaction-check/TransactionCheckElement.ts new file mode 100644 index 0000000..267ceac --- /dev/null +++ b/src/pages/transaction-check/TransactionCheckElement.ts @@ -0,0 +1,141 @@ +import { TemplateResult, controller, target, html } from 'core/utils'; +import { TransactionsService, TransactionStatusService } from 'services/'; +import { AppPaginationElement } from 'components/'; +import { BasePageElement } from 'common/'; +import { TransactionCheckElementTemplate } from 'pages/transaction-check'; +import dayjs from 'dayjs'; + +@controller('transaction-check') +class TransactionCheckElement extends BasePageElement { + private transactionsService: TransactionsService; + private transactionStatusService: TransactionStatusService; + transactionStatuses: []; + @target pagination: AppPaginationElement; + modalData: any = null; + constructor() { + super({ + title: 'Transaction Check', + hideTitleHead: true, + }); + } + + elementConnected = async (): Promise => { + this.transactionStatusService = new TransactionStatusService(this.appMain?.appService); + await this.fetchTransactionStatus(); + this.transactionsService = new TransactionsService(this.appMain?.appService); + this.update(); + this.modalData = this.getData(); + this.pagination?.setCustomRenderItem?.(this.renderSubscription)!; + this.pagination?.setFetchFunc?.(this.getTransactions, this.modalData?.autoInit)!; + if (!this.modalData?.autoInit) { + this.pagination?.executeFetch?.(null, () => this.mappedData(this.modalData.data)); + } + }; + + mappedData = (data) => { + for (const item of data?.items) { + item.formattedDate = dayjs(item.transactionDate).format('YYYY-MM-DD'); + } + return data; + }; + + renderSubscription = (item) => { + const renderEditActions = () => html`
+ + +
`; + const renderRegularActions = () => html`
+ + +
`; + return html` + ${!item.isEdit + ? html`${dayjs(item.transactionDate).format("MMM DD 'YY")}` + : html``} + ${item.description} + + + ${item?.transactionType?.type == 'expense' ? '- ' : ''} + ${Number(item.amount).toLocaleString('en-US', { + maximumFractionDigits: 2, + minimumFractionDigits: 2, + })} + + (${item.currency ? item.currency : 'USD'}) + + ${item.isEdit ? renderEditActions() : renderRegularActions()} + `; + }; + + fetchTransactionStatus = async () => { + this.transactionStatuses = await this.transactionStatusService.getAll(); + }; + + transactionEdit = (item) => { + item.isEdit = !item.isEdit; + this.pagination?.update(); + }; + + transactionEditSave = async (item) => { + const resource = { + transactionDate: dayjs(item.newTransactionDate || item.transactionDate) + .utc(true) + .format(), + }; + await this.updateTransaction(item.id, resource); + }; + + transactionEditComplete = async (item) => { + const completedStatusId = (this.transactionStatuses?.find((item: any) => item.status === 'completed') as any)?.id!; + if (completedStatusId) { + const resource = { + transactionStatusId: completedStatusId, + }; + await this.updateTransaction(item.id, resource); + } + }; + + transactionUpdated = () => { + this.pagination?.executeFetch(); + }; + + getTransactions = async (options): Promise => { + try { + options.embed = 'TransactionType'; + options.sortBy = 'transactionDate|asc'; + const response = await this.transactionsService.check(options); + return this.mappedData(response); + } catch (err) { + throw err; + } + }; + + updateTransaction = async (id, resource): Promise => { + try { + const response = await this.transactionsService.put(id, resource); + return response; + } catch (err) { + throw err; + } finally { + const options = { + page: this.pagination?.page || 1, + rpp: this.pagination?.rpp || 10, + }; + this.pagination?.executeFetch(options); + this.appMain?.triggerTransactionUpdate?.()!; + } + }; + + render = (): TemplateResult => + TransactionCheckElementTemplate({ walletId: this.routerService?.routerState?.data?.walletId }); +} + +export type { TransactionCheckElement }; diff --git a/src/pages/transaction-check/TransactionCheckElementTemplate.ts b/src/pages/transaction-check/TransactionCheckElementTemplate.ts new file mode 100644 index 0000000..b06fa27 --- /dev/null +++ b/src/pages/transaction-check/TransactionCheckElementTemplate.ts @@ -0,0 +1,7 @@ +import { html, nothing, TemplateResult } from 'core/utils'; + +export default (props): TemplateResult => { + return html`
+ +
`; +}; diff --git a/src/pages/transaction-check/index.ts b/src/pages/transaction-check/index.ts new file mode 100644 index 0000000..dcdccfe --- /dev/null +++ b/src/pages/transaction-check/index.ts @@ -0,0 +1,2 @@ +export { default as TransactionCheckElementTemplate } from './TransactionCheckElementTemplate'; +export * from './TransactionCheckElement'; diff --git a/src/pages/wallet-page/WalletPageElement.ts b/src/pages/wallet-page/WalletPageElement.ts index bf17dd4..5082c1f 100644 --- a/src/pages/wallet-page/WalletPageElement.ts +++ b/src/pages/wallet-page/WalletPageElement.ts @@ -70,6 +70,7 @@ class WalletPageElement extends BasePageElement { } options.embed = 'TransactionType'; options.sortBy = 'transactionDate|desc'; + options.noPending = true; const response = await this.transactionsService.getAll(options); return response; } catch (err) { diff --git a/src/services/TransactionStatusService.ts b/src/services/TransactionStatusService.ts new file mode 100644 index 0000000..0918cf2 --- /dev/null +++ b/src/services/TransactionStatusService.ts @@ -0,0 +1,9 @@ +import { AppService, BaseService } from 'core/services'; + +class TransactionStatusService extends BaseService { + constructor(appService: AppService) { + super('/transaction-status', appService); + } +} + +export default TransactionStatusService; diff --git a/src/services/TransactionsService.ts b/src/services/TransactionsService.ts index 4874668..5e1c521 100644 --- a/src/services/TransactionsService.ts +++ b/src/services/TransactionsService.ts @@ -4,6 +4,10 @@ class TransactionsService extends BaseService { constructor(appService: AppService) { super('/transaction', appService); } + + check = (params?: Object, headers?: HeadersInit) => { + return this.appService.get(this.endpoint + '/check', params, headers); + }; } export default TransactionsService; diff --git a/src/services/index.ts b/src/services/index.ts index 31edfd1..61ba003 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -5,3 +5,4 @@ export { default as TransactionsService } from './TransactionsService'; export { default as TransactionTypeService } from './TransactionTypeService'; export { default as SubscriptionService } from './SubscriptionService'; export { default as SubscriptionTypeService } from './SubscriptionTypeService'; +export { default as TransactionStatusService } from './TransactionStatusService'; diff --git a/src/styles/app-pagination/app-pagination.scss b/src/styles/app-pagination/app-pagination.scss index cd21b2c..b87c614 100644 --- a/src/styles/app-pagination/app-pagination.scss +++ b/src/styles/app-pagination/app-pagination.scss @@ -49,6 +49,9 @@ app-pagination { &.col-transactions { grid-template-columns: 1fr 10fr 1fr 1fr; } + &.col-checks { + grid-template-columns: 3fr 8fr 2fr 3fr; + } &.col-wallet { grid-template-columns: 9fr 1fr; } diff --git a/src/styles/core/main.scss b/src/styles/core/main.scss index a3c61ca..1d5c654 100644 --- a/src/styles/core/main.scss +++ b/src/styles/core/main.scss @@ -1,66 +1,76 @@ @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); app-main { - * { - font-family: 'Roboto', sans-serif; - font-size: 14px; - } + * { + font-family: 'Roboto', sans-serif; + font-size: 14px; + } - input, - select, - textarea, - button { - font-family: inherit; - font-size: inherit; - line-height: inherit; - } + input, + select, + textarea, + button { + font-family: inherit; + font-size: inherit; + line-height: inherit; + } - a { - color: $blue-07; - text-decoration: none; + a { + color: $blue-07; + text-decoration: none; - &:hover { - text-decoration: underline; - } - } + &:hover { + text-decoration: underline; + } + } - hr, - .line { - // Horizontal line should look more clean - height: 0; - margin: 15px 0; - overflow: hidden; - background: transparent; - border: 0; - border-bottom: $border-width $border-style $gray-03; - } + hr, + .line { + // Horizontal line should look more clean + height: 0; + margin: 15px 0; + overflow: hidden; + background: transparent; + border: 0; + border-bottom: $border-width $border-style $gray-03; + } - table { - border-spacing: 0; - border-collapse: collapse; - } + table { + border-spacing: 0; + border-collapse: collapse; + } - td, - th { - padding: 0; - } + td, + th { + padding: 0; + } - button { - cursor: pointer; - border-radius: 0; - } + button { + cursor: pointer; + border-radius: 0; + } - details { - summary { - cursor: pointer; - } - &:not([open]) { - > *:not(summary) { - display: none !important; - } - } - } + details { + summary { + cursor: pointer; + } + &:not([open]) { + > *:not(summary) { + display: none !important; + } + } + } - [hidden][hidden] { - display: none !important; - } + [hidden][hidden] { + display: none !important; + } +} + +.d { + &--flex { + display: flex; + > * { + flex: 1; + margin: 0 4px; + } + } }