Browse Source

Merge branch 'main' into feat/transfer-dialog

pull/2433/head
Dhoni77 2 years ago
parent
commit
dbc161d4ca
  1. 10
      CHANGELOG.md
  2. 2
      apps/api/src/helper/object.helper.ts
  3. 5
      apps/client/src/app/app.module.ts
  4. 1
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  5. 9
      apps/client/src/app/components/admin-overview/admin-overview.component.ts
  6. 8
      apps/client/src/app/components/admin-overview/admin-overview.html
  7. 2
      apps/client/src/app/components/header/header.component.html
  8. 6
      apps/client/src/app/components/home-market/home-market.html
  9. 5
      apps/client/src/app/components/portfolio-performance/portfolio-performance.module.ts
  10. 8
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html
  11. 15
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html
  12. 14
      apps/client/src/app/pages/home/home-page.component.ts
  13. 21
      apps/client/src/app/pages/landing/landing-page.html
  14. 2
      apps/client/src/app/pages/landing/landing-page.module.ts
  15. 2
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html
  16. 2
      apps/client/src/app/pages/public/public-page.html
  17. 5
      apps/client/src/app/pages/user-account/user-account-page.module.ts
  18. 1
      apps/client/src/app/services/data.service.ts
  19. 16
      apps/client/src/app/services/import-activities.service.ts
  20. 12
      apps/client/src/assets/oss-friends.json
  21. 16
      libs/ui/src/lib/carousel/carousel-item.directive.ts
  22. 34
      libs/ui/src/lib/carousel/carousel.component.html
  23. 34
      libs/ui/src/lib/carousel/carousel.component.scss
  24. 147
      libs/ui/src/lib/carousel/carousel.component.ts
  25. 14
      libs/ui/src/lib/carousel/carousel.module.ts
  26. 1
      libs/ui/src/lib/carousel/index.ts
  27. 4
      test/import/ok.csv
  28. 2
      test/import/ok.json

10
CHANGELOG.md

@ -9,7 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added support for notes in the activities import
- Added the application version to the endpoint `GET api/v1/admin`
- Introduced a carousel component for the testimonial section on the landing page
### Changed
- Displayed the link to the markets overview on the home page without any permission
### Fixed
- Fixed the style of the active features page in the navigation on desktop
## 2.8.0 - 2023-10-03

2
apps/api/src/helper/object.helper.ts

