mirror of
https://github.com/valitydev/dashboard.git
synced 2024-11-06 02:25:23 +00:00
MI-9: Add withdrawal reports (only dev) (#135)
This commit is contained in:
parent
e4f63bacd6
commit
c057293387
@ -9,6 +9,7 @@
|
||||
"build": "ng build && transloco-optimize dist/assets/i18n",
|
||||
"test": "ng test",
|
||||
"i18n:extract": "transloco-keys-manager extract",
|
||||
"i18n:clean": "transloco-keys-manager extract --remove-extra-keys",
|
||||
"i18n:check": "transloco-keys-manager find --emit-error-on-extra-keys",
|
||||
"coverage": "npx http-server -c-1 -o -p 9875 ./coverage",
|
||||
"lint": "ng lint --max-warnings=0",
|
||||
|
@ -5,3 +5,4 @@ export * from './withdrawals.service';
|
||||
export * from './identities.service';
|
||||
export * from './deposits.service';
|
||||
export * from './wallet-dictionary.service';
|
||||
export * from './reports.service';
|
||||
|
11
src/app/api/wallet/reports.service.ts
Normal file
11
src/app/api/wallet/reports.service.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReportsService as ApiService } from '@vality/swag-wallet';
|
||||
|
||||
import { PartyIdExtension } from '@dsh/app/api/utils/extensions';
|
||||
|
||||
import { createApi } from '../utils';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ReportsService extends createApi(ApiService, [PartyIdExtension]) {}
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslocoService } from '@ngneat/transloco';
|
||||
import { DepositRevert, WithdrawalsTopic, DestinationsTopic, Deposit, Withdrawal } from '@vality/swag-wallet';
|
||||
import { DepositRevert, WithdrawalsTopic, DestinationsTopic, Deposit, Withdrawal, Report } from '@vality/swag-wallet';
|
||||
|
||||
import { DictionaryService } from '../utils';
|
||||
|
||||
@ -64,5 +64,13 @@ export class WalletDictionaryService {
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
}));
|
||||
|
||||
reportStatus$ = this.dictionaryService.create<Report.StatusEnum>(() => ({
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
pending: this.t.translate('wallet.reportStatus.pending', null, 'dictionary'),
|
||||
created: this.t.translate('wallet.reportStatus.created', null, 'dictionary'),
|
||||
canceled: this.t.translate('wallet.reportStatus.canceled', null, 'dictionary'),
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
}));
|
||||
|
||||
constructor(private t: TranslocoService, private dictionaryService: DictionaryService) {}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export const formValueToCreateValue = ({
|
||||
reportType: 'paymentRegistry',
|
||||
});
|
||||
|
||||
const getDateWithTime = (date: string, time: string): string =>
|
||||
export const getDateWithTime = (date: string, time: string): string =>
|
||||
moment(`${moment(date).format('YYYY-MM-DD')}, ${time}`, 'YYYY-MM-DD, HH:mm:ss')
|
||||
.utc()
|
||||
.format();
|
||||
|
@ -22,7 +22,6 @@ import { DepositRevertsModule } from './deposit-reverts/deposit-reverts.module';
|
||||
LayoutModule,
|
||||
FlexLayoutModule,
|
||||
CommonModule,
|
||||
|
||||
ApiModelRefsModule,
|
||||
EmptySearchResultModule,
|
||||
ShowMorePanelModule,
|
||||
|
@ -1,8 +1,4 @@
|
||||
<div
|
||||
*transloco="let t; scope: 'wallet-section'; read: 'walletSection.integrations'"
|
||||
fxLayout="column"
|
||||
fxLayoutGap="32px"
|
||||
>
|
||||
<div fxLayout="column" fxLayoutGap="32px">
|
||||
<nav mat-tab-nav-bar>
|
||||
<a
|
||||
mat-tab-link
|
||||
|
@ -0,0 +1,39 @@
|
||||
<dsh-base-dialog
|
||||
*transloco="let t; scope: 'wallet-section'; read: 'walletSection.reports.createReportDialog'"
|
||||
[title]="t('title')"
|
||||
[subtitle]="t('subtitle')"
|
||||
(cancel)="cancel()"
|
||||
>
|
||||
<div [formGroup]="form" fxLayout="column" fxLayoutGap="16px">
|
||||
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="24px" fxLayoutGap.xs="16px">
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label>{{ t('from') }}</mat-label>
|
||||
<input required formControlName="fromDate" matInput [matDatepicker]="from" [max]="form.value.toTime" />
|
||||
<mat-datepicker-toggle matSuffix [for]="from"></mat-datepicker-toggle>
|
||||
<mat-datepicker #from></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label>{{ t('time') }}</mat-label>
|
||||
<input required dshFormatTimeInput formControlName="fromTime" matInput placeholder="00:00:00" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="24px" fxLayoutGap.xs="16px">
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label>{{ t('to') }}</mat-label>
|
||||
<input required formControlName="toDate" matInput [matDatepicker]="to" [min]="form.value.fromTime" />
|
||||
<mat-datepicker-toggle matSuffix [for]="to"></mat-datepicker-toggle>
|
||||
<mat-datepicker #to></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label>{{ t('time') }}</mat-label>
|
||||
<input required dshFormatTimeInput formControlName="toTime" matInput placeholder="00:00:00" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<dsh-identity-field formControlName="identityID" required></dsh-identity-field>
|
||||
</div>
|
||||
<ng-container dshBaseDialogActions>
|
||||
<button dsh-button color="accent" [disabled]="form.invalid || !!(progress$ | async)" (click)="confirm()">
|
||||
{{ t('confirm') }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</dsh-base-dialog>
|
@ -0,0 +1,72 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { NonNullableFormBuilder, Validators } from '@angular/forms';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { TranslocoService } from '@ngneat/transloco';
|
||||
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { NotifyLogService, progressTo } from '@vality/ng-core';
|
||||
import moment from 'moment/moment';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ReportsService } from '@dsh/app/api/wallet';
|
||||
import { getDateWithTime } from '@dsh/app/sections/payment-section/reports/create-report/form-value-to-create-value';
|
||||
import { BaseDialogResponseStatus } from '@dsh/app/shared/components/dialog/base-dialog';
|
||||
|
||||
const TIME_PATTERN = /^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/;
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'dsh-create-report-dialog',
|
||||
templateUrl: 'create-report-dialog.component.html',
|
||||
styles: [],
|
||||
})
|
||||
export class CreateReportDialogComponent {
|
||||
form = this.fb.group({
|
||||
identityID: this.data.identityID as string,
|
||||
fromDate: [moment().startOf('month').format(), Validators.required],
|
||||
fromTime: ['00:00:00', Validators.pattern(TIME_PATTERN)],
|
||||
toDate: [moment().endOf('day').format(), Validators.required],
|
||||
toTime: ['23:59:59', Validators.pattern(TIME_PATTERN)],
|
||||
});
|
||||
progress$ = new BehaviorSubject(0);
|
||||
|
||||
constructor(
|
||||
private dialogRef: MatDialogRef<CreateReportDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) private data: { identityID?: string },
|
||||
private fb: NonNullableFormBuilder,
|
||||
private reportsService: ReportsService,
|
||||
private log: NotifyLogService,
|
||||
private transloco: TranslocoService
|
||||
) {}
|
||||
|
||||
confirm(): void {
|
||||
const { identityID, fromTime, toTime, fromDate, toDate } = this.form.value;
|
||||
this.reportsService
|
||||
.createReport({
|
||||
identityID,
|
||||
reportParams: {
|
||||
fromTime: getDateWithTime(fromDate, fromTime),
|
||||
toTime: getDateWithTime(toDate, toTime),
|
||||
reportType: 'withdrawalRegistry',
|
||||
},
|
||||
})
|
||||
.pipe(progressTo(this.progress$), untilDestroyed(this))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.log.success(
|
||||
this.transloco.translate('reports.createReportDialog.success', {}, 'wallet-section')
|
||||
);
|
||||
this.dialogRef.close(BaseDialogResponseStatus.Success);
|
||||
},
|
||||
error: (err) => {
|
||||
this.log.error(
|
||||
err,
|
||||
this.transloco.translate('reports.createReportDialog.error', {}, 'wallet-section')
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(BaseDialogResponseStatus.Cancelled);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FetchSuperclass, FetchResult, NotifyLogService } from '@vality/ng-core';
|
||||
import { GetReportsRequestParams, Report } from '@vality/swag-wallet';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
|
||||
import { ReportsService } from '@dsh/app/api/wallet';
|
||||
|
||||
@Injectable()
|
||||
export class FetchReportsService extends FetchSuperclass<
|
||||
Report,
|
||||
Omit<GetReportsRequestParams, 'xRequestID' | 'xRequestDeadline'>
|
||||
> {
|
||||
constructor(private reportsService: ReportsService, private log: NotifyLogService) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected fetch(
|
||||
params: Omit<GetReportsRequestParams, 'xRequestID' | 'xRequestDeadline'>
|
||||
): Observable<FetchResult<Report, string>> {
|
||||
return this.reportsService.getReports(params).pipe(
|
||||
map((result) => ({ result })),
|
||||
catchError((err) => {
|
||||
this.log.error(err);
|
||||
return of({ result: [] });
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
1
src/app/sections/wallet-section/reports/index.ts
Normal file
1
src/app/sections/wallet-section/reports/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './reports.module';
|
@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { ReportsComponent } from './reports.component';
|
||||
|
||||
const ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ReportsComponent,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(ROUTES)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class ReportsRoutingModule {}
|
@ -0,0 +1,62 @@
|
||||
<div fxLayout="column" fxLayoutGap="32px" *transloco="let t; scope: 'wallet-section'; read: 'walletSection.reports'">
|
||||
<div
|
||||
gdColumns="1fr"
|
||||
gdColumns.gt-sm="repeat(2, auto)"
|
||||
gdAlignColumns="center center"
|
||||
gdAlignRows="space-between start"
|
||||
gdGap="32px"
|
||||
>
|
||||
<dsh-filters-group [formGroup]="form">
|
||||
<div class="dsh-body-1" fxHide.lt-md>{{ t('dateRangeDescription') }}:</div>
|
||||
<dsh-date-range-filter
|
||||
formControlName="dateRange"
|
||||
[default]="defaultDateRange"
|
||||
[maxDate]="defaultDateRange.end"
|
||||
></dsh-date-range-filter>
|
||||
<dsh-identity-filter formControlName="identityID"></dsh-identity-filter>
|
||||
</dsh-filters-group>
|
||||
<button dsh-button color="accent" (click)="create()">
|
||||
{{ t('openCreateReport') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<dsh-accordion-table
|
||||
[error]="form.value.identityID ? undefined : t('errors.identityNotSpecified')"
|
||||
[lastUpdated]="lastUpdated$ | async"
|
||||
[columns]="columns$ | async"
|
||||
[data]="reports$ | async"
|
||||
(update)="load()"
|
||||
(more)="more()"
|
||||
[hasMore]="hasMore$ | async"
|
||||
[inProgress]="isLoading$ | async"
|
||||
[contentHeader]="contentHeader"
|
||||
[expanded]="expanded"
|
||||
>
|
||||
<ng-template let-report>
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<div gdColumns="1fr" gdGap="24px">
|
||||
<div gdColumns="1fr" gdColumns.gt-sm="1fr 1fr 1fr" gdGap="24px">
|
||||
<dsh-details-item [title]="t('status')">
|
||||
<dsh-status
|
||||
[color]="reportStatusColor[report.status]"
|
||||
>{{ (reportStatusDict$| async)?.[report.status] }}</dsh-status
|
||||
>
|
||||
</dsh-details-item>
|
||||
<dsh-details-item [title]="t('createdAt')">{{
|
||||
report.createdAt | date : 'dd MMMM yyyy, HH:mm:ss'
|
||||
}}</dsh-details-item>
|
||||
</div>
|
||||
<dsh-details-item [title]="t('period')"
|
||||
>{{ report.fromTime | date : 'dd MMMM yyyy, HH:mm:ss' }} -
|
||||
{{ report.toTime | date : 'dd MMMM yyyy, HH:mm:ss' }}</dsh-details-item
|
||||
>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="report.status === 'created'">
|
||||
<mat-divider></mat-divider>
|
||||
<dsh-report-files [reportID]="report.id" [files]="report.files"></dsh-report-files>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</dsh-accordion-table>
|
||||
</div>
|
127
src/app/sections/wallet-section/reports/reports.component.ts
Normal file
127
src/app/sections/wallet-section/reports/reports.component.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { Breakpoints } from '@angular/cdk/layout';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NonNullableFormBuilder } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslocoService } from '@ngneat/transloco';
|
||||
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { Report } from '@vality/swag-wallet';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import moment from 'moment';
|
||||
import { Observable } from 'rxjs';
|
||||
import { startWith, distinctUntilChanged, filter, map } from 'rxjs/operators';
|
||||
|
||||
import { WalletDictionaryService } from '@dsh/app/api/wallet';
|
||||
import { mapToTimestamp } from '@dsh/app/custom-operators';
|
||||
import { QueryParamsService } from '@dsh/app/shared';
|
||||
import { Column, ExpandedFragment } from '@dsh/app/shared/components/accordion-table';
|
||||
import { BaseDialogResponseStatus } from '@dsh/app/shared/components/dialog/base-dialog';
|
||||
import { StatusColor } from '@dsh/app/theme-manager';
|
||||
import { createDateRangeWithPreset, Preset, DateRange } from '@dsh/components/date-range-filter';
|
||||
|
||||
import { CreateReportDialogComponent } from './components/create-report-dialog/create-report-dialog.component';
|
||||
import { FetchReportsService } from './fetch-reports.service';
|
||||
|
||||
interface Form {
|
||||
dateRange: DateRange;
|
||||
identityID: string;
|
||||
}
|
||||
|
||||
const REPORT_STATUS_COLOR = {
|
||||
[Report.StatusEnum.Created]: StatusColor.Success,
|
||||
[Report.StatusEnum.Pending]: StatusColor.Pending,
|
||||
[Report.StatusEnum.Canceled]: StatusColor.Warn,
|
||||
};
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'dsh-reports',
|
||||
templateUrl: './reports.component.html',
|
||||
providers: [FetchReportsService],
|
||||
})
|
||||
export class ReportsComponent implements OnInit {
|
||||
reports$ = this.fetchReportsService.result$;
|
||||
hasMore$ = this.fetchReportsService.hasMore$;
|
||||
isLoading$ = this.fetchReportsService.isLoading$;
|
||||
columns$: Observable<Column<Report>[]> = this.walletDictionaryService.reportStatus$.pipe(
|
||||
map((reportStatus) => [
|
||||
{ label: 'Created at', field: (r) => r.createdAt, type: 'datetime' },
|
||||
{
|
||||
label: 'Status',
|
||||
field: (d) => d.status,
|
||||
type: 'tag',
|
||||
typeParameters: {
|
||||
color: REPORT_STATUS_COLOR,
|
||||
label: reportStatus,
|
||||
},
|
||||
hide: Breakpoints.Small,
|
||||
},
|
||||
{
|
||||
label: 'Reporting period',
|
||||
field: (d): DateRange => ({
|
||||
start: moment(d.fromTime),
|
||||
end: moment(d.toTime),
|
||||
}),
|
||||
type: 'daterange',
|
||||
hide: Breakpoints.Medium,
|
||||
},
|
||||
])
|
||||
);
|
||||
contentHeader = [{ label: (r) => `${this.transloco.translate('reports.report', {}, 'wallet-section')} #${r.id}` }];
|
||||
defaultDateRange = createDateRangeWithPreset(Preset.Last90days);
|
||||
form = this.fb.group<Form>({ dateRange: this.defaultDateRange, identityID: undefined, ...this.qp.params });
|
||||
lastUpdated$ = this.fetchReportsService.result$.pipe(mapToTimestamp);
|
||||
reportStatusDict$ = this.walletDictionaryService.reportStatus$;
|
||||
reportStatusColor = REPORT_STATUS_COLOR;
|
||||
expanded = new ExpandedFragment(
|
||||
this.fetchReportsService.result$,
|
||||
() => this.fetchReportsService.more(),
|
||||
this.fetchReportsService.hasMore$
|
||||
);
|
||||
|
||||
constructor(
|
||||
private fetchReportsService: FetchReportsService,
|
||||
private fb: NonNullableFormBuilder,
|
||||
private qp: QueryParamsService<Partial<Form>>,
|
||||
private dialog: MatDialog,
|
||||
private transloco: TranslocoService,
|
||||
private walletDictionaryService: WalletDictionaryService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.form.valueChanges
|
||||
.pipe(startWith(this.form.value), distinctUntilChanged(isEqual), untilDestroyed(this))
|
||||
.subscribe((value) => {
|
||||
void this.qp.set(value);
|
||||
if (value.identityID) {
|
||||
this.load();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
load() {
|
||||
const { dateRange, identityID } = this.form.value;
|
||||
this.fetchReportsService.load({
|
||||
fromTime: dateRange.start.utc().format(),
|
||||
toTime: dateRange.end.utc().format(),
|
||||
identityID,
|
||||
type: 'withdrawalRegistry',
|
||||
});
|
||||
}
|
||||
|
||||
more() {
|
||||
this.fetchReportsService.more();
|
||||
}
|
||||
|
||||
create() {
|
||||
this.dialog
|
||||
.open(CreateReportDialogComponent, { data: { identityID: this.form.value.identityID } })
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
filter((r) => r === BaseDialogResponseStatus.Success),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.load();
|
||||
});
|
||||
}
|
||||
}
|
66
src/app/sections/wallet-section/reports/reports.module.ts
Normal file
66
src/app/sections/wallet-section/reports/reports.module.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule, GridModule, ExtendedModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { TranslocoModule } from '@ngneat/transloco';
|
||||
|
||||
import { ReportFilesModule } from '@dsh/app/sections/payment-section/reports/report-files';
|
||||
import { ReportPipesModule } from '@dsh/app/sections/payment-section/reports/report-pipes';
|
||||
import { IdentityFilterModule, ApiModelRefsModule } from '@dsh/app/shared';
|
||||
import { AccordionTableModule } from '@dsh/app/shared/components/accordion-table';
|
||||
import { DialogModule } from '@dsh/app/shared/components/dialog';
|
||||
import { ClaimFieldModule } from '@dsh/app/shared/components/inputs/claim-field';
|
||||
import { IdentityFieldComponent } from '@dsh/app/shared/components/inputs/identity-field';
|
||||
import { ButtonModule } from '@dsh/components/buttons';
|
||||
import { DateRangeFilterModule } from '@dsh/components/date-range-filter';
|
||||
import { FilterModule } from '@dsh/components/filter';
|
||||
import { FiltersGroupModule } from '@dsh/components/filters-group';
|
||||
import { BootstrapIconModule, StatusModule } from '@dsh/components/indicators';
|
||||
import { DetailsItemModule } from '@dsh/components/layout';
|
||||
|
||||
import { CreateReportDialogComponent } from './components/create-report-dialog/create-report-dialog.component';
|
||||
import { ReportsRoutingModule } from './reports-routing.module';
|
||||
import { ReportsComponent } from './reports.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ReportsComponent, CreateReportDialogComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReportsRoutingModule,
|
||||
AccordionTableModule,
|
||||
FlexModule,
|
||||
DateRangeFilterModule,
|
||||
FiltersGroupModule,
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatOptionModule,
|
||||
MatSelectModule,
|
||||
ClaimFieldModule,
|
||||
FilterModule,
|
||||
TranslocoModule,
|
||||
IdentityFilterModule,
|
||||
ButtonModule,
|
||||
GridModule,
|
||||
ExtendedModule,
|
||||
DialogModule,
|
||||
MatInputModule,
|
||||
MatDatepickerModule,
|
||||
MatDialogModule,
|
||||
IdentityFieldComponent,
|
||||
BootstrapIconModule,
|
||||
MatDividerModule,
|
||||
ReportFilesModule,
|
||||
ApiModelRefsModule,
|
||||
DetailsItemModule,
|
||||
ReportPipesModule,
|
||||
StatusModule,
|
||||
],
|
||||
})
|
||||
export class ReportsModule {}
|
@ -1,10 +1,12 @@
|
||||
import { BootstrapIconName } from '@dsh/components/indicators';
|
||||
import { environment } from '@dsh/environments';
|
||||
|
||||
export enum NavbarRouterLink {
|
||||
Wallets = 'wallets',
|
||||
Deposits = 'deposits',
|
||||
Withdrawals = 'withdrawals',
|
||||
Integrations = 'integrations',
|
||||
Reports = 'reports',
|
||||
}
|
||||
|
||||
export interface NavbarItemConfig {
|
||||
@ -18,7 +20,8 @@ export const toNavbarItemConfig = ({
|
||||
deposits,
|
||||
withdrawals,
|
||||
integrations,
|
||||
}: Record<'wallets' | 'deposits' | 'withdrawals' | 'integrations', string>): NavbarItemConfig[] => [
|
||||
reports,
|
||||
}: Record<'wallets' | 'deposits' | 'withdrawals' | 'integrations' | 'reports', string>): NavbarItemConfig[] => [
|
||||
{
|
||||
routerLink: NavbarRouterLink.Wallets,
|
||||
icon: BootstrapIconName.Wallet2,
|
||||
@ -34,6 +37,15 @@ export const toNavbarItemConfig = ({
|
||||
icon: BootstrapIconName.ArrowUpRightCircle,
|
||||
label: withdrawals,
|
||||
},
|
||||
...(environment.production
|
||||
? []
|
||||
: [
|
||||
{
|
||||
routerLink: NavbarRouterLink.Reports,
|
||||
icon: BootstrapIconName.FileText,
|
||||
label: reports,
|
||||
},
|
||||
]),
|
||||
{
|
||||
routerLink: NavbarRouterLink.Integrations,
|
||||
icon: BootstrapIconName.Plug,
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { environment } from '@dsh/environments';
|
||||
|
||||
import { WalletSectionComponent } from './wallet-section.component';
|
||||
|
||||
const WALLET_SECTION_ROUTES: Routes = [
|
||||
@ -24,6 +26,14 @@ const WALLET_SECTION_ROUTES: Routes = [
|
||||
path: 'integrations',
|
||||
loadChildren: () => import('./integrations').then((m) => m.IntegrationsModule),
|
||||
},
|
||||
...(environment.production
|
||||
? []
|
||||
: [
|
||||
{
|
||||
path: 'reports',
|
||||
loadChildren: () => import('./reports').then((m) => m.ReportsModule),
|
||||
},
|
||||
]),
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'wallets',
|
||||
|
@ -32,6 +32,7 @@ export class WalletSectionComponent implements OnInit {
|
||||
|
||||
private getNavbarLabels() {
|
||||
return {
|
||||
reports: this.transloco.translate('walletSection.nav.reports', null, 'wallet-section'),
|
||||
wallets: this.transloco.translate('walletSection.nav.wallets', null, 'wallet-section'),
|
||||
deposits: this.transloco.translate('walletSection.nav.deposits', null, 'wallet-section'),
|
||||
withdrawals: this.transloco.translate('walletSection.nav.withdrawals', null, 'wallet-section'),
|
||||
|
@ -0,0 +1,64 @@
|
||||
<div fxLayout="column" fxLayoutGap="32px">
|
||||
<div fxLayout="column" fxLayoutGap="16px">
|
||||
<dsh-last-updated [lastUpdated]="lastUpdated" (update)="update.emit($event)"></dsh-last-updated>
|
||||
<dsh-row fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="24px">
|
||||
<dsh-row-header-label
|
||||
*ngFor="let column of columns"
|
||||
[fxHide]="isHided(column.hide) | async"
|
||||
[fxFlex]="column.width ?? true"
|
||||
>{{ column.label }}</dsh-row-header-label
|
||||
>
|
||||
</dsh-row>
|
||||
<dsh-accordion
|
||||
*ngIf="data?.length"
|
||||
fxLayout="column"
|
||||
fxLayoutGap="16px"
|
||||
[expanded]="expanded ? (expanded.expanded$ | async) : undefined"
|
||||
(expandedChange)="expanded ? expanded.set($event) : undefined"
|
||||
>
|
||||
<dsh-accordion-item *ngFor="let item of data" #accordionItem>
|
||||
<dsh-row fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="24px">
|
||||
<dsh-row-label
|
||||
*ngFor="let column of columns"
|
||||
[fxHide]="isHided(column.hide) | async"
|
||||
[fxFlex]="column.width ?? true"
|
||||
>
|
||||
<ng-container [ngSwitch]="column.type" *ngIf="column.field(item, column) as value">
|
||||
<ng-template ngSwitchCase="daterange">
|
||||
{{ value.start | date : 'dd MMMM yyyy' }} - {{ value.end | date : 'dd MMMM yyyy' }}
|
||||
</ng-template>
|
||||
<ng-template ngSwitchCase="datetime">
|
||||
{{ value | date : 'dd MMMM yyyy, HH:mm' }}
|
||||
</ng-template>
|
||||
<ng-template ngSwitchCase="tag">
|
||||
<dsh-status [color]="column.typeParameters.color[value]">{{
|
||||
column.typeParameters.label[value]
|
||||
}}</dsh-status>
|
||||
</ng-template>
|
||||
<ng-template ngSwitchDefault>
|
||||
{{ value }}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</dsh-row-label>
|
||||
</dsh-row>
|
||||
<ng-template dshLazyPanelContent>
|
||||
<dsh-card fxFlexFill fxLayout="column" fxLayoutGap="24px">
|
||||
<dsh-accordion-item-content-header (collapse)="accordionItem.collapse($event)">
|
||||
<div fxLayout fxLayoutAlign="space-between" fxLayoutGap="24px">
|
||||
<div *ngFor="let header of contentHeader">{{ header.label(item) }}</div>
|
||||
</div>
|
||||
</dsh-accordion-item-content-header>
|
||||
<ng-container *ngTemplateOutlet="contentTemplate; context: { $implicit: item }"></ng-container>
|
||||
</dsh-card>
|
||||
</ng-template>
|
||||
</dsh-accordion-item>
|
||||
</dsh-accordion>
|
||||
<dsh-show-more-panel
|
||||
*ngIf="hasMore"
|
||||
[isLoading]="inProgress"
|
||||
(showMore)="more.emit($event)"
|
||||
></dsh-show-more-panel>
|
||||
</div>
|
||||
<dsh-empty-search-result [text]="error" *ngIf="!data?.length && !inProgress"></dsh-empty-search-result>
|
||||
<dsh-spinner *ngIf="inProgress && !data?.length" fxLayoutAlign="center"></dsh-spinner>
|
||||
</div>
|
@ -0,0 +1,66 @@
|
||||
import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { Component, Input, Output, EventEmitter, TemplateRef, ContentChild } from '@angular/core';
|
||||
import { of } from 'rxjs';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
|
||||
import { StatusColor } from '@dsh/app/theme-manager';
|
||||
|
||||
import { ExpandedFragment } from './expanded-fragment';
|
||||
|
||||
const HIDED_BREAKPOINTS = [
|
||||
Breakpoints.XSmall,
|
||||
Breakpoints.Small,
|
||||
Breakpoints.Medium,
|
||||
Breakpoints.Large,
|
||||
Breakpoints.XLarge,
|
||||
];
|
||||
|
||||
export interface Column<T extends object> {
|
||||
label: string;
|
||||
width?: string;
|
||||
hide?: boolean | string; // Material Breakpoint
|
||||
field?: (row: T, columns: Column<T>) => unknown; // | string
|
||||
type?: 'daterange' | 'datetime' | 'tag';
|
||||
typeParameters?: {
|
||||
color: Record<PropertyKey, StatusColor>;
|
||||
label: Record<PropertyKey, string>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContentHeader<T extends object> {
|
||||
label: (row: T) => unknown;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-accordion-table',
|
||||
templateUrl: './accordion-table.component.html',
|
||||
styles: [],
|
||||
})
|
||||
export class AccordionTableComponent<T extends object> {
|
||||
@Input() lastUpdated: string;
|
||||
@Input() columns: Column<T>[];
|
||||
@Input() contentHeader: ContentHeader<T>[];
|
||||
@Input() data: T[];
|
||||
@Input() inProgress: boolean;
|
||||
@Input() hasMore: boolean;
|
||||
@Input() error?: string;
|
||||
|
||||
@Output() update = new EventEmitter<void>();
|
||||
@Output() more = new EventEmitter<void>();
|
||||
|
||||
@Input() expanded?: ExpandedFragment;
|
||||
|
||||
@ContentChild(TemplateRef, { static: true }) contentTemplate!: TemplateRef<unknown>;
|
||||
|
||||
constructor(private breakpointObserver: BreakpointObserver) {}
|
||||
|
||||
isHided(hide: Column<T>['hide']) {
|
||||
if (hide === true) return of(true);
|
||||
if (!hide) return of(false);
|
||||
const idx = HIDED_BREAKPOINTS.findIndex((h) => h === hide);
|
||||
return this.breakpointObserver.observe(HIDED_BREAKPOINTS.slice(0, idx)).pipe(
|
||||
map((s) => s.matches),
|
||||
startWith(false)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
|
||||
import { IndicatorsModule } from '@dsh/components/indicators';
|
||||
import { RowModule, AccordionModule, CardModule } from '@dsh/components/layout';
|
||||
import { ShowMorePanelModule } from '@dsh/components/show-more-panel';
|
||||
|
||||
import { AccordionTableComponent } from './accordion-table.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AccordionTableComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
EmptySearchResultModule,
|
||||
ShowMorePanelModule,
|
||||
IndicatorsModule,
|
||||
RowModule,
|
||||
AccordionModule,
|
||||
CardModule,
|
||||
FlexLayoutModule,
|
||||
],
|
||||
exports: [AccordionTableComponent],
|
||||
})
|
||||
export class AccordionTableModule {}
|
@ -0,0 +1,65 @@
|
||||
import { inject, DestroyRef } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import isNil from 'lodash-es/isNil';
|
||||
import { Observable, BehaviorSubject, defer, of } from 'rxjs';
|
||||
import { take, switchMap, shareReplay, map, tap, withLatestFrom, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { Fragment } from '@dsh/app/shared';
|
||||
|
||||
const EMIT_LIMIT = 4;
|
||||
|
||||
export class ExpandedFragment<T extends { id: unknown } = { id: unknown }> {
|
||||
expanded$ = defer(() => this.expandedIndex$);
|
||||
|
||||
private expandedIndex$ = new BehaviorSubject(0);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private data$: Observable<T[]>,
|
||||
private more: () => void,
|
||||
private hasMore$: Observable<boolean> = of(true)
|
||||
) {
|
||||
this.route.fragment
|
||||
.pipe(
|
||||
take(1),
|
||||
switchMap((fragment) =>
|
||||
this.data$.pipe(
|
||||
take(EMIT_LIMIT),
|
||||
map((data) => (fragment ? data.findIndex((item) => this.serialize(item) === fragment) : -1)),
|
||||
withLatestFrom(hasMore$),
|
||||
tap(([index, hasMore]) => {
|
||||
if (!isNil(fragment) && index === -1 && hasMore) {
|
||||
more();
|
||||
}
|
||||
}),
|
||||
map(([index]) => index)
|
||||
)
|
||||
),
|
||||
distinctUntilChanged(),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
shareReplay(1)
|
||||
)
|
||||
.subscribe((index) => {
|
||||
this.set(index);
|
||||
});
|
||||
this.expandedIndex$
|
||||
.pipe(
|
||||
withLatestFrom(this.data$),
|
||||
map(([index, data]) => this.serialize(data[index])),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
)
|
||||
.subscribe((fragment) => this.router.navigate([], { fragment, queryParamsHandling: 'preserve' }));
|
||||
}
|
||||
|
||||
set(expandedIndex: number) {
|
||||
this.expandedIndex$.next(expandedIndex);
|
||||
}
|
||||
|
||||
private serialize(item?: T): Fragment {
|
||||
if (!item) return '';
|
||||
return String(item.id);
|
||||
}
|
||||
}
|
3
src/app/shared/components/accordion-table/index.ts
Normal file
3
src/app/shared/components/accordion-table/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './accordion-table.module';
|
||||
export * from './accordion-table.component';
|
||||
export * from './expanded-fragment';
|
@ -26,7 +26,10 @@
|
||||
|
||||
<ng-template #titleBlock>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between">
|
||||
<div class="dsh-headline">{{ title }}</div>
|
||||
<div fxLayout="column" fxLayoutGap="16px">
|
||||
<div class="dsh-headline">{{ title }}</div>
|
||||
<div class="dsh-body-1" *ngIf="subtitle">{{ subtitle }}</div>
|
||||
</div>
|
||||
<dsh-bi class="base-dialog-title-close" icon="x" size="lg" (click)="cancelDialog()"></dsh-bi>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@ -37,8 +37,8 @@ $max-height-desktop: 90vh;
|
||||
flex: 1 1 100%;
|
||||
box-sizing: border-box;
|
||||
// to make right visual padding
|
||||
margin: -24px;
|
||||
padding: 24px;
|
||||
margin: -24px -24px 0;
|
||||
padding: 24px 24px 0;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { coerceBoolean } from 'coerce-property';
|
||||
})
|
||||
export class BaseDialogComponent {
|
||||
@Input() title: string;
|
||||
@Input() subtitle: string;
|
||||
@coerceBoolean @Input() disabled: boolean;
|
||||
@coerceBoolean @Input() hasDivider = true;
|
||||
@coerceBoolean @Input() noActions = false;
|
||||
|
@ -0,0 +1,13 @@
|
||||
<ng-container *transloco="let t; scope: 'components'; read: 'components.identityFilter'">
|
||||
<dsh-filter
|
||||
[active]="isActive"
|
||||
[label]="t('label')"
|
||||
[activeLabel]="t('label') + ' #' + savedValue"
|
||||
[content]="content"
|
||||
(save)="save()"
|
||||
(clear)="clear()"
|
||||
></dsh-filter>
|
||||
<ng-template #content>
|
||||
<dsh-identity-field [formControl]="control"></dsh-identity-field>
|
||||
</ng-template>
|
||||
</ng-container>
|
@ -0,0 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { createControlProviders } from '@vality/ng-core';
|
||||
import { Identity } from '@vality/swag-wallet';
|
||||
|
||||
import { FilterSuperclass } from '@dsh/components/filter';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-identity-filter',
|
||||
templateUrl: 'identity-filter.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: createControlProviders(() => IdentityFilterComponent),
|
||||
})
|
||||
export class IdentityFilterComponent extends FilterSuperclass<Identity['id']> {}
|
@ -0,0 +1,32 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { TranslocoModule } from '@ngneat/transloco';
|
||||
|
||||
import { IdentityFieldComponent } from '@dsh/app/shared/components/inputs/identity-field';
|
||||
import { FilterModule } from '@dsh/components/filter';
|
||||
|
||||
import { IdentityFilterComponent } from './identity-filter.component';
|
||||
import { ClaimFieldModule } from '../../inputs/claim-field';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslocoModule,
|
||||
ReactiveFormsModule,
|
||||
FilterModule,
|
||||
ClaimFieldModule,
|
||||
FlexModule,
|
||||
MatFormFieldModule,
|
||||
MatOptionModule,
|
||||
MatSelectModule,
|
||||
IdentityFieldComponent,
|
||||
],
|
||||
declarations: [IdentityFilterComponent],
|
||||
exports: [IdentityFilterComponent],
|
||||
})
|
||||
export class IdentityFilterModule {}
|
@ -0,0 +1,2 @@
|
||||
export * from './identity-filter.component';
|
||||
export * from './identity-filter.module';
|
@ -3,3 +3,4 @@ export * from './invoices-filter';
|
||||
export * from './invoice-status-filter';
|
||||
export * from './refund-status-filter';
|
||||
export * from './currency-filter';
|
||||
export * from './identity-filter';
|
||||
|
@ -0,0 +1,8 @@
|
||||
<mat-form-field fxFlex *transloco="let t; scope: 'components'; read: 'components.inputs.identityField'">
|
||||
<mat-label>{{ t('label') }}</mat-label>
|
||||
<mat-select [formControl]="control" [required]="required">
|
||||
<mat-option *ngFor="let identity of identities$ | async" [value]="identity.id">
|
||||
{{ identity.id }} {{ identity.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
@ -0,0 +1,37 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { TranslocoModule } from '@ngneat/transloco';
|
||||
import { FormControlSuperclass, createControlProviders } from '@vality/ng-core';
|
||||
import { Identity } from '@vality/swag-wallet';
|
||||
|
||||
import { IdentitiesService } from '@dsh/app/api/wallet';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-identity-field',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FlexModule,
|
||||
MatFormFieldModule,
|
||||
MatOptionModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
TranslocoModule,
|
||||
],
|
||||
templateUrl: './identity-field.component.html',
|
||||
providers: createControlProviders(() => IdentityFieldComponent),
|
||||
})
|
||||
export class IdentityFieldComponent extends FormControlSuperclass<Identity['id']> {
|
||||
@Input() required = false;
|
||||
|
||||
identities$ = this.identitiesService.identities$;
|
||||
|
||||
constructor(private identitiesService: IdentitiesService) {
|
||||
super();
|
||||
}
|
||||
}
|
1
src/app/shared/components/inputs/identity-field/index.ts
Normal file
1
src/app/shared/components/inputs/identity-field/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './identity-field.component';
|
@ -174,6 +174,14 @@
|
||||
"hideAllStatuses": "Hide all statuses",
|
||||
"showAllStatuses": "Show all statuses"
|
||||
},
|
||||
"identityFilter": {
|
||||
"label": "Wallet holder"
|
||||
},
|
||||
"inputs": {
|
||||
"identityField": {
|
||||
"label": "Wallet holder"
|
||||
}
|
||||
},
|
||||
"internationalBankAccountForm": {
|
||||
"correspondentAccount": "Correspondent account"
|
||||
},
|
||||
|
@ -174,6 +174,14 @@
|
||||
"hideAllStatuses": "Скрыть все статусы",
|
||||
"showAllStatuses": "Показать все статусы"
|
||||
},
|
||||
"identityFilter": {
|
||||
"label": "Владелец кошелька"
|
||||
},
|
||||
"inputs": {
|
||||
"identityField": {
|
||||
"label": "Владелец кошелька"
|
||||
}
|
||||
},
|
||||
"internationalBankAccountForm": {
|
||||
"correspondentAccount": "Корреспондентский счет"
|
||||
},
|
||||
|
@ -153,6 +153,11 @@
|
||||
"DestinationCreated": "DestinationCreated (Receiver of funds is created)",
|
||||
"DestinationUnauthorized": "DestinationUnauthorized (An attempt to authorize the funds receiver failed)"
|
||||
},
|
||||
"reportStatus": {
|
||||
"canceled": "Cancelled",
|
||||
"created": "Created",
|
||||
"pending": "Pending"
|
||||
},
|
||||
"withdrawalStatus": {
|
||||
"Failed": "Failed",
|
||||
"Pending": "Pending",
|
||||
|
@ -153,6 +153,11 @@
|
||||
"DestinationCreated": "DestinationCreated (Приемник средств создан)",
|
||||
"DestinationUnauthorized": "DestinationUnauthorized (Попытка авторизации приемника средств завершилась неудачей)"
|
||||
},
|
||||
"reportStatus": {
|
||||
"canceled": "Отменен",
|
||||
"created": "Создан",
|
||||
"pending": "В процессе"
|
||||
},
|
||||
"withdrawalStatus": {
|
||||
"Failed": "Неуспешно",
|
||||
"Pending": "В процессе",
|
||||
|
@ -43,10 +43,32 @@
|
||||
"sourceID": "Source identifier",
|
||||
"walletID": "Wallet"
|
||||
},
|
||||
"reports": {
|
||||
"createReportDialog": {
|
||||
"confirm": "Generate",
|
||||
"error": "Error generating report",
|
||||
"from": "Start of the period",
|
||||
"subtitle": "Report with the register of operations for the selected period",
|
||||
"success": "Report created successfully",
|
||||
"time": "Time",
|
||||
"title": "Report generation status",
|
||||
"to": "End of the period"
|
||||
},
|
||||
"createdAt": "Created at",
|
||||
"dateRangeDescription": "Reporting period",
|
||||
"errors": {
|
||||
"identityNotSpecified": "Wallet holder not specified"
|
||||
},
|
||||
"openCreateReport": "Create report",
|
||||
"period": "Reporting period",
|
||||
"report": "Report",
|
||||
"status": "Status"
|
||||
},
|
||||
"walletSection": {
|
||||
"nav": {
|
||||
"deposits": "Deposits",
|
||||
"integrations": "Integration",
|
||||
"reports": "Reports",
|
||||
"wallets": "Wallets",
|
||||
"withdrawals": "Withdrawals"
|
||||
}
|
||||
|
@ -43,10 +43,32 @@
|
||||
"sourceID": "Идентификатор источника",
|
||||
"walletID": "Кошелек"
|
||||
},
|
||||
"reports": {
|
||||
"createReportDialog": {
|
||||
"confirm": "Сформировать",
|
||||
"error": "Ошибка при создании отчета",
|
||||
"from": "Начало периода",
|
||||
"subtitle": "Отчет с реестром операций за выбранный период",
|
||||
"success": "Отчет успешно создан",
|
||||
"time": "Время",
|
||||
"title": "Параметры формирования отчета",
|
||||
"to": "Конец периода"
|
||||
},
|
||||
"createdAt": "Дата и время создания",
|
||||
"dateRangeDescription": "Период создания отчетов",
|
||||
"errors": {
|
||||
"identityNotSpecified": "Владелец кошелька не указан"
|
||||
},
|
||||
"openCreateReport": "Создать отчет",
|
||||
"period": "Период отчетности",
|
||||
"report": "Отчет",
|
||||
"status": "Статус"
|
||||
},
|
||||
"walletSection": {
|
||||
"nav": {
|
||||
"deposits": "Пополнения",
|
||||
"integrations": "Интеграция",
|
||||
"reports": "Отчеты",
|
||||
"wallets": "Кошельки",
|
||||
"withdrawals": "Выводы"
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Injector } from '@angular/core';
|
||||
import { AbstractControl, FormControl } from '@angular/forms';
|
||||
import { FormComponentSuperclass } from '@vality/ng-core';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
@ -31,10 +30,6 @@ export abstract class FilterSuperclass<Inner, Outer = Inner> extends FormCompone
|
||||
|
||||
private _savedValue$ = new BehaviorSubject<Inner>(this.empty);
|
||||
|
||||
protected constructor(injector: Injector) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
handleIncomingValue(value: Outer): void {
|
||||
this.set(this.outerToInnerValue(value));
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, HostBinding, Input } from '@angular/core';
|
||||
import { coerceBoolean } from 'coerce-property';
|
||||
|
||||
import { StatusColor } from '../../../app/theme-manager';
|
||||
import { StatusColor } from '@dsh/app/theme-manager';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-status',
|
||||
|
Loading…
Reference in New Issue
Block a user