diff --git a/src/app/domain/domain-info/domain-group/domain-group.component.ts b/src/app/domain/domain-info/domain-group/domain-group.component.ts index 751f385b..b0b46e44 100644 --- a/src/app/domain/domain-info/domain-group/domain-group.component.ts +++ b/src/app/domain/domain-info/domain-group/domain-group.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewChildren, QueryList, Output, EventEmitter, OnInit } from '@angular/core'; +import { Component, Output, EventEmitter, OnInit, AfterViewInit, ViewChild } from '@angular/core'; import { FormControl } from '@angular/forms'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; @@ -6,7 +6,7 @@ import { MatTableDataSource } from '@angular/material/table'; import { UntilDestroy } from '@ngneat/until-destroy'; import { Reference, DomainObject } from '@vality/domain-proto/lib/domain'; import sortBy from 'lodash-es/sortBy'; -import { combineLatest, Observable } from 'rxjs'; +import { combineLatest, Observable, ReplaySubject, defer } from 'rxjs'; import { map, switchMap, startWith, shareReplay } from 'rxjs/operators'; import { Columns } from '../../../../components/table'; @@ -28,46 +28,42 @@ interface Params { templateUrl: './domain-group.component.html', styleUrls: ['./domain-group.component.scss'], }) -export class DomainGroupComponent implements OnInit { +export class DomainGroupComponent implements OnInit, AfterViewInit { @Output() refChange = new EventEmitter<{ ref: Reference; obj: DomainObject }>(); - @ViewChildren(MatPaginator) paginator = new QueryList(); - @ViewChildren(MatSort) sort = new QueryList(); + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; searchControl = new FormControl(''); typesControl = new FormControl(this.queryParamsService.params.types || []); - dataSource$: Observable> = - this.domainStoreService.domain$.pipe( - map((domain) => Array.from(domain).map(([ref, obj]) => ({ ref, obj }))), - switchMap((data) => - combineLatest( - data.map((d) => this.metadataService.getDomainObjectType(d.ref)) - ).pipe( - map((r) => - r.map((type, idx) => ({ - ...data[idx], - type, - stringified: JSON.stringify( - objectToJSON([data[idx].obj, data[idx].ref, type]) - ), - })) - ) + dataSource$: Observable> = defer(() => this.init$).pipe( + switchMap(() => this.domainStoreService.domain$), + map((domain) => Array.from(domain).map(([ref, obj]) => ({ ref, obj }))), + switchMap((data) => + combineLatest(data.map((d) => this.metadataService.getDomainObjectType(d.ref))).pipe( + map((r) => + r.map((type, idx) => ({ + ...data[idx], + type, + stringified: JSON.stringify( + objectToJSON([data[idx].obj, data[idx].ref, type]) + ), + })) ) - ), - switchMap((data: DataSourceItem[]) => - combineLatest([ - this.searchControl.valueChanges.pipe(startWith(this.searchControl.value)), - this.typesControl.valueChanges.pipe(startWith(this.typesControl.value)), - this.paginator.changes.pipe(startWith(this.paginator)), - this.sort.changes.pipe(startWith(this.sort)), - ]).pipe( - map(([searchStr, selectedTypes]) => - this.createMatTableDataSource(data, searchStr, selectedTypes) - ) + ) + ), + switchMap((data: DataSourceItem[]) => + combineLatest([ + this.searchControl.valueChanges.pipe(startWith(this.searchControl.value)), + this.typesControl.valueChanges.pipe(startWith(this.typesControl.value)), + ]).pipe( + map(([searchStr, selectedTypes]) => + this.createMatTableDataSource(data, searchStr, selectedTypes) ) - ), - shareReplay({ refCount: true, bufferSize: 1 }) - ); + ) + ), + shareReplay({ refCount: true, bufferSize: 1 }) + ); cols = new Columns('type', 'ref', 'obj', 'actions'); fields$ = this.metadataService.getDomainFields().pipe( map((fields) => sortBy(fields, 'type')), @@ -78,6 +74,8 @@ export class DomainGroupComponent implements OnInit { ); isLoading$ = this.domainStoreService.isLoading$; + private init$ = new ReplaySubject(1); + constructor( private domainStoreService: DomainStoreService, private metadataService: MetadataService, @@ -90,6 +88,10 @@ export class DomainGroupComponent implements OnInit { }); } + ngAfterViewInit() { + this.init$.next(); + } + openDetails(item: DataSourceItem) { this.refChange.emit({ ref: item.ref, obj: item.obj }); } @@ -102,8 +104,8 @@ export class DomainGroupComponent implements OnInit { const dataSource = new MatTableDataSource( data.filter((d) => selectedTypes.includes(d.type)) ); - dataSource.paginator = this.paginator?.first; - dataSource.sort = this.sort?.first; + dataSource.paginator = this.paginator; + dataSource.sort = this.sort; dataSource.sortData = sortData; dataSource.filterPredicate = filterPredicate; dataSource.filter = searchStr.trim(); diff --git a/src/app/domain/domain-info/domain-group/group-domain-objects.ts b/src/app/domain/domain-info/domain-group/group-domain-objects.ts deleted file mode 100644 index 7dc767c6..00000000 --- a/src/app/domain/domain-info/domain-group/group-domain-objects.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Domain } from '@vality/domain-proto/lib/domain'; -import { Field } from '@vality/thrift-ts'; - -import { clearNullFields } from '@cc/utils/thrift-utils'; - -import { DomainGroup } from './domain-group'; - -function getTypeDef(domainObjDef: Field[]) { - return domainObjDef.reduce( - (acc, { name, type }) => ({ - ...acc, - [name]: type, - }), - {} - ); -} - -function getDomainObjType(obj: any, domainObjDef: Field[]): string | 'undef' { - const typeDef = getTypeDef(domainObjDef); - const fieldName = Object.keys(obj)[0]; - const type = typeDef[fieldName]; - return type ? type : 'undef'; -} - -function getDomainObjVal(obj: any): any { - return Object.values(obj)[0]; -} - -function groupResult(result: any, type: string | 'undef', val: any): any { - if (type === 'undef') { - return { undef: null }; - } - return result[type] ? { [type]: result[type].concat(val) } : { [type]: [val] }; -} - -function groupByType(domain: Domain, domainObjDef: Field[]) { - let result = {}; - for (const [ref, domainObject] of domain) { - const cleared = clearNullFields(domainObject); - const type = getDomainObjType(cleared, domainObjDef); - const val = { - ref, - object: getDomainObjVal(cleared), - }; - result = { - ...result, - ...groupResult(result, type, val), - }; - } - return result; -} - -function sortByName(a: DomainGroup, b: DomainGroup): number { - if (a.name < b.name) { - return -1; - } - if (a.name > b.name) { - return 1; - } - return 0; -} - -export function group(domain: Domain, domainObjDef: Field[]): DomainGroup[] { - return Object.entries(groupByType(domain, domainObjDef)) - .reduce((acc, [name, pairs]) => acc.concat({ name, pairs }), []) - .sort(sortByName); -} diff --git a/src/app/domain/domain-info/domain-group/utils/sort-table-data.ts b/src/app/domain/domain-info/domain-group/utils/sort-table-data.ts index fe0c2553..bb89fdca 100644 --- a/src/app/domain/domain-info/domain-group/utils/sort-table-data.ts +++ b/src/app/domain/domain-info/domain-group/utils/sort-table-data.ts @@ -1,6 +1,8 @@ import { MatSort } from '@angular/material/sort'; +import groupBy from 'lodash-es/groupBy'; import sortBy from 'lodash-es/sortBy'; +import { getUnionValue } from '../../../../../utils'; import { objectToJSON } from '../../../../api/utils'; import { DataSourceItem } from '../types/data-source-item'; @@ -10,17 +12,19 @@ export function sortData(data: DataSourceItem[], sort: MatSort): DataSourceItem[ data = sortBy(data, 'type'); break; case 'obj': - data = sortBy(data, [(o) => JSON.stringify(objectToJSON(o.obj))]); + data = sortBy(data, (o) => JSON.stringify(objectToJSON(o.obj))); break; - case 'ref': - data = sortBy(data, [ - (o) => { - const id = o.ref?.['id']; - if (typeof id === 'number') return id; - return JSON.stringify(objectToJSON(o.ref)); - }, - ]); + case 'ref': { + const groups = groupBy(data, (o) => + typeof getUnionValue(o.ref)?.['id'] === 'number' ? 0 : 1 + ); + + data = [ + ...sortBy(groups[0], (o) => getUnionValue(o.ref)?.['id']), + ...sortBy(groups[1], (o) => JSON.stringify(objectToJSON(getUnionValue(o.ref)))), + ]; break; + } } if (sort.direction === 'desc') return data.reverse(); return data; diff --git a/src/app/sections/shop-details/services/fetch-shop.service.ts b/src/app/sections/shop-details/services/fetch-shop.service.ts index 68cfc542..b4217589 100644 --- a/src/app/sections/shop-details/services/fetch-shop.service.ts +++ b/src/app/sections/shop-details/services/fetch-shop.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { PartyID, ShopID } from '@vality/domain-proto'; -import { BehaviorSubject, merge, of, Subject } from 'rxjs'; +import { PartyID, ShopID, Shop } from '@vality/domain-proto'; +import { BehaviorSubject, merge, of, Subject, Observable } from 'rxjs'; import { catchError, filter, shareReplay, startWith, switchMap } from 'rxjs/operators'; import { PartyManagementService } from '@cc/app/api/payment-processing'; @@ -13,7 +13,7 @@ export class FetchShopService { private hasError$: Subject = new Subject(); // eslint-disable-next-line @typescript-eslint/member-ordering - shop$ = this.getShop$.pipe( + shop$: Observable = this.getShop$.pipe( switchMap(({ partyID, shopID }) => this.partyManagementService.GetShop(partyID, shopID).pipe( catchError((e) => { @@ -24,7 +24,7 @@ export class FetchShopService { ) ), filter((result) => result !== 'error'), - shareReplay(1) + shareReplay(1) ); // eslint-disable-next-line @typescript-eslint/member-ordering @@ -38,4 +38,8 @@ export class FetchShopService { getShop(partyID: PartyID, shopID: ShopID) { this.getShop$.next({ shopID, partyID }); } + + reload() { + if (this.getShop$.value) this.getShop$.next(this.getShop$.value); + } } diff --git a/src/app/sections/shop-details/shop-details.component.html b/src/app/sections/shop-details/shop-details.component.html index 184d849b..3ff28432 100644 --- a/src/app/sections/shop-details/shop-details.component.html +++ b/src/app/sections/shop-details/shop-details.component.html @@ -8,4 +8,12 @@ + + + + diff --git a/src/app/sections/shop-details/shop-details.component.ts b/src/app/sections/shop-details/shop-details.component.ts index 0fd5e075..d426a9d6 100644 --- a/src/app/sections/shop-details/shop-details.component.ts +++ b/src/app/sections/shop-details/shop-details.component.ts @@ -1,10 +1,18 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { combineLatest } from 'rxjs'; -import { pluck } from 'rxjs/operators'; +import { UntilDestroy } from '@ngneat/until-destroy'; +import { BaseDialogService, BaseDialogResponseStatus } from '@vality/ng-core'; +import { combineLatest, switchMap } from 'rxjs'; +import { pluck, filter, withLatestFrom, first } from 'rxjs/operators'; +import { ConfirmActionDialogComponent } from '../../../components/confirm-action-dialog'; +import { getUnionKey } from '../../../utils'; +import { PartyManagementService } from '../../api/payment-processing'; +import { ErrorService } from '../../shared/services/error'; +import { NotificationService } from '../../shared/services/notification'; import { FetchShopService } from './services/fetch-shop.service'; +@UntilDestroy() @Component({ templateUrl: 'shop-details.component.html', providers: [FetchShopService], @@ -16,11 +24,84 @@ export class ShopDetailsComponent { shop$ = this.fetchShopService.shop$; inProgress$ = this.fetchShopService.inProgress$; - categoryID$ = this.fetchShopService.shop$.pipe(pluck('category', 'id')); - constructor(private fetchShopService: FetchShopService, private route: ActivatedRoute) { + constructor( + private fetchShopService: FetchShopService, + private route: ActivatedRoute, + private partyManagementService: PartyManagementService, + private baseDialogService: BaseDialogService, + private errorService: ErrorService, + private notificationService: NotificationService + ) { combineLatest([this.partyID$, this.shopID$]).subscribe(([partyID, shopID]) => { this.fetchShopService.getShop(partyID, shopID); }); } + + toggleBlocking() { + this.shop$ + .pipe( + first(), + switchMap((shop) => + this.baseDialogService + .open(ConfirmActionDialogComponent, { + title: + getUnionKey(shop.blocking) === 'unblocked' + ? 'Block shop' + : 'Unblock shop', + hasReason: true, + }) + .afterClosed() + ), + filter((r) => r.status === BaseDialogResponseStatus.Success), + withLatestFrom(this.shop$, this.partyID$), + switchMap(([{ data }, shop, partyID]) => + getUnionKey(shop.blocking) === 'unblocked' + ? this.partyManagementService.BlockShop(partyID, shop.id, data.reason) + : this.partyManagementService.UnblockShop(partyID, shop.id, data.reason) + ) + ) + .subscribe({ + next: () => { + this.fetchShopService.reload(); + this.notificationService.success(); + }, + error: (err) => { + this.errorService.error(err); + }, + }); + } + + toggleSuspension() { + this.shop$ + .pipe( + first(), + switchMap((shop) => + this.baseDialogService + .open(ConfirmActionDialogComponent, { + title: + getUnionKey(shop.suspension) === 'active' + ? 'Suspend shop' + : 'Activate shop', + }) + .afterClosed() + ), + filter((r) => r.status === BaseDialogResponseStatus.Success), + withLatestFrom(this.shop$, this.partyID$), + switchMap(([, shop, partyID]) => + getUnionKey(shop.suspension) === 'active' + ? this.partyManagementService.SuspendShop(partyID, shop.id) + : this.partyManagementService.ActivateShop(partyID, shop.id) + ) + ) + .subscribe({ + next: () => { + this.fetchShopService.reload(); + this.notificationService.success(); + }, + error: (err) => { + this.errorService.error(err); + }, + }); + } } diff --git a/src/app/sections/shop-details/shop-details.module.ts b/src/app/sections/shop-details/shop-details.module.ts index f9dfc291..12d74f03 100644 --- a/src/app/sections/shop-details/shop-details.module.ts +++ b/src/app/sections/shop-details/shop-details.module.ts @@ -1,11 +1,14 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FlexModule } from '@angular/flex-layout'; +import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { ActionsModule } from '@vality/ng-core'; import { HeadlineModule } from '@cc/components/headline'; +import { ThriftPipesModule } from '../../shared'; import { ShopDetailsRoutingModule } from './shop-details-routing.module'; import { ShopDetailsComponent } from './shop-details.component'; import { ShopMainInfoModule } from './shop-main-info'; @@ -19,6 +22,9 @@ import { ShopMainInfoModule } from './shop-main-info'; MatCardModule, CommonModule, MatProgressSpinnerModule, + ActionsModule, + MatButtonModule, + ThriftPipesModule, ], declarations: [ShopDetailsComponent], }) diff --git a/src/app/shared/utils/index.ts b/src/app/shared/utils/index.ts index 165f97ab..c73d0f87 100644 --- a/src/app/shared/utils/index.ts +++ b/src/app/shared/utils/index.ts @@ -1,6 +1,5 @@ export * from './extract-claim-status'; export * from './component-changes'; export * from './polling-conditions'; -export * from './sort-units'; export * from './deposit-status'; export * from './is-empty-value'; diff --git a/src/app/shared/utils/sort-units.ts b/src/app/shared/utils/sort-units.ts deleted file mode 100644 index 58049aa7..00000000 --- a/src/app/shared/utils/sort-units.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ModificationUnit } from '@vality/domain-proto/lib/claim_management'; -import * as moment from 'moment'; - -export const sortUnitsByCreatedAtAsc = (units: T[]): T[] => - units.slice().sort(({ created_at: a }, { created_at: b }) => moment(a).diff(moment(b))); diff --git a/src/components/confirm-action-dialog/confirm-action-dialog.component.html b/src/components/confirm-action-dialog/confirm-action-dialog.component.html index aa072b05..f42d360f 100644 --- a/src/components/confirm-action-dialog/confirm-action-dialog.component.html +++ b/src/components/confirm-action-dialog/confirm-action-dialog.component.html @@ -1,4 +1,11 @@ - + + + Reason + +