From 2428870ba893bcedffef0c96f79e02124574e705 Mon Sep 17 00:00:00 2001 From: gofnnp Date: Fri, 26 May 2023 01:35:37 +0400 Subject: [PATCH] =?UTF-8?q?dev=20#14253=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F(=D0=BF=D1=80=D0=BE=D1=84=D0=B8=D0=BB=D1=8C)=20=D0=A1?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D1=83=20=D0=BD=D0=B0=20=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=B2=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D1=8B,=20=D0=BD=D0=B0=20=D1=82=D0=BE,=20=D1=85=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D0=B0=D0=B5=D1=82=20=D0=BB=D0=B8=20=D0=B1=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD=D1=81=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D0=B7=D0=B0,=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BB=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D1=83=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA,=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=84=D0=B8=D0=BA=D1=81=D0=B8=D0=BB=20=D0=BD=D0=B5?= =?UTF-8?q?=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B1=D0=B0=D0=B3?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- angular/src/app/app.component.ts | 2 + angular/src/app/app.module.ts | 12 +- .../interceptors/customer-info.interceptor.ts | 50 ++++++ angular/src/app/interface/data.ts | 65 ++++++- .../app/pages/account/account.component.ts | 9 + .../bonus-program.component.html | 4 +- .../bonus-program/bonus-program.component.ts | 114 +++++++------ .../pages/account/orders/orders.component.ts | 160 +++++++++--------- angular/src/app/pages/cart/cart.component.ts | 71 ++++++-- angular/src/app/pages/main/main.component.ts | 4 +- .../app/pages/products/products.component.ts | 5 +- angular/src/app/services/order.service.ts | 47 ++--- .../src/app/state/config/config.actions.ts | 6 +- .../src/app/state/profile/profile.actions.ts | 15 ++ .../src/app/state/profile/profile.effects.ts | 124 ++++++++++++++ .../src/app/state/profile/profile.reducer.ts | 81 +++++++++ 16 files changed, 601 insertions(+), 168 deletions(-) create mode 100644 angular/src/app/interceptors/customer-info.interceptor.ts create mode 100644 angular/src/app/state/profile/profile.actions.ts create mode 100644 angular/src/app/state/profile/profile.effects.ts create mode 100644 angular/src/app/state/profile/profile.reducer.ts diff --git a/angular/src/app/app.component.ts b/angular/src/app/app.component.ts index f21d6ba..36c08bb 100644 --- a/angular/src/app/app.component.ts +++ b/angular/src/app/app.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { PrimeNGConfig } from 'primeng/api'; import * as ConfigActions from './state/config/config.actions'; +import * as ProfileActions from './state/profile/profile.actions'; @Component({ selector: 'app-root', @@ -16,5 +17,6 @@ export class AppComponent implements OnInit { ngOnInit() { this.primengConfig.ripple = false; this.store.dispatch(ConfigActions.getConfig()); + this.store.dispatch(ProfileActions.getProfile()); } } diff --git a/angular/src/app/app.module.ts b/angular/src/app/app.module.ts index ceccb84..859050e 100644 --- a/angular/src/app/app.module.ts +++ b/angular/src/app/app.module.ts @@ -77,6 +77,9 @@ import { PurchaseInfoComponent } from './components/purchase-info/purchase-info. import { DateFilterComponent } from './components/date-filter/date-filter.component'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatNativeDateModule} from '@angular/material/core'; +import { profileReducer } from './state/profile/profile.reducer'; +import { ProfileEffects } from './state/profile/profile.effects'; +import { CustomerInfoInterceptor } from './interceptors/customer-info.interceptor'; const routes: Routes = [ { path: '', redirectTo: 'products', pathMatch: 'full' }, @@ -152,8 +155,8 @@ const routes: Routes = [ CalendarModule, MatIconModule, MdbCarouselModule, - StoreModule.forRoot({ config: configReducer }), - EffectsModule.forRoot([ConfigEffects]), + StoreModule.forRoot({ config: configReducer, profile: profileReducer }), + EffectsModule.forRoot([ConfigEffects, ProfileEffects]), PaginatorModule, InputTextModule, SidebarModule, @@ -178,6 +181,11 @@ const routes: Routes = [ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' }, }, + { + provide: HTTP_INTERCEPTORS, + useClass: CustomerInfoInterceptor, + multi: true, + }, ], bootstrap: [AppComponent], }) diff --git a/angular/src/app/interceptors/customer-info.interceptor.ts b/angular/src/app/interceptors/customer-info.interceptor.ts new file mode 100644 index 0000000..49fe303 --- /dev/null +++ b/angular/src/app/interceptors/customer-info.interceptor.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { + HttpRequest, + HttpHandler, + HttpEvent, + HttpInterceptor, + HttpResponse, + HttpErrorResponse, +} from '@angular/common/http'; +import { Observable, tap } from 'rxjs'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Injectable() +export class CustomerInfoInterceptor implements HttpInterceptor { + constructor(private _snackBar: MatSnackBar) {} + + intercept( + request: HttpRequest, + next: HttpHandler + ): Observable> { + return next.handle(request).pipe( + tap( + (event) => { + if (event instanceof HttpResponse) { + const body = event.body as any; + const url = new URL(event.url || ''); + if (url.pathname.includes('/icard-proxy/customer_info')) { + if ( + body.customer_info?.errorCode === 'Customer_CustomerNotFound' + ) { + this._snackBar.open( + 'Пользователь не найден в системе! Обратитесь к руководству', + 'Ок' + ); + } else if ('Error' in body) { + this._snackBar.open('Произошла ошибка! Попробуйте снова', 'Ок'); + } + } + } + if (event instanceof HttpErrorResponse) { + this._snackBar.open('Произошла ошибка! Попробуйте снова', 'Ок'); + } + }, + (err) => { + console.error('ERROR FROM INTERCEPTOR: ', err); + } + ) + ); + } +} diff --git a/angular/src/app/interface/data.ts b/angular/src/app/interface/data.ts index 997b90e..0c34541 100644 --- a/angular/src/app/interface/data.ts +++ b/angular/src/app/interface/data.ts @@ -35,7 +35,7 @@ export interface UserDataForm { export interface BonusProgramAccount { // BonusProgramName: string; // BonusProgramTypeID: string; - CardNumber: number; + CardNumber: string; Bonuses: number; // HoldedBonuses: number; // BonusProgramAccounts: BonusProgramAccount[]; @@ -286,3 +286,66 @@ export interface IDateFilter { from?: Date; to?: Date; } + +export interface ICustomerInfo { + anonymized: boolean; + birthday: string | Date; + cards: ICardCustomer[]; + categories: ICategoriesCustomer[]; + comment: string | null; + consentStatus: number; + cultureName: string; + email: string | null; + id: string; + iikoCardOrdersSum: number; + isBlocked: boolean; + isDeleted: boolean; + middleName: string | null; + name: string; + personalDataConsentFrom: string | null; + personalDataConsentTo: string | null; + personalDataProcessingFrom: string | null; + personalDataProcessingTo: string | null; + phone: string; + rank: string | null; + referrerId: string | null; + sex: number; + shouldReceiveLoyaltyInfo: boolean; + shouldReceiveOrderStatusInfo: boolean; + shouldReceivePromoActionsInfo: boolean; + surname: string; + userData: any; + walletBalances: IWalletBalance[]; + errorCode?: string; + + first_name: string | null; + last_name: string | null; + street: string | null; + house: string | null; + flat: string | null; + city: string; + selectedTerminal: ITerminal | null; +} + +export interface ICardCustomer { + Id: string; + IsActivated: boolean; + NetworkId: string | null; + Number: string; + OrganizationId: string; + OrganizationName: string | null; + Track: string; + ValidToDate: string | Date; +} + +export interface ICategoriesCustomer {} + +export interface IWalletBalance { + balance: number; + wallet: { + id: string; + name: string; + programType: string; + type: string; + }; +} diff --git a/angular/src/app/pages/account/account.component.ts b/angular/src/app/pages/account/account.component.ts index 422c07a..5ba9aa1 100644 --- a/angular/src/app/pages/account/account.component.ts +++ b/angular/src/app/pages/account/account.component.ts @@ -11,6 +11,8 @@ import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service'; import { MessageService } from 'primeng/api'; import { lastValueFrom } from 'rxjs'; import { CartService } from 'src/app/services/cart.service'; +import { Store } from '@ngrx/store'; +import { getProfile, getTransactions, getTransactionsInfo } from 'src/app/state/profile/profile.actions'; @Component({ selector: 'app-account', @@ -31,6 +33,7 @@ export class AccountComponent implements OnInit { private jsonRpcService: JsonrpcService, private messageService: MessageService, private cartService: CartService, + private store: Store ) { } public currentPage!: Page; @@ -52,6 +55,12 @@ export class AccountComponent implements OnInit { ngOnInit(): void { this.checkAuthorization(true) + + this.store.dispatch(getProfile()) + this.store.dispatch(getTransactions()) + this.store.dispatch(getTransactionsInfo()) + + this.currentPage = this.pageList[0]; document.body.classList.add( 'woocommerce-account', diff --git a/angular/src/app/pages/account/bonus-program/bonus-program.component.html b/angular/src/app/pages/account/bonus-program/bonus-program.component.html index e0077c9..7a635a3 100644 --- a/angular/src/app/pages/account/bonus-program/bonus-program.component.html +++ b/angular/src/app/pages/account/bonus-program/bonus-program.component.html @@ -20,10 +20,10 @@
Имя - {{ + {{ userName }} - Не задано
diff --git a/angular/src/app/pages/account/bonus-program/bonus-program.component.ts b/angular/src/app/pages/account/bonus-program/bonus-program.component.ts index ce37229..28c630f 100644 --- a/angular/src/app/pages/account/bonus-program/bonus-program.component.ts +++ b/angular/src/app/pages/account/bonus-program/bonus-program.component.ts @@ -1,7 +1,23 @@ -import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; -import { lastValueFrom } from 'rxjs'; -import { orderStatuses, PageList, PageListWithBonus } from 'src/app/app.constants'; -import { BonusProgramAccount, Page, Purchase, Transaction } from 'src/app/interface/data'; +import { + Component, + EventEmitter, + Inject, + Input, + OnInit, + Output, +} from '@angular/core'; +import { distinctUntilChanged, lastValueFrom } from 'rxjs'; +import { + orderStatuses, + PageList, + PageListWithBonus, +} from 'src/app/app.constants'; +import { + BonusProgramAccount, + Page, + Purchase, + Transaction, +} from 'src/app/interface/data'; import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service'; import * as moment from 'moment-timezone'; import * as barcode from 'jsbarcode'; @@ -9,38 +25,36 @@ import { environment } from 'src/environments/environment'; import { AppleWalletService } from 'src/app/services/apple-wallet.service'; import { CookiesService } from 'src/app/services/cookies.service'; import { DOCUMENT } from '@angular/common'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { WpJsonService } from 'src/app/services/wp-json.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { Store } from '@ngrx/store'; +import { selectCustomerInfo } from 'src/app/state/profile/profile.reducer'; @Component({ selector: 'app-bonus-program', templateUrl: './bonus-program.component.html', - styleUrls: ['./bonus-program.component.scss'] + styleUrls: ['./bonus-program.component.scss'], }) export class BonusProgramComponent implements OnInit { @Input() currentPage!: Page; - @Output() deauthorization = new EventEmitter(false) + @Output() deauthorization = new EventEmitter(false); public accountData!: BonusProgramAccount; public purchases: Purchase[] = []; public loadingBonuses: boolean = false; readonly orderStatuses = orderStatuses; readonly moment = moment; - readonly pageList = environment.hasBonusProgram ? PageListWithBonus : PageList; + readonly pageList = environment.hasBonusProgram + ? PageListWithBonus + : PageList; public userName: string = ''; public deviceType: 'ios' | 'android' | null = null; + private profile$ = this.store.select(selectCustomerInfo); constructor( private jsonrpc: JsonrpcService, private appleWallet: AppleWalletService, private cookiesService: CookiesService, @Inject(DOCUMENT) private document: Document, - private http: HttpClient, - private wpJsonService: WpJsonService, - private _snackBar: MatSnackBar - ) { } - - + private store: Store + ) {} ngOnInit(): void { this.getAccountData(); @@ -48,50 +62,54 @@ export class BonusProgramComponent implements OnInit { } getAccountData() { - const token = this.cookiesService.getItem('token') + const token = this.cookiesService.getItem('token'); if (!token) { - this.cookiesService.deleteCookie('token') - this.deauthorization.emit(true) - return + this.cookiesService.deleteCookie('token'); + this.deauthorization.emit(true); + return; } this.loadingBonuses = true; - this.wpJsonService.getCustomerInfo(environment.systemId, token, environment.icardProxy).subscribe({ + this.profile$.pipe(distinctUntilChanged()).subscribe({ next: (res) => { - if (res.customer_info?.errorCode === 'Customer_CustomerNotFound' || 'Error' in res) { - // this._snackBar.open('Пользователь не найден в системе! Обратитесь к руководству', 'Ок') + if (!res) return; + if (res.errorCode === 'Customer_CustomerNotFound' || 'Error' in res) { this.loadingBonuses = false; - return + return; } - this.userName = res.customer_info.name + this.userName = res.name; this.accountData = { - CardNumber: res.customer_info.cards[0]?.Number || '', - Bonuses: res.customer_info.walletBalances[0].balance + CardNumber: res.cards[0]?.Number || '', + Bonuses: res.walletBalances[0].balance, + }; + if (this.document.getElementById('barcode')) { + barcode('#barcode') + .options({ font: 'OCR-B' }) // Will affect all barcodes + .EAN13(`${this.accountData.CardNumber}`.padStart(12, '0'), { + fontSize: 18, + textMargin: 0, + }) + .render(); } - barcode("#barcode") - .options({ font: "OCR-B" }) // Will affect all barcodes - .EAN13(`${this.accountData.CardNumber}`.padStart(12, "0"), { fontSize: 18, textMargin: 0 }) - .render(); this.loadingBonuses = false; }, error: (err) => { - console.error('Error: ', err) - } - }) + console.error('Error: ', err); + }, + }); } getTypeDevice() { - const userAgent = window.navigator.userAgent.toLowerCase() + const userAgent = window.navigator.userAgent.toLowerCase(); const ios = /iphone|ipod|ipad/.test(userAgent); - this.deviceType = ios ? 'ios' : 'android' + this.deviceType = ios ? 'ios' : 'android'; } - async addCardToWallet(e: any) { - e.preventDefault() - const token = this.cookiesService.getItem('token') - const accountData = (await lastValueFrom( - this.jsonrpc - .rpc( + e.preventDefault(); + const token = this.cookiesService.getItem('token'); + const accountData = ( + await lastValueFrom( + this.jsonrpc.rpc( { method: 'getTokenData', params: [], @@ -99,19 +117,17 @@ export class BonusProgramComponent implements OnInit { RpcService.authService, true ) - )).data + ) + ).data; if (token && accountData.user_id) { this.appleWallet.generateCard(token, accountData.user_id).subscribe({ next: (res: any) => { - this.document.location.href = res.url + this.document.location.href = res.url; }, error: (err) => { console.log('Error: ', err); - - } - }) + }, + }); } - } - } diff --git a/angular/src/app/pages/account/orders/orders.component.ts b/angular/src/app/pages/account/orders/orders.component.ts index c776657..6787e96 100644 --- a/angular/src/app/pages/account/orders/orders.component.ts +++ b/angular/src/app/pages/account/orders/orders.component.ts @@ -1,14 +1,36 @@ import { HttpErrorResponse } from '@angular/common/http'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { IDateFilter, Page, Purchase, PurchaseInfo } from 'src/app/interface/data'; +import { + IDateFilter, + Page, + Purchase, + PurchaseInfo, +} from 'src/app/interface/data'; import * as moment from 'moment-timezone'; -import { lastValueFrom } from 'rxjs'; +import { + combineLatest, + combineLatestWith, + distinctUntilChanged, + forkJoin, + lastValueFrom, +} from 'rxjs'; import { WpJsonService } from 'src/app/services/wp-json.service'; import { environment } from 'src/environments/environment'; import { CookiesService } from 'src/app/services/cookies.service'; import { MatDialog } from '@angular/material/dialog'; import { PurchaseInfoComponent } from 'src/app/components/purchase-info/purchase-info.component'; import { dateFilterOptions } from 'src/app/app.constants'; +import { Store, select } from '@ngrx/store'; +import { + selectCustomerInfo, + selectProfileState, + selectTransactions, + selectTransactionsInfo, +} from 'src/app/state/profile/profile.reducer'; +import { + getTransactions, + getTransactionsInfo, +} from 'src/app/state/profile/profile.actions'; @Component({ selector: 'app-orders', @@ -25,16 +47,20 @@ export class OrdersComponent implements OnInit { public purchases: Purchase[] = []; public purchasesShortArray: Purchase[] = []; + private profileState$ = this.store.select(selectCustomerInfo); + private transactionsState$ = this.store.select(selectTransactions); + private transactionsInfoState$ = this.store.select(selectTransactionsInfo); - public dateFilterOptions = dateFilterOptions + public dateFilterOptions = dateFilterOptions; public defaultFilterType = 'currentMonth'; private startOfMonth = moment().startOf('month').format('YYYY-MM-DD HH:mm'); - public filteredOfDatePurchases: Purchase[] = [] + public filteredOfDatePurchases: Purchase[] = []; constructor( private wpJsonService: WpJsonService, private cookiesService: CookiesService, - public dialog: MatDialog + public dialog: MatDialog, + private store: Store ) {} ngOnInit(): void { @@ -42,32 +68,40 @@ export class OrdersComponent implements OnInit { } dateFilter(dateFilter: IDateFilter) { - this.lastViewOrder = 3 + this.lastViewOrder = 3; switch (dateFilter.filterType) { case 'between': this.filteredOfDatePurchases = this.purchases.filter((value) => { - return moment(value.transactionCreateDate).isBetween(dateFilter.from, moment(dateFilter.to).add(1, 'day')) - }) + return moment(value.transactionCreateDate).isBetween( + dateFilter.from, + moment(dateFilter.to).add(1, 'day') + ); + }); break; case 'currentMonth': this.filteredOfDatePurchases = this.purchases.filter((value) => { - return moment(value.transactionCreateDate).isAfter(this.startOfMonth) - }) + return moment(value.transactionCreateDate).isAfter(this.startOfMonth); + }); break; case 'lastMonth': this.filteredOfDatePurchases = this.purchases.filter((value) => { - return moment(value.transactionCreateDate).isBetween(moment(this.startOfMonth).subtract(1, 'month'), this.startOfMonth) - }) + return moment(value.transactionCreateDate).isBetween( + moment(this.startOfMonth).subtract(1, 'month'), + this.startOfMonth + ); + }); break; - + default: - this.filteredOfDatePurchases = this.purchases + this.filteredOfDatePurchases = this.purchases; break; } - this.purchasesShortArray = this.filteredOfDatePurchases.slice(0, this.lastViewOrder); - + this.purchasesShortArray = this.filteredOfDatePurchases.slice( + 0, + this.lastViewOrder + ); } async getOrders() { @@ -77,77 +111,49 @@ export class OrdersComponent implements OnInit { this.deauthorization.emit(true); return; } - const customerInfo = await lastValueFrom( - this.wpJsonService.getCustomerInfo( - environment.systemId, - token, - environment.icardProxy - ) - ); - if ( - customerInfo.customer_info?.errorCode === 'Customer_CustomerNotFound' || - 'Error' in customerInfo - ) { - this.ordersLoadingStatus = false; - return; - } - const purchases: Purchase[] = ( - await lastValueFrom( - this.wpJsonService.getTransactions( - environment.systemId, - token, - environment.icardProxy, - 30 - ) - ) - )[customerInfo.customer_info?.id]; - const purchasesInfo: PurchaseInfo[] = ( - await lastValueFrom( - this.wpJsonService.getTransactionsInfo( - environment.systemId, - token, - environment.icardProxy, - 30 - ) - ) - )['purchase']; + combineLatest([this.transactionsState$, this.transactionsInfoState$]) + .pipe(distinctUntilChanged()) + .subscribe({ + next: ([transactions, transactionsInfo]) => { + if (transactions && transactionsInfo) { + this.purchases = transactions + .map((purchase) => { + return { + ...purchase, + more_info: + transactionsInfo.find( + (purchaseInfo: PurchaseInfo) => + Number(purchaseInfo.number) === purchase.orderNumber + ) || null, + }; + }) + .filter((purchase: Purchase) => + [ + 'PayFromWallet', + 'CancelPayFromWallet', + 'RefillWallet', + ].includes(purchase.transactionType) + ); - this.purchases = purchases - .map((purchase) => { - // const id = purchase.ID.slice(0,36).toLowerCase(); - // purchase.Transactions = transactions.filter((transaction) => { - // const same = transaction.Purchase === id; - // transaction.HasPurchase = same; - // return same; - // }); - if (purchasesInfo) { - purchase.more_info = - purchasesInfo.find( - (purchaseInfo: PurchaseInfo) => - Number(purchaseInfo.number) === purchase.orderNumber - ) || null; - } - return purchase; - }) - .filter((purchase: Purchase) => - ['PayFromWallet', 'CancelPayFromWallet', 'RefillWallet'].includes( - purchase.transactionType - ) - ); - - this.dateFilter({filterType: this.defaultFilterType}) - this.ordersLoadingStatus = false; + this.ordersLoadingStatus = false; + this.dateFilter({ filterType: this.defaultFilterType }); + } + }, + }); } getMoreOrders() { this.lastViewOrder += 4; - this.purchasesShortArray = this.filteredOfDatePurchases.slice(0, this.lastViewOrder); + this.purchasesShortArray = this.filteredOfDatePurchases.slice( + 0, + this.lastViewOrder + ); } showPurchaseInfo(purchaseInfo: PurchaseInfo) { const dialogRef = this.dialog.open(PurchaseInfoComponent, { - data: {purchaseInfo}, + data: { purchaseInfo }, }); } } diff --git a/angular/src/app/pages/cart/cart.component.ts b/angular/src/app/pages/cart/cart.component.ts index 3965755..8c3abb6 100644 --- a/angular/src/app/pages/cart/cart.component.ts +++ b/angular/src/app/pages/cart/cart.component.ts @@ -6,8 +6,11 @@ import { Output, } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; +import { Store } from '@ngrx/store'; import { MessageService } from 'primeng/api'; +import { combineLatest } from 'rxjs'; import { SnackBarComponent } from 'src/app/components/snack-bar/snack-bar.component'; +import { ICardCustomer } from 'src/app/interface/data'; import { Order } from 'src/app/models/order'; import { OrderProduct } from 'src/app/models/order-product'; import { @@ -15,6 +18,12 @@ import { ProductAmountAction, } from 'src/app/services/cart.service'; import { OrderService } from 'src/app/services/order.service'; +import { + selectCustomerCards, + selectCustomerWalletBalance, +} from 'src/app/state/profile/profile.reducer'; +import * as ProfileActions from '../../state/profile/profile.actions'; +import { CookiesService } from 'src/app/services/cookies.service'; @Component({ selector: 'app-cart', @@ -30,18 +39,39 @@ export class CartComponent implements OnInit { public visibleSidebar: boolean = false; public isFullScreen!: boolean; public width!: number; + public CardsCustomer!: ICardCustomer[]; + public WalletBalanceCustomer!: number; + + private CardsCustomer$ = this.store.select(selectCustomerCards); + private WalletBalanceCustomer$ = this.store.select( + selectCustomerWalletBalance + ); constructor( private orderService: OrderService, private cartService: CartService, private messageService: MessageService, - private _snackBar: MatSnackBar + private _snackBar: MatSnackBar, + private store: Store, + private cookiesService: CookiesService ) {} - ngOnInit(): void { + async ngOnInit() { this.width = window.innerWidth; this.changeDullScreenMode(); - this.loadCart(); + await this.loadCart(); + combineLatest([this.CardsCustomer$, this.WalletBalanceCustomer$]).subscribe( + { + next: ([cards, balance]) => { + this.CardsCustomer = cards!; + this.WalletBalanceCustomer = balance!; + if (cards && balance) this.loading = false; + }, + error: (err) => { + console.error('Произошла ошибка!'); + }, + } + ); } // Изменение размера окна @@ -72,27 +102,48 @@ export class CartComponent implements OnInit { async loadCart(): Promise { this.loading = true; this.order = await this.orderService.getOrder(true); + const token = this.cookiesService.getItem('token'); + if (!token) { + this._snackBar.open('Авторизуйтесь!', 'Ок'); + this.userNotFound(); + return; + } if (this.order?.userData?.errorCode === 'Customer_CustomerNotFound') { - this.userNotFound() - return + this.userNotFound(); + return; } if (this.order) this.price = this.order.price; - this.loading = false; + this.store.dispatch(ProfileActions.getProfile()); } userNotFound(event: null = null) { - this.visibleSidebar = false - this._snackBar.open('Пользователь не найден в системе! Обратитесь к руководству', 'Ок') + this.visibleSidebar = false; + // this._snackBar.open('Пользователь не найден в системе! Обратитесь к руководству', 'Ок') } - removeFromCart(event: Event, guid: string): void { + async removeFromCart(event: Event, guid: string) { event.preventDefault(); this.orderService.removeFromCart(guid); + await this.loadCart(); } confirmOrder(event: Event): void { event.preventDefault(); this.showAuthoriztion.emit(true); + if (!this.CardsCustomer[0].IsActivated || !this.CardsCustomer.length) { + this._snackBar.open( + 'Ваша карта неактивна! Обратитесь к руководству', + 'Ок' + ); + return; + } + if (this.WalletBalanceCustomer < this.order.price) { + this._snackBar.open( + 'На Вашей карте недостаточно средств для оформления заказа!', + 'Ок' + ); + return; + } this.orderConfirmed = true; // this.confirm.emit(); } @@ -117,7 +168,7 @@ export class CartComponent implements OnInit { orderSubmitted(orderid: number) { this.visibleSidebar = false; - this._snackBar.open(`Заказ оформлен! Номер заказа: ${orderid}`, 'Ок') + this._snackBar.open(`Заказ оформлен! Номер заказа: ${orderid}`, 'Ок'); } confirmClearCart() { diff --git a/angular/src/app/pages/main/main.component.ts b/angular/src/app/pages/main/main.component.ts index 28918db..ba49f7b 100644 --- a/angular/src/app/pages/main/main.component.ts +++ b/angular/src/app/pages/main/main.component.ts @@ -16,6 +16,7 @@ import { MessagingService } from 'src/app/services/messaging.service'; import { CookiesService } from 'src/app/services/cookies.service'; import { Store } from '@ngrx/store'; import * as ConfigActions from '../../state/config/config.actions' +import { getProfile } from 'src/app/state/profile/profile.actions'; @Component({ selector: 'app-main', @@ -62,7 +63,8 @@ export class MainComponent implements OnInit { ngOnInit(): void { this.appendAccount(); - this.store.dispatch(ConfigActions.getConfig()); + // this.store.dispatch(ConfigActions.getConfig()); + // this.store.dispatch(getProfile()); // this.checkRequestPermission() } diff --git a/angular/src/app/pages/products/products.component.ts b/angular/src/app/pages/products/products.component.ts index 3deee00..c0f7c9e 100644 --- a/angular/src/app/pages/products/products.component.ts +++ b/angular/src/app/pages/products/products.component.ts @@ -1,15 +1,12 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { Group, Modifier, ModifiersGroup, Product } from '../../interface/data'; import { v4 as uuidv4 } from 'uuid'; import { DialogService } from 'primeng/dynamicdialog'; import { ProductModalComponent } from 'src/app/components/product-modal/product-modal.component'; import { WpJsonService } from 'src/app/services/wp-json.service'; import { MessageService } from 'primeng/api'; -import { lastValueFrom } from 'rxjs'; import { CartService } from 'src/app/services/cart.service'; -import { CookiesService } from 'src/app/services/cookies.service'; import { ActivatedRoute, Router } from '@angular/router'; -import { MatSelectChange } from '@angular/material/select'; import { MatBottomSheet } from '@angular/material/bottom-sheet'; import { TerminalListComponent } from 'src/app/components/terminal-list/terminal-list.component'; import { GetTerminalsService } from 'src/app/services/get-terminals.service'; diff --git a/angular/src/app/services/order.service.ts b/angular/src/app/services/order.service.ts index 3ce8816..07ec98a 100644 --- a/angular/src/app/services/order.service.ts +++ b/angular/src/app/services/order.service.ts @@ -18,6 +18,10 @@ import { MessageService } from 'primeng/api'; import { map } from 'rxjs/operators'; import { cloneDeep } from 'lodash'; import { environment } from 'src/environments/environment'; +import { Store } from '@ngrx/store'; +import { getProfile } from '../state/profile/profile.actions'; +import * as ProfileActions from '../state/profile/profile.actions'; +import { selectCustomerInfo } from '../state/profile/profile.reducer'; @Injectable({ providedIn: 'root', @@ -30,7 +34,8 @@ export class OrderService { private wpJsonService: WpJsonService, private jsonRpcService: JsonrpcService, private cookiesService: CookiesService, - private messageService: MessageService + private messageService: MessageService, + private store: Store ) {} async getDeliveryTypes(): Promise { @@ -66,11 +71,8 @@ export class OrderService { }); return this.order; } - const additionalInfo = this.wpJsonService.getCustomerInfo( - environment.systemId, - token, - environment.icardProxy - ); + this.store.dispatch(ProfileActions.getProfile()) + const additionalInfo = this.store.select(selectCustomerInfo); const tokenData = this.jsonRpcService.rpc( { @@ -82,17 +84,23 @@ export class OrderService { ); const info = await lastValueFrom( - forkJoin([additionalInfo, tokenData, products]) + forkJoin([tokenData, products]) ); - const customer_info = info[0]?.customer_info; - - this.order = new Order({ - products: products, - userData: customer_info, - phone: info[1].data?.mobile_number, - token: token, - terminal_id: terminal.id, - }); + + additionalInfo.subscribe({ + next: (value) => { + this.order = new Order({ + products: products, + userData: value! as UserData, + phone: info[0].data?.mobile_number, + token: token, + terminal_id: terminal.id, + }); + }, + error: (err) => { + console.error(err); + } + }) } else if (this.order) { this.order.products.length = 0; } @@ -101,11 +109,12 @@ export class OrderService { } async getProducts(cart: Cart): Promise { - const terminal = - JSON.parse(this.cookiesService.getItem('selectedTerminal') || 'null'); + const terminal = JSON.parse( + this.cookiesService.getItem('selectedTerminal') || 'null' + ); const products: OrderProduct[] = []; if (!terminal) { - return products + return products; } const allData = await lastValueFrom( this.wpJsonService.getAllData(`${terminal.label}${terminal.id}`) diff --git a/angular/src/app/state/config/config.actions.ts b/angular/src/app/state/config/config.actions.ts index 9b6d1bd..9eba7d1 100644 --- a/angular/src/app/state/config/config.actions.ts +++ b/angular/src/app/state/config/config.actions.ts @@ -3,11 +3,11 @@ import { createAction, props } from '@ngrx/store'; export const getConfig = createAction('[Config] Get'); export const getSuccess = createAction( - '[Config] Get Success', - props<{ getSuccessResponse: any }>() + '[Config] Get Success', + props<{ getSuccessResponse: any }>() ); export const getFailure = createAction( '[Config] Get Failure', props<{ error: string }>() -); \ No newline at end of file +); diff --git a/angular/src/app/state/profile/profile.actions.ts b/angular/src/app/state/profile/profile.actions.ts new file mode 100644 index 0000000..5b1c8e7 --- /dev/null +++ b/angular/src/app/state/profile/profile.actions.ts @@ -0,0 +1,15 @@ +import { createAction, props } from '@ngrx/store'; + +export const getProfile = createAction('[Profile] Get Profile'); +export const getTransactions = createAction('[Profile] Get Transactions'); +export const getTransactionsInfo = createAction('[Profile] Get TransactionsInfo'); + +export const getSuccess = createAction( + '[Profile] Get Success', + props<{ getSuccessResponse: any }>() +); + +export const getFailure = createAction( + '[Profile] Get Failure', + props<{ error: string }>() +); diff --git a/angular/src/app/state/profile/profile.effects.ts b/angular/src/app/state/profile/profile.effects.ts new file mode 100644 index 0000000..1d75ef1 --- /dev/null +++ b/angular/src/app/state/profile/profile.effects.ts @@ -0,0 +1,124 @@ +import { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import * as ProfileActions from './profile.actions'; +import { WpJsonService } from 'src/app/services/wp-json.service'; +import { environment } from 'src/environments/environment'; +import { CookiesService } from 'src/app/services/cookies.service'; +import { catchError, exhaustMap, map, of, throwError } from 'rxjs'; + +@Injectable() +export class ProfileEffects { + constructor( + private actions$: Actions, + private wpJsonService: WpJsonService, + private cookiesService: CookiesService + ) {} + + getProfile$ = createEffect(() => { + const token = this.cookiesService.getItem('token'); + return this.actions$.pipe( + ofType(ProfileActions.getProfile), + exhaustMap(() => { + return this.wpJsonService + .getCustomerInfo( + environment.systemId, + token || '', + environment.icardProxy + ) + .pipe( + map((value) => { + if ( + value.customer_info?.errorCode === 'Customer_CustomerNotFound' + ) { + throw new Error('Пользователь не найден в системе! Обратитесь к руководству') + } + if ('Error' in value) { + throw new Error('Ошибка получения данных'); + } + return { + getSuccessResponse: value, + }; + }), + map((value) => ProfileActions.getSuccess(value as any)), + catchError((error) => { + return of(ProfileActions.getFailure({ error })); + }) + ); + }) + ); + }); + + getTransactions$ = createEffect(() => { + const token = this.cookiesService.getItem('token'); + return this.actions$.pipe( + ofType(ProfileActions.getTransactions), + exhaustMap(() => { + return this.wpJsonService + .getTransactions( + environment.systemId, + token || '', + environment.icardProxy, + 60 + ) + .pipe( + map((value) => { + if ( + value.customer_info?.errorCode === 'Customer_CustomerNotFound' + ) { + throw new Error('Пользователь не найден в системе! Обратитесь к руководству') + } + if ('Error' in value) { + throw new Error('Ошибка получения данных'); + } + return { + getSuccessResponse: { + transactions: Object.values(value)[0] + }, + }; + }), + map((value) => ProfileActions.getSuccess(value as any)), + catchError((error) => { + return of(ProfileActions.getFailure({ error })); + }) + ); + }) + ); + }); + + getTransactionsInfo$ = createEffect(() => { + const token = this.cookiesService.getItem('token'); + return this.actions$.pipe( + ofType(ProfileActions.getTransactionsInfo), + exhaustMap(() => { + return this.wpJsonService + .getTransactionsInfo( + environment.systemId, + token || '', + environment.icardProxy, + 60 + ) + .pipe( + map((value) => { + if ( + value.customer_info?.errorCode === 'Customer_CustomerNotFound' + ) { + throw new Error('Пользователь не найден в системе! Обратитесь к руководству') + } + if ('Error' in value) { + throw new Error('Ошибка получения данных'); + } + return { + getSuccessResponse: { + transactionsInfo: value.purchase + }, + }; + }), + map((value) => ProfileActions.getSuccess(value as any)), + catchError((error) => { + return of(ProfileActions.getFailure({ error })); + }) + ); + }) + ); + }); +} diff --git a/angular/src/app/state/profile/profile.reducer.ts b/angular/src/app/state/profile/profile.reducer.ts new file mode 100644 index 0000000..e8641a0 --- /dev/null +++ b/angular/src/app/state/profile/profile.reducer.ts @@ -0,0 +1,81 @@ +import { + createFeatureSelector, + createReducer, + createSelector, + on, +} from '@ngrx/store'; +import { getFailure, getSuccess } from './profile.actions'; +import { ICustomerInfo, Purchase, PurchaseInfo } from 'src/app/interface/data'; + +export interface ProfileState { + customer_info: ICustomerInfo | null; + transactions: Purchase[] | null; + transactionsInfo: PurchaseInfo[] | null; +} + +export const initialProfileState: ProfileState = { + customer_info: null, + transactions: null, + transactionsInfo: null, +}; + +const _profileReducer = createReducer( + initialProfileState, + on(getSuccess, (state, { getSuccessResponse }) => { + return { + ...state, + ...getSuccessResponse, + }; + }), + on(getFailure, (state, { error }) => { + console.error(error); + return state; + }) +); + +export function profileReducer(state: any, action: any) { + return _profileReducer(state, action); +} + +export const selectProfileState = + createFeatureSelector('profile'); + +export const selectCustomerInfo = createSelector( + selectProfileState, + (state) => { + return state.customer_info; + } +); + +export const selectCustomerWalletBalance = createSelector( + selectProfileState, + (state) => { + return state?.customer_info?.walletBalances?.reduce( + (previousValue, currentValue) => { + return previousValue + currentValue.balance; + }, + 0 + ); + } +); + +export const selectCustomerCards = createSelector( + selectProfileState, + (state) => { + return state?.customer_info?.cards + } +); + +export const selectTransactions = createSelector( + selectProfileState, + (state) => { + return state.transactions; + } +); + +export const selectTransactionsInfo = createSelector( + selectProfileState, + (state) => { + return state.transactionsInfo; + } +);