Add shop suspending and blocking & fix domain objs sort (#134)

This commit is contained in:
Rinat Arsaev 2022-09-14 16:57:02 +03:00 committed by GitHub
parent 0a35f88926
commit 9543582163
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 193 additions and 131 deletions

View File

@ -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<MatPaginator>();
@ViewChildren(MatSort) sort = new QueryList<MatSort>();
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
searchControl = new FormControl('');
typesControl = new FormControl(this.queryParamsService.params.types || []);
dataSource$: Observable<MatTableDataSource<DataSourceItem>> =
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<MatTableDataSource<DataSourceItem>> = 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<void>(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();

View File

@ -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);
}

View File

@ -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;

View File

@ -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<any> = new Subject();
// eslint-disable-next-line @typescript-eslint/member-ordering
shop$ = this.getShop$.pipe(
shop$: Observable<Shop> = 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<Shop>(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);
}
}

View File

@ -8,4 +8,12 @@
</div>
</mat-card-content>
</mat-card>
<cc-actions *ngIf="shop$ | async as shop">
<button mat-raised-button (click)="toggleSuspension()">
{{ (shop.suspension | ccUnionKey) === 'active' ? 'SUSPEND' : 'ACTIVATE' }}
</button>
<button color="warn" mat-raised-button (click)="toggleBlocking()">
{{ (shop.blocking | ccUnionKey) === 'unblocked' ? 'BLOCK' : 'UNBLOCK' }}
</button>
</cc-actions>
</div>

View File

@ -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);
},
});
}
}

View File

@ -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],
})

View File

@ -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';

View File

@ -1,5 +0,0 @@
import { ModificationUnit } from '@vality/domain-proto/lib/claim_management';
import * as moment from 'moment';
export const sortUnitsByCreatedAtAsc = <T extends ModificationUnit>(units: T[]): T[] =>
units.slice().sort(({ created_at: a }, { created_at: b }) => moment(a).diff(moment(b)));

View File

@ -1,4 +1,11 @@
<cc-base-dialog [title]="dialogData?.['title'] ?? 'Confirm this action'" noContent>
<cc-base-dialog
[noContent]="!(dialogData && dialogData.hasReason)"
[title]="dialogData?.['title'] ?? 'Confirm this action'"
>
<mat-form-field *ngIf="dialogData && dialogData.hasReason" style="width: 100%">
<mat-label>Reason</mat-label>
<input [formControl]="control" matInput />
</mat-form-field>
<cc-base-dialog-actions>
<button mat-button (click)="cancel()">CANCEL</button>
<button color="primary" mat-raised-button (click)="confirm()">

View File

@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { BaseDialogResponseStatus, BaseDialogSuperclass } from '@vality/ng-core';
@Component({
@ -8,13 +9,22 @@ import { BaseDialogResponseStatus, BaseDialogSuperclass } from '@vality/ng-core'
})
export class ConfirmActionDialogComponent extends BaseDialogSuperclass<
ConfirmActionDialogComponent,
{ title?: string; confirmLabel?: string } | void
{ title?: string; confirmLabel?: string; hasReason?: boolean } | void,
{ reason?: string }
> {
control = new FormControl<string>('');
cancel() {
this.dialogRef.close({ status: BaseDialogResponseStatus.Cancelled });
}
confirm() {
this.dialogRef.close({ status: BaseDialogResponseStatus.Success });
this.dialogRef.close({
status: BaseDialogResponseStatus.Success,
data:
this.dialogData && this.dialogData.hasReason
? { reason: this.control.value }
: null,
});
}
}

View File

@ -1,13 +1,26 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { BaseDialogModule } from '@vality/ng-core';
import { ConfirmActionDialogComponent } from './confirm-action-dialog.component';
@NgModule({
imports: [MatDialogModule, MatButtonModule, FlexLayoutModule, BaseDialogModule],
imports: [
MatDialogModule,
MatButtonModule,
FlexLayoutModule,
BaseDialogModule,
MatFormFieldModule,
CommonModule,
MatInputModule,
ReactiveFormsModule,
],
declarations: [ConfirmActionDialogComponent],
exports: [ConfirmActionDialogComponent],
})