Browse Source

Merge cae6dab390 into 753804c011

pull/6264/merge
Kenrick Tandrian 23 hours ago
committed by GitHub
parent
commit
dee113c2ba
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      libs/common/src/lib/interfaces/lookup-item.interface.ts
  2. 2
      libs/ui/src/lib/accounts-table/accounts-table.component.ts
  3. 26
      libs/ui/src/lib/activities-table/activities-table.component.stories.ts
  4. 56
      libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
  5. 2
      libs/ui/src/lib/holdings-table/holdings-table.component.ts
  6. 2
      libs/ui/src/lib/line-chart/line-chart.component.ts
  7. 2
      libs/ui/src/lib/mocks/holdings.ts
  8. 2
      libs/ui/src/lib/no-transactions-info/no-transactions-info.component.ts
  9. 4
      libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts
  10. 6
      libs/ui/src/lib/notifications/confirmation-dialog/confirmation-dialog.component.ts
  11. 10
      libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.component.ts
  12. 8
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts
  13. 8
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
  14. 6
      libs/ui/src/lib/services/admin.service.ts
  15. 2
      libs/ui/src/lib/services/data.service.ts
  16. 2
      libs/ui/src/lib/shared/abstract-mat-form-field.ts
  17. 4
      libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts
  18. 14
      libs/ui/src/lib/tags-selector/tags-selector.component.ts
  19. 6
      libs/ui/src/lib/toggle/toggle.component.ts
  20. 2
      libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
  21. 1
      libs/ui/tsconfig.json

2
libs/common/src/lib/interfaces/lookup-item.interface.ts

@ -7,7 +7,7 @@ export interface LookupItem {
assetSubClass: AssetSubClass;
currency: string;
dataProviderInfo: DataProviderInfo;
dataSource: DataSource;
dataSource: DataSource | null;
name: string;
symbol: string;
}

2
libs/ui/src/lib/accounts-table/accounts-table.component.ts

