Introduced stepper to import activities (#1990)

* Introduced stepper to import activities

* Update changelog
pull/2014/head
Visrut 2 years ago committed by GitHub
parent b24ddc30c9
commit 16a5ace4be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added a stepper to the activities import dialog
- Added a link to manage the benchmarks to the benchmark comparator - Added a link to manage the benchmarks to the benchmark comparator
- Added support for localized routes - Added support for localized routes

@ -0,0 +1,4 @@
export enum ImportStep {
UPLOAD_FILE = 0,
SELECT_ACTIVITIES = 1
}

@ -1,3 +1,7 @@
import {
StepperOrientation,
StepperSelectionEvent
} from '@angular/cdk/stepper';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
@ -8,6 +12,7 @@ import {
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { MatStepper } from '@angular/material/stepper';
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
@ -15,8 +20,10 @@ import { ImportActivitiesService } from '@ghostfolio/client/services/import-acti
import { Position } from '@ghostfolio/common/interfaces'; import { Position } from '@ghostfolio/common/interfaces';
import { AssetClass } from '@prisma/client'; import { AssetClass } from '@prisma/client';
import { isArray, sortBy } from 'lodash'; import { isArray, sortBy } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
import { ImportStep } from './enums/import-step';
import { ImportActivitiesDialogParams } from './interfaces/interfaces'; import { ImportActivitiesDialogParams } from './interfaces/interfaces';
@Component({ @Component({
@ -29,12 +36,14 @@ export class ImportActivitiesDialog implements OnDestroy {
public accounts: CreateAccountDto[] = []; public accounts: CreateAccountDto[] = [];
public activities: Activity[] = []; public activities: Activity[] = [];
public details: any[] = []; public details: any[] = [];
public deviceType: string;
public errorMessages: string[] = []; public errorMessages: string[] = [];
public holdings: Position[] = []; public holdings: Position[] = [];
public isFileSelected = false; public importStep: ImportStep = ImportStep.UPLOAD_FILE;
public maxSafeInteger = Number.MAX_SAFE_INTEGER; public maxSafeInteger = Number.MAX_SAFE_INTEGER;
public mode: 'DIVIDEND'; public mode: 'DIVIDEND';
public selectedActivities: Activity[] = []; public selectedActivities: Activity[] = [];
public stepperOrientation: StepperOrientation;
public uniqueAssetForm: FormGroup; public uniqueAssetForm: FormGroup;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -43,6 +52,7 @@ export class ImportActivitiesDialog implements OnDestroy {
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: ImportActivitiesDialogParams, @Inject(MAT_DIALOG_DATA) public data: ImportActivitiesDialogParams,
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
public dialogRef: MatDialogRef<ImportActivitiesDialog>, public dialogRef: MatDialogRef<ImportActivitiesDialog>,
private importActivitiesService: ImportActivitiesService, private importActivitiesService: ImportActivitiesService,
@ -50,6 +60,10 @@ export class ImportActivitiesDialog implements OnDestroy {
) {} ) {}
public ngOnInit() { public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.stepperOrientation =
this.deviceType === 'mobile' ? 'vertical' : 'horizontal';
this.uniqueAssetForm = this.formBuilder.group({ this.uniqueAssetForm = this.formBuilder.group({
uniqueAsset: [undefined, Validators.required] uniqueAsset: [undefined, Validators.required]
}); });
@ -116,7 +130,15 @@ export class ImportActivitiesDialog implements OnDestroy {
} }
} }
public onLoadDividends() { public onImportStepChange(event: StepperSelectionEvent) {
if (event.selectedIndex === ImportStep.UPLOAD_FILE) {
this.importStep = ImportStep.UPLOAD_FILE;
} else if (event.selectedIndex === ImportStep.SELECT_ACTIVITIES) {
this.importStep = ImportStep.SELECT_ACTIVITIES;
}
}
public onLoadDividends(aStepper: MatStepper) {
this.uniqueAssetForm.controls['uniqueAsset'].disable(); this.uniqueAssetForm.controls['uniqueAsset'].disable();
const { dataSource, symbol } = const { dataSource, symbol } =
@ -130,19 +152,23 @@ export class ImportActivitiesDialog implements OnDestroy {
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ activities }) => { .subscribe(({ activities }) => {
this.activities = activities; this.activities = activities;
this.isFileSelected = true;
aStepper.next();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
public onReset() { public onReset(aStepper: MatStepper) {
this.details = []; this.details = [];
this.errorMessages = []; this.errorMessages = [];
this.isFileSelected = false; this.importStep = ImportStep.SELECT_ACTIVITIES;
this.uniqueAssetForm.controls['uniqueAsset'].enable();
aStepper.reset();
} }
public onSelectFile() { public onSelectFile(aStepper: MatStepper) {
const input = document.createElement('input'); const input = document.createElement('input');
input.accept = 'application/JSON, .csv'; input.accept = 'application/JSON, .csv';
input.type = 'file'; input.type = 'file';
@ -225,8 +251,11 @@ export class ImportActivitiesDialog implements OnDestroy {
error: { error: { message: ['Unexpected format'] } } error: { error: { message: ['Unexpected format'] } }
}); });
} finally { } finally {
this.isFileSelected = true; this.importStep = ImportStep.SELECT_ACTIVITIES;
this.snackBar.dismiss(); this.snackBar.dismiss();
aStepper.next();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
} }
}; };

@ -5,10 +5,23 @@
(closeButtonClicked)="onCancel()" (closeButtonClicked)="onCancel()"
></gf-dialog-header> ></gf-dialog-header>
<div class="flex-grow-1 py-3" mat-dialog-content> <div class="flex-grow-1" mat-dialog-content>
<ng-container *ngIf="!isFileSelected"> <mat-stepper
#stepper
[animationDuration]="0"
[linear]="true"
[orientation]="stepperOrientation"
[selectedIndex]="importStep"
(selectionChange)="onImportStepChange($event)"
>
<mat-step [completed]="importStep === 0" [selected]="importStep === 0">
<ng-template i18n matStepLabel>Select File</ng-template>
<div class="pt-3">
<ng-container *ngIf="mode === 'DIVIDEND'; else selectFile"> <ng-container *ngIf="mode === 'DIVIDEND'; else selectFile">
<form [formGroup]="uniqueAssetForm" (ngSubmit)="onLoadDividends()"> <form
[formGroup]="uniqueAssetForm"
(ngSubmit)="onLoadDividends(stepper)"
>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Holding</mat-label> <mat-label i18n>Holding</mat-label>
<mat-select formControlName="uniqueAsset"> <mat-select formControlName="uniqueAsset">
@ -19,7 +32,7 @@
> >
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div class="d-flex justify-content-center flex-column"> <div class="d-flex flex-column justify-content-center">
<button <button
color="primary" color="primary"
mat-flat-button mat-flat-button
@ -32,12 +45,12 @@
</form> </form>
</ng-container> </ng-container>
<ng-template #selectFile> <ng-template #selectFile>
<div class="d-flex justify-content-center flex-column"> <div class="d-flex flex-column justify-content-center">
<button <button
class="py-4" class="py-4"
color="primary" color="primary"
mat-stroked-button mat-stroked-button
(click)="onSelectFile()" (click)="onSelectFile(stepper)"
> >
<ion-icon class="mr-2" name="cloud-upload-outline"></ion-icon> <ion-icon class="mr-2" name="cloud-upload-outline"></ion-icon>
<span i18n>Choose File</span> <span i18n>Choose File</span>
@ -60,10 +73,15 @@
</p> </p>
</div> </div>
</ng-template> </ng-template>
</ng-container> </div>
<ng-container *ngIf="isFileSelected"> </mat-step>
<mat-step [completed]="importStep === 1" [selected]="importStep === 1">
<ng-template i18n matStepLabel>Select Activities</ng-template>
<div class="pt-3">
<ng-container *ngIf="errorMessages.length === 0; else errorMessage"> <ng-container *ngIf="errorMessages.length === 0; else errorMessage">
<gf-activities-table <gf-activities-table
*ngIf="importStep === 1"
[activities]="activities" [activities]="activities"
[baseCurrency]="data?.user?.settings?.baseCurrency" [baseCurrency]="data?.user?.settings?.baseCurrency"
[deviceType]="data?.deviceType" [deviceType]="data?.deviceType"
@ -79,6 +97,20 @@
[showSymbolColumn]="false" [showSymbolColumn]="false"
(selectedActivities)="updateSelection($event)" (selectedActivities)="updateSelection($event)"
></gf-activities-table> ></gf-activities-table>
<div class="d-flex justify-content-end mt-3">
<button mat-button (click)="onReset(stepper)">
<ng-container i18n>Back</ng-container>
</button>
<button
class="ml-1"
color="primary"
mat-flat-button
[disabled]="!selectedActivities?.length"
(click)="onImportActivities()"
>
<ng-container i18n>Import</ng-container>
</button>
</div>
</ng-container> </ng-container>
<ng-template #errorMessage> <ng-template #errorMessage>
<mat-accordion displayMode="flat"> <mat-accordion displayMode="flat">
@ -102,26 +134,23 @@
><code>{{ details[i] | json }}</code></pre> ><code>{{ details[i] | json }}</code></pre>
</mat-expansion-panel> </mat-expansion-panel>
</mat-accordion> </mat-accordion>
<div class="mt-2"> <div class="d-flex justify-content-end mt-3">
<button mat-button (click)="onReset()"> <button mat-button (click)="onReset(stepper)">
<ion-icon class="mr-2" name="arrow-back-outline"></ion-icon> <ng-container i18n>Back</ng-container>
<span i18n>Back</span>
</button> </button>
</div>
</ng-template>
</ng-container>
</div>
<div *ngIf="isFileSelected" class="justify-content-end" mat-dialog-actions>
<button i18n mat-button (click)="onCancel()">Cancel</button>
<button <button
class="ml-1"
color="primary" color="primary"
mat-flat-button mat-flat-button
[disabled]="!selectedActivities?.length" [disabled]="true"
(click)="onImportActivities()"
> >
<ng-container i18n>Import</ng-container> <ng-container i18n>Import</ng-container>
</button> </button>
</div>
</ng-template>
</div>
</mat-step>
</mat-stepper>
</div> </div>
<gf-dialog-footer <gf-dialog-footer

@ -6,6 +6,7 @@ import { MatDialogModule } from '@angular/material/dialog';
import { MatExpansionModule } from '@angular/material/expansion'; import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatStepperModule } from '@angular/material/stepper';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module'; import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module'; import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module'; import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
@ -25,6 +26,7 @@ import { ImportActivitiesDialog } from './import-activities-dialog.component';
MatExpansionModule, MatExpansionModule,
MatFormFieldModule, MatFormFieldModule,
MatSelectModule, MatSelectModule,
MatStepperModule,
ReactiveFormsModule ReactiveFormsModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]

@ -5,6 +5,16 @@
color: rgba(var(--palette-primary-500), 1); color: rgba(var(--palette-primary-500), 1);
} }
mat-stepper {
::ng-deep {
.mat-step-header {
&[aria-selected='false'] {
pointer-events: none;
}
}
}
}
.mat-expansion-panel { .mat-expansion-panel {
background: none; background: none;
box-shadow: none; box-shadow: none;

Loading…
Cancel
Save