register, forgot password and carnet goods section updates
This commit is contained in:
parent
83db67fd63
commit
404adc0d3d
104
package-lock.json
generated
104
package-lock.json
generated
@ -25,6 +25,7 @@
|
|||||||
"ngx-cookie-service": "^20.0.1",
|
"ngx-cookie-service": "^20.0.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -3509,6 +3510,15 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/adler-32": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
||||||
@ -3955,6 +3965,19 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/cfb": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"adler-32": "~1.3.0",
|
||||||
|
"crc-32": "~1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||||
@ -4173,6 +4196,15 @@
|
|||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codepage": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@ -4352,6 +4384,18 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crc-32": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"crc32": "bin/crc32.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@ -5083,6 +5127,15 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/frac": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||||
@ -8539,6 +8592,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/ssf": {
|
||||||
|
"version": "0.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||||
|
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"frac": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ssri": {
|
"node_modules/ssri": {
|
||||||
"version": "12.0.0",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz",
|
||||||
@ -9214,6 +9279,24 @@
|
|||||||
"which": "bin/which"
|
"which": "bin/which"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wmf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/word": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||||
@ -9427,6 +9510,27 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xlsx": {
|
||||||
|
"version": "0.18.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||||
|
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"adler-32": "~1.3.0",
|
||||||
|
"cfb": "~1.2.1",
|
||||||
|
"codepage": "~1.15.0",
|
||||||
|
"crc-32": "~1.2.1",
|
||||||
|
"ssf": "~0.11.2",
|
||||||
|
"wmf": "~1.0.1",
|
||||||
|
"word": "~0.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"xlsx": "bin/xlsx.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
"ngx-cookie-service": "^20.0.1",
|
"ngx-cookie-service": "^20.0.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { RenderMode, ServerRoute } from '@angular/ssr';
|
import { RenderMode, ServerRoute } from '@angular/ssr';
|
||||||
|
|
||||||
export const serverRoutes: ServerRoute[] = [
|
export const serverRoutes: ServerRoute[] = [
|
||||||
|
{ path: 'forgot-password/:token', renderMode: RenderMode.Client },
|
||||||
{ path: ':appId', renderMode: RenderMode.Client },
|
{ path: ':appId', renderMode: RenderMode.Client },
|
||||||
{ path: ':appId/home', renderMode: RenderMode.Client },
|
{ path: ':appId/home', renderMode: RenderMode.Client },
|
||||||
{ path: ':appId/usersettings', renderMode: RenderMode.Client },
|
{ path: ':appId/usersettings', renderMode: RenderMode.Client },
|
||||||
|
{ path: ':appId/edit-carnet/:headerid', renderMode: RenderMode.Client },
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
renderMode: RenderMode.Prerender
|
renderMode: RenderMode.Prerender
|
||||||
|
|||||||
@ -4,8 +4,9 @@ import { AuthGuard } from './guards/auth.guard';
|
|||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: 'login', loadComponent: () => import('./login/login.component').then(m => m.LoginComponent) },
|
{ path: 'login', loadComponent: () => import('./login/login.component').then(m => m.LoginComponent) },
|
||||||
|
{ path: 'forgot-password/:token', loadComponent: () => import('./forgot-password/forgot-password.component').then(m => m.ForgotPasswordComponent) },
|
||||||
{ path: 'forgot-password', loadComponent: () => import('./forgot-password/forgot-password.component').then(m => m.ForgotPasswordComponent) },
|
{ path: 'forgot-password', loadComponent: () => import('./forgot-password/forgot-password.component').then(m => m.ForgotPasswordComponent) },
|
||||||
{ path: 'register/:token', loadComponent: () => import('./register/register.component').then(m => m.RegisterComponent) },
|
{ path: 'register', loadComponent: () => import('./register/register.component').then(m => m.RegisterComponent) },
|
||||||
{ path: '404', loadComponent: () => import('./shared/components/not-found/not-found.component').then(m => m.NotFoundComponent) },
|
{ path: '404', loadComponent: () => import('./shared/components/not-found/not-found.component').then(m => m.NotFoundComponent) },
|
||||||
{
|
{
|
||||||
path: ':appId',
|
path: ':appId',
|
||||||
|
|||||||
@ -23,7 +23,8 @@
|
|||||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection"
|
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection"
|
||||||
[editable]="stepsCompleted.applicationDetail">
|
[editable]="stepsCompleted.applicationDetail">
|
||||||
<ng-template matStepLabel>Goods Section</ng-template>
|
<ng-template matStepLabel>Goods Section</ng-template>
|
||||||
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid">
|
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid"
|
||||||
|
[userPreferences]="userPreferences">
|
||||||
</app-goods>
|
</app-goods>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import { InsuranceComponent } from '../insurance/insurance.component';
|
|||||||
import { PaymentComponent } from '../payment/payment.component';
|
import { PaymentComponent } from '../payment/payment.component';
|
||||||
import { ShippingComponent } from '../shipping/shipping.component';
|
import { ShippingComponent } from '../shipping/shipping.component';
|
||||||
import { DeliveryComponent } from '../delivery/delivery.component';
|
import { DeliveryComponent } from '../delivery/delivery.component';
|
||||||
|
import { UserPreferencesService } from '../../core/services/user-preference.service';
|
||||||
|
import { UserPreferences } from '../../core/models/user-preference';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-carnet',
|
selector: 'app-add-carnet',
|
||||||
@ -26,6 +28,7 @@ export class AddCarnetComponent {
|
|||||||
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
|
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
|
||||||
isEditMode = false;
|
isEditMode = false;
|
||||||
headerid: number = 0;
|
headerid: number = 0;
|
||||||
|
userPreferences: UserPreferences;
|
||||||
|
|
||||||
// Track completion of each step
|
// Track completion of each step
|
||||||
stepsCompleted = {
|
stepsCompleted = {
|
||||||
@ -39,6 +42,10 @@ export class AddCarnetComponent {
|
|||||||
payment: false
|
payment: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constructor(userPrefenceService: UserPreferencesService) {
|
||||||
|
this.userPreferences = userPrefenceService.getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
onStepChange(event: StepperSelectionEvent): void {
|
onStepChange(event: StepperSelectionEvent): void {
|
||||||
this.currentStep = event.selectedIndex;
|
this.currentStep = event.selectedIndex;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export class ApplicationComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.applicationName = this.route.snapshot.queryParamMap.get('applicationName') || '';
|
this.applicationName = this.route.snapshot.queryParamMap.get('applicationname') || '';
|
||||||
|
|
||||||
// Patch edit form data
|
// Patch edit form data
|
||||||
if (this.applicationName) {
|
if (this.applicationName) {
|
||||||
|
|||||||
@ -23,7 +23,8 @@
|
|||||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection"
|
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection"
|
||||||
[editable]="stepsCompleted.applicationDetail">
|
[editable]="stepsCompleted.applicationDetail">
|
||||||
<ng-template matStepLabel>Goods Section</ng-template>
|
<ng-template matStepLabel>Goods Section</ng-template>
|
||||||
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid">
|
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid"
|
||||||
|
[userPreferences]="userPreferences">
|
||||||
</app-goods>
|
</app-goods>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import { InsuranceComponent } from '../insurance/insurance.component';
|
|||||||
import { PaymentComponent } from '../payment/payment.component';
|
import { PaymentComponent } from '../payment/payment.component';
|
||||||
import { ShippingComponent } from '../shipping/shipping.component';
|
import { ShippingComponent } from '../shipping/shipping.component';
|
||||||
import { TravelPlanComponent } from '../travel-plan/travel-plan.component';
|
import { TravelPlanComponent } from '../travel-plan/travel-plan.component';
|
||||||
|
import { UserPreferences } from '../../core/models/user-preference';
|
||||||
|
import { UserPreferencesService } from '../../core/services/user-preference.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-edit-carnet',
|
selector: 'app-edit-carnet',
|
||||||
@ -27,6 +29,7 @@ export class EditCarnetComponent {
|
|||||||
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
|
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
|
||||||
isEditMode = true; // Set to true for edit mode
|
isEditMode = true; // Set to true for edit mode
|
||||||
headerid: number = 0;
|
headerid: number = 0;
|
||||||
|
userPreferences: UserPreferences;
|
||||||
|
|
||||||
// Track completion of each step
|
// Track completion of each step
|
||||||
stepsCompleted = {
|
stepsCompleted = {
|
||||||
@ -42,6 +45,10 @@ export class EditCarnetComponent {
|
|||||||
|
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
|
|
||||||
|
constructor(userPrefenceService: UserPreferencesService) {
|
||||||
|
this.userPreferences = userPrefenceService.getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const idParam = this.route.snapshot.paramMap.get('headerid');
|
const idParam = this.route.snapshot.paramMap.get('headerid');
|
||||||
this.headerid = idParam ? parseInt(idParam, 10) : 0;
|
this.headerid = idParam ? parseInt(idParam, 10) : 0;
|
||||||
|
|||||||
@ -1 +1,235 @@
|
|||||||
<p>goods works!</p>
|
<div class="goods-container">
|
||||||
|
|
||||||
|
<form [formGroup]="goodsForm">
|
||||||
|
<!-- Purpose Section -->
|
||||||
|
<div class="form-section">
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<mat-label>Goods to be imported as </mat-label>
|
||||||
|
<mat-checkbox formControlName="commercialSample">Commercial Sample</mat-checkbox>
|
||||||
|
<mat-checkbox formControlName="professionalEquipment">Professional Equipment</mat-checkbox>
|
||||||
|
<mat-checkbox formControlName="exhibitionsFair">Exhibitions and Fair</mat-checkbox>
|
||||||
|
|
||||||
|
<mat-error *ngIf="goodsForm.errors?.['atLeastOneRequired']">
|
||||||
|
At least one must be selected
|
||||||
|
</mat-error>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<mat-checkbox labelPosition="before" formControlName="roadVehiclesUsed">Road Vehicles
|
||||||
|
used?</mat-checkbox>
|
||||||
|
<mat-checkbox labelPosition="before" formControlName="horseUsed">Horse used?</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Authorized Representatives Section -->
|
||||||
|
<div class="form-section">
|
||||||
|
<mat-form-field appearance="outline" class="full-width">
|
||||||
|
<mat-label>Authorized Representative(s)</mat-label>
|
||||||
|
<textarea matInput formControlName="authorizedRepresentatives" rows="3"></textarea>
|
||||||
|
<mat-error *ngIf="goodsForm.get('authorizedRepresentatives')?.errors?.['required']">
|
||||||
|
Authorized Representative(s) is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="table-actions">
|
||||||
|
<h3>Goods Items</h3>
|
||||||
|
<div class="actions">
|
||||||
|
<button mat-raised-button color="primary" type="button" (click)="addNewItem()">
|
||||||
|
<mat-icon>add</mat-icon> Add Item
|
||||||
|
</button>
|
||||||
|
<div class="upload-section">
|
||||||
|
<input type="file" accept=".xlsx,.xls,.csv" (change)="fileUpload($event)" hidden #fileInput>
|
||||||
|
<button mat-raised-button type="button" (click)="fileInput.click()" [disabled]="isProcessing">
|
||||||
|
<mat-icon>upload</mat-icon> Upload
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container mat-elevation-z8">
|
||||||
|
<div class="loading-shade" *ngIf="isLoading">
|
||||||
|
<mat-spinner diameter="50"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table mat-table [dataSource]="dataSource" matSort>
|
||||||
|
<!-- Item Number Column -->
|
||||||
|
<ng-container matColumnDef="itemNumber">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Item Number</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.itemNumber }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Description Column -->
|
||||||
|
<ng-container matColumnDef="description">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Description</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.description }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Pieces Column -->
|
||||||
|
<ng-container matColumnDef="pieces">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Pieces</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.pieces }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Weight Column -->
|
||||||
|
<ng-container matColumnDef="weight">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Weight</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ formatDecimalDisplay(item.weight, 4) }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Unit of Measure Column -->
|
||||||
|
<ng-container matColumnDef="unitOfMeasure">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Unit Of Measure</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ getUnitOfMeasureLabel(item.unitOfMeasure) }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Value Column -->
|
||||||
|
<ng-container matColumnDef="value">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Value</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ formatDecimalDisplay(item.value, 2) }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Country of Origin Column -->
|
||||||
|
<ng-container matColumnDef="countryOfOrigin">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Country of Origin</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ getCountryLabel(item.countryOfOrigin) }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Actions Column -->
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||||
|
<td mat-cell *matCellDef="let item; let i = index">
|
||||||
|
<button mat-icon-button color="primary" (click)="editItem(item)" matTooltip="Edit">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button color="warn" (click)="deleteItem(item)" matTooltip="Delete">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||||
|
|
||||||
|
<tr matNoDataRow *matNoDataRow>
|
||||||
|
<td [colSpan]="displayedColumns.length" class="no-data-message">
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<span>No items added</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize!]"
|
||||||
|
[hidePageSize]="true" showFirstLastButtons></mat-paginator>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Item Form -->
|
||||||
|
<div class="form-container" *ngIf="showItemForm">
|
||||||
|
<form [formGroup]="itemForm" (ngSubmit)="saveItem()">
|
||||||
|
<div class="form-header">
|
||||||
|
<h3>{{ isEditing ? 'Edit Item' : 'Add New Item' }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Item Number</mat-label>
|
||||||
|
<input matInput formControlName="itemNumber" required>
|
||||||
|
<mat-error *ngIf="itemForm.get('itemNumber')?.errors?.['required']">
|
||||||
|
Item Number is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="description">
|
||||||
|
<mat-label>Description</mat-label>
|
||||||
|
<input matInput formControlName="description" required>
|
||||||
|
<mat-error *ngIf="itemForm.get('description')?.errors?.['required']">
|
||||||
|
Description is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Pieces</mat-label>
|
||||||
|
<input matInput type="number" formControlName="pieces" required>
|
||||||
|
<mat-error *ngIf="itemForm.get('pieces')?.errors?.['required']">
|
||||||
|
Pieces is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="itemForm.get('pieces')?.errors?.['min']">
|
||||||
|
Must be at least 1
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Weight</mat-label>
|
||||||
|
<input matInput type="number" formControlName="weight" required>
|
||||||
|
<mat-error *ngIf="itemForm.get('weight')?.errors?.['required']">
|
||||||
|
Weight is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="itemForm.get('weight')?.errors?.['min']">
|
||||||
|
Must be positive
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="itemForm.get('weight')?.errors?.['pattern']">
|
||||||
|
Maximum 4 decimal places allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Unit of Measure</mat-label>
|
||||||
|
<mat-select formControlName="unitOfMeasure" required>
|
||||||
|
<mat-option *ngFor="let unit of unitsOfMeasures" [value]="unit.value">
|
||||||
|
{{ unit.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error *ngIf="itemForm.get('unitOfMeasure')?.errors?.['required']">
|
||||||
|
Unit of measure is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Value</mat-label>
|
||||||
|
<input matInput type="number" formControlName="value" required>
|
||||||
|
<mat-error *ngIf="itemForm.get('value')?.errors?.['required']">
|
||||||
|
Value is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="itemForm.get('value')?.errors?.['min']">
|
||||||
|
Must be positive
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="itemForm.get('value')?.errors?.['pattern']">
|
||||||
|
Maximum 2 decimal places allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="country">
|
||||||
|
<mat-label>Country of Origin</mat-label>
|
||||||
|
<mat-select formControlName="countryOfOrigin" required>
|
||||||
|
<mat-option *ngFor="let country of countries" [value]="country.value">
|
||||||
|
{{ country.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error *ngIf="itemForm.get('countryOfOrigin')?.errors?.['required']">
|
||||||
|
Country of origin is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
|
||||||
|
<button mat-raised-button color="primary" type="submit" [disabled]="itemForm.invalid">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!showItemForm" class="form-actions">
|
||||||
|
<button mat-raised-button color="primary" (click)="onSubmit()" [disabled]="goodsForm.invalid">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,169 @@
|
|||||||
|
.goods-container {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
mat-label {
|
||||||
|
margin-top: 5px;
|
||||||
|
color: var(--mat-checkbox-label-text-color, var(--mat-sys-on-surface));
|
||||||
|
font-family: var(--mat-checkbox-label-text-font, var(--mat-sys-body-medium-font));
|
||||||
|
line-height: var(--mat-checkbox-label-text-line-height, var(--mat-sys-body-medium-line-height));
|
||||||
|
font-size: var(--mat-checkbox-label-text-size, var(--mat-sys-body-medium-size));
|
||||||
|
letter-spacing: var(--mat-checkbox-label-text-tracking, var(--mat-sys-body-medium-tracking));
|
||||||
|
font-weight: var(--mat-checkbox-label-text-weight, var(--mat-sys-body-medium-weight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--mat-sys-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
position: relative;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.loading-shade {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-actions {
|
||||||
|
width: 180px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.9rem;
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
margin-bottom: -3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-paginator {
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
// position: fixed;
|
||||||
|
// top: 0;
|
||||||
|
// right: 0;
|
||||||
|
// bottom: 0;
|
||||||
|
// width: 400px;
|
||||||
|
// background: white;
|
||||||
|
// box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
|
// padding: 24px;
|
||||||
|
// overflow-y: auto;
|
||||||
|
// z-index: 100;
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--mat-sys-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,453 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, inject, Input, Output, ViewChild } from '@angular/core';
|
||||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { GoodsService } from '../../core/services/carnet/goods.service';
|
||||||
|
import { NotificationService } from '../../core/services/common/notification.service';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { CustomPaginator } from '../../shared/custom-paginator';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { MatPaginatorIntl, MatPaginator } from '@angular/material/paginator';
|
||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
|
||||||
|
import { UserPreferences } from '../../core/models/user-preference';
|
||||||
|
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
||||||
|
import { Country } from '../../core/models/country';
|
||||||
|
import { UnitOfMeasure } from '../../core/models/unitofmeasure';
|
||||||
|
import { CommonService } from '../../core/services/common/common.service';
|
||||||
|
import { Subject, takeUntil } from 'rxjs';
|
||||||
|
import { Goods, GoodsItem } from '../../core/models/carnet/goods';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-goods',
|
selector: 'app-goods',
|
||||||
imports: [AngularMaterialModule, CommonModule],
|
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule],
|
||||||
templateUrl: './goods.component.html',
|
templateUrl: './goods.component.html',
|
||||||
styleUrl: './goods.component.scss'
|
styleUrl: './goods.component.scss',
|
||||||
|
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }],
|
||||||
})
|
})
|
||||||
export class GoodsComponent {
|
export class GoodsComponent {
|
||||||
@Input() headerid: number = 0;
|
@Input() headerid: number = 0;
|
||||||
|
@Input() userPreferences: UserPreferences = {};
|
||||||
@Output() completed = new EventEmitter<boolean>();
|
@Output() completed = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||||
|
@ViewChild(MatSort) sort!: MatSort;
|
||||||
|
|
||||||
|
// Table configuration
|
||||||
|
displayedColumns: string[] = ['itemNumber', 'description', 'pieces', 'weight', 'unitOfMeasure', 'value', 'countryOfOrigin', 'actions'];
|
||||||
|
dataSource = new MatTableDataSource<any>();
|
||||||
|
|
||||||
|
// Form controls
|
||||||
|
goodsForm: FormGroup;
|
||||||
|
itemForm: FormGroup;
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
isEditing = false;
|
||||||
|
currentItem: GoodsItem | null = null;
|
||||||
|
isLoading = false;
|
||||||
|
showItemForm = false;
|
||||||
|
fileToUpload: File | null = null;
|
||||||
|
isProcessing = false;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
countries: Country[] = [];
|
||||||
|
unitsOfMeasures: UnitOfMeasure[] = [];
|
||||||
|
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
private fb = inject(FormBuilder);
|
||||||
|
private goodsService = inject(GoodsService);
|
||||||
|
private notificationService = inject(NotificationService);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
private errorHandler = inject(ApiErrorHandlerService);
|
||||||
|
private commonService = inject(CommonService);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Main form for goods section
|
||||||
|
this.goodsForm = this.fb.group({
|
||||||
|
commercialSample: [false],
|
||||||
|
professionalEquipment: [false],
|
||||||
|
exhibitionsFair: [false],
|
||||||
|
roadVehiclesUsed: [false],
|
||||||
|
horseUsed: [false],
|
||||||
|
authorizedRepresentatives: ['', Validators.required]
|
||||||
|
}, { validator: this.atLeastOneRequired }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Form for individual items
|
||||||
|
this.itemForm = this.fb.group({
|
||||||
|
itemNumber: ['', Validators.required],
|
||||||
|
description: ['', Validators.required],
|
||||||
|
pieces: [0, [Validators.required, Validators.min(1)]],
|
||||||
|
weight: [0, [Validators.required, Validators.min(1), Validators.pattern(/^\d+(\.\d{1,4})?$/)]],
|
||||||
|
unitOfMeasure: ['', Validators.required],
|
||||||
|
value: [0, [Validators.required, Validators.min(1), Validators.pattern(/^\d+(\.\d{1,2})?$/)]],
|
||||||
|
countryOfOrigin: ['', Validators.required]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.goodsForm.valueChanges.subscribe(() => {
|
||||||
|
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.dataSource.paginator = this.paginator;
|
||||||
|
this.dataSource.sort = this.sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadCountries();
|
||||||
|
this.loadUnitOfMeasures();
|
||||||
|
|
||||||
|
if (this.headerid > 0) {
|
||||||
|
this.goodsService.getGoodDetailsByHeaderId(this.headerid).subscribe({
|
||||||
|
next: (goodsDetail: Goods) => {
|
||||||
|
if (goodsDetail) {
|
||||||
|
this.patchFormData(goodsDetail);
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error: any) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load good details');
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
this.isLoading = false;
|
||||||
|
console.error('Error loading good details:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadGoodsItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadGoodsItems(): void {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.goodsService.getGoodsItemsByHeaderId(this.headerid).subscribe({
|
||||||
|
next: (items: GoodsItem[]) => {
|
||||||
|
this.dataSource.data = items;
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error: any) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load goods items');
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
this.isLoading = false;
|
||||||
|
console.error('Error loading goods items:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCountries(): void {
|
||||||
|
this.commonService.getCountries(0)
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe({
|
||||||
|
next: (countries) => {
|
||||||
|
this.countries = countries;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Failed to load countries', error);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadUnitOfMeasures(): void {
|
||||||
|
this.commonService.getUnitOfMeasures()
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe({
|
||||||
|
next: (units) => {
|
||||||
|
this.unitsOfMeasures = units;
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Failed to load unit of measures', error);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
patchFormData(data: Goods): void {
|
||||||
|
this.goodsForm.patchValue({
|
||||||
|
commercialSample: data.commercialSample || false,
|
||||||
|
professionalEquipment: data.professionalEquipment || false,
|
||||||
|
exhibitionsFair: data.exhibitionsFair || false,
|
||||||
|
roadVehiclesUsed: data.roadVehiclesUsed || false,
|
||||||
|
horseUsed: data.horseUsed || false,
|
||||||
|
authorizedRepresentatives: data.authorizedRepresentatives || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new item to the table
|
||||||
|
addNewItem(): void {
|
||||||
|
this.showItemForm = true;
|
||||||
|
this.isEditing = false;
|
||||||
|
this.currentItem = null;
|
||||||
|
this.itemForm.reset({
|
||||||
|
pieces: 0,
|
||||||
|
weight: 0,
|
||||||
|
value: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit existing item
|
||||||
|
editItem(item: GoodsItem): void {
|
||||||
|
this.showItemForm = true;
|
||||||
|
this.isEditing = true;
|
||||||
|
this.currentItem = item;
|
||||||
|
this.itemForm.patchValue({
|
||||||
|
itemNumber: item.itemNumber,
|
||||||
|
description: item.description,
|
||||||
|
pieces: item.pieces,
|
||||||
|
weight: item.weight,
|
||||||
|
unitOfMeasure: item.unitOfMeasure || 'kg',
|
||||||
|
value: item.value,
|
||||||
|
countryOfOrigin: item.countryOfOrigin || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveItem(): void {
|
||||||
|
if (this.itemForm.invalid || this.goodsForm.invalid) {
|
||||||
|
this.itemForm.markAllAsTouched();
|
||||||
|
this.goodsForm.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onSubmit();
|
||||||
|
|
||||||
|
const itemData: GoodsItem = this.itemForm.value;
|
||||||
|
|
||||||
|
// create a goods items array with a single item
|
||||||
|
let itemDataArray: GoodsItem[] = [];
|
||||||
|
itemDataArray.push(itemData);
|
||||||
|
|
||||||
|
const saveObservable = this.isEditing && this.currentItem
|
||||||
|
? this.goodsService.updateGoodsItem(this.headerid, itemDataArray)
|
||||||
|
: this.goodsService.addGoodsItem(this.headerid, itemDataArray);
|
||||||
|
|
||||||
|
saveObservable.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showSuccess(`Goods ${this.isEditing ? 'updated' : 'added'} successfully`);
|
||||||
|
this.loadGoodsItems();
|
||||||
|
this.cancelEdit();
|
||||||
|
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} goods items`);
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
console.error('Error saving goods item:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete item with confirmation
|
||||||
|
deleteItem(item: any): void {
|
||||||
|
this.currentItem = item;
|
||||||
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||||
|
width: '350px',
|
||||||
|
data: {
|
||||||
|
title: 'Confirm Delete',
|
||||||
|
message: 'Are you sure you want to delete this item?',
|
||||||
|
confirmText: 'Delete',
|
||||||
|
cancelText: 'Cancel'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.goodsService.deleteGoodsItem(this.headerid, this.currentItem!).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showSuccess('Item deleted successfully');
|
||||||
|
this.loadGoodsItems();
|
||||||
|
this.cancelEdit();
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to delete item');
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
console.error('Error deleting item:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle file selection for upload
|
||||||
|
fileUpload(event: any): void {
|
||||||
|
const file: File = event.target.files[0];
|
||||||
|
|
||||||
|
this.processExcelFile(file)
|
||||||
|
.then(response => {
|
||||||
|
console.log('File uploaded successfully:', response);
|
||||||
|
// Handle success (e.g., update UI)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error processing file:', error);
|
||||||
|
this.notificationService.showError('Failed to upload file');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async processExcelFile(file: File): Promise<void> {
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const validTypes = [
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'text/csv'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!validTypes.includes(file.type)) {
|
||||||
|
this.notificationService.showError('Please upload only Excel (XLSX, XLS) or CSV files');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fileToUpload = file;
|
||||||
|
if (!this.fileToUpload || !this.headerid) return;
|
||||||
|
|
||||||
|
this.isProcessing = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.readExcelFile(this.fileToUpload);
|
||||||
|
const items = this.parseExcelData(data);
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
this.notificationService.showError('No valid items found in the file');
|
||||||
|
this.isProcessing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.map(item => {
|
||||||
|
item.countryOfOrigin = this.getCountryValue(item.countryOfOrigin);
|
||||||
|
item.unitOfMeasure = this.getUnitOfMeasureValue(item.unitOfMeasure);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.goodsService.addGoodsItem(this.headerid, items).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showSuccess(`Goods uploaded successfully`);
|
||||||
|
this.loadGoodsItems();
|
||||||
|
this.cancelEdit();
|
||||||
|
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
|
||||||
|
this.fileToUpload = null;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, `Failed to save the upload goods items`);
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
console.error('Error saving goods items:', error);
|
||||||
|
this.fileToUpload = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error instanceof Error ? error.message : 'Failed to process file');
|
||||||
|
} finally {
|
||||||
|
this.isProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Excel file
|
||||||
|
private readExcelFile(file: File): Promise<any[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e: any) => {
|
||||||
|
try {
|
||||||
|
const data = new Uint8Array(e.target.result);
|
||||||
|
const workbook = XLSX.read(data, { type: 'array' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const jsonData = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
resolve(jsonData);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = (error) => reject(error);
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Excel data into our format
|
||||||
|
private parseExcelData(data: any[]): any[] {
|
||||||
|
return data.map(row => ({
|
||||||
|
itemNumber: row['Item Number'] || row['itemNumber'] || '',
|
||||||
|
description: row['Description'] || row['description'] || '',
|
||||||
|
pieces: Number(row['Pieces'] || row['pieces'] || 0),
|
||||||
|
weight: Number(row['Weight'] || row['weight'] || 0),
|
||||||
|
unitOfMeasure: row['Unit of Measure'] || row['unit of measure'] || row['unitofmeasure'] || row['UnitOfMeasure'],
|
||||||
|
value: Number(row['Value'] || row['value'] || 0),
|
||||||
|
countryOfOrigin: row['Country Of Origin'] || row['Country of Origin'] || row['country of origin'] || row['countryoforigin'] || row['CountryOfOrigin'] || ''
|
||||||
|
})).filter(item =>
|
||||||
|
item.itemNumber &&
|
||||||
|
item.description &&
|
||||||
|
item.pieces > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save all goods data
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.goodsForm.invalid) {
|
||||||
|
this.goodsForm.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = this.goodsForm.value;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.goodsService.saveGoodsData(this.headerid, formData).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showSuccess('Goods information saved successfully');
|
||||||
|
this.completed.emit(true);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save goods information');
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
console.error('Error saving goods:', error);
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel item editing
|
||||||
|
cancelEdit(): void {
|
||||||
|
this.showItemForm = false;
|
||||||
|
this.isEditing = false;
|
||||||
|
this.currentItem = null;
|
||||||
|
this.itemForm.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private atLeastOneRequired(formGroup: FormGroup) {
|
||||||
|
return (formGroup.get('commercialSample')?.value ||
|
||||||
|
formGroup.get('professionalEquipment')?.value ||
|
||||||
|
formGroup.get('exhibitionsFair')?.value) ? null : { atLeastOneRequired: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnitOfMeasureLabel(value: string): string {
|
||||||
|
const unit = this.unitsOfMeasures.find(u => u.value === value);
|
||||||
|
return unit ? unit.name : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCountryLabel(value: string): string {
|
||||||
|
const country = this.countries.find(c => c.value === value);
|
||||||
|
return country ? country.name : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnitOfMeasureValue(name: string): string {
|
||||||
|
const unit = this.unitsOfMeasures.find(u => u.name === name);
|
||||||
|
return unit ? unit.value : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCountryValue(name: string): string {
|
||||||
|
const country = this.countries.find(c => c.name === name);
|
||||||
|
return country ? country.value : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDecimalDisplay(value: number, decimalPoints: number): string {
|
||||||
|
if (value === null || value === undefined) return '';
|
||||||
|
|
||||||
|
// Format to show up to n decimal places, removing trailing zeros
|
||||||
|
const parts = value.toString().split('.');
|
||||||
|
if (parts.length === 1) return parts[0]; // No decimals
|
||||||
|
return `${parts[0]}.${parts[1].substring(0, decimalPoints).replace(/0+$/, '')}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,20 @@
|
|||||||
export interface Goods {
|
export interface Goods {
|
||||||
|
commercialSample: boolean;
|
||||||
|
professionalEquipment: boolean;
|
||||||
|
exhibitionsFair: boolean;
|
||||||
|
roadVehiclesUsed: boolean;
|
||||||
|
horseUsed: boolean;
|
||||||
|
authorizedRepresentatives: string;
|
||||||
|
items?: GoodsItem[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GoodsItem {
|
||||||
|
id?: number;
|
||||||
|
itemNumber: string;
|
||||||
|
description: string;
|
||||||
|
pieces: number;
|
||||||
|
weight: number;
|
||||||
|
unitOfMeasure: string;
|
||||||
|
value: number;
|
||||||
|
countryOfOrigin: string;
|
||||||
|
}
|
||||||
5
src/app/core/models/unitofmeasure.ts
Normal file
5
src/app/core/models/unitofmeasure.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface UnitOfMeasure {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
@ -1,9 +1,131 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
import { Goods, GoodsItem } from '../../models/carnet/goods';
|
||||||
|
import { filter, map, Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class GoodsService {
|
export class GoodsService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
constructor() { }
|
private http = inject(HttpClient);
|
||||||
|
private userService = inject(UserService);
|
||||||
|
|
||||||
|
getGoodDetailsByHeaderId(headerid: number): Goods | any {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetGoodsDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
|
||||||
|
filter(response => response.length > 0),
|
||||||
|
map(response => this.mapToGoods(response?.[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToGoods(goodDetails: any): Goods {
|
||||||
|
return {
|
||||||
|
roadVehiclesUsed: goodDetails.AUTOFLAG === 'Y',
|
||||||
|
horseUsed: goodDetails.HORSEFLAG === 'Y',
|
||||||
|
exhibitionsFair: goodDetails.EXHIBITIONSFAIRFLAG === 'Y',
|
||||||
|
professionalEquipment: goodDetails.PROFEQUIPMENTFLAG === 'Y',
|
||||||
|
commercialSample: goodDetails.COMMERCIALSAMPLEFLAG === 'Y',
|
||||||
|
authorizedRepresentatives: goodDetails.AUTHREP
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getGoodsItemsByHeaderId(headerid: number): Observable<GoodsItem[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetGoodsItemstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
|
||||||
|
map(response => this.mapToGoodsItems(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToGoodsItems(data: any[]): GoodsItem[] {
|
||||||
|
return data.map(data => ({
|
||||||
|
id: data.ID,
|
||||||
|
itemNumber: data.ITEMNO,
|
||||||
|
description: data.GOODSDESCRIPTION,
|
||||||
|
pieces: data.NOOFPIECES,
|
||||||
|
weight: data.ITEMWEIGHT,
|
||||||
|
unitOfMeasure: data.ITEMWEIGHTUOM,
|
||||||
|
value: data.ITEMVALUE,
|
||||||
|
countryOfOrigin: data.GOODSORIGINCOUNTRY
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
saveGoodsData(headerid: number, goodsData: Goods): Observable<any> {
|
||||||
|
|
||||||
|
const goods = {
|
||||||
|
P_HEADERID: headerid,
|
||||||
|
P_COMMERCIALSAMPLEFLAG: goodsData.commercialSample ? 'Y' : 'N',
|
||||||
|
P_PROFEQUIPMENTFLAG: goodsData.professionalEquipment ? 'Y' : 'N',
|
||||||
|
P_EXHIBITIONSFAIRFLAG: goodsData.exhibitionsFair ? 'Y' : 'N',
|
||||||
|
P_AUTOFLAG: goodsData.roadVehiclesUsed ? 'Y' : 'N',
|
||||||
|
P_HORSEFLAG: goodsData.horseUsed ? 'Y' : 'N',
|
||||||
|
P_AUTHREP: goodsData.authorizedRepresentatives,
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.patch(`${this.apiUrl}/${this.apiDb}/UpdateExpGoodsAuthRep`, goods);
|
||||||
|
}
|
||||||
|
|
||||||
|
addGoodsItem(headerid: number, items: GoodsItem[]): Observable<any> {
|
||||||
|
|
||||||
|
const goodsItems: any[] = items.map(i => ({
|
||||||
|
ITEMNO: i.itemNumber,
|
||||||
|
ITEMDESCRIPTION: i.description,
|
||||||
|
NOOFPIECES: i.pieces,
|
||||||
|
ITEMWEIGHT: i.weight,
|
||||||
|
ITEMWEIGHTUOM: i.unitOfMeasure,
|
||||||
|
ITEMVALUE: i.value,
|
||||||
|
GOODSORIGINCOUNTRY: i.countryOfOrigin,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const goodsItemsObj = {
|
||||||
|
P_HEADERID: headerid,
|
||||||
|
P_GLTABLE: goodsItems,
|
||||||
|
P_USERID: this.userService.getUser(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.post(`${this.apiUrl}/${this.apiDb}/AddGenerallistItems`, goodsItemsObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGoodsItem(headerid: number, items: GoodsItem[]): Observable<any> {
|
||||||
|
|
||||||
|
const goodsItems: any[] = items.map(i => ({
|
||||||
|
ITEMNO: i.itemNumber,
|
||||||
|
ITEMDESCRIPTION: i.description,
|
||||||
|
NOOFPIECES: i.pieces,
|
||||||
|
ITEMWEIGHT: i.weight,
|
||||||
|
ITEMWEIGHTUOM: i.unitOfMeasure,
|
||||||
|
ITEMVALUE: i.value,
|
||||||
|
GOODSORIGINCOUNTRY: i.countryOfOrigin,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const goodsItemsObj = {
|
||||||
|
P_HEADERID: headerid,
|
||||||
|
P_GLTABLE: goodsItems,
|
||||||
|
P_USERID: this.userService.getUser(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.put(`${this.apiUrl}/${this.apiDb}/EditGenerallistItems`, goodsItemsObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteGoodsItem(headerid: number, item: GoodsItem): Observable<any> {
|
||||||
|
|
||||||
|
const goodsItem = {
|
||||||
|
ITEMNO: item.itemNumber,
|
||||||
|
ITEMDESCRIPTION: item.description,
|
||||||
|
NOOFPIECES: item.pieces,
|
||||||
|
ITEMWEIGHT: item.weight,
|
||||||
|
ITEMWEIGHTUOM: item.unitOfMeasure,
|
||||||
|
ITEMVALUE: item.value,
|
||||||
|
GOODSORIGINCOUNTRY: item.countryOfOrigin,
|
||||||
|
}
|
||||||
|
|
||||||
|
const goodsItemsObj = {
|
||||||
|
P_HEADERID: headerid,
|
||||||
|
P_GLTABLE: [goodsItem],
|
||||||
|
P_USERID: this.userService.getUser(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// delete request with body
|
||||||
|
return this.http.delete(`${this.apiUrl}/${this.apiDb}/DeleteGenerallistItems`, { body: goodsItemsObj });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,24 +49,32 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
register(email: string, password: string, token: string | null): Observable<any> {
|
register(email: string, password: string): Observable<any> {
|
||||||
let headers = new HttpHeaders();
|
|
||||||
|
return this.http.post(
|
||||||
|
`${this.apiUrl}/register`,
|
||||||
|
{ P_EMAILADDR: email, P_PASSWORD: password }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
forgotPassword(email: string, password: string, token: string): Observable<any> {
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
headers = headers.set('Authorization', `Bearer ${token}`);
|
headers = headers.set('Authorization', `Bearer ${token}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.http.post(
|
return this.http.post(
|
||||||
`${this.apiUrl}/register`,
|
`${this.apiUrl}/forgot-password`,
|
||||||
{ P_EMAILADDR: email, P_PASSWORD: password },
|
{ P_EMAILADDR: email, P_PASSWORD: password },
|
||||||
{ headers }
|
{ headers }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
forgotPassword(email: string, password: string): Observable<any> {
|
sendEmail(email: string, type: string): Observable<any> {
|
||||||
return this.http.post(
|
return this.http.post(
|
||||||
`${this.apiUrl}/forgot-password`,
|
`${this.apiUrl}/sendmail`,
|
||||||
{ P_EMAILADDR: email, P_PASSWORD: password }
|
{ P_TO: email, P_MAIL_TYPE: type }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { CargoPolicy } from '../../models/cargo-policy';
|
|||||||
import { CargoSurety } from '../../models/cargo-surety';
|
import { CargoSurety } from '../../models/cargo-surety';
|
||||||
import { CarnetStatus } from '../../models/carnet-status';
|
import { CarnetStatus } from '../../models/carnet-status';
|
||||||
import { Country } from '../../models/country';
|
import { Country } from '../../models/country';
|
||||||
|
import { UnitOfMeasure } from '../../models/unitofmeasure';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -148,6 +149,19 @@ export class CommonService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnitOfMeasures(_spid: number = 0): Observable<UnitOfMeasure[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=013&P_SPID=0`).pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.map((item) => ({
|
||||||
|
name: item.PARAMDESC,
|
||||||
|
id: item.PARAMID,
|
||||||
|
value: item.PARAMVALUE,
|
||||||
|
color: item.ADDLPARAMVALUE1,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
formatUSDate(datetime: Date): string {
|
formatUSDate(datetime: Date): string {
|
||||||
const date = new Date(datetime);
|
const date = new Date(datetime);
|
||||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||||
|
|||||||
@ -1,8 +1,36 @@
|
|||||||
<div class="forgot-password-container">
|
<div class="forgot-password-container">
|
||||||
<div class="forgot-password-card">
|
<div class="forgot-password-card">
|
||||||
<h3 class="subtitle">Forgot Password</h3>
|
<h3 class="subtitle">
|
||||||
|
{{forgotPasswordRequestCompleted ? 'Reset Password' : 'Forgot Password'}}
|
||||||
|
</h3>
|
||||||
|
|
||||||
<form [formGroup]="forgotPasswordForm" (ngSubmit)="onSubmit()" class="forgot-password-form">
|
<form *ngIf="!forgotPasswordRequestCompleted" [formGroup]="forgotPasswordRequestForm" (ngSubmit)="onRequest()"
|
||||||
|
class="forgot-password-form">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Email</mat-label>
|
||||||
|
<input matInput formControlName="email" type="email" required>
|
||||||
|
<mat-icon matSuffix>email</mat-icon>
|
||||||
|
<mat-error *ngIf="email?.errors?.['required']">
|
||||||
|
Email is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="email?.errors?.['email']">
|
||||||
|
Please enter a valid email
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="login">
|
||||||
|
<a href="#" routerLink="/login" class="login-link">Already have an account? Login</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button mat-raised-button color="primary" type="submit"
|
||||||
|
[disabled]="forgotPasswordRequestForm.invalid || isLoading">
|
||||||
|
<span *ngIf="!isLoading">Request</span>
|
||||||
|
<mat-spinner *ngIf="isLoading" diameter="20"></mat-spinner>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form *ngIf="forgotPasswordRequestCompleted" [formGroup]="forgotPasswordForm" (ngSubmit)="onSubmit()"
|
||||||
|
class="forgot-password-form">
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Email</mat-label>
|
<mat-label>Email</mat-label>
|
||||||
<input matInput formControlName="email" type="email" required>
|
<input matInput formControlName="email" type="email" required>
|
||||||
@ -24,11 +52,32 @@
|
|||||||
<mat-error *ngIf="password?.errors?.['required']">
|
<mat-error *ngIf="password?.errors?.['required']">
|
||||||
Password is required
|
Password is required
|
||||||
</mat-error>
|
</mat-error>
|
||||||
|
<mat-error *ngIf="password?.errors?.['minlength']">
|
||||||
|
Password must be at least 8 characters
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="password?.errors?.['pattern']">
|
||||||
|
Password must contain at least one uppercase letter, one lowercase letter, one number, and one
|
||||||
|
special character
|
||||||
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Confirm Password</mat-label>
|
||||||
|
<input matInput formControlName="confirmPassword" [type]="hidePassword ? 'password' : 'text'" required>
|
||||||
|
<button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword">
|
||||||
|
<mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-error *ngIf="confirmPassword?.errors?.['required']">
|
||||||
|
Please confirm your password
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="forgotPasswordForm.errors?.['mismatch'] && confirmPassword?.touched">
|
||||||
|
Passwords do not match
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<!--
|
||||||
<div class="login">
|
<div class="login">
|
||||||
<a href="#" routerLink="/login" class="login-link">Already have an account? Login</a>
|
<a href="#" routerLink="/login" class="login-link">Already have an account? Login</a>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<button mat-raised-button color="primary" type="submit"
|
<button mat-raised-button color="primary" type="submit"
|
||||||
[disabled]="forgotPasswordForm.invalid || isLoading">
|
[disabled]="forgotPasswordForm.invalid || isLoading">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { FormGroup, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
import { FormGroup, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
||||||
import { AuthService } from '../core/services/common/auth.service';
|
import { AuthService } from '../core/services/common/auth.service';
|
||||||
import { NotificationService } from '../core/services/common/notification.service';
|
import { NotificationService } from '../core/services/common/notification.service';
|
||||||
@ -15,10 +15,12 @@ import { CommonModule } from '@angular/common';
|
|||||||
styleUrl: './forgot-password.component.scss'
|
styleUrl: './forgot-password.component.scss'
|
||||||
})
|
})
|
||||||
export class ForgotPasswordComponent {
|
export class ForgotPasswordComponent {
|
||||||
|
forgotPasswordRequestCompleted: boolean = false;
|
||||||
|
forgotPasswordRequestForm: FormGroup;
|
||||||
forgotPasswordForm: FormGroup;
|
forgotPasswordForm: FormGroup;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
hidePassword = true;
|
hidePassword = true;
|
||||||
|
token: string | null = null;
|
||||||
|
|
||||||
private fb = inject(FormBuilder);
|
private fb = inject(FormBuilder);
|
||||||
private authService = inject(AuthService);
|
private authService = inject(AuthService);
|
||||||
@ -26,18 +28,57 @@ export class ForgotPasswordComponent {
|
|||||||
private notificationService = inject(NotificationService);
|
private notificationService = inject(NotificationService);
|
||||||
private errorHandler = inject(ApiErrorHandlerService);
|
private errorHandler = inject(ApiErrorHandlerService);
|
||||||
private themeService = inject(ThemeService);
|
private themeService = inject(ThemeService);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.forgotPasswordRequestForm = this.fb.group({
|
||||||
|
email: ['', [Validators.required, Validators.email]]
|
||||||
|
});
|
||||||
|
|
||||||
this.forgotPasswordForm = this.fb.group({
|
this.forgotPasswordForm = this.fb.group({
|
||||||
email: ['', [Validators.required, Validators.email]],
|
email: ['', [Validators.required, Validators.email]],
|
||||||
password: ['', [Validators.required]]
|
password: ['', [Validators.required, Validators.minLength(8),
|
||||||
});
|
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
|
||||||
|
]],
|
||||||
|
confirmPassword: ['', [Validators.required]]
|
||||||
|
}, { validator: this.passwordMatchValidator });
|
||||||
|
|
||||||
|
// // Get token from URL if present
|
||||||
|
this.token = this.route.snapshot.paramMap.get('token');
|
||||||
|
|
||||||
|
// Check if token is null or empty
|
||||||
|
if (this.token && this.token.trim() !== '') {
|
||||||
|
this.forgotPasswordRequestCompleted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.themeService.setTheme('default');
|
this.themeService.setTheme('default');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRequest(): void {
|
||||||
|
if (this.forgotPasswordRequestForm.invalid) {
|
||||||
|
this.forgotPasswordRequestForm.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
const { email } = this.forgotPasswordRequestForm.value;
|
||||||
|
|
||||||
|
this.authService.sendEmail(email, 'FORGOT_PASSWORD').subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showSuccess('A mail has been sent successfully to your email to setup a new password.');
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, `Request failed. Please try again.`);
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
console.error('Forgot password request failed:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
if (this.forgotPasswordForm.invalid) {
|
if (this.forgotPasswordForm.invalid) {
|
||||||
this.forgotPasswordForm.markAllAsTouched();
|
this.forgotPasswordForm.markAllAsTouched();
|
||||||
@ -47,20 +88,30 @@ export class ForgotPasswordComponent {
|
|||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const { email, password } = this.forgotPasswordForm.value;
|
const { email, password } = this.forgotPasswordForm.value;
|
||||||
|
|
||||||
this.authService.forgotPassword(email, password).subscribe({
|
this.authService.forgotPassword(email, password, this.token!).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.notificationService.showSuccess('Request successful');
|
this.notificationService.showSuccess('Password reset successfully');
|
||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/login']);
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
let errorMessage = this.errorHandler.handleApiError(error, `Request failed. Please try again.`);
|
let errorMessage = this.errorHandler.handleApiError(error, `Password request failed. Please try again.`);
|
||||||
this.notificationService.showError(errorMessage);
|
this.notificationService.showError(errorMessage);
|
||||||
console.error('Forgot password request failed:', error);
|
console.error('Forgot password request failed:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private passwordMatchValidator(formGroup: FormGroup) {
|
||||||
|
const password = formGroup.get('password')?.value;
|
||||||
|
const confirmPassword = formGroup.get('confirmPassword')?.value;
|
||||||
|
return password === confirmPassword ? null : { mismatch: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
get emailRequest() {
|
||||||
|
return this.forgotPasswordRequestForm.get('email');
|
||||||
|
}
|
||||||
|
|
||||||
get email() {
|
get email() {
|
||||||
return this.forgotPasswordForm.get('email');
|
return this.forgotPasswordForm.get('email');
|
||||||
}
|
}
|
||||||
@ -68,4 +119,8 @@ export class ForgotPasswordComponent {
|
|||||||
get password() {
|
get password() {
|
||||||
return this.forgotPasswordForm.get('password');
|
return this.forgotPasswordForm.get('password');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get confirmPassword() {
|
||||||
|
return this.forgotPasswordForm.get('confirmPassword');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,8 +111,8 @@
|
|||||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||||
<td mat-cell *matCellDef="let item">
|
<td mat-cell *matCellDef="let item">
|
||||||
<button mat-icon-button color="primary" (click)="editCarnet(item)"
|
<button mat-icon-button color="primary" (click)="editCarnet(item)"
|
||||||
*ngIf="getCarnetStatusLabel(item.carnetStatus) === 'Pending'"
|
*ngIf="getCarnetStatusLabel(item.carnetStatus) === 'Not Submitted'"
|
||||||
[hidden]="getCarnetStatusLabel(item.carnetStatus) === 'Pending'" matTooltip="Edit">
|
[hidden]="getCarnetStatusLabel(item.carnetStatus) !== 'Not Submitted'" matTooltip="Edit">
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -114,7 +114,7 @@ export class HomeComponent {
|
|||||||
|
|
||||||
newCarnet(event: MatRadioChange): void {
|
newCarnet(event: MatRadioChange): void {
|
||||||
if (event.value) {
|
if (event.value) {
|
||||||
this.navigateTo('add-carnet');
|
this.navigateTo(['add-carnet']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,13 +129,15 @@ export class HomeComponent {
|
|||||||
|
|
||||||
editCarnet(item: any): void {
|
editCarnet(item: any): void {
|
||||||
if (item && item.headerId) {
|
if (item && item.headerId) {
|
||||||
this.navigateTo(`edit-carnet/${item.headerId}?ApplicationName=${item.applicationName}`);
|
this.navigateTo(['edit-carnet', item.headerId], {
|
||||||
|
queryParams: { applicationname: item.applicationName }
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.notificationService.showError('Invalid carnet data');
|
this.notificationService.showError('Invalid carnet data');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateTo(route: string): void {
|
navigateTo(route: any[], extras?: any): void {
|
||||||
this.navigationService.navigate([route]);
|
this.navigationService.navigate(route, extras);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,16 +2,7 @@
|
|||||||
<div class="registration-card">
|
<div class="registration-card">
|
||||||
<h3 class="subtitle">User Registration</h3>
|
<h3 class="subtitle">User Registration</h3>
|
||||||
|
|
||||||
<!-- Show error message if token is invalid -->
|
<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()" class="registration-form">
|
||||||
<div *ngIf="tokenError" class="token-error">
|
|
||||||
<mat-error>
|
|
||||||
<mat-icon>error_outline</mat-icon>
|
|
||||||
<span>Invalid registration link. Please request a new registration link from your
|
|
||||||
administrator.</span>
|
|
||||||
</mat-error>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form *ngIf="!tokenError" [formGroup]="registrationForm" (ngSubmit)="onSubmit()" class="registration-form">
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Email</mat-label>
|
<mat-label>Email</mat-label>
|
||||||
<input matInput formControlName="email" type="email" required>
|
<input matInput formControlName="email" type="email" required>
|
||||||
@ -33,6 +24,27 @@
|
|||||||
<mat-error *ngIf="password?.errors?.['required']">
|
<mat-error *ngIf="password?.errors?.['required']">
|
||||||
Password is required
|
Password is required
|
||||||
</mat-error>
|
</mat-error>
|
||||||
|
<mat-error *ngIf="password?.errors?.['minlength']">
|
||||||
|
Password must be at least 8 characters
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="password?.errors?.['pattern']">
|
||||||
|
Password must contain at least one uppercase letter, one lowercase letter, one number, and one
|
||||||
|
special character
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Confirm Password</mat-label>
|
||||||
|
<input matInput formControlName="confirmPassword" [type]="hidePassword ? 'password' : 'text'" required>
|
||||||
|
<button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword">
|
||||||
|
<mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-error *ngIf="confirmPassword?.errors?.['required']">
|
||||||
|
Please confirm your password
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="registrationForm.errors?.['mismatch'] && confirmPassword?.touched">
|
||||||
|
Passwords do not match
|
||||||
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="login">
|
<div class="login">
|
||||||
|
|||||||
@ -19,8 +19,9 @@ export class RegisterComponent {
|
|||||||
registrationForm: FormGroup;
|
registrationForm: FormGroup;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
hidePassword = true;
|
hidePassword = true;
|
||||||
tokenError: boolean = false;
|
|
||||||
token: string | null = null;
|
// tokenError: boolean = false;
|
||||||
|
//token: string | null = null;
|
||||||
|
|
||||||
private fb = inject(FormBuilder);
|
private fb = inject(FormBuilder);
|
||||||
private authService = inject(AuthService);
|
private authService = inject(AuthService);
|
||||||
@ -33,16 +34,19 @@ export class RegisterComponent {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.registrationForm = this.fb.group({
|
this.registrationForm = this.fb.group({
|
||||||
email: ['', [Validators.required, Validators.email]],
|
email: ['', [Validators.required, Validators.email]],
|
||||||
password: ['', [Validators.required]]
|
password: ['', [Validators.required, Validators.minLength(8),
|
||||||
});
|
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
|
||||||
|
]],
|
||||||
|
confirmPassword: ['', [Validators.required]]
|
||||||
|
}, { validator: this.passwordMatchValidator });
|
||||||
|
|
||||||
// Get token from URL if present
|
// // Get token from URL if present
|
||||||
this.token = this.route.snapshot.paramMap.get('token');
|
// this.token = this.route.snapshot.paramMap.get('token');
|
||||||
|
|
||||||
// Check if token is null or empty
|
// // Check if token is null or empty
|
||||||
if (!this.token || this.token.trim() === '') {
|
// if (!this.token || this.token.trim() === '') {
|
||||||
this.tokenError = true;
|
// this.tokenError = true;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -58,7 +62,7 @@ export class RegisterComponent {
|
|||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const { email, password } = this.registrationForm.value;
|
const { email, password } = this.registrationForm.value;
|
||||||
|
|
||||||
this.authService.register(email, password, this.token).subscribe({
|
this.authService.register(email, password).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.notificationService.showSuccess('Registration successful');
|
this.notificationService.showSuccess('Registration successful');
|
||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/login']);
|
||||||
@ -72,6 +76,12 @@ export class RegisterComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private passwordMatchValidator(formGroup: FormGroup) {
|
||||||
|
const password = formGroup.get('password')?.value;
|
||||||
|
const confirmPassword = formGroup.get('confirmPassword')?.value;
|
||||||
|
return password === confirmPassword ? null : { mismatch: true };
|
||||||
|
}
|
||||||
|
|
||||||
get email() {
|
get email() {
|
||||||
return this.registrationForm.get('email');
|
return this.registrationForm.get('email');
|
||||||
}
|
}
|
||||||
@ -79,4 +89,8 @@ export class RegisterComponent {
|
|||||||
get password() {
|
get password() {
|
||||||
return this.registrationForm.get('password');
|
return this.registrationForm.get('password');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get confirmPassword() {
|
||||||
|
return this.registrationForm.get('confirmPassword');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import { MatAccordion, MatExpansionModule } from '@angular/material/expansion';
|
|||||||
import { MatStepperModule } from '@angular/material/stepper';
|
import { MatStepperModule } from '@angular/material/stepper';
|
||||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule } from '@angular/material-moment-adapter';
|
import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule } from '@angular/material-moment-adapter';
|
||||||
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [],
|
declarations: [],
|
||||||
@ -55,7 +56,8 @@ import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule } from '@angular/m
|
|||||||
MatAccordion,
|
MatAccordion,
|
||||||
MatStepperModule,
|
MatStepperModule,
|
||||||
MatMomentDateModule,
|
MatMomentDateModule,
|
||||||
MatSlideToggleModule
|
MatSlideToggleModule,
|
||||||
|
MatProgressBarModule
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
@ -82,7 +84,8 @@ import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule } from '@angular/m
|
|||||||
MatAccordion,
|
MatAccordion,
|
||||||
MatStepperModule,
|
MatStepperModule,
|
||||||
MatMomentDateModule,
|
MatMomentDateModule,
|
||||||
MatSlideToggleModule
|
MatSlideToggleModule,
|
||||||
|
MatProgressBarModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }
|
{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user