Merge branch 'develop'

This commit is contained in:
Fran Jurmanović
2021-12-12 19:21:02 +01:00
17 changed files with 279 additions and 67 deletions

View File

@@ -32,6 +32,8 @@
{ {
"ignoreRestSiblings": true "ignoreRestSiblings": true
} }
] ],
"no-console": "warn",
"no-debugger": "warn"
} }
} }

View File

@@ -5,6 +5,7 @@ import { isTrue } from 'core/utils';
class BasePageElement extends BaseElement { class BasePageElement extends BaseElement {
public _pageTitle: string = ''; public _pageTitle: string = '';
public hideTitleHead: boolean = false;
@attr hidetitle: string; @attr hidetitle: string;
@attr customtitle: string; @attr customtitle: string;
private _data: any; private _data: any;
@@ -13,6 +14,7 @@ class BasePageElement extends BaseElement {
if (options?.title) { if (options?.title) {
this._pageTitle = options?.title; this._pageTitle = options?.title;
} }
this.hideTitleHead = options?.hideTitleHead || false;
this.connectedCallback = this.connectedCallback.bind(this); this.connectedCallback = this.connectedCallback.bind(this);
this.disconnectedCallback = this.disconnectedCallback.bind(this); this.disconnectedCallback = this.disconnectedCallback.bind(this);
} }
@@ -36,7 +38,9 @@ class BasePageElement extends BaseElement {
}; };
connectedCallback() { connectedCallback() {
this.appMain.setTitle(this.pageTitle); if (!this.hideTitleHead) {
this.appMain.setTitle(this.pageTitle);
}
super.connectedCallback(); super.connectedCallback();
} }
@@ -53,4 +57,5 @@ export default BasePageElement;
export type OptionType = { export type OptionType = {
title?: string; title?: string;
hideTitleHead?: boolean;
}; };

View File

