Browse Source

Validate DTO before form is closed, display errors if any

pull/3230/head
Nicolas Fedor 1 year ago
committed by Thomas Kaul
parent
commit
0a59f5e5ba
  1. 7
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
  2. 28
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html
  3. 14
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  4. 63
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  5. 27
      apps/client/src/app/util/validation.util.ts

7
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts

@ -1,6 +1,7 @@
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
import { DataService } from '@ghostfolio/client/services/data.service';
import { validateObjectForForm } from '@ghostfolio/client/util/validation.util';
import { Currency } from '@ghostfolio/common/interfaces';
import {
@ -115,8 +116,14 @@ export class CreateOrUpdateAccountDialog implements OnDestroy {
if (this.data.account.id) {
(account as UpdateAccountDto).id = this.data.account.id;
validateObjectForForm(account, UpdateAccountDto, this.accountForm, () => {
this.dialogRef.close({ account });
});
} else {
delete (account as CreateAccountDto).id;
validateObjectForForm(account, CreateAccountDto, this.accountForm, () => {
this.dialogRef.close({ account });
});
}
this.dialogRef.close({ account });

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

@ -18,6 +18,9 @@
matInput
(keydown.enter)="$event.stopPropagation()"
/>
<mat-error *ngIf="accountForm.controls['name'].errors?.validationError">
{{ accountForm.controls['name'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div>
@ -27,6 +30,11 @@
formControlName="currency"
[currencies]="currencies"
/>
<mat-error
*ngIf="accountForm.controls['currency'].errors?.validationError"
>
{{ accountForm.controls['currency'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div>
@ -41,6 +49,11 @@
<span class="ml-2" matTextSuffix>{{
accountForm.controls['currency']?.value?.value
}}</span>
<mat-error
*ngIf="accountForm.controls['balance'].errors?.validationError"
>
{{ accountForm.controls['balance'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div [ngClass]="{ 'd-none': platforms?.length < 1 }">
@ -71,6 +84,11 @@
</mat-option>
}
</mat-autocomplete>
<mat-error
*ngIf="accountForm.controls['platformId'].errors?.validationError"
>
{{ accountForm.controls['platformId'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div>
@ -83,6 +101,11 @@
matInput
(keyup.enter)="$event.stopPropagation()"
></textarea>
<mat-error
*ngIf="accountForm.controls['comment'].errors?.validationError"
>
{{ accountForm.controls['comment'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div class="mb-3 px-2">
@ -95,6 +118,11 @@
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Account ID</mat-label>
<input formControlName="accountId" matInput />
<mat-error
*ngIf="accountForm.controls['accountId'].errors?.validationError"
>
{{ accountForm.controls['accountId'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
}

14
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -1,6 +1,7 @@
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { DataService } from '@ghostfolio/client/services/data.service';
import { validateObjectForForm } from '@ghostfolio/client/util/validation.util';
import { getDateFormatString } from '@ghostfolio/common/helper';
import { translate } from '@ghostfolio/ui/i18n';
@ -19,7 +20,8 @@ import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AssetClass, AssetSubClass, Tag, Type } from '@prisma/client';
import { isUUID } from 'class-validator';
import { plainToInstance } from 'class-transformer';
import { Validator, isUUID, validate } from 'class-validator';
import { isToday } from 'date-fns';
import { EMPTY, Observable, Subject, lastValueFrom, of } from 'rxjs';
import { catchError, delay, map, startWith, takeUntil } from 'rxjs/operators';
@ -459,7 +461,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
comment: this.activityForm.controls['comment'].value,
currency: this.activityForm.controls['currency'].value,
customCurrency: this.activityForm.controls['currencyOfUnitPrice'].value,
date: this.activityForm.controls['date'].value,
date: (this.activityForm.controls['date'].value as Date).toISOString(),
dataSource: this.activityForm.controls['dataSource'].value,
fee: this.activityForm.controls['fee'].value,
quantity: this.activityForm.controls['quantity'].value,
@ -476,12 +478,16 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
if (this.data.activity.id) {
(activity as UpdateOrderDto).id = this.data.activity.id;
validateObjectForForm(activity, UpdateOrderDto, this.activityForm, () => {
this.dialogRef.close({ activity });
});
} else {
(activity as CreateOrderDto).updateAccountBalance =
this.activityForm.controls['updateAccountBalance'].value;
validateObjectForForm(activity, CreateOrderDto, this.activityForm, () => {
this.dialogRef.close({ activity });
});
}
this.dialogRef.close({ activity });
}
public ngOnDestroy() {

63
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -71,6 +71,11 @@
>
</mat-option>
</mat-select>
<mat-error
*ngIf="activityForm.controls['type'].errors?.validationError"
>
{{ activityForm.controls['tye'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div [ngClass]="{ 'mb-3': data.activity.id }">
@ -103,6 +108,11 @@
</div>
</mat-option>
</mat-select>
<mat-error
*ngIf="activityForm.controls['accountId'].errors?.validationError"
>
{{ activityForm.controls['accountId'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div class="mb-3" [ngClass]="{ 'd-none': data.activity.id }">
@ -124,6 +134,11 @@
formControlName="searchSymbol"
[isLoading]="isLoading"
/>
<mat-error
*ngIf="activityForm.controls['searchSymbol'].errors?.validationError"
>
{{ activityForm.controls['searchSymbol'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div
@ -137,6 +152,11 @@
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Name</mat-label>
<input formControlName="name" matInput />
<mat-error
*ngIf="activityForm.controls['name'].errors?.validationError"
>
{{ activityForm.controls['name'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div class="d-none">
@ -147,12 +167,22 @@
currency
}}</mat-option>
</mat-select>
<mat-error
*ngIf="activityForm.controls['currency'].errors?.validationError"
>
{{ activityForm.controls['currency'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div class="d-none">
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Data Source</mat-label>
<input formControlName="dataSource" matInput />
<mat-error
*ngIf="activityForm.controls['dataSource'].errors?.validationError"
>
{{ activityForm.controls['dataSource'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div class="mb-3">
@ -167,6 +197,11 @@
/>
</mat-datepicker-toggle>
<mat-datepicker #date disabled="false" />
<mat-error
*ngIf="activityForm.controls['date'].errors?.validationError"
>
{{ activityForm.controls['date'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div
@ -182,6 +217,11 @@
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Quantity</mat-label>
<input formControlName="quantity" matInput type="number" />
<mat-error
*ngIf="activityForm.controls['quantity'].errors?.validationError"
>
{{ activityForm.controls['quantity'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div
@ -271,6 +311,11 @@
<span class="ml-2" matTextSuffix>{{
activityForm.controls['currency'].value
}}</span>
<mat-error
*ngIf="activityForm.controls['unitPrice'].errors?.validationError"
>
{{ activityForm.controls['unitPrice'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div
@ -312,6 +357,9 @@
<span class="ml-2" matTextSuffix>{{
activityForm.controls['currency'].value
}}</span>
<mat-error *ngIf="activityForm.controls['fee'].errors?.validationError">
{{ activityForm.controls['fee'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div class="mb-3">
@ -324,6 +372,11 @@
matInput
(keyup.enter)="$event.stopPropagation()"
></textarea>
<mat-error
*ngIf="activityForm.controls['comment'].errors?.validationError"
>
{{ activityForm.controls['comment'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div
@ -340,6 +393,11 @@
>{{ assetClass.label }}</mat-option
>
</mat-select>
<mat-error
*ngIf="activityForm.controls['assetClass'].errors?.validationError"
>
{{ activityForm.controls['assetClass'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div
@ -356,6 +414,11 @@
>{{ assetSubClass.label }}</mat-option
>
</mat-select>
<mat-error
*ngIf="activityForm.controls['assetSubClass'].errors?.validationError"
>
{{ activityForm.controls['assetSubClass'].errors?.validationError }}
</mat-error>
</mat-form-field>
</div>
<div class="mb-3" [ngClass]="{ 'd-none': tags?.length < 1 }">

27
apps/client/src/app/util/validation.util.ts

@ -0,0 +1,27 @@
import { FormGroup } from '@angular/forms';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
export function validateObjectForForm<T>(
obj: T,
ctor: { new (): T },
form: FormGroup,
onSuccess: () => void
): void {
const objInstance = plainToInstance(ctor, obj);
validate(objInstance as object).then((errors) => {
if (errors.length === 0) {
onSuccess();
} else {
errors.forEach((error) => {
const formControl = form.get(error.property);
if (formControl) {
formControl.setErrors({
validationError: Object.values(error.constraints)[0]
});
}
});
}
});
}
Loading…
Cancel
Save