mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
600 lines
18 KiB
600 lines
18 KiB
<gf-activities-filter
|
|
[allFilters]="allFilters"
|
|
[isLoading]="isLoading"
|
|
[ngClass]="{ 'd-none': !hasPermissionToFilter }"
|
|
[placeholder]="placeholder"
|
|
(valueChanged)="filters$.next($event)"
|
|
></gf-activities-filter>
|
|
|
|
<div *ngIf="hasPermissionToCreateActivity" class="d-flex justify-content-end">
|
|
<button
|
|
class="align-items-center d-flex"
|
|
mat-stroked-button
|
|
(click)="onImport()"
|
|
>
|
|
<ion-icon class="mr-2" name="cloud-upload-outline"></ion-icon>
|
|
<ng-container i18n>Import Activities</ng-container>...
|
|
</button>
|
|
<button
|
|
*ngIf="hasPermissionToExportActivities"
|
|
class="mx-1 no-min-width px-2"
|
|
mat-stroked-button
|
|
[matMenuTriggerFor]="activitiesMenu"
|
|
(click)="$event.stopPropagation()"
|
|
>
|
|
<ion-icon name="ellipsis-vertical"></ion-icon>
|
|
</button>
|
|
<mat-menu #activitiesMenu="matMenu" xPosition="before">
|
|
<button
|
|
mat-menu-item
|
|
[disabled]="dataSource.data.length === 0"
|
|
(click)="onImportDividends()"
|
|
>
|
|
<ion-icon class="mr-2" name="color-wand-outline"></ion-icon>
|
|
<ng-container i18n>Import Dividends</ng-container>...
|
|
</button>
|
|
<button
|
|
*ngIf="hasPermissionToExportActivities"
|
|
class="align-items-center d-flex"
|
|
mat-menu-item
|
|
[disabled]="dataSource.data.length === 0"
|
|
(click)="onExport()"
|
|
>
|
|
<ion-icon class="mr-2" name="cloud-download-outline"></ion-icon>
|
|
<span i18n>Export Activities</span>
|
|
</button>
|
|
<button
|
|
*ngIf="hasPermissionToExportActivities"
|
|
class="align-items-center d-flex"
|
|
mat-menu-item
|
|
[disabled]="!hasDrafts"
|
|
(click)="onExportDrafts()"
|
|
>
|
|
<ion-icon class="mr-2" name="calendar-clear-outline"></ion-icon>
|
|
<span i18n>Export Drafts as ICS</span>
|
|
</button>
|
|
<button
|
|
class="align-items-center d-flex"
|
|
mat-menu-item
|
|
(click)="onDeleteAllActivities()"
|
|
>
|
|
<ion-icon class="mr-2" name="trash-outline"></ion-icon>
|
|
<span i18n>Delete all Activities</span>
|
|
</button>
|
|
</mat-menu>
|
|
</div>
|
|
|
|
<div class="activities">
|
|
<table
|
|
class="gf-table w-100"
|
|
mat-table
|
|
matSort
|
|
matSortActive="date"
|
|
matSortDirection="desc"
|
|
[dataSource]="dataSource"
|
|
>
|
|
<ng-container matColumnDef="select">
|
|
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
|
<mat-checkbox
|
|
color="primary"
|
|
[checked]="
|
|
areAllRowsSelected() && !hasErrors && selectedRows.hasValue()
|
|
"
|
|
[disabled]="hasErrors"
|
|
[indeterminate]="selectedRows.hasValue() && !areAllRowsSelected()"
|
|
(change)="$event ? toggleAllRows() : null"
|
|
></mat-checkbox>
|
|
</th>
|
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
<mat-checkbox
|
|
color="primary"
|
|
[checked]="element.error ? false : selectedRows.isSelected(element)"
|
|
[disabled]="element.error"
|
|
(change)="$event ? selectedRows.toggle(element) : null"
|
|
(click)="$event.stopPropagation()"
|
|
></mat-checkbox>
|
|
</td>
|
|
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="importStatus">
|
|
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
|
<ng-container i18n></ng-container>
|
|
</th>
|
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
<div
|
|
*ngIf="element.error"
|
|
class="d-flex"
|
|
matTooltipPosition="above"
|
|
[matTooltip]="element.error.message"
|
|
>
|
|
<ion-icon class="text-danger" name="alert-circle-outline"></ion-icon>
|
|
</div>
|
|
</td>
|
|
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="count">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="d-none d-lg-table-cell px-1 text-right"
|
|
i18n
|
|
mat-header-cell
|
|
></th>
|
|
<td
|
|
*matCellDef="let element; let i = index"
|
|
class="d-none d-lg-table-cell px-1 text-right"
|
|
mat-cell
|
|
>
|
|
{{
|
|
dataSource.data.length > pageSize
|
|
? dataSource.data.length - pageSize * pageIndex - i
|
|
: dataSource.data.length - i
|
|
}}
|
|
</td>
|
|
<td
|
|
*matFooterCellDef
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-footer-cell
|
|
></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="date">
|
|
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
|
<ng-container i18n>Date</ng-container>
|
|
</th>
|
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
<div class="d-flex">
|
|
{{ element.date | date: defaultDateFormat }}
|
|
</div>
|
|
</td>
|
|
<td *matFooterCellDef class="px-1" i18n mat-footer-cell>Total</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="type">
|
|
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
|
<ng-container i18n>Type</ng-container>
|
|
</th>
|
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
<div
|
|
class="d-inline-flex p-1 type-badge"
|
|
[ngClass]="{
|
|
buy: element.type === 'BUY',
|
|
dividend: element.type === 'DIVIDEND',
|
|
fee: element.type === 'FEE',
|
|
item: element.type === 'ITEM',
|
|
liability: element.type === 'LIABILITY',
|
|
sell: element.type === 'SELL'
|
|
}"
|
|
>
|
|
<ion-icon
|
|
*ngIf="element.type === 'BUY' || element.type === 'DIVIDEND'"
|
|
name="arrow-up-circle-outline"
|
|
></ion-icon>
|
|
<ion-icon
|
|
*ngIf="element.type === 'FEE'"
|
|
name="hammer-outline"
|
|
></ion-icon>
|
|
<ion-icon
|
|
*ngIf="element.type === 'ITEM'"
|
|
name="cube-outline"
|
|
></ion-icon>
|
|
<ion-icon
|
|
*ngIf="element.type === 'LIABILITY'"
|
|
name="flame-outline"
|
|
></ion-icon>
|
|
<ion-icon
|
|
*ngIf="element.type === 'SELL'"
|
|
name="arrow-down-circle-outline"
|
|
></ion-icon>
|
|
<span class="d-none d-lg-block mx-1">{{ element.type }}</span>
|
|
</div>
|
|
</td>
|
|
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="nameWithSymbol">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="px-1"
|
|
mat-header-cell
|
|
mat-sort-header="SymbolProfile.symbol"
|
|
>
|
|
<ng-container i18n>Name</ng-container>
|
|
</th>
|
|
<td *matCellDef="let element" class="line-height-1 px-1" mat-cell>
|
|
<div class="d-flex align-items-center">
|
|
<div>
|
|
<span class="text-truncate">{{ element.SymbolProfile?.name }}</span>
|
|
<span
|
|
*ngIf="element.isDraft"
|
|
class="badge badge-secondary ml-1"
|
|
i18n
|
|
>Draft</span
|
|
>
|
|
</div>
|
|
</div>
|
|
<div *ngIf="!isUUID(element.SymbolProfile?.symbol)">
|
|
<small class="text-muted">{{
|
|
element.SymbolProfile?.symbol | gfSymbol
|
|
}}</small>
|
|
</div>
|
|
</td>
|
|
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="currency">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-header-cell
|
|
mat-sort-header="SymbolProfile.currency"
|
|
>
|
|
<ng-container i18n>Currency</ng-container>
|
|
</th>
|
|
<td
|
|
*matCellDef="let element"
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-cell
|
|
>
|
|
{{ element.SymbolProfile?.currency }}
|
|
</td>
|
|
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
|
{{ baseCurrency }}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="quantity">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="d-none d-lg-table-cell justify-content-end px-1"
|
|
mat-header-cell
|
|
mat-sort-header
|
|
>
|
|
<ng-container i18n>Quantity</ng-container>
|
|
</th>
|
|
<td
|
|
*matCellDef="let element"
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-cell
|
|
>
|
|
<div class="d-flex justify-content-end">
|
|
<gf-value
|
|
[isCurrency]="true"
|
|
[locale]="locale"
|
|
[value]="isLoading ? undefined : element.quantity"
|
|
></gf-value>
|
|
</div>
|
|
</td>
|
|
<td
|
|
*matFooterCellDef
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-footer-cell
|
|
></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="unitPrice">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="d-none d-lg-table-cell justify-content-end px-1"
|
|
mat-header-cell
|
|
mat-sort-header
|
|
>
|
|
<ng-container i18n>Unit Price</ng-container>
|
|
</th>
|
|
<td
|
|
*matCellDef="let element"
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-cell
|
|
>
|
|
<div class="d-flex justify-content-end">
|
|
<gf-value
|
|
[isCurrency]="true"
|
|
[locale]="locale"
|
|
[value]="isLoading ? undefined : element.unitPrice"
|
|
></gf-value>
|
|
</div>
|
|
</td>
|
|
<td
|
|
*matFooterCellDef
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-footer-cell
|
|
></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="fee">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="d-none d-lg-table-cell justify-content-end px-1"
|
|
mat-header-cell
|
|
mat-sort-header
|
|
>
|
|
<ng-container i18n>Fee</ng-container>
|
|
</th>
|
|
<td
|
|
*matCellDef="let element"
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-cell
|
|
>
|
|
<div class="d-flex justify-content-end">
|
|
<gf-value
|
|
[isCurrency]="true"
|
|
[locale]="locale"
|
|
[value]="isLoading ? undefined : element.fee"
|
|
></gf-value>
|
|
</div>
|
|
</td>
|
|
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
|
<div class="d-flex justify-content-end">
|
|
<gf-value
|
|
[isCurrency]="true"
|
|
[locale]="locale"
|
|
[value]="isLoading ? undefined : totalFees"
|
|
></gf-value>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="value">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="d-none d-lg-table-cell justify-content-end px-1"
|
|
mat-header-cell
|
|
mat-sort-header
|
|
>
|
|
<ng-container i18n>Value</ng-container>
|
|
</th>
|
|
<td
|
|
*matCellDef="let element"
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-cell
|
|
>
|
|
<div class="d-flex justify-content-end">
|
|
<gf-value
|
|
[isCurrency]="true"
|
|
[locale]="locale"
|
|
[value]="isLoading ? undefined : element.value"
|
|
></gf-value>
|
|
</div>
|
|
</td>
|
|
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
|
<div class="d-flex justify-content-end">
|
|
<gf-value
|
|
*ngIf="totalValue !== null"
|
|
[isAbsolute]="true"
|
|
[isCurrency]="true"
|
|
[locale]="locale"
|
|
[value]="isLoading ? undefined : totalValue"
|
|
></gf-value>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="valueInBaseCurrency">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="d-lg-none d-xl-none justify-content-end px-1"
|
|
mat-header-cell
|
|
mat-sort-header
|
|
>
|
|
<ng-container i18n>Value</ng-container>
|
|
</th>
|
|
<td *matCellDef="let element" class="d-lg-none d-xl-none px-1" mat-cell>
|
|
<div class="d-flex justify-content-end">
|
|
<gf-value
|
|
[isCurrency]="true"
|
|
[locale]="locale"
|
|
[value]="isLoading ? undefined : element.valueInBaseCurrency"
|
|
></gf-value>
|
|
</div>
|
|
</td>
|
|
<td *matFooterCellDef class="d-lg-none d-xl-none px-1" mat-footer-cell>
|
|
<div class="d-flex justify-content-end">
|
|
<gf-value
|
|
*ngIf="totalValue !== null"
|
|
[isAbsolute]="true"
|
|
[isCurrency]="true"
|
|
[locale]="locale"
|
|
[value]="isLoading ? undefined : totalValue"
|
|
></gf-value>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="account">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="px-1"
|
|
mat-header-cell
|
|
mat-sort-header="Account.name"
|
|
>
|
|
<span class="d-none d-lg-block" i18n>Account</span>
|
|
</th>
|
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
<div class="d-flex">
|
|
<gf-symbol-icon
|
|
*ngIf="element.Account?.Platform?.url"
|
|
class="mr-1"
|
|
[tooltip]="element.Account?.Platform?.name"
|
|
[url]="element.Account?.Platform?.url"
|
|
></gf-symbol-icon>
|
|
<span class="d-none d-lg-block">{{ element.Account?.name }}</span>
|
|
</div>
|
|
</td>
|
|
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="comment">
|
|
<th
|
|
*matHeaderCellDef
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-header-cell
|
|
></th>
|
|
<td
|
|
*matCellDef="let element"
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-cell
|
|
>
|
|
<button
|
|
*ngIf="element.comment"
|
|
class="mx-1 no-min-width px-2"
|
|
mat-button
|
|
title="Note"
|
|
(click)="onOpenComment(element.comment); $event.stopPropagation()"
|
|
>
|
|
<ion-icon name="document-text-outline"></ion-icon>
|
|
</button>
|
|
</td>
|
|
<td
|
|
*matFooterCellDef
|
|
class="d-none d-lg-table-cell px-1"
|
|
mat-footer-cell
|
|
></td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="actions">
|
|
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell>
|
|
<button
|
|
*ngIf="
|
|
!hasPermissionToCreateActivity && hasPermissionToExportActivities
|
|
"
|
|
class="mx-1 no-min-width px-2"
|
|
mat-button
|
|
[matMenuTriggerFor]="activitiesMenu"
|
|
(click)="$event.stopPropagation()"
|
|
>
|
|
<ion-icon name="ellipsis-vertical"></ion-icon>
|
|
</button>
|
|
<mat-menu #activitiesMenu="matMenu" xPosition="before">
|
|
<button
|
|
*ngIf="hasPermissionToCreateActivity"
|
|
class="align-items-center d-flex"
|
|
mat-menu-item
|
|
(click)="onImport()"
|
|
>
|
|
<ion-icon class="mr-2" name="cloud-upload-outline"></ion-icon>
|
|
<ng-container i18n>Import Activities</ng-container>...
|
|
</button>
|
|
<button
|
|
*ngIf="hasPermissionToCreateActivity"
|
|
mat-menu-item
|
|
[disabled]="dataSource.data.length === 0"
|
|
(click)="onImportDividends()"
|
|
>
|
|
<ion-icon class="mr-2" name="color-wand-outline"></ion-icon>
|
|
<ng-container i18n>Import Dividends</ng-container>...
|
|
</button>
|
|
<button
|
|
*ngIf="hasPermissionToExportActivities"
|
|
class="align-items-center d-flex"
|
|
mat-menu-item
|
|
[disabled]="dataSource.data.length === 0"
|
|
(click)="onExport()"
|
|
>
|
|
<ion-icon class="mr-2" name="cloud-download-outline"></ion-icon>
|
|
<span i18n>Export Activities</span>
|
|
</button>
|
|
<button
|
|
*ngIf="hasPermissionToExportActivities"
|
|
class="align-items-center d-flex"
|
|
mat-menu-item
|
|
[disabled]="!hasDrafts"
|
|
(click)="onExportDrafts()"
|
|
>
|
|
<ion-icon class="mr-2" name="calendar-clear-outline"></ion-icon>
|
|
<span i18n>Export Drafts as ICS</span>
|
|
</button>
|
|
</mat-menu>
|
|
</th>
|
|
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
|
<button
|
|
*ngIf="showActions"
|
|
class="mx-1 no-min-width px-2"
|
|
mat-button
|
|
[matMenuTriggerFor]="activityMenu"
|
|
(click)="$event.stopPropagation()"
|
|
>
|
|
<ion-icon name="ellipsis-horizontal"></ion-icon>
|
|
</button>
|
|
<mat-menu #activityMenu="matMenu" xPosition="before">
|
|
<button mat-menu-item (click)="onUpdateActivity(element)">
|
|
<ion-icon class="mr-2" name="create-outline"></ion-icon>
|
|
<span i18n>Edit</span>
|
|
</button>
|
|
<button mat-menu-item (click)="onCloneActivity(element)">
|
|
<ion-icon class="mr-2" name="copy-outline"></ion-icon>
|
|
<span i18n>Clone</span>
|
|
</button>
|
|
<button
|
|
mat-menu-item
|
|
[disabled]="!element.isDraft"
|
|
(click)="onExportDraft(element.id)"
|
|
>
|
|
<ion-icon class="mr-2" name="calendar-clear-outline"></ion-icon>
|
|
<span i18n>Export Draft as ICS</span>
|
|
</button>
|
|
<button mat-menu-item (click)="onDeleteActivity(element.id)">
|
|
<ion-icon class="mr-2" name="trash-outline"></ion-icon>
|
|
<span i18n>Delete</span>
|
|
</button>
|
|
</mat-menu>
|
|
</td>
|
|
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
</ng-container>
|
|
|
|
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
|
<tr
|
|
*matRowDef="let row; columns: displayedColumns"
|
|
mat-row
|
|
[ngClass]="{
|
|
'cursor-pointer':
|
|
hasPermissionToOpenDetails &&
|
|
!row.isDraft &&
|
|
row.type !== 'FEE' &&
|
|
row.type !== 'ITEM' &&
|
|
row.type !== 'LIABILITY'
|
|
}"
|
|
(click)="onClickActivity(row)"
|
|
></tr>
|
|
<tr
|
|
*matFooterRowDef="displayedColumns"
|
|
mat-footer-row
|
|
[ngClass]="{
|
|
'd-none':
|
|
isLoading || dataSource.data.length === 0 || showFooter === false
|
|
}"
|
|
></tr>
|
|
</table>
|
|
</div>
|
|
|
|
<mat-paginator
|
|
[ngClass]="{
|
|
'd-none':
|
|
(isLoading && dataSource.data.length === 0) ||
|
|
dataSource.data.length <= pageSize
|
|
}"
|
|
[pageSize]="pageSize"
|
|
[showFirstLastButtons]="true"
|
|
(page)="onChangePage($event)"
|
|
></mat-paginator>
|
|
|
|
<ngx-skeleton-loader
|
|
*ngIf="isLoading"
|
|
animation="pulse"
|
|
class="px-4 py-3"
|
|
[theme]="{
|
|
height: '1.5rem',
|
|
width: '100%'
|
|
}"
|
|
></ngx-skeleton-loader>
|
|
|
|
<div
|
|
*ngIf="
|
|
dataSource.data.length === 0 && hasPermissionToCreateActivity && !isLoading
|
|
"
|
|
class="p-3 text-center"
|
|
>
|
|
<gf-no-transactions-info-indicator
|
|
[hasBorder]="false"
|
|
></gf-no-transactions-info-indicator>
|
|
</div>
|
|
|