@ -77,7 +77,7 @@ export class GfAccountsTableComponent implements OnChanges, OnDestroy {
@ViewChild(MatSort) sort: MatSort;
public dataSource = new MatTableDataSource<Account>();
public displayedColumns = [];
public displayedColumns: string[] = [];
public isLoading = true;
public routeQueryParams: Subscription;

26
libs/ui/src/lib/activities-table/activities-table.component.stories.ts

@ -59,7 +59,7 @@ const activities: Activity[] = [
SymbolProfile: {
assetClass: 'EQUITY',
assetSubClass: 'ETF',
comment: null,
comment: undefined,
countries: [],
createdAt: new Date('2021-06-06T16:12:20.982Z'),
currency: 'USD',
@ -74,12 +74,12 @@ const activities: Activity[] = [
isin: 'US9220427424',
name: 'Vanguard Total World Stock Index Fund ETF Shares',
updatedAt: new Date('2025-10-01T20:09:39.500Z'),
scraperConfiguration: null,
scraperConfiguration: undefined,
sectors: [],
symbol: 'VT',
symbolMapping: {},
url: 'https://www.vanguard.com',
userId: null,
userId: undefined,
activitiesCount: 267,
dateOfFirstActivity: new Date('2018-05-31T16:00:00.000Z')
},
@ -126,7 +126,7 @@ const activities: Activity[] = [
SymbolProfile: {
assetClass: 'EQUITY',
assetSubClass: 'ETF',
comment: null,
comment: undefined,
countries: [],
createdAt: new Date('2021-06-06T16:12:20.982Z'),
currency: 'USD',
@ -141,12 +141,12 @@ const activities: Activity[] = [
isin: 'US9220427424',
name: 'Vanguard Total World Stock Index Fund ETF Shares',
updatedAt: new Date('2025-10-01T20:09:39.500Z'),
scraperConfiguration: null,
scraperConfiguration: undefined,
sectors: [],
symbol: 'VT',
symbolMapping: {},
url: 'https://www.vanguard.com',
userId: null,
userId: undefined,
activitiesCount: 267,
dateOfFirstActivity: new Date('2018-05-31T16:00:00.000Z')
},
@ -193,7 +193,7 @@ const activities: Activity[] = [
SymbolProfile: {
assetClass: 'LIQUIDITY',
assetSubClass: 'CRYPTOCURRENCY',
comment: null,
comment: undefined,
countries: [],
createdAt: new Date('2024-03-12T15:15:21.217Z'),
currency: 'USD',
@ -208,12 +208,12 @@ const activities: Activity[] = [
isin: 'CA4639181029',
name: 'iShares Bitcoin Trust',
updatedAt: new Date('2025-09-29T03:14:07.742Z'),
scraperConfiguration: null,
scraperConfiguration: undefined,
sectors: [],
symbol: 'IBIT',
symbolMapping: {},
url: 'https://www.ishares.com',
userId: null,
userId: undefined,
activitiesCount: 6,
dateOfFirstActivity: new Date('2024-01-01T08:00:00.000Z')
},
@ -280,7 +280,7 @@ const activities: Activity[] = [
symbol: 'BNDW',
symbolMapping: {},
url: 'https://vanguard.com',
userId: null,
userId: undefined,
activitiesCount: 38,
dateOfFirstActivity: new Date('2022-04-13T20:05:48.742Z')
},
@ -327,7 +327,7 @@ const activities: Activity[] = [
SymbolProfile: {
assetClass: 'EQUITY',
assetSubClass: 'ETF',
comment: null,
comment: undefined,
countries: [],
createdAt: new Date('2021-06-06T16:12:20.982Z'),
currency: 'USD',
@ -342,12 +342,12 @@ const activities: Activity[] = [
isin: 'US9220427424',
name: 'Vanguard Total World Stock Index Fund ETF Shares',
updatedAt: new Date('2025-10-01T20:09:39.500Z'),
scraperConfiguration: null,
scraperConfiguration: undefined,
sectors: [],
symbol: 'VT',
symbolMapping: {},
url: 'https://www.vanguard.com',
userId: null,
userId: undefined,
activitiesCount: 267,
dateOfFirstActivity: new Date('2018-05-31T16:00:00.000Z')
},

56
libs/ui/src/lib/fire-calculator/fire-calculator.component.ts

@ -100,10 +100,15 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
public calculatorForm = this.formBuilder.group({
// @ts-ignore
annualInterestRate: new FormControl<number>(undefined),
// @ts-ignore
paymentPerPeriod: new FormControl<number>(undefined),
// @ts-ignore
principalInvestmentAmount: new FormControl<number>(undefined),
// @ts-ignore
projectedTotalAmount: new FormControl<number>(undefined),
// @ts-ignore
retirementDate: new FormControl<Date>(undefined)
});
public chart: Chart<'bar'>;
@ -150,25 +155,25 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
this.calculatorForm
.get('annualInterestRate')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
.subscribe((annualInterestRate) => {
this.annualInterestRateChanged.emit(annualInterestRate);
});
this.calculatorForm
.get('paymentPerPeriod')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
.subscribe((savingsRate) => {
this.savingsRateChanged.emit(savingsRate);
});
this.calculatorForm
.get('projectedTotalAmount')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
.subscribe((projectedTotalAmount) => {
this.projectedTotalAmountChanged.emit(projectedTotalAmount);
});
this.calculatorForm
.get('retirementDate')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
.subscribe((retirementDate) => {
this.retirementDateChanged.emit(retirementDate);
});
@ -196,11 +201,11 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
this.calculatorForm.patchValue(
{
annualInterestRate:
this.calculatorForm.get('annualInterestRate').value,
this.calculatorForm.get('annualInterestRate')?.value,
paymentPerPeriod: this.getPMT(),
principalInvestmentAmount: this.calculatorForm.get(
'principalInvestmentAmount'
).value,
)?.value,
projectedTotalAmount:
Math.round(this.getProjectedTotalAmount()) || 0,
retirementDate:
@ -210,7 +215,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
emitEvent: false
}
);
this.calculatorForm.get('principalInvestmentAmount').disable();
this.calculatorForm.get('principalInvestmentAmount')?.disable();
this.changeDetectorRef.markForCheck();
});
@ -219,34 +224,36 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
if (this.hasPermissionToUpdateUserSettings === true) {
this.calculatorForm
.get('annualInterestRate')
.enable({ emitEvent: false });
this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false });
?.enable({ emitEvent: false });
this.calculatorForm.get('paymentPerPeriod')?.enable({ emitEvent: false });
this.calculatorForm
.get('projectedTotalAmount')
.enable({ emitEvent: false });
?.enable({ emitEvent: false });
} else {
this.calculatorForm
.get('annualInterestRate')
.disable({ emitEvent: false });
this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false });
?.disable({ emitEvent: false });
this.calculatorForm
.get('paymentPerPeriod')
?.disable({ emitEvent: false });
this.calculatorForm
.get('projectedTotalAmount')
.disable({ emitEvent: false });
?.disable({ emitEvent: false });
}
this.calculatorForm.get('retirementDate').disable({ emitEvent: false });
this.calculatorForm.get('retirementDate')?.disable({ emitEvent: false });
}
public setMonthAndYear(
normalizedMonthAndYear: Date,
datepicker: MatDatepicker<Date>
) {
const retirementDate = this.calculatorForm.get('retirementDate').value;
const retirementDate = this.calculatorForm.get('retirementDate')!.value;
const newRetirementDate = setMonth(
setYear(retirementDate, normalizedMonthAndYear.getFullYear()),
normalizedMonthAndYear.getMonth()
);
this.calculatorForm.get('retirementDate').setValue(newRetirementDate);
this.calculatorForm.get('retirementDate')?.setValue(newRetirementDate);
datepicker.close();
}
@ -360,6 +367,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
// Calculate retirement date
// if we want to retire at month x, we need the projectedTotalAmount at month x-1
// @ts-ignore
const lastPeriodDate = sub(this.getRetirementDate(), { months: 1 });
const yearsToRetire = lastPeriodDate.getFullYear() - currentYear;
@ -426,12 +434,12 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
}
private getPeriodsToRetire(): number {
if (this.calculatorForm.get('projectedTotalAmount').value) {
if (this.calculatorForm.get('projectedTotalAmount')?.value) {
let periods = this.fireCalculatorService.calculatePeriodsToRetire({
P: this.getP(),
PMT: this.getPMT(),
r: this.getR(),
totalAmount: this.calculatorForm.get('projectedTotalAmount').value
totalAmount: this.calculatorForm.get('projectedTotalAmount')!.value
});
if (periods === Infinity) {
@ -452,13 +460,13 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
}
}
private getPMT() {
return this.calculatorForm.get('paymentPerPeriod').value;
private getPMT(): number {
return this.calculatorForm.get('paymentPerPeriod')!.value;
}
private getProjectedTotalAmount() {
if (this.calculatorForm.get('projectedTotalAmount').value) {
return this.calculatorForm.get('projectedTotalAmount').value;
if (this.calculatorForm.get('projectedTotalAmount')?.value) {
return this.calculatorForm.get('projectedTotalAmount')!.value;
}
const { totalAmount } =
@ -473,10 +481,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
}
private getR() {
return this.calculatorForm.get('annualInterestRate').value / 100;
return this.calculatorForm.get('annualInterestRate')!.value / 100;
}
private getRetirementDate(): Date {
private getRetirementDate(): Date | undefined {
if (this.periodsToRetire === Number.MAX_SAFE_INTEGER) {
return undefined;
}

2
libs/ui/src/lib/holdings-table/holdings-table.component.ts

@ -62,7 +62,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy {
@ViewChild(MatSort) sort: MatSort;
public dataSource = new MatTableDataSource<PortfolioPosition>();
public displayedColumns = [];
public displayedColumns: string[] = [];
public ignoreAssetSubClasses = [AssetSubClass.CASH];
public isLoading = true;
public routeQueryParams: Subscription;

2
libs/ui/src/lib/line-chart/line-chart.component.ts

@ -316,7 +316,9 @@ export class GfLineChartComponent
},
duration: delayBetweenPoints,
easing: 'linear',
// @ts-ignore
from: NaN,
// @ts-ignore
type: 'number'
};
}

2
libs/ui/src/lib/mocks/holdings.ts

@ -158,7 +158,7 @@ export const holdings: PortfolioPosition[] = [
sectors: [],
symbol: 'bitcoin',
tags: [],
url: null,
url: undefined,
valueInBaseCurrency: 54666.7898248
},
{

2
libs/ui/src/lib/no-transactions-info/no-transactions-info.component.ts

@ -24,5 +24,5 @@ export class GfNoTransactionsInfoComponent {
@HostBinding('class.has-border') @Input() hasBorder = true;
public routerLinkPortfolioActivities =
internalRoutes.portfolio.subRoutes.activities.routerLink;
internalRoutes.portfolio.subRoutes?.activities.routerLink;
}

4
libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts

@ -11,8 +11,8 @@ import { AlertDialogParams } from './interfaces/interfaces';
templateUrl: './alert-dialog.html'
})
export class GfAlertDialogComponent {
public discardLabel: string;
public message: string;
public discardLabel?: string;
public message?: string;
public title: string;
public constructor(public dialogRef: MatDialogRef<GfAlertDialogComponent>) {}

6
libs/ui/src/lib/notifications/confirmation-dialog/confirmation-dialog.component.ts

@ -13,10 +13,10 @@ import { ConfirmDialogParams } from './interfaces/interfaces';
templateUrl: './confirmation-dialog.html'
})
export class GfConfirmationDialogComponent {
public confirmLabel: string;
public confirmLabel?: string;
public confirmType: ConfirmationDialogType;
public discardLabel: string;
public message: string;
public discardLabel?: string;
public message?: string;
public title: string;
public constructor(

10
libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.component.ts

@ -18,25 +18,25 @@ import { MatInputModule } from '@angular/material/input';
})
export class GfPromptDialogComponent {
public confirmLabel: string;
public defaultValue: string;
public defaultValue?: string;
public discardLabel: string;
public formControl = new FormControl('');
public title: string;
public valueLabel: string;
public valueLabel?: string;
public constructor(public dialogRef: MatDialogRef<GfPromptDialogComponent>) {}
public initialize(aParams: {
confirmLabel?: string;
confirmLabel: string;
defaultValue?: string;
discardLabel?: string;
discardLabel: string;
title: string;
valueLabel?: string;
}) {
this.confirmLabel = aParams.confirmLabel;
this.defaultValue = aParams.defaultValue;
this.discardLabel = aParams.discardLabel;
this.formControl.setValue(aParams.defaultValue);
this.formControl.setValue(aParams.defaultValue ?? null);
this.title = aParams.title;
this.valueLabel = aParams.valueLabel;
}

8
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts

@ -70,10 +70,10 @@ export class GfPortfolioFilterFormComponent
private formBuilder: FormBuilder
) {
this.filterForm = this.formBuilder.group({
account: new FormControl<string>(null),
assetClass: new FormControl<string>(null),
holding: new FormControl<PortfolioPosition>(null),
tag: new FormControl<string>(null)
account: new FormControl<string | null>(null),
assetClass: new FormControl<string | null>(null),
holding: new FormControl<PortfolioPosition | null>(null),
tag: new FormControl<string | null>(null)
});
}

8
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts

@ -140,16 +140,18 @@ export class GfPortfolioProportionChartComponent
if (
chartData[this.data[symbol][this.keys[0]].toUpperCase()]
.subCategory[this.data[symbol][this.keys[1]]]
?.subCategory?.[this.data[symbol][this.keys[1]]]
) {
// @ts-ignore
chartData[
this.data[symbol][this.keys[0]].toUpperCase()
].subCategory[this.data[symbol][this.keys[1]]].value = chartData[
this.data[symbol][this.keys[0]].toUpperCase()
].subCategory[this.data[symbol][this.keys[1]]].value.plus(
].subCategory?.[this.data[symbol][this.keys[1]]].value.plus(
this.data[symbol].value || 0
);
} else {
// @ts-ignore
chartData[
this.data[symbol][this.keys[0]].toUpperCase()
].subCategory[this.data[symbol][this.keys[1]] ?? UNKNOWN_KEY] = {
@ -278,12 +280,14 @@ export class GfPortfolioProportionChartComponent
Object.keys(item.subCategory ?? {}).forEach((subCategory) => {
if (item.name === UNKNOWN_KEY) {
// @ts-ignore
backgroundColorSubCategory.push(item.color);
} else {
backgroundColorSubCategory.push(
Color(item.color).lighten(lightnessRatio).hex()
);
}
// @ts-ignore
dataSubCategory.push(item.subCategory[subCategory].value.toNumber());
labelSubCategory.push(subCategory);

6
libs/ui/src/lib/services/admin.service.ts

@ -132,7 +132,7 @@ export class AdminService {
public fetchJobs({ status }: { status?: JobStatus[] }) {
let params = new HttpParams();
if (status?.length > 0) {
if (status && status.length > 0) {
params = params.append('status', status.join(','));
}
@ -158,8 +158,8 @@ export class AdminService {
}) {
let params = new HttpParams();
params = params.append('skip', skip);
params = params.append('take', take);
if (skip) params = params.append('skip', skip);
if (take) params = params.append('take', take);
return this.http.get<AdminUsersResponse>('/api/v1/admin/user', { params });
}

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

@ -89,7 +89,7 @@ export class DataService {
public buildFiltersAsQueryParams({ filters }: { filters?: Filter[] }) {
let params = new HttpParams();
if (filters?.length > 0) {
if (filters && filters.length > 0) {
const {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,

2
libs/ui/src/lib/shared/abstract-mat-form-field.ts

@ -137,7 +137,7 @@ export abstract class AbstractMatFormField<T>
public ngDoCheck() {
if (this.ngControl) {
this.errorState = this.ngControl.invalid && this.ngControl.touched;
this.errorState = !!(this.ngControl.invalid && this.ngControl.touched);
this.stateChanges.next();
}
}

4
libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts

@ -185,7 +185,7 @@ export class GfSymbolAutocompleteComponent
public ngDoCheck() {
if (this.ngControl) {
this.validateRequired();
this.errorState = this.ngControl.invalid && this.ngControl.touched;
this.errorState = !!(this.ngControl.invalid && this.ngControl.touched);
this.stateChanges.next();
}
}
@ -225,7 +225,7 @@ export class GfSymbolAutocompleteComponent
? !super.value?.dataSource || !super.value?.symbol
: false;
if (requiredCheck) {
this.ngControl.control.setErrors({ invalidData: true });
this.ngControl.control?.setErrors({ invalidData: true });
}
}
}

14
libs/ui/src/lib/tags-selector/tags-selector.component.ts

@ -77,7 +77,7 @@ export class GfTagsSelectorComponent
this.tagInputControl.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((value) => {
this.filteredOptions.next(this.filterTags(value));
this.filteredOptions.next(this.filterTags(value ?? ''));
});
addIcons({ addCircleOutline, closeOutline });
@ -100,21 +100,23 @@ export class GfTagsSelectorComponent
if (!tag && this.hasPermissionToCreateTag) {
tag = {
id: undefined,
id: '',
name: event.option.value as string,
userId: null
};
}
this.tagsSelected.update((tags) => {
return [...(tags ?? []), tag];
});
if (tag) {
this.tagsSelected.update((tags) => {
return [...(tags ?? []), tag];
});
}
const newTags = this.tagsSelected();
this.onChange(newTags);
this.onTouched();
this.tagInput.nativeElement.value = '';
this.tagInputControl.setValue(undefined);
this.tagInputControl.setValue(null);
}
public onRemoveTag(tag: Tag) {

6
libs/ui/src/lib/toggle/toggle.component.ts

@ -26,13 +26,15 @@ export class GfToggleComponent implements OnChanges {
@Output() valueChange = new EventEmitter<Pick<ToggleOption, 'value'>>();
public optionFormControl = new FormControl<string>(undefined);
public optionFormControl = new FormControl<string | null>(null);
public ngOnChanges() {
this.optionFormControl.setValue(this.defaultValue);
}
public onValueChange() {
this.valueChange.emit({ value: this.optionFormControl.value });
if (this.optionFormControl.value !== null) {
this.valueChange.emit({ value: this.optionFormControl.value });
}
}
}

2
libs/ui/src/lib/treemap-chart/treemap-chart.component.ts

@ -25,7 +25,7 @@ import {
} from '@angular/core';
import { DataSource } from '@prisma/client';
import { Big } from 'big.js';
import type { TooltipOptions, ChartData } from 'chart.js';
import type { ChartData, TooltipOptions } from 'chart.js';
import { LinearScale } from 'chart.js';
import { Chart, Tooltip } from 'chart.js';
import { TreemapController, TreemapElement } from 'chartjs-chart-treemap';

1
libs/ui/tsconfig.json

@ -19,6 +19,7 @@
"target": "es2020",
// TODO: Remove once solved in tsconfig.base.json
"strict": false,
"strictNullChecks": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

Loading…
Cancel
Save