Browse Source

Feature/add total column to tag management (#7970)

* Add total column (accountCount + activityCount)

* Update changelog
pull/7868/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
7b899489b8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 18
      apps/client/src/app/components/admin-tag/admin-tag.component.html
  3. 28
      apps/client/src/app/components/admin-tag/admin-tag.component.ts
  4. 2
      libs/common/src/lib/types/index.ts
  5. 6
      libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts
  6. 5
      libs/ui/src/lib/services/data.service.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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Added the total column to the table of the tag management in the admin control panel
## 3.75.0 - 2026-09-28 ## 3.75.0 - 2026-09-28
### Changed ### Changed

18
apps/client/src/app/components/admin-tag/admin-tag.component.html

@ -77,6 +77,24 @@
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="total">
<th
*matHeaderCellDef
class="justify-content-end px-1"
mat-header-cell
mat-sort-header="totalCount"
>
<ng-container i18n>Total</ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
<gf-value
class="d-inline-block justify-content-end"
[locale]="locale()"
[value]="getTotalCount(element)"
/>
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="actions" stickyEnd>
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th> <th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell> <td *matCellDef="let element" class="px-1 text-center" mat-cell>

28
apps/client/src/app/components/admin-tag/admin-tag.component.ts

@ -7,6 +7,7 @@ import {
getLowercase, getLowercase,
isSystemTag isSystemTag
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { TagWithAccountAndActivityCount } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { NotificationService } from '@ghostfolio/ui/notifications'; import { NotificationService } from '@ghostfolio/ui/notifications';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
@ -32,7 +33,6 @@ import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
import { Tag } from '@prisma/client';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { import {
createOutline, createOutline,
@ -63,17 +63,20 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int
export class GfAdminTagComponent implements OnInit { export class GfAdminTagComponent implements OnInit {
public readonly locale = input(getLocale()); public readonly locale = input(getLocale());
protected dataSource = new MatTableDataSource<Tag>(); protected dataSource =
new MatTableDataSource<TagWithAccountAndActivityCount>();
protected readonly displayedColumns = [ protected readonly displayedColumns = [
'name', 'name',
'userId', 'userId',
'accounts', 'accounts',
'activities', 'activities',
'total',
'actions' 'actions'
]; ];
protected readonly isSystemTag = isSystemTag; protected readonly isSystemTag = isSystemTag;
protected readonly pageSize = DEFAULT_PAGE_SIZE; protected readonly pageSize = DEFAULT_PAGE_SIZE;
protected tags: Tag[]; protected tags: TagWithAccountAndActivityCount[];
protected readonly translate = translate; protected readonly translate = translate;
private readonly deviceType = computed( private readonly deviceType = computed(
@ -120,6 +123,13 @@ export class GfAdminTagComponent implements OnInit {
this.fetchTags(); this.fetchTags();
} }
protected getTotalCount({
accountCount,
activityCount
}: TagWithAccountAndActivityCount) {
return accountCount + activityCount;
}
protected onDeleteTag(aId: string) { protected onDeleteTag(aId: string) {
this.notificationService.confirm({ this.notificationService.confirm({
confirmFn: () => { confirmFn: () => {
@ -130,7 +140,7 @@ export class GfAdminTagComponent implements OnInit {
}); });
} }
protected onUpdateTag({ id }: Tag) { protected onUpdateTag({ id }: TagWithAccountAndActivityCount) {
this.router.navigate([], { this.router.navigate([], {
queryParams: { editTagDialog: true, tagId: id } queryParams: { editTagDialog: true, tagId: id }
}); });
@ -164,9 +174,13 @@ export class GfAdminTagComponent implements OnInit {
this.dataSource.sort = this.sort(); this.dataSource.sort = this.sort();
this.dataSource.sortingDataAccessor = (tag, path) => { this.dataSource.sortingDataAccessor = (tag, path) => {
return path === 'name' if (path === 'name') {
? translate(tag.name).toLocaleLowerCase() return translate(tag.name).toLocaleLowerCase();
: (getLowercase(tag, path) as number | string); } else if (path === 'totalCount') {
return this.getTotalCount(tag);
}
return getLowercase(tag, path) as number | string;
}; };
this.dataService.updateInfo(); this.dataService.updateInfo();

2
libs/common/src/lib/types/index.ts

@ -24,6 +24,7 @@ import type { PropertyKey } from './property-key.type';
import type { RequestWithUser } from './request-with-user.type'; import type { RequestWithUser } from './request-with-user.type';
import type { SectorName } from './sector-name.type'; import type { SectorName } from './sector-name.type';
import type { SubscriptionOfferKey } from './subscription-offer-key.type'; import type { SubscriptionOfferKey } from './subscription-offer-key.type';
import type { TagWithAccountAndActivityCount } from './tag-with-account-and-activity-count.type';
import type { ToggleOption } from './toggle-option.type'; import type { ToggleOption } from './toggle-option.type';
import type { UserWithSettings } from './user-with-settings.type'; import type { UserWithSettings } from './user-with-settings.type';
import type { ViewMode } from './view-mode.type'; import type { ViewMode } from './view-mode.type';
@ -55,6 +56,7 @@ export type {
RequestWithUser, RequestWithUser,
SectorName, SectorName,
SubscriptionOfferKey, SubscriptionOfferKey,
TagWithAccountAndActivityCount,
ToggleOption, ToggleOption,
UserWithSettings, UserWithSettings,
ViewMode ViewMode

6
libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts

@ -0,0 +1,6 @@
import { Tag } from '@prisma/client';
export type TagWithAccountAndActivityCount = Tag & {
accountCount: number;
activityCount: number;
};

5
libs/ui/src/lib/services/data.service.ts

@ -62,7 +62,8 @@ import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
import type { import type {
AiPromptMode, AiPromptMode,
DateRange, DateRange,
GroupBy GroupBy,
TagWithAccountAndActivityCount
} from '@ghostfolio/common/types'; } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
@ -843,7 +844,7 @@ export class DataService {
} }
public fetchTags() { public fetchTags() {
return this.http.get<Tag[]>('/api/v1/tags'); return this.http.get<TagWithAccountAndActivityCount[]>('/api/v1/tags');
} }
public fetchWatchlist() { public fetchWatchlist() {

Loading…
Cancel
Save