mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
Add shop suspending and blocking & fix domain objs sort (#134)
This commit is contained in:
parent
0a35f88926
commit
9543582163
@ -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();
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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],
|
||||
})
|
||||
|
@ -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';
|
||||
|
@ -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)));
|
@ -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()">
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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],
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user