@ -3,7 +3,7 @@ import { cloneDeep, isArray, isObject } from 'lodash';
export function hasNotDefinedValuesInObject(aObject: Object): boolean {
for (const key in aObject) {
if (aObject[key] === null || aObject[key] === null) {
if (aObject[key] === null || aObject[key] === undefined) {
return true;
} else if (isObject(aObject[key])) {
return hasNotDefinedValuesInObject(aObject[key]);

5
apps/client/src/app/app.module.ts

@ -1,6 +1,6 @@
import { Platform } from '@angular/cdk/platform';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatChipsModule } from '@angular/material/chips';
import {
@ -35,6 +35,7 @@ export function NgxStripeFactory(): string {
}
@NgModule({
bootstrap: [AppComponent],
declarations: [AppComponent],
imports: [
AppRoutingModule,
@ -72,6 +73,6 @@ export function NgxStripeFactory(): string {
useFactory: NgxStripeFactory
}
],
bootstrap: [AppComponent]
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}

1
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts

@ -13,7 +13,6 @@ import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import {
AdminMarketDataDetails,
ScraperConfiguration,
UniqueAsset
} from '@ghostfolio/common/interfaces';
import { translate } from '@ghostfolio/ui/i18n';

9
apps/client/src/app/components/admin-overview/admin-overview.component.ts

@ -43,7 +43,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
public transactionCount: number;
public userCount: number;
public user: User;
public version = environment.version;
public version: string;
private unsubscribeSubject = new Subject<void>();
@ -204,15 +204,18 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
this.adminService
.fetchAdminData()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ exchangeRates, settings, transactionCount, userCount }) => {
.subscribe(
({ exchangeRates, settings, transactionCount, userCount, version }) => {
this.coupons = (settings[PROPERTY_COUPONS] as Coupon[]) ?? [];
this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[];
this.exchangeRates = exchangeRates;
this.transactionCount = transactionCount;
this.userCount = userCount;
this.version = version;
this.changeDetectorRef.markForCheck();
});
}
);
}
private generateCouponCode(aLength: number) {

8
apps/client/src/app/components/admin-overview/admin-overview.html

@ -5,14 +5,16 @@
<mat-card-content>
<div class="d-flex my-3">
<div class="w-50" i18n>Version</div>
<div class="w-50">{{ version }}</div>
<div class="w-50">
<gf-value [value]="version" />
</div>
</div>
<div class="d-flex my-3">
<div class="w-50" i18n>User Count</div>
<div class="w-50">
<gf-value
precision="0"
[locale]="user?.settings?.locale"
[precision]="0"
[value]="userCount"
></gf-value>
</div>
@ -21,8 +23,8 @@
<div class="w-50" i18n>Activity Count</div>
<div class="w-50">
<gf-value
precision="0"
[locale]="user?.settings?.locale"
[precision]="0"
[value]="transactionCount"
></gf-value>
<div *ngIf="transactionCount && userCount">

2
apps/client/src/app/components/header/header.component.html

@ -272,7 +272,7 @@
mat-flat-button
[ngClass]="{
'font-weight-bold': currentRoute === routeFeatures,
'text-decoration-underline': currentRoute === routeFeatuers
'text-decoration-underline': currentRoute === routeFeatures
}"
[routerLink]="routerLinkFeatures"
>Features</a

6
apps/client/src/app/components/home-market/home-market.html

@ -1,6 +1,6 @@
<div class="container">
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Markets</h1>
<div class="mb-5 row">
<div *ngIf="hasPermissionToAccessFearAndGreedIndex" class="mb-5 row">
<div class="col-xs-12 col-md-8 offset-md-2">
<div class="mb-2 text-center text-muted">
<small i18n>Last {{ numberOfDays }} Days</small>
@ -8,15 +8,15 @@
<gf-line-chart
class="mb-3"
symbol="Fear & Greed Index"
yMax="100"
yMin="0"
[colorScheme]="user?.settings?.colorScheme"
[historicalDataItems]="historicalDataItems"
[isAnimated]="true"
[locale]="user?.settings?.locale"
[showXAxis]="true"
[showYAxis]="true"
[yMax]="100"
[yMaxLabel]="greedLabel"
[yMin]="0"
[yMinLabel]="fearLabel"
></gf-line-chart>
<gf-fear-and-greed-index

5
apps/client/src/app/components/portfolio-performance/portfolio-performance.module.ts

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { GfValueModule } from '@ghostfolio/ui/value';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -8,6 +8,7 @@ import { PortfolioPerformanceComponent } from './portfolio-performance.component
@NgModule({
declarations: [PortfolioPerformanceComponent],
exports: [PortfolioPerformanceComponent],
imports: [CommonModule, GfValueModule, NgxSkeletonLoaderModule]
imports: [CommonModule, GfValueModule, NgxSkeletonLoaderModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfPortfolioPerformanceModule {}

8
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html

@ -213,11 +213,11 @@
<div class="col-md-6 mb-3">
<div class="h5" i18n>Sectors</div>
<gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency"
[baseCurrency]="data.baseCurrency"
[colorScheme]="data.colorScheme"
[isInPercent]="true"
[keys]="['name']"
[locale]="user?.settings?.locale"
[locale]="data.locale"
[maxItems]="10"
[positions]="sectors"
></gf-portfolio-proportion-chart>
@ -225,11 +225,11 @@
<div class="col-md-6 mb-3">
<div class="h5" i18n>Countries</div>
<gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency"
[baseCurrency]="data.baseCurrency"
[colorScheme]="data.colorScheme"
[isInPercent]="true"
[keys]="['name']"
[locale]="user?.settings?.locale"
[locale]="data.locale"
[maxItems]="10"
[positions]="countries"
></gf-portfolio-proportion-chart>

15
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html

@ -10,7 +10,11 @@
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Name</mat-label>
<input formControlName="name" matInput />
<input
formControlName="name"
matInput
(keydown.enter)="$event.stopPropagation()"
/>
</mat-form-field>
</div>
<div>
@ -26,7 +30,12 @@
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Cash Balance</mat-label>
<input formControlName="balance" matInput type="number" />
<input
formControlName="balance"
matInput
type="number"
(keydown.enter)="$event.stopPropagation()"
/>
<span class="ml-2" matTextSuffix>{{ data.account.currency }}</span>
</mat-form-field>
</div>
@ -66,7 +75,7 @@
</div>
</div>
<div class="justify-content-end" mat-dialog-actions>
<button i18n mat-button (click)="onCancel()">Cancel</button>
<button i18n mat-button type="button" (click)="onCancel()">Cancel</button>
<button
color="primary"
mat-flat-button

14
apps/client/src/app/pages/home/home-page.component.ts

@ -1,8 +1,6 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { TabConfiguration, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -15,7 +13,6 @@ import { takeUntil } from 'rxjs/operators';
})
export class HomePageComponent implements OnDestroy, OnInit {
public deviceType: string;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public tabs: TabConfiguration[] = [];
public user: User;
@ -23,17 +20,9 @@ export class HomePageComponent implements OnDestroy, OnInit {
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private deviceService: DeviceDetectorService,
private userService: UserService
) {
const { globalPermissions } = this.dataService.fetchInfo();
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
globalPermissions,
permissions.enableFearAndGreedIndex
);
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => {
@ -57,8 +46,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
{
iconName: 'newspaper-outline',
label: $localize`Markets`,
path: ['/home', 'market'],
showCondition: this.hasPermissionToAccessFearAndGreedIndex
path: ['/home', 'market']
}
];
this.user = state.user;

21
apps/client/src/app/pages/landing/landing-page.html

@ -320,31 +320,36 @@
<div class="row my-5">
<div class="col-12">
<h2 class="h4 mb-1 text-center" i18n>
<h2 class="h4 mb-3 text-center" i18n>
What our <strong>users</strong> are saying
</h2>
</div>
<div *ngFor="let testimonial of testimonials" class="col-md-6">
<div class="d-flex flex-row py-3">
<div class="d-flex justify-content-center">
<div class="col-md-8 offset-md-2">
<gf-carousel [aria-label]="'Testimonials'">
<div *ngFor="let testimonial of testimonials" gf-carousel-item>
<div class="d-flex px-3">
<gf-logo
class="mr-3 mt-2 pt-1"
size="medium"
[showLabel]="false"
></gf-logo>
</div>
<div>
<div>{{ testimonial.quote }}</div>
<div class="mt-2 text-muted">
<a *ngIf="testimonial.url" target="_blank" [href]="testimonial.url"
<a
*ngIf="testimonial.url"
target="_blank"
[href]="testimonial.url"
>{{ testimonial.author }}</a
>
<span *ngIf="!testimonial.url">{{ testimonial.author }}</span>, {{
testimonial.country }}
<span *ngIf="!testimonial.url">{{ testimonial.author }}</span>,
{{ testimonial.country }}
</div>
</div>
</div>
</div>
</gf-carousel>
</div>
</div>

2
apps/client/src/app/pages/landing/landing-page.module.ts

@ -4,6 +4,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router';
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
import { GfCarouselModule } from '@ghostfolio/ui/carousel';
import { GfLogoModule } from '@ghostfolio/ui/logo';
import { GfValueModule } from '@ghostfolio/ui/value';
@ -14,6 +15,7 @@ import { LandingPageComponent } from './landing-page.component';
declarations: [LandingPageComponent],
imports: [
CommonModule,
GfCarouselModule,
GfLogoModule,
GfValueModule,
GfWorldMapChartModule,

2
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html

@ -8,7 +8,7 @@
<div class="flex-grow-1" mat-dialog-content>
<mat-stepper
#stepper
[animationDuration]="0"
animationDuration="0"
[linear]="true"
[orientation]="stepperOrientation"
[selectedIndex]="importStep"

2
apps/client/src/app/pages/public/public-page.html

@ -131,9 +131,9 @@
<div class="row">
<div class="col-lg">
<gf-holdings-table
pageSize="7"
[deviceType]="deviceType"
[hasPermissionToShowValues]="false"
[pageSize]="7"
[positions]="positionsArray"
></gf-holdings-table>
</div>

5
apps/client/src/app/pages/user-account/user-account-page.module.ts

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatTabsModule } from '@angular/material/tabs';
import { GfUserAccountAccessModule } from '@ghostfolio/client/components/user-account-access/user-account-access.module';
import { GfUserAccountMembershipModule } from '@ghostfolio/client/components/user-account-membership/user-account-membership.module';
@ -17,6 +17,7 @@ import { UserAccountPageComponent } from './user-account-page.component';
GfUserAccountSettingsModule,
MatTabsModule,
UserAccountPageRoutingModule
]
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class UserAccountPageModule {}

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

@ -36,7 +36,6 @@ import {
} from '@ghostfolio/common/interfaces';
import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
import { AccountWithValue, DateRange, GroupBy } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n';
import { DataSource, Order as OrderModel } from '@prisma/client';
import { format, parseISO } from 'date-fns';
import { cloneDeep, groupBy, isNumber } from 'lodash';

16
apps/client/src/app/services/import-activities.service.ts

@ -15,6 +15,7 @@ import { catchError } from 'rxjs/operators';
})
export class ImportActivitiesService {
private static ACCOUNT_KEYS = ['account', 'accountid'];
private static COMMENT_KEYS = ['comment', 'note'];
private static CURRENCY_KEYS = ['ccy', 'currency', 'currencyprimary'];
private static DATA_SOURCE_KEYS = ['datasource'];
private static DATE_KEYS = ['date', 'tradedate'];
@ -52,6 +53,7 @@ export class ImportActivitiesService {
for (const [index, item] of content.entries()) {
activities.push({
accountId: this.parseAccount({ item, userAccounts }),
comment: this.parseComment({ item }),
currency: this.parseCurrency({ content, index, item }),
dataSource: this.parseDataSource({ item }),
date: this.parseDate({ content, index, item }),
@ -122,6 +124,7 @@ export class ImportActivitiesService {
private convertToCreateOrderDto({
accountId,
comment,
date,
fee,
quantity,
@ -132,6 +135,7 @@ export class ImportActivitiesService {
}: Activity): CreateOrderDto {
return {
accountId,
comment,
fee,
quantity,
type,
@ -174,6 +178,18 @@ export class ImportActivitiesService {
return undefined;
}
private parseComment({ item }: { item: any }) {
item = this.lowercaseKeys(item);
for (const key of ImportActivitiesService.COMMENT_KEYS) {
if (item[key]) {
return item[key];
}
}
return undefined;
}
private parseCurrency({
content,
index,

12
apps/client/src/assets/oss-friends.json

@ -1,11 +1,6 @@
{
"createdAt": "2023-09-25T08:15:38.055Z",
"createdAt": "2023-10-05T00:00:00.000Z",
"data": [
{
"name": "Appsmith",
"description": "Build build custom software on top of your data.",
"href": "https://www.appsmith.com"
},
{
"name": "BoxyHQ",
"description": "BoxyHQ’s suite of APIs for security and privacy helps engineering teams build and ship compliant cloud applications faster.",
@ -66,11 +61,6 @@
"description": "Mockoon is the easiest and quickest way to design and run mock REST APIs.",
"href": "https://mockoon.com"
},
{
"name": "Novu",
"description": "The open-source notification infrastructure for developers. Simple components and APIs for managing all communication channels in one place.",
"href": "https://novu.co"
},
{
"name": "OpenBB",
"description": "Democratizing investment research through an open source financial ecosystem. The OpenBB Terminal allows everyone to perform investment research, from everywhere.",

16
libs/ui/src/lib/carousel/carousel-item.directive.ts

@ -0,0 +1,16 @@
import { FocusableOption } from '@angular/cdk/a11y';
import { Directive, ElementRef, HostBinding } from '@angular/core';
@Directive({
selector: '[gf-carousel-item]'
})
export class CarouselItem implements FocusableOption {
@HostBinding('attr.role') readonly role = 'listitem';
@HostBinding('tabindex') tabindex = '-1';
public constructor(readonly element: ElementRef<HTMLElement>) {}
public focus(): void {
this.element.nativeElement.focus({ preventScroll: true });
}
}

34
libs/ui/src/lib/carousel/carousel.component.html

@ -0,0 +1,34 @@
<button
*ngIf="this.showPrevArrow"
aria-hidden="true"
aria-label="previous"
class="carousel-nav carousel-nav-prev no-min-width position-absolute"
mat-stroked-button
tabindex="-1"
(click)="previous()"
>
<ion-icon name="chevron-back-outline"></ion-icon>
</button>
<div
#contentWrapper
class="overflow-hidden"
role="region"
(keyup)="onKeydown($event)"
>
<div #list class="d-flex carousel-content" role="list" tabindex="0">
<ng-content></ng-content>
</div>
</div>
<button
*ngIf="this.showNextArrow"
aria-hidden="true"
aria-label="next"
class="carousel-nav carousel-nav-next no-min-width position-absolute"
mat-stroked-button
tabindex="-1"
(click)="next()"
>
<ion-icon name="chevron-forward-outline"></ion-icon>
</button>

34
libs/ui/src/lib/carousel/carousel.component.scss

@ -0,0 +1,34 @@
:host {
display: block;
position: relative;
::ng-deep {
[gf-carousel-item] {
flex-shrink: 0;
width: 100%;
}
}
button {
top: 50%;
transform: translateY(-50%);
&.carousel-nav-prev {
left: -50px;
}
&.carousel-nav-next {
right: -50px;
}
}
.carousel-content {
flex-direction: row;
outline: none;
transition: transform 0.5s ease-in-out;
.animations-disabled & {
transition: none;
}
}
}

147
libs/ui/src/lib/carousel/carousel.component.ts

@ -0,0 +1,147 @@
import { FocusKeyManager } from '@angular/cdk/a11y';
import { LEFT_ARROW, RIGHT_ARROW, TAB } from '@angular/cdk/keycodes';
import {
AfterContentInit,
ChangeDetectionStrategy,
Component,
ContentChildren,
ElementRef,
HostBinding,
Inject,
Input,
Optional,
QueryList,
ViewChild
} from '@angular/core';
import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations';
import { CarouselItem } from './carousel-item.directive';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-carousel',
styleUrls: ['./carousel.component.scss'],
templateUrl: './carousel.component.html'
})
export class CarouselComponent implements AfterContentInit {
@ContentChildren(CarouselItem) public items!: QueryList<CarouselItem>;
@HostBinding('class.animations-disabled')
public readonly animationsDisabled: boolean;
@Input('aria-label') public ariaLabel: string | undefined;
@ViewChild('list') public list!: ElementRef<HTMLElement>;
public showPrevArrow = false;
public showNextArrow = true;
private index = 0;
private keyManager!: FocusKeyManager<CarouselItem>;
private position = 0;
public constructor(
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationsModule?: string
) {
this.animationsDisabled = animationsModule === 'NoopAnimations';
}
public ngAfterContentInit() {
this.keyManager = new FocusKeyManager<CarouselItem>(this.items);
}
public next() {
for (let i = this.index; i < this.items.length; i++) {
if (this.isOutOfView(i)) {
this.index = i;
this.scrollToActiveItem();
break;
}
}
}
public onKeydown({ keyCode }: KeyboardEvent) {
const manager = this.keyManager;
const previousActiveIndex = manager.activeItemIndex;
if (keyCode === LEFT_ARROW) {
manager.setPreviousItemActive();
} else if (keyCode === RIGHT_ARROW) {
manager.setNextItemActive();
} else if (keyCode === TAB && !manager.activeItem) {
manager.setFirstItemActive();
}
if (
manager.activeItemIndex != null &&
manager.activeItemIndex !== previousActiveIndex
) {
this.index = manager.activeItemIndex;
this.updateItemTabIndices();
if (this.isOutOfView(this.index)) {
this.scrollToActiveItem();
}
}
}
public previous() {
for (let i = this.index; i > -1; i--) {
if (this.isOutOfView(i)) {
this.index = i;
this.scrollToActiveItem();
break;
}
}
}
private isOutOfView(index: number, side?: 'start' | 'end') {
const { offsetWidth, offsetLeft } =
this.items.toArray()[index].element.nativeElement;
if ((!side || side === 'start') && offsetLeft - this.position < 0) {
return true;
}
return (
(!side || side === 'end') &&
offsetWidth + offsetLeft - this.position >
this.list.nativeElement.clientWidth
);
}
private scrollToActiveItem() {
if (!this.isOutOfView(this.index)) {
return;
}
const itemsArray = this.items.toArray();
let targetItemIndex = this.index;
if (this.index > 0 && !this.isOutOfView(this.index - 1)) {
targetItemIndex =
itemsArray.findIndex((_, i) => !this.isOutOfView(i)) + 1;
}
this.position =
itemsArray[targetItemIndex].element.nativeElement.offsetLeft;
this.list.nativeElement.style.transform = `translateX(-${this.position}px)`;
this.showPrevArrow = this.index > 0;
this.showNextArrow = false;
for (let i = itemsArray.length - 1; i > -1; i--) {
if (this.isOutOfView(i, 'end')) {
this.showNextArrow = true;
break;
}
}
}
private updateItemTabIndices() {
this.items.forEach((item: CarouselItem) => {
if (this.keyManager != null) {
item.tabindex = item === this.keyManager.activeItem ? '0' : '-1';
}
});
}
}

14
libs/ui/src/lib/carousel/carousel.module.ts

@ -0,0 +1,14 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { CarouselItem } from './carousel-item.directive';
import { CarouselComponent } from './carousel.component';
@NgModule({
declarations: [CarouselComponent, CarouselItem],
exports: [CarouselComponent, CarouselItem],
imports: [CommonModule, MatButtonModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfCarouselModule {}

1
libs/ui/src/lib/carousel/index.ts

@ -0,0 +1 @@
export * from './carousel.module';

4
test/import/ok.csv

@ -1,5 +1,5 @@
Date,Code,Currency,Price,Quantity,Action,Fee
16-09-2021,MSFT,USD,298.580,5,buy,19.00
Date,Code,Currency,Price,Quantity,Action,Fee,Note
16-09-2021,MSFT,USD,298.580,5,buy,19.00,My first order 🤓
17/11/2021,MSFT,USD,0.62,5,dividend,0.00
01.01.2022,Penthouse Apartment,USD,500000.0,1,item,0.00
20500606,MSFT,USD,0.00,0,buy,0.00

Can't render this file because it has a wrong number of fields in line 3.

2
test/import/ok.json

@ -52,7 +52,7 @@
},
{
"accountId": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0",
"comment": "My first order",
"comment": "My first order 🤓",
"fee": 19,
"quantity": 5,
"type": "BUY",

Loading…
Cancel
Save