From 338839197d010647a3b977aed3376e4b31d91bc2 Mon Sep 17 00:00:00 2001 From: Frane Caleta Date: Tue, 12 Sep 2023 23:49:54 +0200 Subject: [PATCH] Feature - add drag and drop functionality on import --- .../file-drop.directive.ts | 29 +++++ .../import-activities-dialog.component.ts | 111 ++++++++++++++++++ .../import-activities-dialog.html | 15 ++- .../import-activities-dialog.module.ts | 3 +- .../import-activities-dialog.scss | 30 +++++ 5 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/file-drop.directive.ts diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/file-drop.directive.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/file-drop.directive.ts new file mode 100644 index 000000000..75eb7f1c8 --- /dev/null +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/file-drop.directive.ts @@ -0,0 +1,29 @@ +import { Directive, HostListener, Output, EventEmitter } from '@angular/core'; + +@Directive({ + selector: '[appFileDrop]' +}) +export class FileDropDirective { + @Output() filesDropped = new EventEmitter(); + + @HostListener('dragenter', ['$event']) onDragEnter(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + @HostListener('dragover', ['$event']) onDragOver(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + @HostListener('drop', ['$event']) onDrop(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + + // Prevent the browser's default behavior for handling the file drop + event.dataTransfer.dropEffect = 'copy'; + + const files = event.dataTransfer.files; + this.filesDropped.emit(files); + } +} 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 46a307318..1a23e667a 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 @@ -175,6 +175,117 @@ export class ImportActivitiesDialog implements OnDestroy { aStepper.reset(); } + public onFilesDropped(files: FileList): void { + if (files.length === 0) { + return; + } + + const droppedFile = files[0]; + + // Check the file type and handle it accordingly (JSON/CSV) + if ( + droppedFile.type === 'application/json' || + droppedFile.name.endsWith('.json') + ) { + // Handle JSON file + const reader = new FileReader(); + reader.readAsText(droppedFile, 'UTF-8'); + + reader.onload = async (readerEvent) => { + const fileContent = readerEvent.target.result as string; + + try { + const content = JSON.parse(fileContent); + + this.accounts = content.accounts; + + if (!Array.isArray(content.activities)) { + if (Array.isArray(content.orders)) { + // Handle orders (activities) data + const { activities } = + await this.importActivitiesService.importJson({ + accounts: content.accounts, + activities: content.orders, + isDryRun: true + }); + this.activities = activities; + } else { + throw new Error('Unexpected JSON format'); + } + } else { + // Handle activities data + const { activities } = + await this.importActivitiesService.importJson({ + accounts: content.accounts, + activities: content.activities, + isDryRun: true + }); + this.activities = activities; + } + } catch (error) { + console.error(error); + this.handleImportError({ + activities: [], + error: { + error: { message: ['Error handling JSON file'] } + } + }); + } finally { + this.importStep = ImportStep.SELECT_ACTIVITIES; + this.snackBar.dismiss(); + // Proceed with further steps or updates as needed + this.changeDetectorRef.markForCheck(); + } + }; + } else if ( + droppedFile.type === 'text/csv' || + droppedFile.name.endsWith('.csv') + ) { + // Handle CSV file + const reader = new FileReader(); + reader.readAsText(droppedFile, 'UTF-8'); + + reader.onload = async (readerEvent) => { + const fileContent = readerEvent.target.result as string; + + try { + const data = await this.importActivitiesService.importCsv({ + fileContent, + isDryRun: true, + userAccounts: this.data.user.accounts + }); + this.activities = data.activities; + } catch (error) { + console.error(error); + this.handleImportError({ + activities: error?.activities ?? [], + error: { + error: { message: error?.error?.message ?? [error?.message] } + } + }); + } finally { + this.importStep = ImportStep.SELECT_ACTIVITIES; + this.snackBar.dismiss(); + // Proceed with further steps or updates as needed + this.changeDetectorRef.markForCheck(); + } + }; + } else { + // Handle unsupported file type + console.error('Unsupported file format'); + this.handleImportError({ + activities: [], + error: { + error: { message: ['Unsupported file format'] } + } + }); + this.importStep = ImportStep.SELECT_ACTIVITIES; + this.snackBar.dismiss(); + // Proceed with further steps or updates as needed + this.changeDetectorRef.markForCheck(); + } + } + public onSelectFile(aStepper: MatStepper) { const input = document.createElement('input'); input.accept = 'application/JSON, .csv'; 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 02071c137..86533e35f 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 @@ -70,13 +70,18 @@