Browse Source

Feature/extend GUI to delete watchlist item (#4624)

* Extend GUI to delete watchlist item

* Update changelog
pull/4577/head
Kenrick Tandrian 3 days ago
committed by GitHub
parent
commit
73f009e43b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 25
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  3. 2
      apps/client/src/app/components/home-watchlist/home-watchlist.html
  4. 4
      apps/client/src/app/services/data.service.ts
  5. 33
      libs/ui/src/lib/benchmark/benchmark.component.html
  6. 35
      libs/ui/src/lib/benchmark/benchmark.component.ts

6
CHANGELOG.md

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Added support to delete an asset from the watchlist (experimental)
## 2.157.1 - 2025-04-29
### Added

25
apps/client/src/app/components/home-watchlist/home-watchlist.component.ts

@ -1,6 +1,10 @@
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { Benchmark, User } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
Benchmark,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
@ -41,6 +45,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
export class HomeWatchlistComponent implements OnDestroy, OnInit {
public deviceType: string;
public hasPermissionToCreateWatchlistItem: boolean;
public hasPermissionToDeleteWatchlistItem: boolean;
public user: User;
public watchlist: Benchmark[];
@ -75,6 +80,10 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit {
this.user.permissions,
permissions.createWatchlistItem
);
this.hasPermissionToDeleteWatchlistItem = hasPermission(
this.user.permissions,
permissions.deleteWatchlistItem
);
this.changeDetectorRef.markForCheck();
}
@ -85,6 +94,20 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit {
this.loadWatchlistData();
}
public onWatchlistItemDeleted({
dataSource,
symbol
}: AssetProfileIdentifier) {
this.dataService
.deleteWatchlistItem({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe({
next: () => {
return this.loadWatchlistData();
}
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();

2
apps/client/src/app/components/home-watchlist/home-watchlist.html

@ -12,8 +12,10 @@
<gf-benchmark
[benchmarks]="watchlist"
[deviceType]="deviceType"
[hasPermissionToDeleteItem]="hasPermissionToDeleteWatchlistItem"
[locale]="user?.settings?.locale || undefined"
[user]="user"
(itemDeleted)="onWatchlistItemDeleted($event)"
/>
</div>
</div>

4
apps/client/src/app/services/data.service.ts

@ -327,6 +327,10 @@ export class DataService {
return this.http.delete<any>(`/api/v1/user/${aId}`);
}
public deleteWatchlistItem({ dataSource, symbol }: AssetProfileIdentifier) {
return this.http.delete<any>(`/api/v1/watchlist/${dataSource}/${symbol}`);
}
public fetchAccesses() {
return this.http.get<Access[]>('/api/v1/access');
}

33
libs/ui/src/lib/benchmark/benchmark.component.html

@ -113,6 +113,39 @@
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
@if (hasPermissionToDeleteItem) {
<button
class="mx-1 no-min-width px-2"
mat-button
[matMenuTriggerFor]="benchmarkMenu"
(click)="$event.stopPropagation()"
>
<ion-icon name="ellipsis-horizontal" />
</button>
}
<mat-menu #benchmarkMenu="matMenu" xPosition="before">
<button
mat-menu-item
[disabled]="!hasPermissionToDeleteItem"
(click)="
onDeleteItem({
dataSource: element.dataSource,
symbol: element.symbol
})
"
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="trash-outline" />
<span i18n>Delete</span>
</span>
</button>
</mat-menu>
</td>
</ng-container>
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr
*matRowDef="let row; columns: displayedColumns"

35
libs/ui/src/lib/benchmark/benchmark.component.ts

@ -1,3 +1,5 @@
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
@ -13,11 +15,15 @@ import {
CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
OnDestroy
OnDestroy,
Output
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu';
import { MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { isNumber } from 'lodash';
@ -33,6 +39,8 @@ import { BenchmarkDetailDialogParams } from './benchmark-detail-dialog/interface
CommonModule,
GfTrendIndicatorComponent,
GfValueComponent,
MatButtonModule,
MatMenuModule,
MatTableModule,
NgxSkeletonLoaderModule,
RouterModule
@ -45,10 +53,19 @@ import { BenchmarkDetailDialogParams } from './benchmark-detail-dialog/interface
export class GfBenchmarkComponent implements OnChanges, OnDestroy {
@Input() benchmarks: Benchmark[];
@Input() deviceType: string;
@Input() hasPermissionToDeleteItem: boolean;
@Input() locale = getLocale();
@Input() user: User;
public displayedColumns = ['name', 'date', 'change', 'marketCondition'];
@Output() itemDeleted = new EventEmitter<AssetProfileIdentifier>();
public displayedColumns = [
'name',
'date',
'change',
'marketCondition',
'actions'
];
public isLoading = true;
public isNumber = isNumber;
public resolveMarketCondition = resolveMarketCondition;
@ -58,6 +75,7 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
public constructor(
private dialog: MatDialog,
private notificationService: NotificationService,
private route: ActivatedRoute,
private router: Router
) {
@ -89,11 +107,22 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
'trend200d',
'date',
'change',
'marketCondition'
'marketCondition',
'actions'
];
}
}
public onDeleteItem({ dataSource, symbol }: AssetProfileIdentifier) {
this.notificationService.confirm({
confirmFn: () => {
this.itemDeleted.emit({ dataSource, symbol });
},
confirmType: ConfirmationDialogType.Warn,
title: $localize`Do you really want to delete this item?`
});
}
public onOpenBenchmarkDialog({ dataSource, symbol }: AssetProfileIdentifier) {
this.router.navigate([], {
queryParams: { dataSource, symbol, benchmarkDetailDialog: true }

Loading…
Cancel
Save