diff --git a/CHANGELOG.md b/CHANGELOG.md index cca265329..4e1b9e095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the usability of the activities import - Improved the usage of the premium indicator component - Removed the intro image in dark mode - Refactored the `TransactionsPageComponent` to `ActivitiesPageComponent` diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index 9a6c871e5..18b543c09 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -17,13 +17,13 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { DataSource, Order as OrderModel } from '@prisma/client'; import { format, parseISO } from 'date-fns'; -import { isArray } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject, Subscription } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { CreateOrUpdateActivityDialog } from './create-or-update-activity-dialog/create-or-update-activity-dialog.component'; import { ImportActivitiesDialog } from './import-activities-dialog/import-activities-dialog.component'; +import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces'; @Component({ host: { class: 'page' }, @@ -51,10 +51,8 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit { private dialog: MatDialog, private icsService: IcsService, private impersonationStorageService: ImpersonationStorageService, - private importActivitiesService: ImportActivitiesService, private route: ActivatedRoute, private router: Router, - private snackBar: MatSnackBar, private userService: UserService ) { this.routeQueryParams = route.queryParams @@ -186,88 +184,20 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit { } public onImport() { - const input = document.createElement('input'); - input.accept = 'application/JSON, .csv'; - input.type = 'file'; - - input.onchange = (event) => { - this.snackBar.open('⏳ ' + $localize`Importing data...`); - - // Getting the file reference - const file = (event.target as HTMLInputElement).files[0]; - - // Setting up the reader - const reader = new FileReader(); - reader.readAsText(file, 'UTF-8'); - - reader.onload = async (readerEvent) => { - const fileContent = readerEvent.target.result as string; - - try { - if (file.name.endsWith('.json')) { - const content = JSON.parse(fileContent); - - if (!isArray(content.activities)) { - if (isArray(content.orders)) { - this.handleImportError({ - activities: [], - error: { - error: { - message: [`orders needs to be renamed to activities`] - } - } - }); - return; - } else { - throw new Error(); - } - } - - try { - await this.importActivitiesService.importJson({ - content: content.activities - }); - - this.handleImportSuccess(); - } catch (error) { - console.error(error); - this.handleImportError({ error, activities: content.activities }); - } - - return; - } else if (file.name.endsWith('.csv')) { - try { - await this.importActivitiesService.importCsv({ - fileContent, - userAccounts: this.user.accounts - }); - - this.handleImportSuccess(); - } catch (error) { - console.error(error); - this.handleImportError({ - activities: error?.activities ?? [], - error: { - error: { message: error?.error?.message ?? [error?.message] } - } - }); - } - - return; - } - - throw new Error(); - } catch (error) { - console.error(error); - this.handleImportError({ - activities: [], - error: { error: { message: ['Unexpected format'] } } - }); - } - }; - }; + const dialogRef = this.dialog.open(ImportActivitiesDialog, { + data: { + deviceType: this.deviceType, + user: this.user + }, + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); - input.click(); + dialogRef + .afterClosed() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.fetchActivities(); + }); } public onUpdateActivity(aActivity: OrderModel) { @@ -315,37 +245,6 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } - private handleImportError({ - activities, - error - }: { - activities: any[]; - error: any; - }) { - this.snackBar.dismiss(); - - this.dialog.open(ImportActivitiesDialog, { - data: { - activities, - deviceType: this.deviceType, - messages: error?.error?.message - }, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - }); - } - - private handleImportSuccess() { - this.fetchActivities(); - - this.snackBar.open( - '✅ ' + $localize`Import has been completed`, - undefined, - { - duration: 3000 - } - ); - } - private openCreateActivityDialog(aActivity?: Activity): void { this.userService .get() diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index c9a87376e..5e4b5d5b8 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -1,10 +1,14 @@ import { ChangeDetectionStrategy, + ChangeDetectorRef, Component, Inject, OnDestroy } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; +import { isArray } from 'lodash'; import { Subject } from 'rxjs'; import { ImportActivitiesDialogParams } from './interfaces/interfaces'; @@ -17,34 +21,156 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; }) export class ImportActivitiesDialog implements OnDestroy { public details: any[] = []; + public errorMessages: string[] = []; private unsubscribeSubject = new Subject(); public constructor( + private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: ImportActivitiesDialogParams, - public dialogRef: MatDialogRef + public dialogRef: MatDialogRef, + private importActivitiesService: ImportActivitiesService, + private snackBar: MatSnackBar ) {} - public ngOnInit() { - for (const message of this.data.messages) { + public ngOnInit() {} + + public onCancel(): void { + this.dialogRef.close(); + } + + public onImport() { + const input = document.createElement('input'); + input.accept = 'application/JSON, .csv'; + input.type = 'file'; + + input.onchange = (event) => { + this.snackBar.open('⏳ ' + $localize`Importing data...`); + + // Getting the file reference + const file = (event.target as HTMLInputElement).files[0]; + + // Setting up the reader + const reader = new FileReader(); + reader.readAsText(file, 'UTF-8'); + + reader.onload = async (readerEvent) => { + const fileContent = readerEvent.target.result as string; + + console.log(fileContent); + + try { + if (file.name.endsWith('.json')) { + const content = JSON.parse(fileContent); + + if (!isArray(content.activities)) { + if (isArray(content.orders)) { + this.handleImportError({ + activities: [], + error: { + error: { + message: [`orders needs to be renamed to activities`] + } + } + }); + return; + } else { + throw new Error(); + } + } + + try { + await this.importActivitiesService.importJson({ + content: content.activities + }); + + this.handleImportSuccess(); + } catch (error) { + console.error(error); + this.handleImportError({ error, activities: content.activities }); + } + + return; + } else if (file.name.endsWith('.csv')) { + try { + await this.importActivitiesService.importCsv({ + fileContent, + userAccounts: this.data.user.accounts + }); + + this.handleImportSuccess(); + } catch (error) { + console.error(error); + this.handleImportError({ + activities: error?.activities ?? [], + error: { + error: { message: error?.error?.message ?? [error?.message] } + } + }); + } + + return; + } + + throw new Error(); + } catch (error) { + console.error(error); + this.handleImportError({ + activities: [], + error: { error: { message: ['Unexpected format'] } } + }); + } + }; + }; + + input.click(); + } + + public onReset() { + this.details = []; + this.errorMessages = []; + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } + + private handleImportError({ + activities, + error + }: { + activities: any[]; + error: any; + }) { + this.snackBar.dismiss(); + + this.errorMessages = error?.error?.message; + + for (const message of this.errorMessages) { if (message.includes('activities.')) { let [index] = message.split(' '); index = index.replace('activities.', ''); [index] = index.split('.'); - this.details.push(this.data.activities[index]); + this.details.push(activities[index]); } else { this.details.push(''); } } - } - public onCancel(): void { - this.dialogRef.close(); + this.changeDetectorRef.markForCheck(); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); + private handleImportSuccess() { + this.snackBar.open( + '✅ ' + $localize`Import has been completed`, + undefined, + { + duration: 3000 + } + ); + + this.dialogRef.close(); } } diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html index b8069e849..d38d23a1e 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -1,32 +1,67 @@
- - - - -
-
- + +
+ +

+ The following file formats are expected: + CSV + or + JSON +

+
+
+ + + + + +
+
+ +
+
{{ message }}
-
{{ message }}
-
- - -
{{ details[i] | json }}
- - + + +
{{ details[i] | json }}
+ + +
+ +
+