From 1e177770a078a2f65b545a3ae63b9d32f1947e0f Mon Sep 17 00:00:00 2001 From: David Requeno Date: Tue, 29 Jul 2025 00:10:53 -0600 Subject: [PATCH] Feature/move accounts table to ui 20250729 * Move accounts table component to @ghostfolio/ui library * Add Storybook story with sample data from live demo * Update imports in client application --- .../access-table/access-table.component.html | 84 -------- .../access-table/access-table.component.scss | 11 - .../access-table/access-table.component.ts | 111 ---------- .../accounts-table.component.scss | 13 -- .../holding-detail-dialog.component.ts | 2 +- .../pages/accounts/accounts-page.component.ts | 2 +- .../accounts-table.component.html | 2 +- .../accounts-table.component.scss | 13 ++ .../accounts-table.component.stories.ts | 199 ++++++++++++++++++ .../accounts-table.component.ts | 2 +- libs/ui/src/lib/accounts-table/index.ts | 1 + 11 files changed, 217 insertions(+), 223 deletions(-) delete mode 100644 apps/client/src/app/components/access-table/access-table.component.html delete mode 100644 apps/client/src/app/components/access-table/access-table.component.scss delete mode 100644 apps/client/src/app/components/access-table/access-table.component.ts delete mode 100644 apps/client/src/app/components/accounts-table/accounts-table.component.scss rename {apps/client/src/app/components => libs/ui/src/lib}/accounts-table/accounts-table.component.html (99%) create mode 100644 libs/ui/src/lib/accounts-table/accounts-table.component.scss create mode 100644 libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts rename {apps/client/src/app/components => libs/ui/src/lib}/accounts-table/accounts-table.component.ts (99%) create mode 100644 libs/ui/src/lib/accounts-table/index.ts diff --git a/apps/client/src/app/components/access-table/access-table.component.html b/apps/client/src/app/components/access-table/access-table.component.html deleted file mode 100644 index 44aee1644..000000000 --- a/apps/client/src/app/components/access-table/access-table.component.html +++ /dev/null @@ -1,84 +0,0 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Alias - {{ element.alias }} - Grantee - {{ element.grantee }} - Permission -
- @if (element.permissions.includes('READ')) { - - View - } @else if (element.permissions.includes('READ_RESTRICTED')) { - - Restricted view - } -
-
Details - @if (element.type === 'PUBLIC') { - - @if (user?.settings?.isExperimentalFeatures) { -
- GET {{ baseUrl }}/api/v1/public/{{ - element.id - }}/portfolio -
- } - } -
- - - @if (element.type === 'PUBLIC') { - -
- } - -
-
-
diff --git a/apps/client/src/app/components/access-table/access-table.component.scss b/apps/client/src/app/components/access-table/access-table.component.scss deleted file mode 100644 index 22a5d6732..000000000 --- a/apps/client/src/app/components/access-table/access-table.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -:host { - display: block; - - a { - color: rgba(var(--palette-primary-500), 1); - - &:hover { - color: rgba(var(--palette-primary-300), 1); - } - } -} diff --git a/apps/client/src/app/components/access-table/access-table.component.ts b/apps/client/src/app/components/access-table/access-table.component.ts deleted file mode 100644 index c94f86df1..000000000 --- a/apps/client/src/app/components/access-table/access-table.component.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; -import { Access, User } from '@ghostfolio/common/interfaces'; -import { publicRoutes } from '@ghostfolio/common/routes/routes'; - -import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'; -import { CommonModule } from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - CUSTOM_ELEMENTS_SCHEMA, - EventEmitter, - Input, - OnChanges, - Output -} from '@angular/core'; -import { MatButtonModule } from '@angular/material/button'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { MatTableDataSource, MatTableModule } from '@angular/material/table'; -import { RouterModule } from '@angular/router'; -import { IonIcon } from '@ionic/angular/standalone'; -import { addIcons } from 'ionicons'; -import { - ellipsisHorizontal, - linkOutline, - lockClosedOutline, - lockOpenOutline -} from 'ionicons/icons'; -import ms from 'ms'; - -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - ClipboardModule, - CommonModule, - IonIcon, - MatButtonModule, - MatMenuModule, - MatTableModule, - RouterModule - ], - schemas: [CUSTOM_ELEMENTS_SCHEMA], - selector: 'gf-access-table', - templateUrl: './access-table.component.html', - styleUrls: ['./access-table.component.scss'] -}) -export class GfAccessTableComponent implements OnChanges { - @Input() accesses: Access[]; - @Input() showActions: boolean; - @Input() user: User; - - @Output() accessDeleted = new EventEmitter(); - - public baseUrl = window.location.origin; - public dataSource: MatTableDataSource; - public displayedColumns = []; - - public constructor( - private clipboard: Clipboard, - private notificationService: NotificationService, - private snackBar: MatSnackBar - ) { - addIcons({ - ellipsisHorizontal, - linkOutline, - lockClosedOutline, - lockOpenOutline - }); - } - - public ngOnChanges() { - this.displayedColumns = ['alias', 'grantee', 'type', 'details']; - - if (this.showActions) { - this.displayedColumns.push('actions'); - } - - if (this.accesses) { - this.dataSource = new MatTableDataSource(this.accesses); - } - } - - public getPublicUrl(aId: string): string { - const languageCode = this.user.settings.language; - - return `${this.baseUrl}/${languageCode}/${publicRoutes.public.path}/${aId}`; - } - - public onCopyUrlToClipboard(aId: string): void { - this.clipboard.copy(this.getPublicUrl(aId)); - - this.snackBar.open( - '✅ ' + $localize`Link has been copied to the clipboard`, - undefined, - { - duration: ms('3 seconds') - } - ); - } - - public onDeleteAccess(aId: string) { - this.notificationService.confirm({ - confirmFn: () => { - this.accessDeleted.emit(aId); - }, - confirmType: ConfirmationDialogType.Warn, - title: $localize`Do you really want to revoke this granted access?` - }); - } -} diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.scss b/apps/client/src/app/components/accounts-table/accounts-table.component.scss deleted file mode 100644 index 990b8b294..000000000 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.scss +++ /dev/null @@ -1,13 +0,0 @@ -:host { - display: block; - - .gf-table { - th { - ::ng-deep { - .mat-sort-header-container { - justify-content: inherit; - } - } - } - } -} diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 50065e3da..db7112c30 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -1,5 +1,5 @@ import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; -import { GfAccountsTableComponent } from '@ghostfolio/client/components/accounts-table/accounts-table.component'; +import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module'; import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module'; import { DataService } from '@ghostfolio/client/services/data.service'; diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index ff17c861f..9b0ca04d2 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -4,7 +4,7 @@ import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto import { AccountDetailDialog } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component'; import { GfAccountDetailDialogModule } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.module'; import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces'; -import { GfAccountsTableComponent } from '@ghostfolio/client/components/accounts-table/accounts-table.component'; +import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.html b/libs/ui/src/lib/accounts-table/accounts-table.component.html similarity index 99% rename from apps/client/src/app/components/accounts-table/accounts-table.component.html rename to libs/ui/src/lib/accounts-table/accounts-table.component.html index 609c76ee1..8a7bc98a9 100644 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -353,4 +353,4 @@ width: '100%' }" /> -} +} \ No newline at end of file diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.scss b/libs/ui/src/lib/accounts-table/accounts-table.component.scss new file mode 100644 index 000000000..ec8d69662 --- /dev/null +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.scss @@ -0,0 +1,13 @@ +:host { + display: block; + + .gf-table { + th { + ::ng-deep { + .mat-sort-header-container { + justify-content: inherit; + } + } + } + } + } \ No newline at end of file diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts new file mode 100644 index 000000000..18b32fb71 --- /dev/null +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts @@ -0,0 +1,199 @@ +import { moduleMetadata } from '@storybook/angular'; +import type { Meta, StoryObj } from '@storybook/angular'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { MatButtonModule } from '@angular/material/button'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; +import { RouterModule } from '@angular/router'; +import { IonIcon } from '@ionic/angular/standalone'; +import { CommonModule } from '@angular/common'; + +import { GfAccountsTableComponent } from './accounts-table.component'; +import { GfEntityLogoComponent } from '../entity-logo'; +import { GfValueComponent } from '../value'; + +const mockAccounts = [ + { + id: '1', + name: 'Checking Account', + currency: 'USD', + balance: 15000, + value: 15000, + valueInBaseCurrency: 15000, + transactionCount: 25, + allocationInPercentage: 0.15, + isExcluded: false, + comment: 'Primary checking account', + platform: { + name: 'Bank of America', + url: 'https://www.bankofamerica.com' + } + }, + { + id: '2', + name: 'Trading Account', + currency: 'USD', + balance: 5000, + value: 125000, + valueInBaseCurrency: 125000, + transactionCount: 127, + allocationInPercentage: 0.65, + isExcluded: false, + comment: null, + platform: { + name: 'Interactive Brokers', + url: 'https://www.interactivebrokers.com' + } + }, + { + id: '3', + name: 'Savings Account', + currency: 'EUR', + balance: 20000, + value: 20000, + valueInBaseCurrency: 21600, + transactionCount: 8, + allocationInPercentage: 0.2, + isExcluded: false, + comment: 'Emergency fund', + platform: { + name: 'Deutsche Bank', + url: 'https://www.deutsche-bank.de' + } + }, + { + id: '4', + name: 'Excluded Account', + currency: 'USD', + balance: 1000, + value: 1000, + valueInBaseCurrency: 1000, + transactionCount: 3, + allocationInPercentage: 0, + isExcluded: true, + comment: null, + platform: { + name: 'Local Credit Union', + url: null + } + } +]; + +export default { + title: 'Accounts Table', + component: GfAccountsTableComponent, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + NgxSkeletonLoaderModule, + MatButtonModule, + MatMenuModule, + MatSortModule, + MatTableModule, + RouterModule.forRoot([]), + IonIcon, + GfEntityLogoComponent, + GfValueComponent + ] + }) + ] +} as Meta; + +type Story = StoryObj; + +export const Loading: Story = { + args: { + accounts: [], + baseCurrency: 'USD', + deviceType: 'web', + locale: 'en-US', + showActions: false, + showAllocationInPercentage: false, + showBalance: true, + showFooter: true, + showTransactions: true, + showValue: true, + showValueInBaseCurrency: true, + totalBalanceInBaseCurrency: 0, + totalValueInBaseCurrency: 0, + transactionCount: 0 + } +}; + +export const Default: Story = { + args: { + accounts: mockAccounts, + baseCurrency: 'USD', + deviceType: 'web', + locale: 'en-US', + showActions: false, + showAllocationInPercentage: false, + showBalance: true, + showFooter: true, + showTransactions: true, + showValue: true, + showValueInBaseCurrency: true, + totalBalanceInBaseCurrency: 56600, + totalValueInBaseCurrency: 161600, + transactionCount: 163 + } +}; + +export const WithActions: Story = { + args: { + accounts: mockAccounts, + baseCurrency: 'USD', + deviceType: 'web', + locale: 'en-US', + showActions: true, + showAllocationInPercentage: true, + showBalance: true, + showFooter: true, + showTransactions: true, + showValue: true, + showValueInBaseCurrency: true, + totalBalanceInBaseCurrency: 56600, + totalValueInBaseCurrency: 161600, + transactionCount: 163 + } +}; + +export const MobileView: Story = { + args: { + accounts: mockAccounts, + baseCurrency: 'USD', + deviceType: 'mobile', + locale: 'en-US', + showActions: false, + showAllocationInPercentage: false, + showBalance: false, + showFooter: false, + showTransactions: true, + showValue: false, + showValueInBaseCurrency: true, + totalBalanceInBaseCurrency: 56600, + totalValueInBaseCurrency: 161600, + transactionCount: 163 + } +}; + +export const WithoutFooter: Story = { + args: { + accounts: mockAccounts, + baseCurrency: 'USD', + deviceType: 'web', + locale: 'en-US', + showActions: false, + showAllocationInPercentage: true, + showBalance: true, + showFooter: false, + showTransactions: true, + showValue: true, + showValueInBaseCurrency: true, + totalBalanceInBaseCurrency: 56600, + totalValueInBaseCurrency: 161600, + transactionCount: 163 + } +}; \ No newline at end of file diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts similarity index 99% rename from apps/client/src/app/components/accounts-table/accounts-table.component.ts rename to libs/ui/src/lib/accounts-table/accounts-table.component.ts index a7ce6e488..798750aea 100644 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -175,4 +175,4 @@ export class GfAccountsTableComponent implements OnChanges, OnDestroy { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); } -} +} \ No newline at end of file diff --git a/libs/ui/src/lib/accounts-table/index.ts b/libs/ui/src/lib/accounts-table/index.ts new file mode 100644 index 000000000..446a0ced1 --- /dev/null +++ b/libs/ui/src/lib/accounts-table/index.ts @@ -0,0 +1 @@ +export * from './accounts-table.component'; \ No newline at end of file