From ac13cdcc11fe2be1422720663f535d92d7fdb698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Jurmanovi=C4=87?= Date: Mon, 12 Jul 2021 22:26:32 +0200 Subject: [PATCH 01/11] remember me checkbox login form --- .../input-field/InputFieldElement.ts | 5 ++- src/components/menu-item/MenuItemElement.ts | 2 +- src/pages/login-page/LoginPageElement.ts | 14 ++++++- src/styles/input-field/input-field.scss | 40 ++++++++++++------- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/components/input-field/InputFieldElement.ts b/src/components/input-field/InputFieldElement.ts index 29ccb62..3da6361 100644 --- a/src/components/input-field/InputFieldElement.ts +++ b/src/components/input-field/InputFieldElement.ts @@ -131,7 +131,10 @@ class InputFieldElement extends BaseComponentElement { />`; }; - return html`
+ return html`
${renderMessage(this.label)}${renderError(this.error)} ${renderInput(this.type)}
`; }; diff --git a/src/components/menu-item/MenuItemElement.ts b/src/components/menu-item/MenuItemElement.ts index 0abf9bb..1b1718a 100644 --- a/src/components/menu-item/MenuItemElement.ts +++ b/src/components/menu-item/MenuItemElement.ts @@ -3,7 +3,7 @@ import { html, TemplateResult } from 'core/utils'; import { AppMainElement } from 'components/app-main/AppMainElement'; import { BaseComponentElement } from 'common/'; import { deviceWidths } from 'core/constants'; -import { MenuLayoutElement } from 'layouts'; +import { MenuLayoutElement } from 'layouts/'; @controller class MenuItemElement extends BaseComponentElement { diff --git a/src/pages/login-page/LoginPageElement.ts b/src/pages/login-page/LoginPageElement.ts index 5d75bf6..7835d72 100644 --- a/src/pages/login-page/LoginPageElement.ts +++ b/src/pages/login-page/LoginPageElement.ts @@ -41,7 +41,11 @@ class LoginPageElement extends BasePageElement { const formObject: any = {}; this.inputs.forEach((input: InputFieldElement) => { const inputType = input.inp; - formObject[input.name] = (inputType as HTMLInputElement).value; + if (input.type === 'checkbox') { + formObject[input.name] = (inputType as HTMLInputElement).checked; + } else { + formObject[input.name] = (inputType as HTMLInputElement).value; + } }); return formObject; } @@ -93,6 +97,14 @@ class LoginPageElement extends BasePageElement { data-targets="login-page.inputs" data-rules="required" > + + `; }; diff --git a/src/styles/input-field/input-field.scss b/src/styles/input-field/input-field.scss index e5f0b84..facad00 100644 --- a/src/styles/input-field/input-field.scss +++ b/src/styles/input-field/input-field.scss @@ -1,17 +1,29 @@ input-field { - label { - cursor: pointer; - } - input { - width: 100%; - box-sizing: border-box; - margin: 0; - padding: 5px 0 !important; - border: 1px solid $gray-03; - border-radius: 2px; - } - .input-error { - color: $red-04; - margin: 3px 0; + .input-main { + label { + cursor: pointer; + } + input { + width: 100%; + box-sizing: border-box; + margin: 0; + padding: 5px 0 !important; + border: 1px solid $gray-03; + border-radius: 2px; + } + .input-error { + color: $red-04; + margin: 3px 0; + } + &--checkbox { + label { + display: inline-block; + } + input { + margin-left: 12px !important; + display: inline-block !important; + width: auto !important; + } + } } } From 4a69e8c370713287f1a39021898370cdc386abd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Jurmanovi=C4=87?= Date: Sun, 25 Jul 2021 10:48:03 +0200 Subject: [PATCH 02/11] fixed subscription table --- .../SubscriptionListElement.ts | 48 +++++++++- src/pages/wallet-page/WalletPageElement.ts | 45 ++++++++- src/styles/app-pagination/app-pagination.scss | 91 +++++++++++++++++++ 3 files changed, 180 insertions(+), 4 deletions(-) diff --git a/src/pages/subscription-list/SubscriptionListElement.ts b/src/pages/subscription-list/SubscriptionListElement.ts index 56ff39a..ae30d66 100644 --- a/src/pages/subscription-list/SubscriptionListElement.ts +++ b/src/pages/subscription-list/SubscriptionListElement.ts @@ -3,6 +3,7 @@ import { html, TemplateResult } from 'core/utils'; import { SubscriptionService } from 'services/'; import { AppMainElement, AppPaginationElement } from 'components/'; import { BasePageElement } from 'common/'; +import dayjs from 'dayjs'; @controller class SubscriptionListElement extends BasePageElement { @@ -18,6 +19,7 @@ class SubscriptionListElement extends BasePageElement { this.subscriptionService = new SubscriptionService(this.appMain?.appService); this.update(); this.pagination?.setFetchFunc?.(this.getSubscriptions, true)!; + this.pagination?.setCustomRenderItem?.(this.renderSubscription)!; this.appMain.addEventListener('tokenchange', this.update); this.appMain.addEventListener('transactionupdate', this.transactionUpdated); }; @@ -31,6 +33,25 @@ class SubscriptionListElement extends BasePageElement { this.pagination?.executeFetch(); }; + renderSubscription = (item) => html` + ${dayjs(item.lastTransactionDate).format("MMM DD 'YY")} + every ${item.customRange} ${item.rangeName} + ${item.description} + ${dayjs(item.nextTransaction).format("MMM DD 'YY")} + + + ${item?.transactionType?.type == 'expense' ? '- ' : ''} + ${Number(item.amount).toLocaleString('en-US', { + maximumFractionDigits: 2, + minimumFractionDigits: 2, + })} + + (${item.currency ? item.currency : 'USD'}) + + `; + getSubscriptions = async (options): Promise => { try { if (this?.routerService?.routerState?.data) { @@ -39,9 +60,29 @@ class SubscriptionListElement extends BasePageElement { options['walletId'] = walletId; } } - options.embed = 'TransactionType'; + options.embed = 'TransactionType,SubscriptionType'; options.sortBy = 'dateCreated|desc'; const response = await this.subscriptionService.getAll(options); + response?.items?.map?.((i) => { + switch (i.subscriptionType.type) { + case 'monthly': + i.rangeName = i.customRange != 1 ? 'Months' : 'Month'; + i.nextTransaction = dayjs(i.lastTransactionDate).add(i.customRange, 'month'); + break; + case 'yearly': + i.rangeName = i.customRange != 1 ? 'Years' : 'Year'; + i.nextTransaction = dayjs(i.lastTransactionDate).add(i.customRange, 'year'); + break; + case 'daily': + i.rangeName = i.customRange != 1 ? 'Days' : 'Day'; + i.nextTransaction = dayjs(i.lastTransactionDate).add(i.customRange, 'day'); + break; + case 'weekly': + i.rangeName = i.customRange != 1 ? 'Weeks' : 'Week'; + i.nextTransaction = dayjs(i.lastTransactionDate).add(7 * i.customRange, 'day'); + break; + } + }); return response; } catch (err) { throw err; @@ -57,7 +98,10 @@ class SubscriptionListElement extends BasePageElement { }; return html`
${renderWallet()} - +
`; }; } diff --git a/src/pages/wallet-page/WalletPageElement.ts b/src/pages/wallet-page/WalletPageElement.ts index c9d7afd..51a708a 100644 --- a/src/pages/wallet-page/WalletPageElement.ts +++ b/src/pages/wallet-page/WalletPageElement.ts @@ -3,6 +3,7 @@ import { html, TemplateResult } from 'core/utils'; import { SubscriptionService, TransactionsService, WalletService } from 'services/'; import { AppMainElement, AppPaginationElement, WalletHeaderElement } from 'components/'; import { BasePageElement } from 'common/'; +import dayjs from 'dayjs'; @controller class WalletPageElement extends BasePageElement { @@ -32,6 +33,7 @@ class WalletPageElement extends BasePageElement { this.update(); this.pagination?.setFetchFunc?.(this.getTransactions, true)!; this.paginationSub?.setFetchFunc?.(this.getSubscriptions, true)!; + this.paginationSub?.setCustomRenderItem?.(this.renderSubscription)!; this.appMain.addEventListener('tokenchange', this.update); this.appMain.addEventListener('transactionupdate', this.transactionUpdated); }; @@ -64,6 +66,25 @@ class WalletPageElement extends BasePageElement { } }; + renderSubscription = (item) => html` + ${dayjs(item.lastTransactionDate).format("MMM DD 'YY")} + every ${item.customRange} ${item.rangeName} + ${item.description} + ${dayjs(item.nextTransaction).format("MMM DD 'YY")} + + + ${item?.transactionType?.type == 'expense' ? '- ' : ''} + ${Number(item.amount).toLocaleString('en-US', { + maximumFractionDigits: 2, + minimumFractionDigits: 2, + })} + + (${item.currency ? item.currency : 'USD'}) + + `; + getSubscriptions = async (options): Promise => { try { if (this?.routerService?.routerState?.data) { @@ -72,9 +93,29 @@ class WalletPageElement extends BasePageElement { options['walletId'] = walletId; } } - options.embed = 'TransactionType'; + options.embed = 'TransactionType,SubscriptionType'; options.sortBy = 'dateCreated|desc'; const response = await this.subscriptionService.getAll(options); + response?.items?.map?.((i) => { + switch (i.subscriptionType.type) { + case 'monthly': + i.rangeName = i.customRange != 1 ? 'Months' : 'Month'; + i.nextTransaction = dayjs(i.lastTransactionDate).add(i.customRange, 'month'); + break; + case 'yearly': + i.rangeName = i.customRange != 1 ? 'Years' : 'Year'; + i.nextTransaction = dayjs(i.lastTransactionDate).add(i.customRange, 'year'); + break; + case 'daily': + i.rangeName = i.customRange != 1 ? 'Days' : 'Day'; + i.nextTransaction = dayjs(i.lastTransactionDate).add(i.customRange, 'day'); + break; + case 'weekly': + i.rangeName = i.customRange != 1 ? 'Weeks' : 'Week'; + i.nextTransaction = dayjs(i.lastTransactionDate).add(7 * i.customRange, 'day'); + break; + } + }); return response; } catch (err) { throw err; @@ -162,7 +203,7 @@ class WalletPageElement extends BasePageElement {

Transactions

Subscriptions

- +
`; }; } diff --git a/src/styles/app-pagination/app-pagination.scss b/src/styles/app-pagination/app-pagination.scss index ff587bf..d1da237 100644 --- a/src/styles/app-pagination/app-pagination.scss +++ b/src/styles/app-pagination/app-pagination.scss @@ -97,5 +97,96 @@ app-pagination { } } } + table.subscription-table { + margin: 0 0 3em 0; + padding: 0; + width: 100%; + background-color: $gray-09; + border: 1px solid $gray-09; + border-radius: 4px; + display: block; + &.--loading { + min-height: 80px; + tr { + td { + visibility: hidden; + } + } + } + tr { + background-color: $gray-07; + padding: 4px 12px; + border-radius: 4px; + display: grid; + margin: 6px 8px; + &.col-3 { + grid-template-columns: repeat(3, 1fr); + } + &.col-2 { + grid-template-columns: repeat(2, 1fr); + } + &.col-1 { + grid-template-columns: 1fr; + } + &.col-4 { + grid-template-columns: repeat(4, 1fr); + } + &.col-5 { + grid-template-columns: repeat(5, 1fr); + } + &.col-6 { + grid-template-columns: repeat(6, 1fr); + } + &.col-subscription { + grid-template-columns: 1fr 2fr 10fr 1fr 2fr; + } + td, + th { + margin: 0 12px; + overflow: hidden; // Or flex might break + list-style: none; + &.--left { + text-align: left; + } + &.--right { + text-align: right; + } + &.balance-cell { + .balance { + display: inline; + &.--positive { + color: $green-01; + } + &.--negative { + color: $red-01; + } + } + .currency { + display: inline; + color: $gray-10; + } + } + } + } + .paginate { + position: relative; + height: 33px; + margin-bottom: 7px; + .--total { + position: absolute; + left: 7px; + bottom: 9px; + color: $gray-05; + } + .--footer { + position: absolute; + right: 7px; + .--pages { + margin-right: 7px; + color: $gray-05; + } + } + } + } } } From e73e5b025baf78df96ec4249ed908d75415de6b9 Mon Sep 17 00:00:00 2001 From: Fran Jurmanovic Date: Fri, 30 Jul 2021 19:56:56 +0200 Subject: [PATCH 03/11] added subscription edit --- package.json | 2 +- .../app-dropdown/AppDropdownElement.ts | 12 +- src/components/app-form/AppFormElement.ts | 20 +- .../input-field/InputFieldElement.ts | 25 +- src/core/services/app-service/AppService.ts | 2 +- src/core/services/base-service/BaseService.ts | 12 +- src/pages/index.ts | 1 + .../SubscriptionCreateElement.ts | 6 +- .../SubscriptionEditElement.ts | 259 ++++++++++++++++++ .../SubscriptionListElement.ts | 21 ++ src/services/SubscriptionService.ts | 6 +- src/styles/app-pagination/app-pagination.scss | 3 +- yarn.lock | 38 +-- 13 files changed, 369 insertions(+), 38 deletions(-) create mode 100644 src/pages/subscription-edit/SubscriptionEditElement.ts diff --git a/package.json b/package.json index f8212b7..22e29b7 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "uglify-js": "^3.13.9", "uglifyjs-webpack-plugin": "^2.2.0", "webpack": "^5.38.1", - "webpack-cli": "^4.7.0", + "webpack-cli": "^4.7.2", "webpack-dev-server": "^3.11.2" } } diff --git a/src/components/app-dropdown/AppDropdownElement.ts b/src/components/app-dropdown/AppDropdownElement.ts index 8b62bc9..fe79955 100644 --- a/src/components/app-dropdown/AppDropdownElement.ts +++ b/src/components/app-dropdown/AppDropdownElement.ts @@ -33,6 +33,7 @@ class AppDropdownElement extends BaseComponentElement { page: number = 1; rpp: number = 30; validator: Validator; + itemValue: any = null; constructor() { super(); @@ -119,7 +120,10 @@ class AppDropdownElement extends BaseComponentElement { }; get selectedItem() { - const { value, valuekey, items } = this; + const { value, valuekey, items, itemValue } = this; + if (itemValue) { + return itemValue; + } const item = items?.find((item) => { return value == item[valuekey]; }); @@ -173,6 +177,7 @@ class AppDropdownElement extends BaseComponentElement { itemSelected = (e) => { const value = (e.target as HTMLSpanElement).getAttribute('data-value'); + this.itemValue = null; this.setValue(value); this.setOpen(false); this.appForm?.inputChange(e); @@ -183,6 +188,11 @@ class AppDropdownElement extends BaseComponentElement { this.update(); }; + setItemValue = (itemValue) => { + this.itemValue = itemValue; + this.update(); + } + render = () => { const { label, error, errorMessage, isOpen, searchPhrase, items, selectedItem, displaykey, valuekey } = this; diff --git a/src/components/app-form/AppFormElement.ts b/src/components/app-form/AppFormElement.ts index ed946f3..af7d956 100644 --- a/src/components/app-form/AppFormElement.ts +++ b/src/components/app-form/AppFormElement.ts @@ -77,13 +77,27 @@ class AppFormElement extends BaseComponentElement { formObject[input.name] = input._value; }); this.appDropdown?.forEach((input: AppDropdownElement) => { - if (input.required && input.value) { - formObject[input.name] = input._value; - } + formObject[input.name] = input._value; }); return formObject; } + set = (data): any => { + for (let i = 0; i < this.inputField.length; i++) { + const input = this.inputField[i]; + if(data?.[input.name]) { + input._value = data[input.name] + this.update() + } + } + this.appDropdown?.forEach((input: AppDropdownElement) => { + if(data?.[input.name]) { + input.setValue(data[input.name]) + this.update() + } + }); + } + getInput = (name: string): InputFieldElement | AppDropdownElement => { let formObject; this.inputField.forEach((input: InputFieldElement) => { diff --git a/src/components/input-field/InputFieldElement.ts b/src/components/input-field/InputFieldElement.ts index 3da6361..d970dd6 100644 --- a/src/components/input-field/InputFieldElement.ts +++ b/src/components/input-field/InputFieldElement.ts @@ -18,6 +18,7 @@ class InputFieldElement extends BaseComponentElement { @target main: HTMLElement; @target inp: HTMLElement; @closest appForm: AppFormElement; + @attr disabled: string; valid: boolean; displayError: boolean; randId: string; @@ -36,12 +37,16 @@ class InputFieldElement extends BaseComponentElement { //this.validate(); }; + attributeChangedCallback() { + this.update(); + } + setError = (error) => { this.validator.error = error; }; get error(): string { - return this.validator.error; + return this.validator?.error; } get isValid(): boolean { @@ -53,8 +58,23 @@ class InputFieldElement extends BaseComponentElement { } get _value() { + if (this.type == 'checkbox') { + return (this.inp as HTMLInputElement)?.checked; + } return (this.inp as HTMLInputElement)?.value; } + + get _disabled() { + return this.disabled == "true" + } + + set _value(value) { + if (this.type == 'checkbox') { + (this.inp as HTMLInputElement).checked = (value as boolean); + } else { + (this.inp as HTMLInputElement).value = (value as string); + } + } validate = (): boolean => { const valid = this.validator.validate(); @@ -88,7 +108,6 @@ class InputFieldElement extends BaseComponentElement { }; render = (): TemplateResult => { - console.log('e'); const renderMessage = (label: string) => { if (this.label) { return html``; @@ -113,6 +132,7 @@ class InputFieldElement extends BaseComponentElement { step="0.01" data-target="input-field.inp" id="${this.randId}" + ?disabled=${this._disabled} app-action=" input:input-field#inputChange blur:input-field#validateDisplay ${this.customAction ? this.customAction : ''} " />`; @@ -122,6 +142,7 @@ class InputFieldElement extends BaseComponentElement { autocomplete="${this.name}" type="${this.type}" data-target="input-field.inp" + ?disabled=${this._disabled} id="${this.randId}" app-action=" input:input-field#inputChange diff --git a/src/core/services/app-service/AppService.ts b/src/core/services/app-service/AppService.ts index a4c3d37..674a052 100644 --- a/src/core/services/app-service/AppService.ts +++ b/src/core/services/app-service/AppService.ts @@ -50,7 +50,7 @@ class AppService { } }; - delete = async (url: string, data: Object, headersParam: HeadersInit): Promise => { + delete = async (url: string, data?: Object, headersParam?: HeadersInit): Promise => { headersParam = { ...headersParam, Authorization: `BEARER ${this.appMain?.authStore?.token}`, diff --git a/src/core/services/base-service/BaseService.ts b/src/core/services/base-service/BaseService.ts index b412bc4..ade5b48 100644 --- a/src/core/services/base-service/BaseService.ts +++ b/src/core/services/base-service/BaseService.ts @@ -7,20 +7,20 @@ class BaseService { return this.appService.get(this.endpoint, params, headers); }; - get = (params?: Object, headers?: HeadersInit) => { - return this.appService.get(this.endpoint, params, headers); + get = (id: string, params?: Object, headers?: HeadersInit) => { + return this.appService.get(this.endpoint + `/${id}`, params, headers); }; - put = (data?: Object, headers?: HeadersInit) => { - return this.appService.put(this.endpoint, data, headers); + put = (id: string, data?: any, headers?: HeadersInit) => { + return this.appService.put(this.endpoint + `/${id || data?.id || ''}`, data, headers); }; post = (data?: Object, headers?: HeadersInit) => { return this.appService.post(this.endpoint, data, headers); }; - delete = (data?: Object, headers?: HeadersInit) => { - return this.appService.delete(this.endpoint, data, headers); + delete = (id:string, data?: any, headers?: HeadersInit) => { + return this.appService.delete(this.endpoint + `/${id || data?.id || ''}`, data, headers); }; } diff --git a/src/pages/index.ts b/src/pages/index.ts index 314fc18..d9c7138 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -10,3 +10,4 @@ export * from './transaction-create/TransactionCreateElement'; export * from './subscription-create/SubscriptionCreateElement'; export * from './subscription-list/SubscriptionListElement'; export * from './wallet-page/WalletPageElement'; +export * from './subscription-edit/SubscriptionEditElement'; \ No newline at end of file diff --git a/src/pages/subscription-create/SubscriptionCreateElement.ts b/src/pages/subscription-create/SubscriptionCreateElement.ts index dc6c9ee..52c2913 100644 --- a/src/pages/subscription-create/SubscriptionCreateElement.ts +++ b/src/pages/subscription-create/SubscriptionCreateElement.ts @@ -142,18 +142,18 @@ class SubscriptionCreateElement extends BasePageElement { if (response?.id) { this.appMain.triggerTransactionUpdate(); - this.appMain.pushToast('success', 'Transaction created successfully!'); + this.appMain.pushToast('success', 'Subscription created successfully!'); if (walletData.walletId) { this.appMain?.closeModal(); } else { - this.routerService.goTo('/history', { + this.routerService.goTo('/subscriptions', { walletId: response.walletId, }); } } } catch (err) { - this.errorMessage = 'Unable to create transaction!'; + this.errorMessage = 'Unable to create subscription!'; this.update(); } }; diff --git a/src/pages/subscription-edit/SubscriptionEditElement.ts b/src/pages/subscription-edit/SubscriptionEditElement.ts new file mode 100644 index 0000000..c19fd9a --- /dev/null +++ b/src/pages/subscription-edit/SubscriptionEditElement.ts @@ -0,0 +1,259 @@ +import { targets, controller, target } from '@github/catalyst'; +import { html, TemplateResult } from 'core/utils'; +import { + AuthService, + SubscriptionService, + SubscriptionTypeService, + TransactionTypeService, + WalletService, +} from 'services/'; +import { AppFormElement, InputFieldElement } from 'components/'; +import { BasePageElement } from 'common/'; +import { AppDropdownElement } from 'components/app-dropdown/AppDropdownElement'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +dayjs.extend(utc); + +@controller +class SubscriptionEditElement extends BasePageElement { + @targets inputs: Array; + @target appForm: AppFormElement; + private subscriptionService: SubscriptionService; + private transactionTypeService: TransactionTypeService; + private subscriptionTypeService: SubscriptionTypeService; + private walletService: WalletService; + walletData: any = null; + authService: AuthService; + errorMessage: string; + private initial: boolean = false; + constructor() { + super({ + title: 'Edit Subscription', + }); + } + elementConnected = (): void => { + this.walletService = new WalletService(this.appMain?.appService); + this.subscriptionService = new SubscriptionService(this.appMain?.appService); + this.transactionTypeService = new TransactionTypeService(this.appMain?.appService); + this.subscriptionTypeService = new SubscriptionTypeService(this.appMain?.appService); + this.authService = new AuthService(this.appMain.appService); + this.walletData = this.getData(); + this.getSubscription(this.walletData.id); + this.update(); + if (this.walletData && this.walletData.walletId) { + this.setTransactionType(); + } else { + this.initial = true; + } + }; + + get hasEndCheck(): InputFieldElement | AppDropdownElement { + for (const i in this.inputs) { + if (this.inputs[i]?.name == 'hasEnd') { + return this.inputs[i]; + } + } + } + + get nameInput(): InputFieldElement | AppDropdownElement { + for (const i in this.inputs) { + if (this.inputs[i]?.name == 'name') { + return this.inputs[i]; + } + } + } + + get values(): any { + const formObject: any = {}; + this.inputs.forEach((input: InputFieldElement) => { + const inputType = input.inp; + formObject[input.name] = (inputType as HTMLInputElement).value; + }); + return formObject; + } + + setTransactionType = async () => { + this.appForm.isValid = false; + try { + const response = await this.transactionTypeService.getAll(); + this.walletData.transactionTypeId = response?.find((type) => type.type == this.walletData.transactionType)?.id; + } catch (err) { + } finally { + this.appForm.isValid = true; + } + }; + + getSubscription = async (id) => { + try { + const response = await this.subscriptionService.get(id, { + embed: 'Wallet' + }); + const wallet = this.appForm.getInput('wallet'); + if (wallet) { + (wallet as AppDropdownElement).setItemValue(response.wallet); + } + response.wallet = response.walletId + response.endDate = dayjs(response.endDate).format('YYYY-MM-DD') + this.appForm.set(response); + } catch (err) { + + } + } + + getWallets = async (options): Promise => { + try { + const response = await this.walletService.getAll(options); + return response; + } catch (err) {} + }; + + getTypes = async (options): Promise => { + try { + const response = await this.transactionTypeService.getAll(options); + return response; + } catch (err) {} + }; + + getSubs = async (options): Promise => { + try { + const response = await this.subscriptionTypeService.getAll(options); + return response; + } catch (err) {} + }; + + onSubmit = async (values): Promise => { + try { + if (!this.validate()) { + return; + } + + const { + description: description, + wallet: walletId, + amount, + endDate, + } = values; + + const endDateFormat = dayjs(endDate).utc(true).format(); + + const walletData = this.walletData; + + const formData = { + description, + amount, + hasEnd: (this.hasEndCheck?.inp as HTMLInputElement)?.checked, + endDate: endDateFormat, + walletId: walletData && walletData.walletId ? walletData.walletId : walletId, + }; + const response = await this.subscriptionService.put(this.walletData.id, formData); + + if (response?.id) { + this.appMain.triggerTransactionUpdate(); + this.appMain.pushToast('success', 'Subscription edited successfully!'); + + if (walletData.walletId) { + this.appMain?.closeModal(); + } else { + this.routerService.goTo('/subscriptions', { + walletId: response.walletId, + }); + } + } + } catch (err) { + this.errorMessage = 'Unable to edit subscription!'; + this.update(); + } + }; + + validate(): boolean { + let _return = true; + this.inputs.forEach((input: InputFieldElement) => { + const valid: boolean = input.validate(); + if (!valid) _return = false; + }); + return _return; + } + + onCheck = () => { + this.appForm.update(); + this.appForm.validate(); + this.appForm.update(); + }; + + renderForms = () => { + const renderInput = (type, name, label, rules, hide?, customAction?) => { + return html``; + }; + + const renderNumericInput = (pattern, name, label, rules, hide?, customAction?) => { + if (hide) { + return html``; + } + return html``; + }; + + const renderDropdown = (fetch, name, label, rules, hide?) => { + if (hide) { + return html``; + } + return html``; + }; + return html` +
+ ${renderNumericInput('^d+(?:.d{1,2})?$', 'amount', 'Amount', 'required', false)} + ${renderInput('text', 'description', 'Description', 'required')} + ${renderInput('checkbox', 'hasEnd', 'Existing End Date', '', false, 'change:subscription-edit#onCheck')} + ${renderInput( + 'date', + 'endDate', + 'End date', + 'required', + !(this.hasEndCheck?.inp as HTMLInputElement)?.checked + )} + ${renderDropdown( + 'subscription-edit#getWallets', + 'wallet', + 'Wallet', + 'required', + this.walletData && this.walletData.walletId + )} + ${this.errorMessage ? html`
${this.errorMessage}
` : html``}`; + }; + + render = (): TemplateResult => { + return html` + + + `; + }; +} + +export type { SubscriptionEditElement }; diff --git a/src/pages/subscription-list/SubscriptionListElement.ts b/src/pages/subscription-list/SubscriptionListElement.ts index ae30d66..61f8d63 100644 --- a/src/pages/subscription-list/SubscriptionListElement.ts +++ b/src/pages/subscription-list/SubscriptionListElement.ts @@ -33,6 +33,23 @@ class SubscriptionListElement extends BasePageElement { this.pagination?.executeFetch(); }; + subscriptionEdit = (id) => { + const _modal = this.appMain.appModal; + if (_modal) { + this.appMain.closeModal(); + } else { + this.appMain.createModal('subscription-edit', { + id: id + }); + } + } + + subscriptionEnd = (id) => { + if (confirm('Are you sure you want to end this subscription?')) { + this.subscriptionService.endSubscription(id); + } + } + renderSubscription = (item) => html` ${dayjs(item.lastTransactionDate).format("MMM DD 'YY")} every ${item.customRange} ${item.rangeName} @@ -50,6 +67,10 @@ class SubscriptionListElement extends BasePageElement { (${item.currency ? item.currency : 'USD'}) + + + + `; getSubscriptions = async (options): Promise => { diff --git a/src/services/SubscriptionService.ts b/src/services/SubscriptionService.ts index a5c89fa..f1b45a6 100644 --- a/src/services/SubscriptionService.ts +++ b/src/services/SubscriptionService.ts @@ -1,9 +1,13 @@ import { AppService, BaseService } from 'core/services'; class SubscriptionService extends BaseService { - constructor(appService: AppService) { + constructor(public appService: AppService) { super('/subscription', appService); } + + endSubscription = (id) => { + return this.appService.put(this.endpoint + `/end/${id || ''}`, null, null); + }; } export default SubscriptionService; diff --git a/src/styles/app-pagination/app-pagination.scss b/src/styles/app-pagination/app-pagination.scss index d1da237..27a822b 100644 --- a/src/styles/app-pagination/app-pagination.scss +++ b/src/styles/app-pagination/app-pagination.scss @@ -138,13 +138,14 @@ app-pagination { grid-template-columns: repeat(6, 1fr); } &.col-subscription { - grid-template-columns: 1fr 2fr 10fr 1fr 2fr; + grid-template-columns: 1fr 2fr 10fr 1fr 2fr 1fr; } td, th { margin: 0 12px; overflow: hidden; // Or flex might break list-style: none; + align-self: center; &.--left { text-align: left; } diff --git a/yarn.lock b/yarn.lock index 9d60438..e3f0e70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1207,22 +1207,22 @@ "@webassemblyjs/ast" "1.11.0" "@xtuc/long" "4.2.2" -"@webpack-cli/configtest@^1.0.3": - version "1.0.3" - resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.3.tgz" - integrity sha512-WQs0ep98FXX2XBAfQpRbY0Ma6ADw8JR6xoIkaIiJIzClGOMqVRvPCWqndTxf28DgFopWan0EKtHtg/5W1h0Zkw== +"@webpack-cli/configtest@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.4.tgz#f03ce6311c0883a83d04569e2c03c6238316d2aa" + integrity sha512-cs3XLy+UcxiP6bj0A6u7MLLuwdXJ1c3Dtc0RkKg+wiI1g/Ti1om8+/2hc2A2B60NbBNAbMgyBMHvyymWm/j4wQ== -"@webpack-cli/info@^1.2.4": - version "1.2.4" - resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.4.tgz" - integrity sha512-ogE2T4+pLhTTPS/8MM3IjHn0IYplKM4HbVNMCWA9N4NrdPzunwenpCsqKEXyejMfRu6K8mhauIPYf8ZxWG5O6g== +"@webpack-cli/info@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.3.0.tgz#9d78a31101a960997a4acd41ffd9b9300627fe2b" + integrity sha512-ASiVB3t9LOKHs5DyVUcxpraBXDOKubYu/ihHhU+t1UPpxsivg6Od2E2qU4gJCekfEddzRBzHhzA/Acyw/mlK/w== dependencies: envinfo "^7.7.3" -"@webpack-cli/serve@^1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.4.0.tgz" - integrity sha512-xgT/HqJ+uLWGX+Mzufusl3cgjAcnqYYskaB7o0vRcwOEfuu6hMzSILQpnIzFMGsTaeaX4Nnekl+6fadLbl1/Vg== +"@webpack-cli/serve@^1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.5.1.tgz#b5fde2f0f79c1e120307c415a4c1d5eb15a6f278" + integrity sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -6595,15 +6595,15 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -webpack-cli@^4.7.0: - version "4.7.0" - resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.7.0.tgz" - integrity sha512-7bKr9182/sGfjFm+xdZSwgQuFjgEcy0iCTIBxRUeteJ2Kr8/Wz0qNJX+jw60LU36jApt4nmMkep6+W5AKhok6g== +webpack-cli@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.7.2.tgz#a718db600de6d3906a4357e059ae584a89f4c1a5" + integrity sha512-mEoLmnmOIZQNiRl0ebnjzQ74Hk0iKS5SiEEnpq3dRezoyR3yPaeQZCMCe+db4524pj1Pd5ghZXjT41KLzIhSLw== dependencies: "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.0.3" - "@webpack-cli/info" "^1.2.4" - "@webpack-cli/serve" "^1.4.0" + "@webpack-cli/configtest" "^1.0.4" + "@webpack-cli/info" "^1.3.0" + "@webpack-cli/serve" "^1.5.1" colorette "^1.2.1" commander "^7.0.0" execa "^5.0.0" From 3320d1ad09cd0405dbe2b2dc2afc5efd627a722f Mon Sep 17 00:00:00 2001 From: Fran Jurmanovic Date: Fri, 30 Jul 2021 22:26:09 +0200 Subject: [PATCH 04/11] transaction edit --- .../app-pagination/AppPaginationElement.ts | 14 ++ src/pages/index.ts | 3 +- .../SubscriptionEditElement.ts | 8 +- .../TransactionEditElement.ts | 235 ++++++++++++++++++ src/styles/app-pagination/app-pagination.scss | 3 +- 5 files changed, 257 insertions(+), 6 deletions(-) create mode 100644 src/pages/transaction-edit/TransactionEditElement.ts diff --git a/src/components/app-pagination/AppPaginationElement.ts b/src/components/app-pagination/AppPaginationElement.ts index fd91bdf..fb0fcc7 100644 --- a/src/components/app-pagination/AppPaginationElement.ts +++ b/src/components/app-pagination/AppPaginationElement.ts @@ -95,6 +95,17 @@ class AppPaginationElement extends BaseComponentElement { } }; + transactionEdit = (id) => { + const _modal = this.appMain.appModal; + if (_modal) { + this.appMain.closeModal(); + } else { + this.appMain.createModal('transaction-edit', { + id: id + }); + } + } + render = (): TemplateResult => { const { rpp, totalItems, page, items } = this; @@ -117,6 +128,9 @@ class AppPaginationElement extends BaseComponentElement { (${item.currency ? item.currency : 'USD'}) + + + `; const renderItems = this.customRenderItems diff --git a/src/pages/index.ts b/src/pages/index.ts index d9c7138..fd97128 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -10,4 +10,5 @@ export * from './transaction-create/TransactionCreateElement'; export * from './subscription-create/SubscriptionCreateElement'; export * from './subscription-list/SubscriptionListElement'; export * from './wallet-page/WalletPageElement'; -export * from './subscription-edit/SubscriptionEditElement'; \ No newline at end of file +export * from './subscription-edit/SubscriptionEditElement'; +export * from './transaction-edit/TransactionEditElement'; \ No newline at end of file diff --git a/src/pages/subscription-edit/SubscriptionEditElement.ts b/src/pages/subscription-edit/SubscriptionEditElement.ts index c19fd9a..7000197 100644 --- a/src/pages/subscription-edit/SubscriptionEditElement.ts +++ b/src/pages/subscription-edit/SubscriptionEditElement.ts @@ -38,8 +38,8 @@ class SubscriptionEditElement extends BasePageElement { this.subscriptionTypeService = new SubscriptionTypeService(this.appMain?.appService); this.authService = new AuthService(this.appMain.appService); this.walletData = this.getData(); - this.getSubscription(this.walletData.id); this.update(); + this.getSubscription(this.walletData?.id); if (this.walletData && this.walletData.walletId) { this.setTransactionType(); } else { @@ -92,8 +92,8 @@ class SubscriptionEditElement extends BasePageElement { if (wallet) { (wallet as AppDropdownElement).setItemValue(response.wallet); } - response.wallet = response.walletId - response.endDate = dayjs(response.endDate).format('YYYY-MM-DD') + response.wallet = response.walletId; + response.endDate = dayjs(response.endDate).format('YYYY-MM-DD'); this.appForm.set(response); } catch (err) { @@ -151,7 +151,7 @@ class SubscriptionEditElement extends BasePageElement { this.appMain.triggerTransactionUpdate(); this.appMain.pushToast('success', 'Subscription edited successfully!'); - if (walletData.walletId) { + if (walletData.id) { this.appMain?.closeModal(); } else { this.routerService.goTo('/subscriptions', { diff --git a/src/pages/transaction-edit/TransactionEditElement.ts b/src/pages/transaction-edit/TransactionEditElement.ts new file mode 100644 index 0000000..28d6074 --- /dev/null +++ b/src/pages/transaction-edit/TransactionEditElement.ts @@ -0,0 +1,235 @@ +import { targets, controller, target } from '@github/catalyst'; +import { html, TemplateResult } from 'core/utils'; +import { AuthService, TransactionsService, TransactionTypeService, WalletService } from 'services/'; +import { AppFormElement, InputFieldElement } from 'components/'; +import { RouterService } from 'core/services'; +import { BasePageElement } from 'common/'; +import { AppDropdownElement } from 'components/app-dropdown/AppDropdownElement'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +dayjs.extend(utc); + +@controller +class TransactionEditElement extends BasePageElement { + @targets inputs: Array; + @target appForm: AppFormElement; + private transactionService: TransactionsService; + private transactionTypeService: TransactionTypeService; + private walletService: WalletService; + walletData: any = null; + authService: AuthService; + errorMessage: string; + private initial: boolean = false; + constructor() { + super({ + title: 'Edit Transaction', + }); + } + elementConnected = (): void => { + this.walletService = new WalletService(this.appMain?.appService); + this.transactionService = new TransactionsService(this.appMain?.appService); + this.transactionTypeService = new TransactionTypeService(this.appMain?.appService); + this.authService = new AuthService(this.appMain.appService); + this.walletData = this.getData(); + this.update(); + this.getTransaction(this.walletData?.id) + if (this.walletData && this.walletData.walletId) { + this.setTransactionType(); + } else { + this.initial = true; + } + }; + + getTransaction = async (id) => { + try { + const response = await this.transactionService.get(id, { + embed: 'Wallet,TransactionType' + }); + const wallet = this.appForm.getInput('wallet'); + if (wallet) { + (wallet as AppDropdownElement).setItemValue(response.wallet); + } + const transactionType = this.appForm.getInput('transactionType'); + if (transactionType) { + (transactionType as AppDropdownElement).setItemValue(response.transactionType); + } + response.wallet = response.walletId; + response.transactionType = response.transactionTypeId; + response.transactionDate = dayjs(response.transactionDate).format('YYYY-MM-DD'); + this.appForm.set(response); + } catch (err) { + + } + } + + get nameInput(): InputFieldElement | AppDropdownElement { + for (const i in this.inputs) { + if (this.inputs[i]?.name == 'name') { + return this.inputs[i]; + } + } + } + + get values(): any { + const formObject: any = {}; + this.inputs.forEach((input: InputFieldElement) => { + const inputType = input.inp; + formObject[input.name] = (inputType as HTMLInputElement).value; + }); + return formObject; + } + + setTransactionType = async () => { + this.appForm.isValid = false; + try { + const response = await this.transactionTypeService.getAll(); + this.walletData.transactionTypeId = response?.find((type) => type.type == this.walletData.transactionType)?.id; + } catch (err) { + } finally { + this.appForm.isValid = true; + } + }; + + getWallets = async (options): Promise => { + try { + const response = await this.walletService.getAll(options); + return response; + } catch (err) {} + }; + + getTypes = async (options): Promise => { + try { + const response = await this.transactionTypeService.getAll(options); + return response; + } catch (err) {} + }; + + onSubmit = async (values): Promise => { + try { + if (!this.validate()) { + return; + } + + const { + description: description, + wallet: walletId, + amount, + transactionType: transactionTypeId, + transactionDate, + } = values; + + const formattedDate = dayjs(transactionDate).utc(true).format(); + + const walletData = this.walletData; + + const formData = { + description, + amount, + walletId: walletData && walletData.walletId ? walletData.walletId : walletId, + transactionDate: formattedDate, + transactionTypeId: + walletData && walletData.transactionTypeId ? walletData.transactionTypeId : transactionTypeId, + }; + + const response = await this.transactionService.put(this.walletData?.id, formData); + + if (response?.id) { + this.appMain.triggerTransactionUpdate(); + this.appMain.pushToast('success', 'Transaction edited successfully!'); + + if (walletData.id) { + this.appMain?.closeModal(); + } else { + this.routerService.goTo('/history', { + walletId: response.walletId, + }); + } + } + } catch (err) { + this.errorMessage = 'Unable to edit transaction!'; + this.update(); + } + }; + + validate(): boolean { + let _return = true; + this.inputs.forEach((input: InputFieldElement) => { + const valid: boolean = input.validate(); + if (!valid) _return = false; + }); + return _return; + } + + render = (): TemplateResult => { + const renderInput = (type, name, label, rules, hide?, customAction?) => { + if (hide) { + return html``; + } + return html``; + }; + + const renderNumericInput = (pattern, name, label, rules, hide?, customAction?) => { + if (hide) { + return html``; + } + return html``; + }; + + const renderDropdown = (fetch, name, label, rules, hide?) => { + if (hide) { + return html``; + } + return html``; + }; + + return html` + + ${renderNumericInput('^d+(?:.d{1,2})?$', 'amount', 'Amount', 'required', false)} + ${renderInput('text', 'description', 'Description', 'required')} + ${renderInput('date', 'transactionDate', 'Transaction date', 'required')} + ${renderDropdown( + 'transaction-edit#getWallets', + 'wallet', + 'Wallet', + 'required', + this.walletData && this.walletData.walletId + )} + ${renderDropdown( + 'transaction-edit#getTypes', + 'transactionType', + 'Transaction Type', + 'required', + this.walletData && this.walletData.walletId + )} + ${this.errorMessage ? html`
${this.errorMessage}
` : html``} +
+ `; + }; +} + +export type { TransactionEditElement }; diff --git a/src/styles/app-pagination/app-pagination.scss b/src/styles/app-pagination/app-pagination.scss index 27a822b..bcfc9cd 100644 --- a/src/styles/app-pagination/app-pagination.scss +++ b/src/styles/app-pagination/app-pagination.scss @@ -47,13 +47,14 @@ app-pagination { grid-template-columns: repeat(6, 1fr); } &.col-transactions { - grid-template-columns: auto 1fr auto; + grid-template-columns: 1fr 10fr 1fr 1fr; } td, th { margin: 0 12px; overflow: hidden; // Or flex might break list-style: none; + align-self: center; &.--left { text-align: left; } From 4abcba0143cfc1d577dffd0d7d4137b7a7ef2240 Mon Sep 17 00:00:00 2001 From: Fran Jurmanovic Date: Fri, 30 Jul 2021 23:41:02 +0200 Subject: [PATCH 05/11] wallet edit --- src/common/core/BaseElement/BaseElement.ts | 2 + src/components/app-link/AppLinkElement.ts | 10 +- src/components/app-menu/AppMenuElement.ts | 8 +- .../app-pagination/AppPaginationElement.ts | 8 ++ src/components/menu-item/MenuItemElement.ts | 13 ++- src/pages/index.ts | 3 +- src/pages/wallet-edit/WalletEditElement.ts | 98 +++++++++++++++++++ src/pages/wallet-list/WalletListElement.ts | 29 +++++- src/styles/app-pagination/app-pagination.scss | 3 + 9 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 src/pages/wallet-edit/WalletEditElement.ts diff --git a/src/common/core/BaseElement/BaseElement.ts b/src/common/core/BaseElement/BaseElement.ts index 163694b..81d2249 100644 --- a/src/common/core/BaseElement/BaseElement.ts +++ b/src/common/core/BaseElement/BaseElement.ts @@ -9,6 +9,7 @@ class BaseElement extends HTMLElement { private _appMain: AppMainElement; public loader: Loader; public elementDisconnectCallbacks: Array = []; + public initialized: boolean = false; constructor() { super(); this.connectedCallback = this.connectedCallback.bind(this); @@ -86,6 +87,7 @@ class BaseElement extends HTMLElement { this.bindEvents('data-action'); this.bindEvents('app-action'); this.updateCallback(); + this.initialized = true; }; connectedCallback(): void { diff --git a/src/components/app-link/AppLinkElement.ts b/src/components/app-link/AppLinkElement.ts index ef529df..d994a71 100644 --- a/src/components/app-link/AppLinkElement.ts +++ b/src/components/app-link/AppLinkElement.ts @@ -14,6 +14,7 @@ class AppLinkElement extends BaseComponentElement { @target main: Element; constructor() { super(); + this.attributeChangedCallback = this.attributeChangedCallback.bind(this); } elementConnected = (): void => { @@ -34,6 +35,12 @@ class AppLinkElement extends BaseComponentElement { } }; + attributeChangedCallback(changed) { + if(this.initialized && changed == 'data-title') { + this.update(); + } + } + goTo = (e: Event): void => { e.preventDefault(); if (!isTrue(this.goBack) && this.to) { @@ -46,7 +53,8 @@ class AppLinkElement extends BaseComponentElement { get disabled(): boolean { if (isTrue(this.goBack)) { - return this.routerService.emptyState; + console.log(this.routerService) + return this.routerService?.emptyState; } return false; } diff --git a/src/components/app-menu/AppMenuElement.ts b/src/components/app-menu/AppMenuElement.ts index f9f05fb..a884e03 100644 --- a/src/components/app-menu/AppMenuElement.ts +++ b/src/components/app-menu/AppMenuElement.ts @@ -42,6 +42,7 @@ class AppMenuElement extends BaseComponentElement { setWallets = (wallets: Array, totalWallets: number): void => { this.walletData = wallets; this.totalWallets = totalWallets; + console.log(wallets) this.update(); }; @@ -99,13 +100,15 @@ class AppMenuElement extends BaseComponentElement { render = (): TemplateResult => { const { isAuth, totalWallets, walletData } = this; + console.log(walletData) + const regularMenu = (path: string, title: string, action?: string, className?: string): TemplateResult => { if (action) { return html` - ${title} + `; } - return html`${title}`; + return html``; }; const menuButton = (title: string, action?: string): TemplateResult => { return html`