@@ -5,6 +5,7 @@ import { closest, controller, target } from 'core/utils';
import { AppLoaderElement } from 'components/app-loader/AppLoaderElement'; import { AppLoaderElement } from 'components/app-loader/AppLoaderElement';
import { ToastPortalElement } from 'components/toast-portal/ToastPortalElement'; import { ToastPortalElement } from 'components/toast-portal/ToastPortalElement';
import { BasePageElement } from 'common/'; import { BasePageElement } from 'common/';
import { TransactionsService } from 'services/';
@controller('app-main') @controller('app-main')
class AppMainElement extends HTMLElement { class AppMainElement extends HTMLElement {
@@ -12,6 +13,8 @@ class AppMainElement extends HTMLElement {
public authStore: AuthStore; public authStore: AuthStore;
private httpClient: HttpClient; private httpClient: HttpClient;
public appService: AppService; public appService: AppService;
private transactionsService: TransactionsService;
private subscriptionChecked: boolean = false;
//public shadow: any; //public shadow: any;
@target appModal: AppModalElement; @target appModal: AppModalElement;
@target mainRoot: AppRootElement; @target mainRoot: AppRootElement;
@@ -36,6 +39,7 @@ class AppMainElement extends HTMLElement {
this.httpClient = new HttpClient(); this.httpClient = new HttpClient();
this.appService = new AppService(this, this.httpClient); this.appService = new AppService(this, this.httpClient);
this.routerService = new RouterService(this, mainRoot); this.routerService = new RouterService(this, mainRoot);
this.transactionsService = new TransactionsService(this.appService);
this.authStore = new AuthStore(this, this.appService); this.authStore = new AuthStore(this, this.appService);
this.routerService.setRoutes([ this.routerService.setRoutes([
{ {
@@ -113,6 +117,8 @@ class AppMainElement extends HTMLElement {
this.routerService.init(); this.routerService.init();
this.addEventListener('mousedown', this.setActiveElement, false); this.addEventListener('mousedown', this.setActiveElement, false);
this.addEventListener('tokenchange', this.closeOffToken); this.addEventListener('tokenchange', this.closeOffToken);
this.addEventListener('routechanged', this.checkSubscriptions);
this.checkSubscriptions();
} }
closeOffToken = () => { 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 = () => { disconnectedCallback = () => {
this.removeEventListener('mousedown', this.setActiveElement); this.removeEventListener('mousedown', this.setActiveElement);
this.removeEventListener('tokenchange', this.closeOffToken); this.removeEventListener('tokenchange', this.closeOffToken);
this.removeEventListener('routechanged', this.checkSubscriptions);
}; };
setActiveElement = (e) => { setActiveElement = (e) => {

View File

@@ -60,7 +60,7 @@ class AppPaginationElement extends BaseComponentElement {
this.update(); this.update();
}; };
executeFetch = async (options?): Promise<void> => { executeFetch = async (options?, fetchFunc = this.fetchFunc): Promise<void> => {
if (!options) { if (!options) {
options = { options = {
rpp: this.rpp || 5, rpp: this.rpp || 5,
@@ -70,7 +70,7 @@ class AppPaginationElement extends BaseComponentElement {
try { try {
this.loader?.start?.(); this.loader?.start?.();
const response = await this.fetchFunc(options); const response = await fetchFunc(options);
this.loader?.stop?.(); this.loader?.stop?.();
this.setItems(response?.items); this.setItems(response?.items);
this.totalItems = response?.totalRecords; this.totalItems = response?.totalRecords;

View File

@@ -12,6 +12,7 @@ class InputFieldElement extends BaseComponentElement {
@attr rules: string; @attr rules: string;
@attr pattern: string; @attr pattern: string;
@attr customAction: string; @attr customAction: string;
@attr initialValue: string;
@target main: HTMLElement; @target main: HTMLElement;
@target inp: HTMLElement; @target inp: HTMLElement;
@closest appForm: AppFormElement; @closest appForm: AppFormElement;
@@ -31,6 +32,9 @@ class InputFieldElement extends BaseComponentElement {
this.validator = new Validator(this, this.appForm, this.rules); this.validator = new Validator(this, this.appForm, this.rules);
this.randId = `${name}${randomId()}`; this.randId = `${name}${randomId()}`;
this.update(); this.update();
if (this.initialValue) {
this._value = this.initialValue;
}
}; };
attributeChangedCallback() { attributeChangedCallback() {

View File

@@ -41,6 +41,7 @@ class HistoryPageElement extends BasePageElement {
} }
options.embed = 'TransactionType'; options.embed = 'TransactionType';
options.sortBy = 'transactionDate|desc'; options.sortBy = 'transactionDate|desc';
options.noPending = true;
const response = await this.transactionsService.getAll(options); const response = await this.transactionsService.getAll(options);
return response; return response;
} catch (err) { } catch (err) {
@@ -48,6 +49,12 @@ class HistoryPageElement extends BasePageElement {
} }
}; };
transactionCheck = () => {
this.appMain.createModal('transaction-check', {
autoInit: true,
});
};
render = (): TemplateResult => render = (): TemplateResult =>
HistoryPageElementTemplate({ walletId: this.routerService?.routerState?.data?.walletId }); HistoryPageElementTemplate({ walletId: this.routerService?.routerState?.data?.walletId });
} }

View File

@@ -1,16 +1,12 @@
import { html, nothing, TemplateResult } from 'core/utils'; import { html, nothing, TemplateResult } from 'core/utils';
export default (props): TemplateResult => { export default (props): TemplateResult => {
const { walletId } = props;
const renderWallet = () => {
if (walletId) {
return html`<span>${walletId}</span>`;
}
return nothing;
};
return html`<div> return html`<div>
${renderWallet()} <div class="wallet-buttons">
<button class="btn btn-squared btn-primary" app-action="click:history-page#transactionCheck">
Check Transactions
</button>
</div>
<app-pagination data-target="history-page.pagination"></app-pagination> <app-pagination data-target="history-page.pagination"></app-pagination>
</div>`; </div>`;
}; };

View File

@@ -13,3 +13,4 @@ export * from './wallet-page';
export * from './subscription-edit'; export * from './subscription-edit';
export * from './transaction-edit'; export * from './transaction-edit';
export * from './wallet-edit'; export * from './wallet-edit';
export * from './transaction-check';

View File

@@ -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<void> => {
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`<div class="d--flex">
<button class="btn btn-rounded btn-red" @click="${() => this.transactionEdit(item)}}">Cancel</button>
<button class="btn btn-rounded btn-primary" @click="${() => this.transactionEditSave(item)}}">Save</button>
</div>`;
const renderRegularActions = () => html`<div class="d--flex">
<button class="btn btn-rounded btn-primary" @click="${() => this.transactionEdit(item)}}">Edit</button>
<button class="btn btn-rounded btn-green" @click="${() => this.transactionEditComplete(item)}}">Complete</button>
</div>`;
return html`<tr class="col-checks">
${!item.isEdit
? html`<td class="--left">${dayjs(item.transactionDate).format("MMM DD 'YY")}</td>`
: html`<input-field
data-type="date"
data-name="${item.name}"
data-targets="transaction-check.inputs"
data-initial-value="${item.formattedDate}"
@change="${(e) => (item.newTransactionDate = e.target.value)}"
></input-field>`}
<td class="--left">${item.description}</td>
<td class="balance-cell --right">
<span
class="balance ${item.amount > 0 && item?.transactionType?.type != 'expense' ? '--positive' : '--negative'}"
>
${item?.transactionType?.type == 'expense' ? '- ' : ''}
${Number(item.amount).toLocaleString('en-US', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
})}
</span>
<span class="currency">(${item.currency ? item.currency : 'USD'})</span>
</td>
<td class="--right">${item.isEdit ? renderEditActions() : renderRegularActions()}</td>
</tr>`;
};
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<any> => {
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<any> => {
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 };

View File

@@ -0,0 +1,7 @@
import { html, nothing, TemplateResult } from 'core/utils';
export default (props): TemplateResult => {
return html`<div>
<app-pagination data-target="transaction-check.pagination"></app-pagination>
</div>`;
};

View File

@@ -0,0 +1,2 @@
export { default as TransactionCheckElementTemplate } from './TransactionCheckElementTemplate';
export * from './TransactionCheckElement';

View File

@@ -70,6 +70,7 @@ class WalletPageElement extends BasePageElement {
} }
options.embed = 'TransactionType'; options.embed = 'TransactionType';
options.sortBy = 'transactionDate|desc'; options.sortBy = 'transactionDate|desc';
options.noPending = true;
const response = await this.transactionsService.getAll(options); const response = await this.transactionsService.getAll(options);
return response; return response;
} catch (err) { } catch (err) {

View File

@@ -0,0 +1,9 @@
import { AppService, BaseService } from 'core/services';
class TransactionStatusService extends BaseService {
constructor(appService: AppService) {
super('/transaction-status', appService);
}
}
export default TransactionStatusService;

View File

@@ -4,6 +4,10 @@ class TransactionsService extends BaseService {
constructor(appService: AppService) { constructor(appService: AppService) {
super('/transaction', appService); super('/transaction', appService);
} }
check = (params?: Object, headers?: HeadersInit) => {
return this.appService.get(this.endpoint + '/check', params, headers);
};
} }
export default TransactionsService; export default TransactionsService;

View File

@@ -5,3 +5,4 @@ export { default as TransactionsService } from './TransactionsService';
export { default as TransactionTypeService } from './TransactionTypeService'; export { default as TransactionTypeService } from './TransactionTypeService';
export { default as SubscriptionService } from './SubscriptionService'; export { default as SubscriptionService } from './SubscriptionService';
export { default as SubscriptionTypeService } from './SubscriptionTypeService'; export { default as SubscriptionTypeService } from './SubscriptionTypeService';
export { default as TransactionStatusService } from './TransactionStatusService';

View File

@@ -49,6 +49,9 @@ app-pagination {
&.col-transactions { &.col-transactions {
grid-template-columns: 1fr 10fr 1fr 1fr; grid-template-columns: 1fr 10fr 1fr 1fr;
} }
&.col-checks {
grid-template-columns: 3fr 8fr 2fr 3fr;
}
&.col-wallet { &.col-wallet {
grid-template-columns: 9fr 1fr; grid-template-columns: 9fr 1fr;
} }

View File

@@ -1,66 +1,76 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
app-main { app-main {
* { * {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-size: 14px; font-size: 14px;
} }
input, input,
select, select,
textarea, textarea,
button { button {
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
line-height: inherit; line-height: inherit;
} }
a { a {
color: $blue-07; color: $blue-07;
text-decoration: none; text-decoration: none;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
} }
} }
hr, hr,
.line { .line {
// Horizontal line should look more clean // Horizontal line should look more clean
height: 0; height: 0;
margin: 15px 0; margin: 15px 0;
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border: 0; border: 0;
border-bottom: $border-width $border-style $gray-03; border-bottom: $border-width $border-style $gray-03;
} }
table { table {
border-spacing: 0; border-spacing: 0;
border-collapse: collapse; border-collapse: collapse;
} }
td, td,
th { th {
padding: 0; padding: 0;
} }
button { button {
cursor: pointer; cursor: pointer;
border-radius: 0; border-radius: 0;
} }
details { details {
summary { summary {
cursor: pointer; cursor: pointer;
} }
&:not([open]) { &:not([open]) {
> *:not(summary) { > *:not(summary) {
display: none !important; display: none !important;
} }
} }
} }
[hidden][hidden] { [hidden][hidden] {
display: none !important; display: none !important;
} }
}
.d {
&--flex {
display: flex;
> * {
flex: 1;
margin: 0 4px;
}
}
} }