import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { DEFAULT_PAGE_SIZE, TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config'; import { getLocale } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { SelectionModel } from '@angular/cdk/collections'; import { CommonModule } from '@angular/common'; import { AfterViewInit, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatMenuModule } from '@angular/material/menu'; import { MatPaginator, MatPaginatorModule, PageEvent } from '@angular/material/paginator'; import { MatSort, MatSortModule, Sort, SortDirection } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { IonIcon } from '@ionic/angular/standalone'; import { isUUID } from 'class-validator'; import { endOfToday, isAfter } from 'date-fns'; import { addIcons } from 'ionicons'; import { alertCircleOutline, calendarClearOutline, cloudDownloadOutline, cloudUploadOutline, colorWandOutline, copyOutline, createOutline, documentTextOutline, ellipsisHorizontal, ellipsisVertical, trashOutline } from 'ionicons/icons'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject, Subscription, takeUntil } from 'rxjs'; import { GfActivityTypeComponent } from '../activity-type/activity-type.component'; import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component'; import { GfNoTransactionsInfoComponent } from '../no-transactions-info/no-transactions-info.component'; import { GfValueComponent } from '../value/value.component'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CommonModule, GfActivityTypeComponent, GfEntityLogoComponent, GfNoTransactionsInfoComponent, GfSymbolModule, GfValueComponent, IonIcon, MatButtonModule, MatCheckboxModule, MatMenuModule, MatPaginatorModule, MatSortModule, MatTableModule, MatTooltipModule, NgxSkeletonLoaderModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-activities-table', styleUrls: ['./activities-table.component.scss'], templateUrl: './activities-table.component.html' }) export class GfActivitiesTableComponent implements AfterViewInit, OnChanges, OnDestroy, OnInit { @Input() baseCurrency: string; @Input() dataSource: MatTableDataSource; @Input() deviceType: string; @Input() hasActivities: boolean; @Input() hasPermissionToCreateActivity: boolean; @Input() hasPermissionToDeleteActivity: boolean; @Input() hasPermissionToExportActivities: boolean; @Input() hasPermissionToOpenDetails = true; @Input() locale = getLocale(); @Input() pageIndex: number; @Input() pageSize = DEFAULT_PAGE_SIZE; @Input() showAccountColumn = true; @Input() showActions = true; @Input() showCheckbox = false; @Input() showNameColumn = true; @Input() sortColumn: string; @Input() sortDirection: SortDirection; @Input() sortDisabled = false; @Input() totalItems = Number.MAX_SAFE_INTEGER; @Output() activitiesDeleted = new EventEmitter(); @Output() activityClicked = new EventEmitter(); @Output() activityDeleted = new EventEmitter(); @Output() activityToClone = new EventEmitter(); @Output() activityToUpdate = new EventEmitter(); @Output() export = new EventEmitter(); @Output() exportDrafts = new EventEmitter(); @Output() import = new EventEmitter(); @Output() importDividends = new EventEmitter(); @Output() pageChanged = new EventEmitter(); @Output() selectedActivities = new EventEmitter(); @Output() sortChanged = new EventEmitter(); @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; public displayedColumns = []; public endOfToday = endOfToday(); public hasDrafts = false; public hasErrors = false; public isAfter = isAfter; public isLoading = true; public isUUID = isUUID; public routeQueryParams: Subscription; public selectedRows = new SelectionModel(true, []); private unsubscribeSubject = new Subject(); public constructor(private notificationService: NotificationService) { addIcons({ alertCircleOutline, calendarClearOutline, cloudDownloadOutline, cloudUploadOutline, colorWandOutline, copyOutline, createOutline, documentTextOutline, ellipsisHorizontal, ellipsisVertical, trashOutline }); } public ngOnInit() { if (this.showCheckbox) { this.toggleAllRows(); this.selectedRows.changed .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((selectedRows) => { this.selectedActivities.emit(selectedRows.source.selected); }); } } public ngAfterViewInit() { if (this.dataSource) { this.dataSource.paginator = this.paginator; } this.sort.sortChange.subscribe((value: Sort) => { this.sortChanged.emit(value); }); } public ngOnChanges() { this.displayedColumns = [ 'select', 'importStatus', 'icon', 'nameWithSymbol', 'type', 'date', 'quantity', 'unitPrice', 'fee', 'value', 'currency', 'valueInBaseCurrency', 'account', 'comment', 'actions' ]; if (!this.showAccountColumn) { this.displayedColumns = this.displayedColumns.filter((column) => { return column !== 'account'; }); } if (!this.showCheckbox) { this.displayedColumns = this.displayedColumns.filter((column) => { return column !== 'importStatus' && column !== 'select'; }); } if (!this.showNameColumn) { this.displayedColumns = this.displayedColumns.filter((column) => { return column !== 'nameWithSymbol'; }); } if (this.dataSource) { this.isLoading = false; } } public areAllRowsSelected() { const numSelectedRows = this.selectedRows.selected.length; const numTotalRows = this.dataSource.data.length; return numSelectedRows === numTotalRows; } public isExcludedFromAnalysis(activity: Activity) { return ( activity.account?.isExcluded || activity.tags?.some(({ id }) => { return id === TAG_ID_EXCLUDE_FROM_ANALYSIS; }) ); } public onChangePage(page: PageEvent) { this.pageChanged.emit(page); } public onClickActivity(activity: Activity) { if (this.showCheckbox) { if (!activity.error) { this.selectedRows.toggle(activity); } } else if ( this.hasPermissionToOpenDetails && this.isExcludedFromAnalysis(activity) === false && activity.isDraft === false && ['BUY', 'DIVIDEND', 'SELL'].includes(activity.type) ) { this.activityClicked.emit({ dataSource: activity.SymbolProfile.dataSource, symbol: activity.SymbolProfile.symbol }); } } public onCloneActivity(aActivity: OrderWithAccount) { this.activityToClone.emit(aActivity); } public onDeleteActivities() { this.notificationService.confirm({ confirmFn: () => { this.activitiesDeleted.emit(); }, confirmType: ConfirmationDialogType.Warn, title: $localize`Do you really want to delete these activities?` }); } public onDeleteActivity(aId: string) { this.notificationService.confirm({ confirmFn: () => { this.activityDeleted.emit(aId); }, confirmType: ConfirmationDialogType.Warn, title: $localize`Do you really want to delete this activity?` }); } public onExport() { this.export.emit(); } public onExportDraft(aActivityId: string) { this.exportDrafts.emit([aActivityId]); } public onExportDrafts() { this.exportDrafts.emit( this.dataSource.filteredData .filter((activity) => { return activity.isDraft; }) .map((activity) => { return activity.id; }) ); } public onImport() { this.import.emit(); } public onImportDividends() { this.importDividends.emit(); } public onOpenComment(aComment: string) { this.notificationService.alert({ title: aComment }); } public onUpdateActivity(aActivity: OrderWithAccount) { this.activityToUpdate.emit(aActivity); } public toggleAllRows() { if (this.areAllRowsSelected()) { this.selectedRows.clear(); } else { this.dataSource.data.forEach((row) => { this.selectedRows.select(row); }); } this.selectedActivities.emit(this.selectedRows.selected); } public ngOnDestroy() { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); } }